pamoja_sensors/ina219/sim.rs
1//! An INA219 that is not there.
2//!
3//! A current monitor's current and power registers count in steps the calibration sets, and
4//! the calibration comes from the shunt and the largest current a driver is told about. So
5//! [`reporting`] takes the same shunt and largest current a driver is given with
6//! [`with_shunt`](super::Ina219::with_shunt), and holds the registers a part across that load
7//! reports once calibrated for them. [`part`] is the common breakout carrying 500 mA at 12 V,
8//! with the 100 mΩ shunt sized for 3.2 A a driver starts with.
9
10use pamoja_hal::sim::WordPart;
11
12use super::{
13 bus_register, current_register, minimum_current_lsb_microamps, power_register, register,
14 shunt_register,
15};
16
17/// The shunt [`part`] sits across, in milliohms: the common breakout's.
18pub const SHUNT_MILLIOHMS: u32 = 100;
19
20/// The largest current [`part`] is sized for, in microamps.
21pub const MAX_MICROAMPS: u32 = 3_200_000;
22
23/// The bus voltage [`part`] reports, in millivolts.
24pub const BUS_MILLIVOLTS: u32 = 12_000;
25
26/// The current [`part`] reports, in microamps.
27pub const MICROAMPS: i32 = 500_000;
28
29/// The conversion-ready flag, CNVR, bit 1 of the bus voltage register.
30const CONVERSION_READY: u16 = 1 << 1;
31
32/// An INA219 answering at an address, carrying [`MICROAMPS`] at [`BUS_MILLIVOLTS`] through
33/// [`SHUNT_MILLIOHMS`].
34///
35/// # Arguments
36///
37/// * `address` - the address it answers to, [`BASE_ADDRESS`](super::BASE_ADDRESS) plus what
38/// its A1 and A0 pins add.
39///
40/// # Returns
41///
42/// The part, which a driver with its default shunt reads.
43#[must_use]
44pub fn part(address: u8) -> WordPart {
45 reporting(
46 address,
47 SHUNT_MILLIOHMS,
48 MAX_MICROAMPS,
49 BUS_MILLIVOLTS,
50 MICROAMPS,
51 )
52}
53
54/// An INA219 that reads what it is asked to.
55///
56/// # Arguments
57///
58/// * `address` - the address it answers to.
59/// * `shunt_milliohms` - the shunt, as the driver is told with `with_shunt`.
60/// * `max_microamps` - the largest current, as the driver is told with `with_shunt`.
61/// * `bus_millivolts` - the bus voltage it reports.
62/// * `microamps` - the current it reports; negative flows the other way through the shunt.
63///
64/// # Returns
65///
66/// The part. Its readings land on the steps its registers count in: 4 mV of bus, 10 µV of
67/// shunt, and the calibration's current step.
68///
69/// # Examples
70///
71/// ```
72/// use pamoja_hal::script::DelayLog;
73/// use pamoja_sensors::ina219::{sim, Ina219, BASE_ADDRESS};
74///
75/// let part = sim::reporting(BASE_ADDRESS, 100, 3_200_000, 5_000, 250_000);
76/// let mut monitor = Ina219::new(part, BASE_ADDRESS, DelayLog::new());
77/// let reading = monitor.measure().expect("the part answers");
78/// assert_eq!(reading.bus_millivolts(), 5_000);
79/// ```
80#[must_use]
81pub fn reporting(
82 address: u8,
83 shunt_milliohms: u32,
84 max_microamps: u32,
85 bus_millivolts: u32,
86 microamps: i32,
87) -> WordPart {
88 let lsb = minimum_current_lsb_microamps(max_microamps);
89 let shunt_microvolts = i64::from(microamps) * i64::from(shunt_milliohms) / 1_000;
90 let microwatts = i64::from(bus_millivolts) * i64::from(microamps).abs() / 1_000;
91 WordPart::new(address)
92 .holding(
93 register::SHUNT_VOLTAGE,
94 shunt_register(shunt_microvolts as i32) as u16,
95 )
96 .holding(
97 register::BUS_VOLTAGE,
98 bus_register(bus_millivolts) | CONVERSION_READY,
99 )
100 .holding(register::CURRENT, current_register(microamps, lsb) as u16)
101 .holding(register::POWER, power_register(microwatts as u32, lsb))
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::ina219::{conversion_ready, Ina219, BASE_ADDRESS};
108 use pamoja_hal::script::DelayLog;
109
110 #[test]
111 fn the_flag_is_the_one_the_decoder_reads() {
112 assert!(conversion_ready(CONVERSION_READY));
113 }
114
115 #[test]
116 fn a_driver_with_the_same_shunt_reads_the_load() {
117 let mut monitor = Ina219::new(part(BASE_ADDRESS), BASE_ADDRESS, DelayLog::new());
118 let reading = monitor.measure().expect("a reading");
119 let step = i64::from(minimum_current_lsb_microamps(MAX_MICROAMPS));
120 assert_eq!(reading.bus_millivolts(), BUS_MILLIVOLTS);
121 assert!((i64::from(reading.current_microamps()) - i64::from(MICROAMPS)).abs() < step);
122 assert_eq!(reading.shunt_microvolts(), 50_000);
123
124 let charging = reporting(0x41, 20, 8_000_000, 13_800, -2_500_000);
125 let mut monitor = Ina219::new(charging, 0x41, DelayLog::new()).with_shunt(20, 8_000_000);
126 let reading = monitor.measure().expect("a reading");
127 let step = i64::from(minimum_current_lsb_microamps(8_000_000));
128 assert!((i64::from(reading.current_microamps()) + 2_500_000).abs() < step);
129 assert_eq!(reading.bus_millivolts(), 13_800);
130 }
131}