1use embedded_hal::delay::DelayNs;
4use embedded_hal::i2c::I2c;
5use pamoja_core::Sensor;
6
7use super::{
8 calibration, conversion_ready, minimum_current_lsb_microamps, register, Configuration, Mode,
9 Reading,
10};
11use crate::driver::{read_word, write_word};
12use crate::error::{DriverError, SensorError};
13
14pub const STATUS_POLLS: u8 = 20;
17
18#[derive(Debug)]
49pub struct Ina219<I2C, D> {
50 bus: I2C,
51 delay: D,
52 address: u8,
53 configuration: Configuration,
54 shunt_milliohms: u32,
55 current_lsb_microamps: u32,
56 initialized: bool,
57}
58
59impl<I2C, D> Ina219<I2C, D> {
60 pub fn new(bus: I2C, address: u8, delay: D) -> Self {
74 Ina219 {
75 bus,
76 delay,
77 address,
78 configuration: Configuration::default(),
79 shunt_milliohms: 100,
80 current_lsb_microamps: minimum_current_lsb_microamps(3_200_000),
81 initialized: false,
82 }
83 }
84
85 pub fn with_shunt(mut self, milliohms: u32, max_microamps: u32) -> Self {
100 self.shunt_milliohms = milliohms;
101 self.current_lsb_microamps = minimum_current_lsb_microamps(max_microamps);
102 self.initialized = false;
103 self
104 }
105
106 pub fn with_current_lsb(mut self, microamps: u32) -> Self {
116 self.current_lsb_microamps = microamps;
117 self.initialized = false;
118 self
119 }
120
121 pub fn with_configuration(mut self, configuration: Configuration) -> Self {
131 self.configuration = configuration;
132 self.initialized = false;
133 self
134 }
135
136 pub fn current_lsb_microamps(&self) -> u32 {
138 self.current_lsb_microamps
139 }
140
141 pub fn calibration(&self) -> u16 {
143 calibration(self.current_lsb_microamps, self.shunt_milliohms)
144 }
145
146 pub fn release(self) -> (I2C, D) {
152 (self.bus, self.delay)
153 }
154
155 fn triggered(&self) -> Configuration {
156 Configuration {
157 reset: false,
158 mode: Mode::ShuntAndBusTriggered,
159 ..self.configuration
160 }
161 }
162}
163
164impl<I2C: I2c, D: DelayNs> Ina219<I2C, D> {
165 pub fn init(&mut self) -> Result<(), DriverError<I2C::Error>> {
174 let reset = Configuration {
175 reset: true,
176 ..Configuration::default()
177 };
178 self.write(register::CONFIGURATION, reset.bits())?;
179 let settings = Configuration {
180 reset: false,
181 ..self.configuration
182 };
183 self.write(register::CONFIGURATION, settings.bits())?;
184 let calibration = self.calibration();
185 self.write(register::CALIBRATION, calibration)?;
186 if self.read(register::CALIBRATION)? != calibration {
187 return Err(SensorError::Identity.into());
188 }
189 self.initialized = true;
190 Ok(())
191 }
192
193 pub fn measure(&mut self) -> Result<Reading, DriverError<I2C::Error>> {
206 if !self.initialized {
207 self.init()?;
208 }
209 let triggered = self.triggered();
210 self.write(register::CONFIGURATION, triggered.bits())?;
211 self.delay.delay_us(triggered.conversion_micros());
212
213 let mut bus = None;
214 for _ in 0..STATUS_POLLS {
215 let word = self.read(register::BUS_VOLTAGE)?;
216 if conversion_ready(word) {
217 bus = Some(word);
218 break;
219 }
220 self.delay.delay_ms(1);
221 }
222 let bus = bus.ok_or(DriverError::Timeout)?;
223
224 let shunt = self.read(register::SHUNT_VOLTAGE)? as i16;
225 let current = self.read(register::CURRENT)? as i16;
226 let power = self.read(register::POWER)?;
227 Ok(Reading {
228 shunt,
229 bus,
230 current,
231 power,
232 current_lsb_microamps: self.current_lsb_microamps,
233 })
234 }
235
236 fn read(&mut self, register: u8) -> Result<u16, DriverError<I2C::Error>> {
237 read_word(&mut self.bus, self.address, register).map_err(DriverError::Bus)
238 }
239
240 fn write(&mut self, register: u8, value: u16) -> Result<(), DriverError<I2C::Error>> {
241 write_word(&mut self.bus, self.address, register, value).map_err(DriverError::Bus)
242 }
243}
244
245impl<I2C, D> Sensor for Ina219<I2C, D>
246where
247 I2C: I2c + Send,
248 I2C::Error: core::fmt::Debug,
249 D: DelayNs + Send,
250{
251 type Reading = Reading;
252
253 async fn read(&mut self) -> pamoja_core::Result<Reading> {
254 self.measure().map_err(pamoja_core::Error::from)
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261 use crate::ina219::{
262 bus_register, current_register, power_register, shunt_register, Adc, BASE_ADDRESS,
263 CONFIG_RESET,
264 };
265 use alloc::vec;
266 use alloc::vec::Vec;
267 use embedded_hal::i2c::{ErrorKind, NoAcknowledgeSource};
268 use pamoja_hal::script::{block_on, DelayLog, I2cScript, I2cStep};
269
270 const LSB: u32 = 98;
271
272 fn word(address: u8, register: u8, value: u16) -> I2cStep {
273 let [high, low] = value.to_be_bytes();
274 I2cStep::write(address, [register, high, low])
275 }
276
277 fn reply(address: u8, register: u8, value: u16) -> I2cStep {
278 I2cStep::write_read(address, [register], value.to_be_bytes())
279 }
280
281 fn init_steps(address: u8, settings: u16, calibration: u16) -> Vec<I2cStep> {
282 vec![
283 word(address, register::CONFIGURATION, CONFIG_RESET | 0x8000),
284 word(address, register::CONFIGURATION, settings),
285 word(address, register::CALIBRATION, calibration),
286 reply(address, register::CALIBRATION, calibration),
287 ]
288 }
289
290 fn measure_steps(address: u8, triggered: u16) -> Vec<I2cStep> {
291 vec![
292 word(address, register::CONFIGURATION, triggered),
293 reply(address, register::BUS_VOLTAGE, bus_register(12_000) | 0x02),
294 reply(
295 address,
296 register::SHUNT_VOLTAGE,
297 shunt_register(100_000) as u16,
298 ),
299 reply(
300 address,
301 register::CURRENT,
302 current_register(1_000_000, LSB) as u16,
303 ),
304 reply(address, register::POWER, power_register(12_000_000, LSB)),
305 ]
306 }
307
308 #[test]
309 fn init_resets_configures_calibrates_and_reads_the_calibration_back() {
310 let calibration = calibration(LSB, 100);
311 assert_eq!(calibration, 4179, "the datasheet design example");
312 let mut steps = init_steps(BASE_ADDRESS, CONFIG_RESET, calibration);
313 steps.extend(measure_steps(BASE_ADDRESS, CONFIG_RESET & !0x0004));
314 let mut monitor = Ina219::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new())
315 .with_shunt(100, 3_200_000);
316 assert_eq!(monitor.current_lsb_microamps(), LSB);
317 monitor.init().unwrap();
318 let reading = monitor.measure().unwrap();
319 assert_eq!(reading.bus_millivolts(), 12_000);
320 assert_eq!(reading.shunt_microvolts(), 100_000);
321 assert_eq!(
322 reading.current_microamps(),
323 current_register(1_000_000, LSB) as i32 * LSB as i32
324 );
325 assert!(!reading.math_overflow());
326 let (bus, delay) = monitor.release();
327 assert!(bus.done());
328 assert_eq!(
329 delay.waits_ns(),
330 [1_064_000],
331 "12-bit shunt and bus conversions"
332 );
333 }
334
335 #[test]
336 fn the_adc_settings_set_the_wait_and_reach_the_register() {
337 let configuration = Configuration {
338 bus_adc: Adc::Samples16,
339 shunt_adc: Adc::Samples16,
340 ..Configuration::default()
341 };
342 let settings = configuration.bits();
343 let triggered = (settings & !0x0007) | 0x0003;
344 let mut steps = init_steps(0x41, settings, calibration(100, 100));
345 steps.extend(measure_steps(0x41, triggered));
346 let mut monitor = Ina219::new(I2cScript::new(steps), 0x41, DelayLog::new())
347 .with_shunt(100, 3_200_000)
348 .with_current_lsb(100)
349 .with_configuration(configuration);
350 monitor.measure().unwrap();
351 let (bus, delay) = monitor.release();
352 assert!(bus.done());
353 assert_eq!(delay.waits_ns(), [17_020_000]);
354 }
355
356 #[test]
357 fn a_calibration_that_does_not_read_back_is_an_identity_failure() {
358 let calibration = calibration(LSB, 100);
359 let steps = [
360 word(BASE_ADDRESS, register::CONFIGURATION, CONFIG_RESET | 0x8000),
361 word(BASE_ADDRESS, register::CONFIGURATION, CONFIG_RESET),
362 word(BASE_ADDRESS, register::CALIBRATION, calibration),
363 reply(BASE_ADDRESS, register::CALIBRATION, 0x0000),
364 ];
365 let mut monitor = Ina219::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new());
366 assert_eq!(
367 monitor.init(),
368 Err(DriverError::Sensor(SensorError::Identity))
369 );
370 }
371
372 #[test]
373 fn a_conversion_that_never_finishes_is_a_timeout() {
374 let calibration = calibration(LSB, 100);
375 let mut steps = init_steps(BASE_ADDRESS, CONFIG_RESET, calibration);
376 steps.push(word(
377 BASE_ADDRESS,
378 register::CONFIGURATION,
379 CONFIG_RESET & !0x0004,
380 ));
381 for _ in 0..STATUS_POLLS {
382 steps.push(reply(
383 BASE_ADDRESS,
384 register::BUS_VOLTAGE,
385 bus_register(12_000) & !0x0002,
386 ));
387 }
388 let mut monitor = Ina219::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new());
389 assert_eq!(monitor.measure(), Err(DriverError::Timeout));
390 assert!(monitor.release().0.done());
391 }
392
393 #[test]
394 fn a_bus_fault_surfaces_as_a_bus_error() {
395 let kind = ErrorKind::NoAcknowledge(NoAcknowledgeSource::Address);
396 let mut monitor = Ina219::new(
397 I2cScript::new([I2cStep::fault(BASE_ADDRESS, kind)]),
398 BASE_ADDRESS,
399 DelayLog::new(),
400 );
401 assert!(matches!(monitor.init(), Err(DriverError::Bus(_))));
402 }
403
404 #[test]
405 fn as_a_sensor_it_reads_and_maps_to_one_quantity() {
406 let mut steps = init_steps(BASE_ADDRESS, CONFIG_RESET, calibration(LSB, 100));
407 steps.extend(measure_steps(BASE_ADDRESS, CONFIG_RESET & !0x0004));
408 let monitor = Ina219::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new());
409 let mut watts = monitor.map(|reading| reading.power_microwatts() as f32 / 1e6);
410 let reading = block_on(watts.read()).unwrap();
411 assert!((reading - 12.0).abs() < 0.01);
412 }
413
414 #[test]
415 fn calibration_and_one_reading_make_the_transfers_the_datasheet_gives() {
416 use crate::ina219::{bus_register, current_register, power_register, shunt_register};
417
418 const PART: u8 = crate::ina219::BASE_ADDRESS;
419 const LSB: u32 = 98;
420 let bus = I2cScript::new([
421 I2cStep::write(PART, [0x00, 0xB9, 0x9F]),
422 I2cStep::write(PART, [0x00, 0x39, 0x9F]),
423 I2cStep::write(PART, [0x05, 0x10, 0x53]),
424 I2cStep::write_read(PART, [0x05], [0x10, 0x53]),
425 I2cStep::write(PART, [0x00, 0x39, 0x9B]),
426 I2cStep::write_read(PART, [0x02], (bus_register(12_000) | 0x02).to_be_bytes()),
427 I2cStep::write_read(PART, [0x01], shunt_register(100_000).to_be_bytes()),
428 I2cStep::write_read(PART, [0x04], current_register(1_000_000, LSB).to_be_bytes()),
429 I2cStep::write_read(PART, [0x03], power_register(12_000_000, LSB).to_be_bytes()),
430 ]);
431
432 let mut monitor = Ina219::new(bus, PART, DelayLog::new()).with_shunt(100, 3_200_000);
433 let reading = block_on(Sensor::read(&mut monitor)).expect("the scripted part answers");
434 assert_eq!(reading.bus_millivolts(), 12_000);
435 assert_eq!(reading.shunt_microvolts(), 100_000);
436 assert!((reading.current_microamps() - 1_000_000).abs() < LSB as i32);
437 }
438}