embedded_devices/devices/texas_instruments/ina228/
mod.rs

1//! The INA228 is an ultra-precise digital power monitor with a 20-bit delta-sigma ADC specifically
2//! designed for current-sensing applications. The device can measure a full-scale differential
3//! input of ±163.84 mV or ±40.96 mV across a resistive shunt sense element with common-mode
4//! voltage support from –0.3 V to +85 V.
5//!
6//! The INA228 reports current, bus voltage, temperature, power, energy and charge accumulation
7//! while employing a precision ±0.5% integrated oscillator, all while performing the needed
8//! calculations in the background. The integrated temperature sensor is ±1°C accurate for die
9//! temperature measurement and is useful in monitoring the system ambient temperature.
10//!
11//! The low offset and gain drift design of the INA228 allows the device to be used in precise
12//! systems that do not undergo multi-temperature calibration during manufacturing. Further, the
13//! very low offset voltage and noise allow for use in mA to kA sensing applications and provide a
14//! wide dynamic range without significant power dissipation losses on the sensing shunt element.
15//! The low input bias current of the device permits the use of larger current- sense resistors,
16//! thus providing accurate current measurements in the micro-amp range.
17//!
18//! The device allows for selectable ADC conversion times from 50 μs to 4.12 ms as well as sample
19//! averaging from 1x to 1024x, which further helps reduce the noise of the measured data.
20//!
21//! ## Usage (sync)
22//!
23//! ```rust
24//! # #[cfg(feature = "sync")] mod test {
25//! # fn test<I, D>(mut i2c: I, delay: D) -> Result<(), I::Error>
26//! # where
27//! #   I: embedded_hal::i2c::I2c + embedded_hal::i2c::ErrorType,
28//! #   D: embedded_hal::delay::DelayNs
29//! # {
30//! use embedded_devices::devices::texas_instruments::ina228::{INA228Sync, address::Address, address::Pin};
31//! use embedded_devices::sensor::OneshotSensorSync;
32//! use uom::si::electric_current::{ampere, milliampere};
33//! use uom::si::electric_potential::millivolt;
34//! use uom::si::electrical_resistance::ohm;
35//! use uom::si::power::milliwatt;
36//! use uom::si::f64::{ElectricCurrent, ElectricalResistance};
37//! use uom::si::thermodynamic_temperature::degree_celsius;
38//!
39//! // Create and initialize the device
40//! let mut ina228 = INA228Sync::new_i2c(delay, i2c, Address::A0A1(Pin::Gnd, Pin::Gnd));
41//! ina228.init(
42//!   // Most units use a 100mΩ shunt resistor
43//!   ElectricalResistance::new::<ohm>(0.1),
44//!   // Maximum expected current 3A
45//!   ElectricCurrent::new::<ampere>(3.0),
46//! ).unwrap();
47//!
48//! // One-shot read all values
49//! let measurement = ina228.measure().unwrap();
50//! let bus_voltage = measurement.bus_voltage.get::<millivolt>();
51//! let temperature = measurement.temperature.get::<degree_celsius>();
52//! let current = measurement.current.get::<milliampere>();
53//! let power = measurement.power.get::<milliwatt>();
54//! println!("Current measurement: {:?}mV, {:?}mA, {:?}mW, {:?}°C", bus_voltage, current, power, temperature);
55//! # Ok(())
56//! # }
57//! # }
58//! ```
59//!
60//! ## Usage (async)
61//!
62//! ```rust
63//! # #[cfg(feature = "async")] mod test {
64//! # async fn test<I, D>(mut i2c: I, delay: D) -> Result<(), I::Error>
65//! # where
66//! #   I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType,
67//! #   D: embedded_hal_async::delay::DelayNs
68//! # {
69//! use embedded_devices::devices::texas_instruments::ina228::{INA228Async, address::Address, address::Pin};
70//! use embedded_devices::sensor::OneshotSensorAsync;
71//! use uom::si::electric_current::{ampere, milliampere};
72//! use uom::si::electric_potential::millivolt;
73//! use uom::si::electrical_resistance::ohm;
74//! use uom::si::power::milliwatt;
75//! use uom::si::f64::{ElectricCurrent, ElectricalResistance};
76//! use uom::si::thermodynamic_temperature::degree_celsius;
77//!
78//! // Create and initialize the device
79//! let mut ina228 = INA228Async::new_i2c(delay, i2c, Address::A0A1(Pin::Gnd, Pin::Gnd));
80//! ina228.init(
81//!   // Most units use a 100mΩ shunt resistor
82//!   ElectricalResistance::new::<ohm>(0.1),
83//!   // Maximum expected current 3A
84//!   ElectricCurrent::new::<ampere>(3.0),
85//! ).await.unwrap();
86//!
87//! // One-shot read all values
88//! let measurement = ina228.measure().await.unwrap();
89//! let bus_voltage = measurement.bus_voltage.get::<millivolt>();
90//! let temperature = measurement.temperature.get::<degree_celsius>();
91//! let current = measurement.current.get::<milliampere>();
92//! let power = measurement.power.get::<milliwatt>();
93//! println!("Current measurement: {:?}mV, {:?}mA, {:?}mW, {:?}°C", bus_voltage, current, power, temperature);
94//! # Ok(())
95//! # }
96//! # }
97//! ```
98
99use self::address::Address;
100
101use embedded_devices_derive::{forward_register_fns, sensor};
102use embedded_interfaces::TransportError;
103use registers::AdcRange;
104use uom::si::electric_current::ampere;
105use uom::si::electric_potential::volt;
106use uom::si::electrical_resistance::ohm;
107use uom::si::f64::ThermodynamicTemperature;
108use uom::si::f64::{ElectricCharge, ElectricCurrent, ElectricPotential, ElectricalResistance, Energy, Power};
109
110pub mod address;
111pub mod registers;
112
113#[cfg_attr(feature = "defmt", derive(defmt::Format))]
114#[derive(Debug, thiserror::Error)]
115pub enum InitError<BusError> {
116    /// Transport error
117    #[error("transport error")]
118    Transport(#[from] TransportError<(), BusError>),
119    /// Invalid Device Id was encountered
120    #[error("invalid device id {0:#04x}")]
121    InvalidDeviceId(u16),
122    /// Invalid Manufacturer Id was encountered
123    #[error("invalid manufacturer id {0:#04x}")]
124    InvalidManufacturerId(u16),
125}
126
127#[derive(Debug, thiserror::Error)]
128pub enum MeasurementError<BusError> {
129    /// Transport error
130    #[error("transport error")]
131    Transport(#[from] TransportError<(), BusError>),
132    /// The conversion ready flag was not set within the expected time frame.
133    #[error("conversion timeout")]
134    Timeout,
135    /// Measurement was ready, but an overflow occurred. The power and
136    /// current measurement may be incorrect.
137    #[error("overflow in measurement")]
138    Overflow(Measurement),
139}
140
141#[derive(Debug, thiserror::Error)]
142pub enum ContinuousMeasurementError<BusError> {
143    /// Transport error
144    #[error("transport error")]
145    Transport(#[from] TransportError<(), BusError>),
146    /// Measurement was ready, but an overflow occurred. The power and
147    /// current measurement may be incorrect.
148    #[error("overflow in measurement")]
149    Overflow(Measurement),
150}
151
152/// Measurement data
153#[derive(Debug, embedded_devices_derive::Measurement)]
154pub struct Measurement {
155    /// Measured voltage across the shunt
156    pub shunt_voltage: ElectricPotential,
157    /// Measured voltage on the bus
158    #[measurement(Voltage)]
159    pub bus_voltage: ElectricPotential,
160    /// Measured die temperature
161    #[measurement(Temperature)]
162    pub temperature: ThermodynamicTemperature,
163    /// Measured current
164    #[measurement(Current)]
165    pub current: ElectricCurrent,
166    /// Measured power
167    #[measurement(Power)]
168    pub power: Power,
169    /// Measured energy
170    #[measurement(Energy)]
171    pub energy: Energy,
172    /// Measured charge
173    #[measurement(Charge)]
174    pub charge: ElectricCharge,
175}
176
177/// The INA228 is an ultra-precise digital power monitor with a 20-bit delta-sigma ADC specifically
178/// designed for current-sensing applications. The device can measure a full-scale differential
179/// input of ±163.84 mV or ±40.96 mV across a resistive shunt sense element with common-mode
180/// voltage support from –0.3 V to +85 V.
181///
182/// For a full description and usage examples, refer to the [module documentation](self).
183#[maybe_async_cfg::maybe(
184    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), RegisterInterface),
185    sync(feature = "sync"),
186    async(feature = "async")
187)]
188pub struct INA228<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> {
189    /// The delay provider
190    delay: D,
191    /// The interface to communicate with the device
192    interface: I,
193    /// Shunt resistance
194    shunt_resistance: ElectricalResistance,
195    /// Maximum expected current
196    max_expected_current: ElectricCurrent,
197    /// Configured nA/LSB for current readings
198    pub current_lsb_na: i64,
199    /// The configured adc range
200    pub adc_range: self::registers::AdcRange,
201}
202
203pub trait INA228Register {}
204
205#[maybe_async_cfg::maybe(
206    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), I2cDevice),
207    sync(feature = "sync"),
208    async(feature = "async")
209)]
210impl<D, I> INA228<D, embedded_interfaces::i2c::I2cDevice<I, hal::i2c::SevenBitAddress>>
211where
212    I: hal::i2c::I2c<hal::i2c::SevenBitAddress> + hal::i2c::ErrorType,
213    D: hal::delay::DelayNs,
214{
215    /// Initializes a new device with the given address on the specified bus.
216    /// This consumes the I2C bus `I`.
217    ///
218    /// Before using this device, you should call the [`Self::init`] method which
219    /// saves the calibration values to enable current and power output.
220    #[inline]
221    pub fn new_i2c(delay: D, interface: I, address: Address) -> Self {
222        Self {
223            delay,
224            interface: embedded_interfaces::i2c::I2cDevice::new(interface, address.into()),
225            shunt_resistance: Default::default(),
226            max_expected_current: Default::default(),
227            current_lsb_na: 1,
228            adc_range: AdcRange::Div4,
229        }
230    }
231}
232
233#[forward_register_fns]
234#[sensor(Voltage, Temperature, Current, Power, Energy, Charge)]
235#[maybe_async_cfg::maybe(
236    idents(
237        hal(sync = "embedded_hal", async = "embedded_hal_async"),
238        RegisterInterface,
239        ResettableDevice
240    ),
241    sync(feature = "sync"),
242    async(feature = "async")
243)]
244impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> INA228<D, I> {
245    /// Soft-resets the device, verifies its device id and manufacturer id and
246    /// calibrates it with the given shunt resistor value and maximum expected current.
247    ///
248    /// You can change the values later using [`Self::calibrate`].
249    pub async fn init(
250        &mut self,
251        shunt_resistance: ElectricalResistance,
252        max_expected_current: ElectricCurrent,
253    ) -> Result<(), InitError<I::BusError>> {
254        use crate::device::ResettableDevice;
255        use registers::{DeviceId, ManufacturerId};
256
257        // Reset the device and wait until it is ready. The datasheet specifies
258        // 300µs startup time, so we round up to half a millisecond.
259        self.reset().await?;
260        self.delay.delay_us(500).await;
261
262        // Verify we are talking to the correct device
263        let manufacturer_id = self.read_register::<ManufacturerId>().await?.read_id();
264        if manufacturer_id != ManufacturerId::default().read_id() {
265            return Err(InitError::InvalidManufacturerId(manufacturer_id));
266        }
267        let device_id = self.read_register::<DeviceId>().await?.read_id();
268        if device_id != DeviceId::default().read_id() {
269            return Err(InitError::InvalidDeviceId(device_id));
270        }
271
272        // Calibrate the device
273        self.calibrate(shunt_resistance, max_expected_current).await?;
274        Ok(())
275    }
276
277    /// Writes the given shunt resistance and maximum expected current into the device
278    /// configuration register. Automatically selects the adc range appropriately, leaving 10%
279    /// headroom if possible. This required to enable power and current output.
280    pub async fn calibrate(
281        &mut self,
282        shunt_resistance: ElectricalResistance,
283        max_expected_current: ElectricCurrent,
284    ) -> Result<(), TransportError<(), I::BusError>> {
285        self.shunt_resistance = shunt_resistance;
286        self.max_expected_current = max_expected_current;
287        let max_expected_shunt_voltage = shunt_resistance * max_expected_current;
288
289        let shunt_resistance = shunt_resistance.get::<ohm>();
290        let max_expected_current = max_expected_current.get::<ampere>();
291        let max_expected_shunt_voltage = max_expected_shunt_voltage.get::<volt>();
292
293        self.current_lsb_na = ((1_000_000_000f64 / (1 << 19) as f64) * max_expected_current) as i64;
294        let shunt_resistance_mohm = (1_000.0 * shunt_resistance) as i64;
295
296        // If the expected shunt voltage exceeds this threshold, we enable the input prescaler
297        let div4_threshold = 0.036; // 36mV
298        self.adc_range = if max_expected_shunt_voltage > div4_threshold {
299            self::registers::AdcRange::Div4
300        } else {
301            self::registers::AdcRange::Div1
302        };
303
304        // Set adc range
305        let reg_conf = self.read_register::<self::registers::Configuration>().await?;
306        self.write_register(reg_conf.with_adc_range(self.adc_range)).await?;
307
308        // Calibration Register = 13107.2 x 10^6 x CURRENT_LSB (expected current / 1<<19) x R_SHUNT (in Ω) * 4 / adc_range,
309        // we juggle some numbers around to get a higher precision calculation with u32.
310        let cal = 524_288 * self.current_lsb_na * shunt_resistance_mohm / (10_000_000 * self.adc_range.factor() as i64);
311        self.write_register(self::registers::ShuntCalibration::default().with_raw_value(cal as u16))
312            .await?;
313
314        Ok(())
315    }
316}
317
318#[maybe_async_cfg::maybe(
319    idents(
320        hal(sync = "embedded_hal", async = "embedded_hal_async"),
321        RegisterInterface,
322        ResettableDevice
323    ),
324    sync(feature = "sync"),
325    async(feature = "async")
326)]
327impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::device::ResettableDevice
328    for INA228<D, I>
329{
330    type Error = TransportError<(), I::BusError>;
331
332    /// Performs a soft-reset of the device, restoring internal registers to power-on reset values.
333    async fn reset(&mut self) -> Result<(), Self::Error> {
334        self.write_register(self::registers::Configuration::default().with_reset(true))
335            .await?;
336        Ok(())
337    }
338}
339
340#[maybe_async_cfg::maybe(
341    idents(
342        hal(sync = "embedded_hal", async = "embedded_hal_async"),
343        RegisterInterface,
344        OneshotSensor
345    ),
346    sync(feature = "sync"),
347    async(feature = "async")
348)]
349impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::sensor::OneshotSensor
350    for INA228<D, I>
351{
352    type Error = MeasurementError<I::BusError>;
353    type Measurement = Measurement;
354
355    /// Performs a one-shot measurement. This will set the operating mode to
356    /// [`self::registers::OperatingMode::Triggered´] and enable all conversion outputs causing the
357    /// device to perform a single conversion a return to sleep afterwards.
358    async fn measure(&mut self) -> Result<Self::Measurement, Self::Error> {
359        let reg_adc_conf = self.read_register::<self::registers::AdcConfiguration>().await?;
360
361        // Initiate measurement
362        self.write_register(
363            reg_adc_conf
364                .with_operating_mode(self::registers::OperatingMode::Triggered)
365                .with_enable_temperature(true)
366                .with_enable_shunt(true)
367                .with_enable_bus(true),
368        )
369        .await?;
370
371        // Wait until measurement is ready, plus 100µs extra.
372        let measurement_time_us = reg_adc_conf.read_bus_conversion_time().us()
373            + reg_adc_conf.read_shunt_conversion_time().us()
374            + reg_adc_conf.read_temperature_conversion_time().us();
375        self.delay
376            .delay_us(100 + measurement_time_us * reg_adc_conf.read_average_count().factor() as u32)
377            .await;
378
379        // Wait for the conversion ready flag to be set
380        const TRIES: u8 = 5;
381        for _ in 0..TRIES {
382            let diag = self.read_register::<self::registers::DiagnosticsAndAlert>().await?;
383            if diag.read_conversion_ready() {
384                let bus_voltage = self.read_register::<self::registers::BusVoltage>().await?;
385                let shunt_voltage = self.read_register::<self::registers::ShuntVoltage>().await?;
386                let temperature = self.read_register::<self::registers::Temperature>().await?;
387                let current = self.read_register::<self::registers::Current>().await?;
388                let energy = self.read_register::<self::registers::Energy>().await?;
389                let charge = self.read_register::<self::registers::Charge>().await?;
390                // Reading this register clears the conversion_ready flag
391                let power = self.read_register::<self::registers::Power>().await?;
392
393                let measurement = Measurement {
394                    shunt_voltage: shunt_voltage.read_voltage(self.adc_range),
395                    bus_voltage: bus_voltage.read_value(),
396                    temperature: temperature.read_value(),
397                    current: current.read_current(self.current_lsb_na),
398                    power: power.read_power(self.current_lsb_na),
399                    energy: energy.read_energy(self.current_lsb_na),
400                    charge: charge.read_charge(self.current_lsb_na),
401                };
402
403                if diag.read_math_overflow() {
404                    return Err(MeasurementError::Overflow(measurement));
405                } else {
406                    return Ok(measurement);
407                }
408            }
409
410            self.delay.delay_us(100).await;
411        }
412
413        Err(MeasurementError::Timeout)
414    }
415}
416
417#[maybe_async_cfg::maybe(
418    idents(
419        hal(sync = "embedded_hal", async = "embedded_hal_async"),
420        RegisterInterface,
421        ContinuousSensor
422    ),
423    sync(feature = "sync"),
424    async(feature = "async")
425)]
426impl<D: hal::delay::DelayNs, I: embedded_interfaces::registers::RegisterInterface> crate::sensor::ContinuousSensor
427    for INA228<D, I>
428{
429    type Error = ContinuousMeasurementError<I::BusError>;
430    type Measurement = Measurement;
431
432    /// Starts continuous measurement.
433    async fn start_measuring(&mut self) -> Result<(), Self::Error> {
434        let reg_adc_conf = self.read_register::<self::registers::AdcConfiguration>().await?;
435        self.write_register(
436            reg_adc_conf
437                .with_operating_mode(self::registers::OperatingMode::Continuous)
438                .with_enable_temperature(true)
439                .with_enable_shunt(true)
440                .with_enable_bus(true),
441        )
442        .await?;
443        Ok(())
444    }
445
446    /// Stops continuous measurement.
447    async fn stop_measuring(&mut self) -> Result<(), Self::Error> {
448        let reg_adc_conf = self.read_register::<self::registers::AdcConfiguration>().await?;
449        self.write_register(
450            reg_adc_conf
451                .with_operating_mode(self::registers::OperatingMode::Continuous)
452                .with_enable_temperature(false)
453                .with_enable_shunt(false)
454                .with_enable_bus(false),
455        )
456        .await?;
457        Ok(())
458    }
459
460    /// Expected amount of time between measurements in microseconds.
461    async fn measurement_interval_us(&mut self) -> Result<u32, Self::Error> {
462        let reg_adc_conf = self.read_register::<self::registers::AdcConfiguration>().await?;
463        let measurement_time_us = reg_adc_conf.read_bus_conversion_time().us()
464            + reg_adc_conf.read_shunt_conversion_time().us()
465            + reg_adc_conf.read_temperature_conversion_time().us();
466        Ok(measurement_time_us)
467    }
468
469    /// Returns the most recent measurement. Will never return None.
470    async fn current_measurement(&mut self) -> Result<Option<Self::Measurement>, Self::Error> {
471        let diag = self.read_register::<self::registers::DiagnosticsAndAlert>().await?;
472        let bus_voltage = self.read_register::<self::registers::BusVoltage>().await?;
473        let shunt_voltage = self.read_register::<self::registers::ShuntVoltage>().await?;
474        let temperature = self.read_register::<self::registers::Temperature>().await?;
475        let current = self.read_register::<self::registers::Current>().await?;
476        let energy = self.read_register::<self::registers::Energy>().await?;
477        let charge = self.read_register::<self::registers::Charge>().await?;
478        // Reading this register clears the conversion_ready flag
479        let power = self.read_register::<self::registers::Power>().await?;
480
481        let measurement = Measurement {
482            shunt_voltage: shunt_voltage.read_voltage(self.adc_range),
483            bus_voltage: bus_voltage.read_value(),
484            temperature: temperature.read_value(),
485            current: current.read_current(self.current_lsb_na),
486            power: power.read_power(self.current_lsb_na),
487            energy: energy.read_energy(self.current_lsb_na),
488            charge: charge.read_charge(self.current_lsb_na),
489        };
490
491        if diag.read_math_overflow() {
492            Err(ContinuousMeasurementError::Overflow(measurement))
493        } else {
494            Ok(Some(measurement))
495        }
496    }
497
498    /// Check if new measurements are available.
499    async fn is_measurement_ready(&mut self) -> Result<bool, Self::Error> {
500        let diag = self.read_register::<self::registers::DiagnosticsAndAlert>().await?;
501        Ok(diag.read_conversion_ready())
502    }
503
504    /// Wait indefinitely until new measurements are available and return them. Checks whether data
505    /// is ready in intervals of 100us.
506    async fn next_measurement(&mut self) -> Result<Self::Measurement, Self::Error> {
507        loop {
508            let diag = self.read_register::<self::registers::DiagnosticsAndAlert>().await?;
509            if diag.read_conversion_ready() {
510                let bus_voltage = self.read_register::<self::registers::BusVoltage>().await?;
511                let shunt_voltage = self.read_register::<self::registers::ShuntVoltage>().await?;
512                let temperature = self.read_register::<self::registers::Temperature>().await?;
513                let current = self.read_register::<self::registers::Current>().await?;
514                let energy = self.read_register::<self::registers::Energy>().await?;
515                let charge = self.read_register::<self::registers::Charge>().await?;
516                // Reading this register clears the conversion_ready flag
517                let power = self.read_register::<self::registers::Power>().await?;
518
519                let measurement = Measurement {
520                    shunt_voltage: shunt_voltage.read_voltage(self.adc_range),
521                    bus_voltage: bus_voltage.read_value(),
522                    temperature: temperature.read_value(),
523                    current: current.read_current(self.current_lsb_na),
524                    power: power.read_power(self.current_lsb_na),
525                    energy: energy.read_energy(self.current_lsb_na),
526                    charge: charge.read_charge(self.current_lsb_na),
527                };
528
529                if diag.read_math_overflow() {
530                    return Err(ContinuousMeasurementError::Overflow(measurement));
531                } else {
532                    return Ok(measurement);
533                }
534            }
535
536            self.delay.delay_us(100).await;
537        }
538    }
539}