ina3221_dd/lib.rs
1#![cfg_attr(not(any(test, feature = "std")), no_std)]
2//! # INA3221 Triple-Channel Current/Voltage Monitor Interface
3//!
4//! This crate provides a bisync-based driver for the INA3221 triple-channel, high-side
5//! current and bus voltage monitor, built upon the `device-driver` crate for robust,
6//! declarative register definitions via a YAML manifest. It supports both asynchronous
7//! (`async`) and blocking operation through a unified API, using the [`bisync`](https://docs.rs/bisync)
8//! crate for seamless compatibility with both `embedded-hal` and `embedded-hal-async` traits.
9//!
10//! ## Features
11//!
12//! * **Declarative Register Map:** Full device configuration defined in `device.yaml`.
13//! * **Unified Async/Blocking Support:** Write your code once and use it in both async and blocking contexts via bisync.
14//! * **Type-Safe API:** High-level functions for reading voltages and currents
15//! and a generated low-level API (`ll`) for direct register access.
16//! * **Triple-Channel Monitoring:** Simultaneously monitor 3 independent power rails.
17//! * **`defmt` and `log` Integration:** Optional support for logging and debugging.
18//!
19//! ## Getting Started
20//!
21//! To use the driver, instantiate `Ina3221` (blocking) or `Ina3221Async` (async) with your I2C bus implementation:
22//!
23//! ```rust,no_run
24//! # use embedded_hal::i2c::I2c;
25//! # use ina3221_dd::{Ina3221, ChannelId};
26//! let i2c_bus = todo!();
27//! let mut ina = Ina3221::new(i2c_bus);
28//!
29//! let bus_voltage = ina.get_bus_voltage(ChannelId::Channel1)?;
30//! # Ok::<(), ina3221_dd::Ina3221Error<std::io::Error>>(())
31//! ```
32//!
33//! For async environments, use `Ina3221Async` (re-exported from the `asynchronous` module):
34//!
35//! ```rust,no_run
36//! # use embedded_hal_async::i2c::I2c;
37//! # use ina3221_dd::{Ina3221Async, ChannelId};
38//! let i2c_bus = todo!();
39//! let mut ina = Ina3221Async::new(i2c_bus);
40//!
41//! let bus_voltage = ina.get_bus_voltage(ChannelId::Channel1).await?;
42//! # Ok::<(), ina3221_dd::Ina3221Error<std::io::Error>>(())
43//! ```
44//!
45//! For a detailed register map, please refer to the `device.yaml` file in the
46//! [repository](https://github.com/okhsunrog/ina3221-dd).
47//!
48//! ## Supported Devices
49//!
50//! The INA3221 is found in various embedded devices for power monitoring,
51//! including NVIDIA Jetson boards and other development platforms.
52//!
53
54#[macro_use]
55pub(crate) mod fmt;
56
57use thiserror::Error;
58
59device_driver::create_device!(device_name: Ina3221LowLevel, manifest: "device.yaml");
60
61// Re-export uom types when feature is enabled
62#[cfg(feature = "uom")]
63pub use uom::si::electric_current::milliampere;
64#[cfg(feature = "uom")]
65pub use uom::si::electric_potential::{microvolt, millivolt};
66#[cfg(feature = "uom")]
67pub use uom::si::electrical_resistance::milliohm;
68#[cfg(feature = "uom")]
69pub use uom::si::f32::{ElectricCurrent, ElectricPotential, ElectricalResistance};
70
71/// INA3221 default I2C address (A0 pin = GND)
72/// Other addresses: 0x41 (A0=VS+), 0x42 (A0=SDA), 0x43 (A0=SCL)
73pub const INA3221_I2C_ADDR_GND: u8 = 0x40;
74pub const INA3221_I2C_ADDR_VS: u8 = 0x41;
75pub const INA3221_I2C_ADDR_SDA: u8 = 0x42;
76pub const INA3221_I2C_ADDR_SCL: u8 = 0x43;
77
78/// Shunt voltage LSB: 40µV
79pub const SHUNT_VOLTAGE_LSB_UV: f32 = 40.0;
80/// Bus voltage LSB: 8mV
81pub const BUS_VOLTAGE_LSB_MV: f32 = 8.0;
82
83#[derive(Debug, Error)]
84#[cfg_attr(feature = "defmt", derive(defmt::Format))]
85pub enum Ina3221Error<I2cErr> {
86 #[error("I2C error")]
87 I2c(I2cErr),
88 #[error("Invalid channel")]
89 InvalidChannel,
90 #[error("Feature or specific mode not supported/implemented: {0}")]
91 NotSupported(&'static str),
92}
93
94/// Channel identifier for the INA3221's three monitoring channels
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96#[cfg_attr(feature = "defmt", derive(defmt::Format))]
97pub enum ChannelId {
98 Channel1,
99 Channel2,
100 Channel3,
101}
102
103/// Operating mode for the INA3221
104///
105/// Controls whether the device performs continuous measurements, single-shot (triggered),
106/// or enters power-down mode.
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
108#[cfg_attr(feature = "defmt", derive(defmt::Format))]
109#[repr(u8)]
110pub enum OperatingMode {
111 /// Power-down mode - minimal power consumption
112 PowerDown = 0,
113 /// Shunt voltage only, single-shot (triggered)
114 ShuntTriggered = 1,
115 /// Bus voltage only, single-shot (triggered)
116 BusTriggered = 2,
117 /// Shunt and bus voltage, single-shot (triggered)
118 ShuntBusTriggered = 3,
119 /// Shunt voltage only, continuous
120 ShuntContinuous = 5,
121 /// Bus voltage only, continuous
122 BusContinuous = 6,
123 /// Shunt and bus voltage, continuous (default)
124 #[default]
125 ShuntBusContinuous = 7,
126}
127
128impl OperatingMode {
129 /// Create from raw register value (bits 2-0)
130 pub fn from_raw(value: u8) -> Self {
131 match value & 0x07 {
132 0 | 4 => Self::PowerDown,
133 1 => Self::ShuntTriggered,
134 2 => Self::BusTriggered,
135 3 => Self::ShuntBusTriggered,
136 5 => Self::ShuntContinuous,
137 6 => Self::BusContinuous,
138 7 => Self::ShuntBusContinuous,
139 _ => Self::PowerDown,
140 }
141 }
142}
143
144/// Averaging mode - number of samples to average for each measurement
145///
146/// Higher averaging reduces noise but increases conversion time.
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
148#[cfg_attr(feature = "defmt", derive(defmt::Format))]
149#[repr(u8)]
150pub enum AveragingMode {
151 /// 1 sample (no averaging, default)
152 #[default]
153 Samples1 = 0,
154 /// 4 samples averaged
155 Samples4 = 1,
156 /// 16 samples averaged
157 Samples16 = 2,
158 /// 64 samples averaged
159 Samples64 = 3,
160 /// 128 samples averaged
161 Samples128 = 4,
162 /// 256 samples averaged
163 Samples256 = 5,
164 /// 512 samples averaged
165 Samples512 = 6,
166 /// 1024 samples averaged
167 Samples1024 = 7,
168}
169
170impl AveragingMode {
171 /// Create from raw register value (bits 11-9)
172 pub fn from_raw(value: u8) -> Self {
173 match value & 0x07 {
174 0 => Self::Samples1,
175 1 => Self::Samples4,
176 2 => Self::Samples16,
177 3 => Self::Samples64,
178 4 => Self::Samples128,
179 5 => Self::Samples256,
180 6 => Self::Samples512,
181 7 => Self::Samples1024,
182 _ => Self::Samples1,
183 }
184 }
185
186 /// Get the number of samples for this mode
187 pub fn sample_count(&self) -> u16 {
188 match self {
189 Self::Samples1 => 1,
190 Self::Samples4 => 4,
191 Self::Samples16 => 16,
192 Self::Samples64 => 64,
193 Self::Samples128 => 128,
194 Self::Samples256 => 256,
195 Self::Samples512 => 512,
196 Self::Samples1024 => 1024,
197 }
198 }
199}
200
201/// Conversion time for ADC measurements
202///
203/// Longer conversion times provide better accuracy but slower measurements.
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
205#[cfg_attr(feature = "defmt", derive(defmt::Format))]
206#[repr(u8)]
207pub enum ConversionTime {
208 /// 140 µs
209 Us140 = 0,
210 /// 204 µs
211 Us204 = 1,
212 /// 332 µs
213 Us332 = 2,
214 /// 588 µs
215 Us588 = 3,
216 /// 1.1 ms (default)
217 #[default]
218 Ms1_1 = 4,
219 /// 2.116 ms
220 Ms2_116 = 5,
221 /// 4.156 ms
222 Ms4_156 = 6,
223 /// 8.244 ms
224 Ms8_244 = 7,
225}
226
227impl ConversionTime {
228 /// Create from raw register value
229 pub fn from_raw(value: u8) -> Self {
230 match value & 0x07 {
231 0 => Self::Us140,
232 1 => Self::Us204,
233 2 => Self::Us332,
234 3 => Self::Us588,
235 4 => Self::Ms1_1,
236 5 => Self::Ms2_116,
237 6 => Self::Ms4_156,
238 7 => Self::Ms8_244,
239 _ => Self::Ms1_1,
240 }
241 }
242
243 /// Get the conversion time in microseconds
244 pub fn as_micros(&self) -> u32 {
245 match self {
246 Self::Us140 => 140,
247 Self::Us204 => 204,
248 Self::Us332 => 332,
249 Self::Us588 => 588,
250 Self::Ms1_1 => 1100,
251 Self::Ms2_116 => 2116,
252 Self::Ms4_156 => 4156,
253 Self::Ms8_244 => 8244,
254 }
255 }
256}
257
258/// Alert flags from the Mask/Enable register
259#[derive(Debug, Clone, Copy, Default)]
260#[cfg_attr(feature = "defmt", derive(defmt::Format))]
261pub struct AlertFlags {
262 /// Conversion ready flag
263 pub conversion_ready: bool,
264 /// Timing control alert flag
265 pub timing_control: bool,
266 /// Power valid alert flag
267 pub power_valid: bool,
268 /// Channel 1 warning alert flag
269 pub warning_ch1: bool,
270 /// Channel 2 warning alert flag
271 pub warning_ch2: bool,
272 /// Channel 3 warning alert flag
273 pub warning_ch3: bool,
274 /// Summation alert flag
275 pub summation: bool,
276 /// Channel 1 critical alert flag
277 pub critical_ch1: bool,
278 /// Channel 2 critical alert flag
279 pub critical_ch2: bool,
280 /// Channel 3 critical alert flag
281 pub critical_ch3: bool,
282}
283
284pub struct Ina3221Interface<I2CBus> {
285 i2c_bus: I2CBus,
286 address: u8,
287}
288
289impl<I2CBus> Ina3221Interface<I2CBus> {
290 pub fn new(i2c_bus: I2CBus, address: u8) -> Self {
291 Self { i2c_bus, address }
292 }
293}
294
295#[path = "."]
296mod asynchronous {
297 use bisync::asynchronous::*;
298 use device_driver::AsyncRegisterInterface as RegisterInterface;
299 use embedded_hal_async::i2c::I2c;
300 mod driver;
301 pub use driver::*;
302}
303pub use asynchronous::Ina3221 as Ina3221Async;
304
305#[path = "."]
306mod blocking {
307 use bisync::synchronous::*;
308 use device_driver::RegisterInterface;
309 use embedded_hal::i2c::I2c;
310 #[allow(clippy::duplicate_mod)]
311 mod driver;
312 pub use driver::*;
313}
314pub use blocking::Ina3221;