Skip to main content

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}