Skip to main content

pamoja_sensors/ina226/
sim.rs

1//! An INA226 that is not there.
2//!
3//! As with the [INA219](crate::ina219::sim), a current monitor's current and power registers
4//! count in steps the calibration sets, so [`reporting`] takes the same shunt and largest
5//! current a driver is given with [`with_shunt`](super::Ina226::with_shunt). The part carries
6//! TI's manufacturer id and the INA226 die id, and its mask/enable register keeps the three
7//! flags the part sets for itself, with the conversion-ready flag set, so every conversion
8//! reads as finished. [`part`] carries 500 mA at 12 V through the 100 mΩ shunt sized for
9//! 3.2 A a driver starts with.
10
11use pamoja_hal::sim::WordPart;
12
13use super::{
14    bus_register, current_register, minimum_current_lsb_microamps, power_register, register,
15    shunt_register, MaskEnable, DEVICE_ID, MANUFACTURER_ID,
16};
17
18/// The shunt [`part`] sits across, in milliohms.
19pub const SHUNT_MILLIOHMS: u32 = 100;
20
21/// The largest current [`part`] is sized for, in microamps.
22pub const MAX_MICROAMPS: u32 = 3_200_000;
23
24/// The bus voltage [`part`] reports, in microvolts.
25pub const BUS_MICROVOLTS: u32 = 12_000_000;
26
27/// The current [`part`] reports, in microamps.
28pub const MICROAMPS: i32 = 500_000;
29
30/// An INA226 answering at an address, carrying [`MICROAMPS`] at [`BUS_MICROVOLTS`] through
31/// [`SHUNT_MILLIOHMS`].
32///
33/// # Arguments
34///
35/// * `address` - the address its A1 and A0 pins select.
36///
37/// # Returns
38///
39/// The part, which a driver with its default shunt reads.
40#[must_use]
41pub fn part(address: u8) -> WordPart {
42    reporting(
43        address,
44        SHUNT_MILLIOHMS,
45        MAX_MICROAMPS,
46        BUS_MICROVOLTS,
47        MICROAMPS,
48    )
49}
50
51/// An INA226 that reads what it is asked to.
52///
53/// # Arguments
54///
55/// * `address` - the address it answers to.
56/// * `shunt_milliohms` - the shunt, as the driver is told with `with_shunt`.
57/// * `max_microamps` - the largest current, as the driver is told with `with_shunt`.
58/// * `bus_microvolts` - the bus voltage it reports.
59/// * `microamps` - the current it reports; negative flows the other way through the shunt.
60///
61/// # Returns
62///
63/// The part. Its readings land on the steps its registers count in: 1.25 mV of bus,
64/// 2.5 µV of shunt, and the calibration's current step.
65///
66/// # Examples
67///
68/// ```
69/// use pamoja_hal::script::DelayLog;
70/// use pamoja_sensors::ina226::{sim, Ina226, BASE_ADDRESS};
71///
72/// // 24 V on the bus and 1 A through a 100 mΩ shunt, for a part calibrated to 3.2 A.
73/// let part = sim::reporting(BASE_ADDRESS, 100, 3_200_000, 24_000_000, 1_000_000);
74/// let mut monitor = Ina226::new(part, BASE_ADDRESS, DelayLog::new());
75/// let reading = monitor.measure().expect("the part answers");
76/// assert_eq!(reading.bus_microvolts(), 24_000_000);
77/// ```
78#[must_use]
79pub fn reporting(
80    address: u8,
81    shunt_milliohms: u32,
82    max_microamps: u32,
83    bus_microvolts: u32,
84    microamps: i32,
85) -> WordPart {
86    let lsb = minimum_current_lsb_microamps(max_microamps);
87    let shunt_nanovolts = i64::from(microamps) * i64::from(shunt_milliohms);
88    let microwatts = i64::from(bus_microvolts) * i64::from(microamps).abs() / 1_000_000;
89    let quiet = MaskEnable::from_register(0);
90    let flags = MaskEnable {
91        alert_function_flag: true,
92        conversion_ready_flag: true,
93        math_overflow: true,
94        ..quiet
95    }
96    .to_register();
97    let ready = MaskEnable {
98        conversion_ready_flag: true,
99        ..quiet
100    }
101    .to_register();
102    WordPart::new(address)
103        .holding(register::MANUFACTURER_ID, MANUFACTURER_ID)
104        .holding(register::DIE_ID, DEVICE_ID << 4)
105        .holding(register::MASK_ENABLE, ready)
106        .read_only(register::MASK_ENABLE, flags)
107        .holding(
108            register::SHUNT_VOLTAGE,
109            shunt_register(shunt_nanovolts as i32) as u16,
110        )
111        .holding(register::BUS_VOLTAGE, bus_register(bus_microvolts))
112        .holding(register::CURRENT, current_register(microamps, lsb) as u16)
113        .holding(register::POWER, power_register(microwatts as u32, lsb))
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119    use crate::ina226::{identify, Ina226};
120    use pamoja_hal::script::DelayLog;
121
122    #[test]
123    fn the_part_identifies_as_an_ina226() {
124        let part = part(0x40);
125        assert!(identify(
126            part.word(register::MANUFACTURER_ID),
127            part.word(register::DIE_ID)
128        )
129        .is_ok());
130    }
131
132    #[test]
133    fn a_driver_with_the_same_shunt_reads_the_load() {
134        let mut monitor = Ina226::new(part(0x40), 0x40, DelayLog::new());
135        let reading = monitor.measure().expect("a reading");
136        let step = i64::from(minimum_current_lsb_microamps(MAX_MICROAMPS));
137        assert_eq!(reading.bus_microvolts(), BUS_MICROVOLTS);
138        assert!((i64::from(reading.current_microamps()) - i64::from(MICROAMPS)).abs() < step);
139
140        let discharging = reporting(0x45, 2, 20_000_000, 3_300_000, -10_000_000);
141        let mut monitor = Ina226::new(discharging, 0x45, DelayLog::new()).with_shunt(2, 20_000_000);
142        let reading = monitor.measure().expect("a reading");
143        let step = i64::from(minimum_current_lsb_microamps(20_000_000));
144        assert!((i64::from(reading.current_microamps()) + 10_000_000).abs() < step);
145        assert_eq!(reading.bus_microvolts(), 3_300_000);
146    }
147}