pamoja_sensors/ina219.rs
1//! Texas Instruments INA219 high-side current, voltage, and power monitor.
2//!
3//! The INA219 measures the voltage across a shunt resistor and the bus voltage, and,
4//! once its calibration register is programmed, computes current and power on the
5//! chip. This module builds the calibration value and decodes each register into a
6//! physical quantity, following the datasheet's equations and its worked design
7//! example, so a solar-battery or microgrid node reads amps and watts directly.
8//!
9//! Currents, voltages, and powers are returned in integer micro-units (microvolts,
10//! microamps, microwatts) so the conversions stay exact without floating point.
11
12/// The INA219 register addresses.
13pub mod register {
14 /// Configuration register: bus-voltage range, gain, ADC settings, and mode.
15 pub const CONFIGURATION: u8 = 0x00;
16 /// Shunt voltage register, signed, 10 µV per count.
17 pub const SHUNT_VOLTAGE: u8 = 0x01;
18 /// Bus voltage register, value in bits 15:3, 4 mV per count.
19 pub const BUS_VOLTAGE: u8 = 0x02;
20 /// Power register, scaled by the calibration register.
21 pub const POWER: u8 = 0x03;
22 /// Current register, scaled by the calibration register.
23 pub const CURRENT: u8 = 0x04;
24 /// Calibration register, sets the current and power scale.
25 pub const CALIBRATION: u8 = 0x05;
26}
27
28/// The power-on value of the configuration register (0x399F): 32 V bus range, gain
29/// /8, 12-bit ADCs, and continuous shunt-and-bus conversion.
30pub const CONFIG_RESET: u16 = 0x399F;
31
32/// Computes the calibration register value for a chosen current resolution and shunt.
33///
34/// This is the datasheet's calibration equation, `Cal = trunc(0.04096 / (Current_LSB
35/// * R_shunt))`, expressed in integer micro-units: with the current LSB in microamps
36/// and the shunt in milliohms, the fixed `0.04096` becomes `40_960_000`.
37///
38/// # Arguments
39///
40/// * `current_lsb_microamps` - the amps-per-count the current register should carry.
41/// * `shunt_milliohms` - the shunt resistor value, in milliohms.
42///
43/// # Returns
44///
45/// The 16-bit value to program into the calibration register. Returns `0` if either
46/// argument is `0`, which is the chip's own uncalibrated state.
47pub fn calibration(current_lsb_microamps: u32, shunt_milliohms: u32) -> u16 {
48 let denominator = current_lsb_microamps.saturating_mul(shunt_milliohms);
49 if denominator == 0 {
50 return 0;
51 }
52 (40_960_000 / denominator) as u16
53}
54
55/// Returns the smallest current LSB, in microamps, that still spans a full-scale
56/// current.
57///
58/// The current register is 15 bits of magnitude, so the minimum resolution is the
59/// maximum expected current divided by 32768, rounded up to the next whole microamp.
60/// The datasheet then rounds this up further to a convenient round number.
61///
62/// # Arguments
63///
64/// * `max_expected_microamps` - the largest current the application will measure.
65///
66/// # Returns
67///
68/// The minimum current LSB in microamps.
69pub fn minimum_current_lsb_microamps(max_expected_microamps: u32) -> u32 {
70 max_expected_microamps.div_ceil(32_768)
71}
72
73/// Decodes the shunt voltage register to microvolts.
74///
75/// # Arguments
76///
77/// * `raw` - the signed shunt voltage register.
78///
79/// # Returns
80///
81/// The shunt voltage in microvolts, at 10 µV per count.
82pub fn shunt_microvolts(raw: i16) -> i32 {
83 raw as i32 * 10
84}
85
86/// Decodes the bus voltage register to millivolts.
87///
88/// The voltage occupies bits 15:3, so the register is shifted right by three before
89/// scaling by the 4 mV LSB; the low bits are the conversion-ready and overflow flags.
90///
91/// # Arguments
92///
93/// * `raw` - the bus voltage register.
94///
95/// # Returns
96///
97/// The bus voltage in millivolts.
98pub fn bus_millivolts(raw: u16) -> u32 {
99 (raw >> 3) as u32 * 4
100}
101
102/// Returns whether the bus voltage register's conversion-ready (CNVR) flag is set.
103///
104/// # Arguments
105///
106/// * `raw` - the bus voltage register.
107///
108/// # Returns
109///
110/// `true` if a conversion has completed and the data is ready to read.
111pub fn conversion_ready(raw: u16) -> bool {
112 raw & 0x0002 != 0
113}
114
115/// Returns whether the bus voltage register's math-overflow (OVF) flag is set.
116///
117/// # Arguments
118///
119/// * `raw` - the bus voltage register.
120///
121/// # Returns
122///
123/// `true` if the power or current calculation overflowed and the readings are invalid.
124pub fn math_overflow(raw: u16) -> bool {
125 raw & 0x0001 != 0
126}
127
128/// Decodes the current register to microamps for a given current LSB.
129///
130/// # Arguments
131///
132/// * `raw` - the signed current register.
133/// * `current_lsb_microamps` - the current LSB the calibration register was set for.
134///
135/// # Returns
136///
137/// The current in microamps.
138pub fn current_microamps(raw: i16, current_lsb_microamps: u32) -> i32 {
139 raw as i32 * current_lsb_microamps as i32
140}
141
142/// Decodes the power register to microwatts for a given current LSB.
143///
144/// The power LSB is fixed by the datasheet at twenty times the current LSB.
145///
146/// # Arguments
147///
148/// * `raw` - the power register.
149/// * `current_lsb_microamps` - the current LSB the calibration register was set for.
150///
151/// # Returns
152///
153/// The power in microwatts.
154pub fn power_microwatts(raw: u16, current_lsb_microamps: u32) -> u32 {
155 raw as u32 * (20 * current_lsb_microamps)
156}
157
158/// Builds the shunt voltage register a monitor reports for a shunt voltage.
159///
160/// The inverse of [`shunt_microvolts`], so a node can be written and tested against
161/// what a monitor sends without one attached.
162///
163/// # Arguments
164///
165/// * `microvolts` - the shunt voltage in microvolts.
166///
167/// # Returns
168///
169/// The signed shunt voltage register, at 10 µV per count.
170pub fn shunt_register(microvolts: i32) -> i16 {
171 (microvolts / 10) as i16
172}
173
174/// Builds the bus voltage register a monitor reports for a bus voltage.
175///
176/// The inverse of [`bus_millivolts`], with the conversion-ready flag set and the
177/// overflow flag clear, which is what a completed conversion reads as.
178///
179/// # Arguments
180///
181/// * `millivolts` - the bus voltage in millivolts.
182///
183/// # Returns
184///
185/// The bus voltage register, the voltage in bits 15:3 at 4 mV per count.
186pub fn bus_register(millivolts: u32) -> u16 {
187 (((millivolts / 4) as u16) << 3) | 0x0002
188}
189
190/// Builds the current register a monitor reports for a current.
191///
192/// The inverse of [`current_microamps`].
193///
194/// # Arguments
195///
196/// * `microamps` - the current in microamps.
197/// * `current_lsb_microamps` - the current LSB the calibration register was set for.
198///
199/// # Returns
200///
201/// The signed current register, or zero if `current_lsb_microamps` is zero.
202pub fn current_register(microamps: i32, current_lsb_microamps: u32) -> i16 {
203 if current_lsb_microamps == 0 {
204 return 0;
205 }
206 (microamps / current_lsb_microamps as i32) as i16
207}
208
209/// Builds the power register a monitor reports for a power.
210///
211/// The inverse of [`power_microwatts`]; the power LSB is fixed by the datasheet at
212/// twenty times the current LSB.
213///
214/// # Arguments
215///
216/// * `microwatts` - the power in microwatts.
217/// * `current_lsb_microamps` - the current LSB the calibration register was set for.
218///
219/// # Returns
220///
221/// The power register, or zero if `current_lsb_microamps` is zero.
222pub fn power_register(microwatts: u32, current_lsb_microamps: u32) -> u16 {
223 if current_lsb_microamps == 0 {
224 return 0;
225 }
226 (microwatts / (20 * current_lsb_microamps)) as u16
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 // The datasheet's worked design example (Table 8): max expected current 15 A,
234 // shunt 2 mΩ, current LSB rounded to 1 mA/bit, bus range 16 V.
235 const CURRENT_LSB: u32 = 1_000; // 1 mA in microamps
236
237 #[test]
238 fn every_register_survives_a_round_trip_through_its_builder() {
239 // The datasheet's worked design example reads 11.98 V, 10 A, and 119.8 W, and
240 // each builder reproduces the register that decodes back to it.
241 assert_eq!(bus_millivolts(bus_register(11_980)), 11_980);
242 assert_eq!(bus_register(11_980) >> 3, 0x5D98 >> 3);
243 assert!(conversion_ready(bus_register(11_980)));
244 assert!(!math_overflow(bus_register(11_980)));
245 assert_eq!(current_register(10_000_000, CURRENT_LSB), 0x2710);
246 assert_eq!(
247 current_microamps(current_register(10_000_000, CURRENT_LSB), CURRENT_LSB),
248 10_000_000
249 );
250 assert_eq!(power_register(119_800_000, CURRENT_LSB), 0x1766);
251 assert_eq!(
252 power_microwatts(power_register(119_800_000, CURRENT_LSB), CURRENT_LSB),
253 119_800_000
254 );
255 assert_eq!(shunt_microvolts(shunt_register(20_000)), 20_000);
256 assert_eq!(current_register(1, 0), 0);
257 assert_eq!(power_register(1, 0), 0);
258 }
259
260 #[test]
261 fn calibration_matches_the_datasheet_example() {
262 // Cal = trunc(0.04096 / (0.001 A * 0.002 Ω)) = 20480 = 0x5000.
263 assert_eq!(calibration(CURRENT_LSB, 2), 20_480);
264 assert_eq!(calibration(CURRENT_LSB, 2), 0x5000);
265 }
266
267 #[test]
268 fn minimum_current_lsb_matches_the_datasheet_example() {
269 // 15 A / 32768 = 457.76 µA, computed up to the next whole microamp.
270 assert_eq!(minimum_current_lsb_microamps(15_000_000), 458);
271 }
272
273 #[test]
274 fn shunt_register_decodes_per_the_datasheet() {
275 // Table 8: shunt register 0x07D0 = 2000 → 20 mV.
276 assert_eq!(shunt_microvolts(0x07D0), 20_000);
277 // Negative full scale at gain /8: -320 mV is register 0x8300 (Figure 20).
278 assert_eq!(shunt_microvolts(i16::from_be_bytes([0x83, 0x00])), -320_000);
279 }
280
281 #[test]
282 fn bus_register_decodes_per_the_datasheet() {
283 // Table 8: bus register 0x5D98 → shifted 0x0BB3 = 2995 → 11.98 V.
284 assert_eq!(bus_millivolts(0x5D98), 11_980);
285 assert!(!conversion_ready(0x5D98));
286 assert!(!math_overflow(0x5D98));
287 // A reading with both status flags set.
288 assert!(conversion_ready(0x1F43));
289 assert!(math_overflow(0x1F43));
290 }
291
292 #[test]
293 fn current_register_decodes_to_the_example_load() {
294 // Table 8: current register 0x2710 = 10000 → 10.0 A.
295 assert_eq!(current_microamps(0x2710, CURRENT_LSB), 10_000_000);
296 }
297
298 #[test]
299 fn power_register_decodes_to_the_example_load() {
300 // Table 8: power register 0x1766 = 5990 → 119.8 W (power LSB 20 mW).
301 assert_eq!(power_microwatts(0x1766, CURRENT_LSB), 119_800_000);
302 }
303
304 #[test]
305 fn an_uncalibrated_request_returns_zero() {
306 assert_eq!(calibration(0, 2), 0);
307 assert_eq!(calibration(CURRENT_LSB, 0), 0);
308 }
309}