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;