Skip to main content

pamoja_sensors/ina226/
driver.rs

1//! The INA226 driven over I2C: identify, configure, calibrate, trigger, and read.
2
3use embedded_hal::delay::DelayNs;
4use embedded_hal::i2c::I2c;
5use pamoja_core::Sensor;
6
7use super::{
8    calibration, identify, minimum_current_lsb_microamps, register, Configuration, DieId,
9    MaskEnable, Mode, Reading,
10};
11use crate::driver::{read_word, write_word};
12use crate::error::{DriverError, SensorError};
13
14/// How many times the Mask/Enable register is polled, 1 ms apart, after the
15/// conversion time has elapsed before the conversion is called overdue.
16pub const STATUS_POLLS: u8 = 20;
17
18/// An INA226 on an I2C bus, measuring shunt and bus voltage, current, and power on
19/// demand.
20///
21/// [`init`](Ina226::init) resets the part, checks the manufacturer and die id
22/// registers, writes the configuration, programs the calibration register for the
23/// shunt and the current resolution, and reads the calibration back.
24/// [`measure`](Ina226::measure) writes the triggered shunt-and-bus mode, which
25/// starts one conversion of each, waits the conversion time for the averaging and
26/// conversion-time settings, polls the conversion-ready flag in the Mask/Enable
27/// register, then reads the four data registers. Reading the Mask/Enable register
28/// clears the flag, which is why the poll is the only place it is read. As a
29/// [`Sensor`] its reading is the [`Reading`]; one quantity is selected with
30/// [`Sensor::map`].
31///
32/// # Examples
33///
34/// A battery bank's draw, read through an INA226 that is not plugged in yet: 24 V on the
35/// bus and 1 A through a 100 mΩ shunt, with the part calibrated for up to 3.2 A.
36///
37/// ```
38/// use pamoja_core::Sensor;
39/// use pamoja_hal::script::{block_on, DelayLog};
40/// use pamoja_sensors::ina226::{sim, Ina226, BASE_ADDRESS};
41///
42/// let part = sim::reporting(BASE_ADDRESS, 100, 3_200_000, 24_000_000, 1_000_000);
43/// let mut bank = Ina226::new(part, BASE_ADDRESS, DelayLog::new()).with_shunt(100, 3_200_000);
44/// let reading = block_on(bank.read())?;
45/// assert_eq!(reading.bus_microvolts(), 24_000_000);
46/// assert!((reading.current_amps() - 1.0).abs() < 0.001);
47/// # Ok::<(), pamoja_core::Error>(())
48/// ```
49#[derive(Debug)]
50pub struct Ina226<I2C, D> {
51    bus: I2C,
52    delay: D,
53    address: u8,
54    configuration: Configuration,
55    shunt_milliohms: u32,
56    current_lsb_microamps: u32,
57    die: Option<DieId>,
58}
59
60impl<I2C, D> Ina226<I2C, D> {
61    /// Wraps an I2C bus and the part's address.
62    ///
63    /// # Arguments
64    ///
65    /// * `bus` - the I2C bus the part is on.
66    /// * `address` - the address the A1 and A0 pins select, from
67    ///   [`address`](super::address).
68    /// * `delay` - the timer that paces the conversions.
69    ///
70    /// # Returns
71    ///
72    /// The driver, with the reset configuration and a 100 mΩ shunt sized for 3.2 A.
73    pub fn new(bus: I2C, address: u8, delay: D) -> Self {
74        Ina226 {
75            bus,
76            delay,
77            address,
78            configuration: Configuration::RESET,
79            shunt_milliohms: 100,
80            current_lsb_microamps: minimum_current_lsb_microamps(3_200_000),
81            die: None,
82        }
83    }
84
85    /// Sets the shunt and the largest current expected through it.
86    ///
87    /// The current resolution is the smallest the datasheet allows for that maximum;
88    /// the calibration register follows from the two by the datasheet's equation.
89    ///
90    /// # Arguments
91    ///
92    /// * `milliohms` - the shunt resistance.
93    /// * `max_microamps` - the largest current the shunt will carry.
94    ///
95    /// # Returns
96    ///
97    /// The driver.
98    pub fn with_shunt(mut self, milliohms: u32, max_microamps: u32) -> Self {
99        self.shunt_milliohms = milliohms;
100        self.current_lsb_microamps = minimum_current_lsb_microamps(max_microamps);
101        self.die = None;
102        self
103    }
104
105    /// Sets the current resolution outright, for a round figure such as 100 µA.
106    ///
107    /// # Arguments
108    ///
109    /// * `microamps` - microamps per count of the Current register.
110    ///
111    /// # Returns
112    ///
113    /// The driver.
114    pub fn with_current_lsb(mut self, microamps: u32) -> Self {
115        self.current_lsb_microamps = microamps;
116        self.die = None;
117        self
118    }
119
120    /// Sets the averaging and conversion times; the mode is chosen per conversion.
121    ///
122    /// # Arguments
123    ///
124    /// * `configuration` - the settings to write.
125    ///
126    /// # Returns
127    ///
128    /// The driver.
129    pub fn with_configuration(mut self, configuration: Configuration) -> Self {
130        self.configuration = configuration;
131        self.die = None;
132        self
133    }
134
135    /// Returns the current resolution in microamps per count.
136    pub fn current_lsb_microamps(&self) -> u32 {
137        self.current_lsb_microamps
138    }
139
140    /// Returns the calibration word the driver programs.
141    pub fn calibration(&self) -> u16 {
142        calibration(self.current_lsb_microamps, self.shunt_milliohms)
143    }
144
145    /// Returns the die id read at initialization, if it has happened.
146    pub fn die_id(&self) -> Option<DieId> {
147        self.die
148    }
149
150    /// Gives back the bus and the delay.
151    ///
152    /// # Returns
153    ///
154    /// The bus and delay the driver was built from.
155    pub fn release(self) -> (I2C, D) {
156        (self.bus, self.delay)
157    }
158
159    fn triggered(&self) -> Configuration {
160        Configuration {
161            reset: false,
162            mode: Mode::ShuntAndBusTriggered,
163            ..self.configuration
164        }
165    }
166}
167
168impl<I2C: I2c, D: DelayNs> Ina226<I2C, D> {
169    /// Resets the part, checks its identity, writes the configuration and
170    /// calibration, and reads the calibration back.
171    ///
172    /// # Errors
173    ///
174    /// Returns [`DriverError::Bus`] if the bus fails, and [`DriverError::Sensor`]
175    /// with [`SensorError::Identity`] if the id registers are not an INA226's or the
176    /// calibration register does not hold what was written.
177    pub fn init(&mut self) -> Result<(), DriverError<I2C::Error>> {
178        let reset = Configuration {
179            reset: true,
180            ..Configuration::RESET
181        };
182        self.write(register::CONFIGURATION, reset.to_register())?;
183        let manufacturer = self.read(register::MANUFACTURER_ID)?;
184        let die = self.read(register::DIE_ID)?;
185        let die = identify(manufacturer, die)?;
186
187        let settings = Configuration {
188            reset: false,
189            ..self.configuration
190        };
191        self.write(register::CONFIGURATION, settings.to_register())?;
192        let calibration = self.calibration();
193        self.write(register::CALIBRATION, calibration)?;
194        if self.read(register::CALIBRATION)? != calibration {
195            return Err(SensorError::Identity.into());
196        }
197        self.die = Some(die);
198        Ok(())
199    }
200
201    /// Triggers one shunt and bus conversion and returns every result.
202    ///
203    /// Initializes the part first if [`init`](Ina226::init) has not run.
204    ///
205    /// # Returns
206    ///
207    /// The four registers with the current resolution they scale by.
208    ///
209    /// # Errors
210    ///
211    /// Returns [`DriverError::Bus`] if the bus fails, [`DriverError::Timeout`] if
212    /// the conversion-ready flag never sets, or any error of [`init`](Ina226::init).
213    pub fn measure(&mut self) -> Result<Reading, DriverError<I2C::Error>> {
214        if self.die.is_none() {
215            self.init()?;
216        }
217        let triggered = self.triggered();
218        self.write(register::CONFIGURATION, triggered.to_register())?;
219        self.delay.delay_us(triggered.update_microseconds());
220
221        let mut status = None;
222        for _ in 0..STATUS_POLLS {
223            let mask = MaskEnable::from_register(self.read(register::MASK_ENABLE)?);
224            if mask.conversion_ready_flag {
225                status = Some(mask);
226                break;
227            }
228            self.delay.delay_ms(1);
229        }
230        let status = status.ok_or(DriverError::Timeout)?;
231
232        let shunt = self.read(register::SHUNT_VOLTAGE)? as i16;
233        let bus = self.read(register::BUS_VOLTAGE)?;
234        let current = self.read(register::CURRENT)? as i16;
235        let power = self.read(register::POWER)?;
236        Ok(Reading {
237            shunt,
238            bus,
239            current,
240            power,
241            current_lsb_microamps: self.current_lsb_microamps,
242            math_overflow: status.math_overflow,
243        })
244    }
245
246    /// Programs the alert pin: which limit it watches and the limit itself.
247    ///
248    /// # Arguments
249    ///
250    /// * `mask` - the Mask/Enable settings; one alert function at a time.
251    /// * `limit` - the Alert Limit register value, in the units of the register the
252    ///   function compares against.
253    ///
254    /// # Errors
255    ///
256    /// Returns [`DriverError::Bus`] if the bus fails.
257    pub fn set_alert(
258        &mut self,
259        mask: MaskEnable,
260        limit: u16,
261    ) -> Result<(), DriverError<I2C::Error>> {
262        self.write(register::ALERT_LIMIT, limit)?;
263        self.write(register::MASK_ENABLE, mask.to_register())
264    }
265
266    fn read(&mut self, register: u8) -> Result<u16, DriverError<I2C::Error>> {
267        read_word(&mut self.bus, self.address, register).map_err(DriverError::Bus)
268    }
269
270    fn write(&mut self, register: u8, value: u16) -> Result<(), DriverError<I2C::Error>> {
271        write_word(&mut self.bus, self.address, register, value).map_err(DriverError::Bus)
272    }
273}
274
275impl<I2C, D> Sensor for Ina226<I2C, D>
276where
277    I2C: I2c + Send,
278    I2C::Error: core::fmt::Debug,
279    D: DelayNs + Send,
280{
281    type Reading = Reading;
282
283    async fn read(&mut self) -> pamoja_core::Result<Reading> {
284        self.measure().map_err(pamoja_core::Error::from)
285    }
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use crate::ina226::{
292        bus_register, current_register, power_register, shunt_register, Averaging, ConversionTime,
293        BASE_ADDRESS, CONFIG_RESET, MANUFACTURER_ID,
294    };
295    use alloc::vec;
296    use alloc::vec::Vec;
297    use embedded_hal::i2c::{ErrorKind, NoAcknowledgeSource};
298    use pamoja_hal::script::{block_on, DelayLog, I2cScript, I2cStep};
299
300    const LSB: u32 = 98;
301    const DIE: u16 = 0x2260;
302
303    fn word(address: u8, register: u8, value: u16) -> I2cStep {
304        let [high, low] = value.to_be_bytes();
305        I2cStep::write(address, [register, high, low])
306    }
307
308    fn reply(address: u8, register: u8, value: u16) -> I2cStep {
309        I2cStep::write_read(address, [register], value.to_be_bytes())
310    }
311
312    fn init_steps(address: u8, settings: u16, calibration: u16) -> Vec<I2cStep> {
313        vec![
314            word(address, register::CONFIGURATION, CONFIG_RESET | 0x8000),
315            reply(address, register::MANUFACTURER_ID, MANUFACTURER_ID),
316            reply(address, register::DIE_ID, DIE),
317            word(address, register::CONFIGURATION, settings),
318            word(address, register::CALIBRATION, calibration),
319            reply(address, register::CALIBRATION, calibration),
320        ]
321    }
322
323    fn measure_steps(address: u8, triggered: u16, mask: u16) -> Vec<I2cStep> {
324        vec![
325            word(address, register::CONFIGURATION, triggered),
326            reply(address, register::MASK_ENABLE, mask),
327            reply(
328                address,
329                register::SHUNT_VOLTAGE,
330                shunt_register(50_000_000) as u16,
331            ),
332            reply(address, register::BUS_VOLTAGE, bus_register(12_000_000)),
333            reply(
334                address,
335                register::CURRENT,
336                current_register(1_000_000, LSB) as u16,
337            ),
338            reply(address, register::POWER, power_register(12_000_000, LSB)),
339        ]
340    }
341
342    #[test]
343    fn init_resets_identifies_configures_and_calibrates_then_measure_reads_all_four() {
344        let calibration = calibration(LSB, 100);
345        let mut steps = init_steps(BASE_ADDRESS, CONFIG_RESET, calibration);
346        steps.extend(measure_steps(BASE_ADDRESS, 0x4123, 0x0008));
347        let mut monitor = Ina226::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new())
348            .with_shunt(100, 3_200_000);
349        assert_eq!(monitor.die_id(), None);
350        monitor.init().unwrap();
351        assert_eq!(monitor.die_id().map(|die| die.device), Some(0x226));
352
353        let reading = monitor.measure().unwrap();
354        assert_eq!(reading.bus_microvolts(), 12_000_000);
355        assert_eq!(reading.shunt_nanovolts(), 50_000_000);
356        assert!(!reading.math_overflow);
357        let (bus, delay) = monitor.release();
358        assert!(bus.done());
359        assert_eq!(
360            delay.waits_ns(),
361            [2_200_000],
362            "two 1.1 ms conversions, one sample"
363        );
364    }
365
366    #[test]
367    fn averaging_and_conversion_times_set_the_wait_and_the_overflow_flag_is_kept() {
368        let configuration = Configuration {
369            averaging: Averaging::Samples16,
370            bus_conversion_time: ConversionTime::Us8244,
371            shunt_conversion_time: ConversionTime::Us8244,
372            ..Configuration::RESET
373        };
374        let settings = configuration.to_register();
375        let triggered = (settings & !0x0007) | 0x0003;
376        let mut steps = init_steps(0x45, settings, calibration(100, 100));
377        steps.extend(measure_steps(0x45, triggered, 0x0008 | 0x0004));
378        let mut monitor = Ina226::new(I2cScript::new(steps), 0x45, DelayLog::new())
379            .with_shunt(100, 3_200_000)
380            .with_current_lsb(100)
381            .with_configuration(configuration);
382        let reading = monitor.measure().unwrap();
383        assert!(reading.math_overflow);
384        let (bus, delay) = monitor.release();
385        assert!(bus.done());
386        let expected = Configuration {
387            mode: Mode::ShuntAndBusTriggered,
388            ..configuration
389        }
390        .update_microseconds();
391        assert_eq!(delay.waits_ns(), [expected * 1_000]);
392        assert_eq!(expected, 2 * 8_244 * 16);
393    }
394
395    #[test]
396    fn a_part_that_is_not_an_ina226_is_refused_at_the_id_registers() {
397        let steps = [
398            word(BASE_ADDRESS, register::CONFIGURATION, CONFIG_RESET | 0x8000),
399            reply(BASE_ADDRESS, register::MANUFACTURER_ID, MANUFACTURER_ID),
400            reply(BASE_ADDRESS, register::DIE_ID, 0x2190),
401        ];
402        let mut monitor = Ina226::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new());
403        assert_eq!(
404            monitor.init(),
405            Err(DriverError::Sensor(SensorError::Identity))
406        );
407        assert!(monitor.release().0.done());
408    }
409
410    #[test]
411    fn a_conversion_that_never_finishes_is_a_timeout() {
412        let mut steps = init_steps(BASE_ADDRESS, CONFIG_RESET, calibration(LSB, 100));
413        steps.push(word(BASE_ADDRESS, register::CONFIGURATION, 0x4123));
414        for _ in 0..STATUS_POLLS {
415            steps.push(reply(BASE_ADDRESS, register::MASK_ENABLE, 0x0000));
416        }
417        let mut monitor = Ina226::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new());
418        assert_eq!(monitor.measure(), Err(DriverError::Timeout));
419        assert!(monitor.release().0.done());
420    }
421
422    #[test]
423    fn a_bus_fault_surfaces_as_a_bus_error() {
424        let kind = ErrorKind::NoAcknowledge(NoAcknowledgeSource::Address);
425        let mut monitor = Ina226::new(
426            I2cScript::new([I2cStep::fault(BASE_ADDRESS, kind)]),
427            BASE_ADDRESS,
428            DelayLog::new(),
429        );
430        assert!(matches!(monitor.init(), Err(DriverError::Bus(_))));
431    }
432
433    #[test]
434    fn an_alert_writes_the_limit_then_the_mask() {
435        let mut steps = init_steps(BASE_ADDRESS, CONFIG_RESET, calibration(LSB, 100));
436        let mask = MaskEnable {
437            bus_over_limit: true,
438            ..MaskEnable::default()
439        };
440        steps.push(word(
441            BASE_ADDRESS,
442            register::ALERT_LIMIT,
443            bus_register(13_000_000),
444        ));
445        steps.push(word(
446            BASE_ADDRESS,
447            register::MASK_ENABLE,
448            mask.to_register(),
449        ));
450        let mut monitor = Ina226::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new());
451        monitor.init().unwrap();
452        monitor.set_alert(mask, bus_register(13_000_000)).unwrap();
453        assert!(monitor.release().0.done());
454    }
455
456    #[test]
457    fn as_a_sensor_it_reads_and_maps_to_one_quantity() {
458        let mut steps = init_steps(BASE_ADDRESS, CONFIG_RESET, calibration(LSB, 100));
459        steps.extend(measure_steps(BASE_ADDRESS, 0x4123, 0x0008));
460        let monitor = Ina226::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new());
461        let mut volts = monitor.map(|reading| reading.bus_volts());
462        assert!((block_on(volts.read()).unwrap() - 12.0).abs() < 0.001);
463    }
464
465    #[test]
466    fn calibration_and_one_reading_make_the_transfers_the_datasheet_gives() {
467        use crate::ina226::{bus_register, current_register, power_register, shunt_register};
468
469        const PART: u8 = crate::ina226::BASE_ADDRESS;
470        const LSB: u32 = 98;
471        let bus = I2cScript::new([
472            I2cStep::write(PART, [0x00, 0xC1, 0x27]),
473            I2cStep::write_read(PART, [0xFE], [0x54, 0x49]),
474            I2cStep::write_read(PART, [0xFF], [0x22, 0x60]),
475            I2cStep::write(PART, [0x00, 0x41, 0x27]),
476            I2cStep::write(PART, [0x05, 0x02, 0x0A]),
477            I2cStep::write_read(PART, [0x05], [0x02, 0x0A]),
478            I2cStep::write(PART, [0x00, 0x41, 0x23]),
479            I2cStep::write_read(PART, [0x06], [0x00, 0x08]),
480            I2cStep::write_read(PART, [0x01], shunt_register(50_000_000).to_be_bytes()),
481            I2cStep::write_read(PART, [0x02], bus_register(12_000_000).to_be_bytes()),
482            I2cStep::write_read(PART, [0x04], current_register(1_000_000, LSB).to_be_bytes()),
483            I2cStep::write_read(PART, [0x03], power_register(12_000_000, LSB).to_be_bytes()),
484        ]);
485
486        let mut monitor = Ina226::new(bus, PART, DelayLog::new()).with_shunt(100, 3_200_000);
487        let reading = block_on(Sensor::read(&mut monitor)).expect("the scripted part answers");
488        assert_eq!(reading.bus_microvolts(), 12_000_000);
489        assert!((reading.current_amps() - 1.0).abs() < 0.001);
490    }
491}