1use embedded_hal::delay::DelayNs;
4use embedded_hal::i2c::I2c;
5use pamoja_core::Sensor;
6
7use super::{
8 calibration, identify, minimum_current_lsb_microamps, register, Configuration, DieId,
9 MaskEnable, Mode, 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)]
50pub struct Ina226<I2C, D> {
51 bus: I2C,
52 delay: D,
53 address: u8,
54 configuration: Configuration,
55 shunt_milliohms: u32,
56 current_lsb_microamps: u32,
57 die: Option<DieId>,
58}
59
60impl<I2C, D> Ina226<I2C, D> {
61 pub fn new(bus: I2C, address: u8, delay: D) -> Self {
74 Ina226 {
75 bus,
76 delay,
77 address,
78 configuration: Configuration::RESET,
79 shunt_milliohms: 100,
80 current_lsb_microamps: minimum_current_lsb_microamps(3_200_000),
81 die: None,
82 }
83 }
84
85 pub fn with_shunt(mut self, milliohms: u32, max_microamps: u32) -> Self {
99 self.shunt_milliohms = milliohms;
100 self.current_lsb_microamps = minimum_current_lsb_microamps(max_microamps);
101 self.die = None;
102 self
103 }
104
105 pub fn with_current_lsb(mut self, microamps: u32) -> Self {
115 self.current_lsb_microamps = microamps;
116 self.die = None;
117 self
118 }
119
120 pub fn with_configuration(mut self, configuration: Configuration) -> Self {
130 self.configuration = configuration;
131 self.die = None;
132 self
133 }
134
135 pub fn current_lsb_microamps(&self) -> u32 {
137 self.current_lsb_microamps
138 }
139
140 pub fn calibration(&self) -> u16 {
142 calibration(self.current_lsb_microamps, self.shunt_milliohms)
143 }
144
145 pub fn die_id(&self) -> Option<DieId> {
147 self.die
148 }
149
150 pub fn release(self) -> (I2C, D) {
156 (self.bus, self.delay)
157 }
158
159 fn triggered(&self) -> Configuration {
160 Configuration {
161 reset: false,
162 mode: Mode::ShuntAndBusTriggered,
163 ..self.configuration
164 }
165 }
166}
167
168impl<I2C: I2c, D: DelayNs> Ina226<I2C, D> {
169 pub fn init(&mut self) -> Result<(), DriverError<I2C::Error>> {
178 let reset = Configuration {
179 reset: true,
180 ..Configuration::RESET
181 };
182 self.write(register::CONFIGURATION, reset.to_register())?;
183 let manufacturer = self.read(register::MANUFACTURER_ID)?;
184 let die = self.read(register::DIE_ID)?;
185 let die = identify(manufacturer, die)?;
186
187 let settings = Configuration {
188 reset: false,
189 ..self.configuration
190 };
191 self.write(register::CONFIGURATION, settings.to_register())?;
192 let calibration = self.calibration();
193 self.write(register::CALIBRATION, calibration)?;
194 if self.read(register::CALIBRATION)? != calibration {
195 return Err(SensorError::Identity.into());
196 }
197 self.die = Some(die);
198 Ok(())
199 }
200
201 pub fn measure(&mut self) -> Result<Reading, DriverError<I2C::Error>> {
214 if self.die.is_none() {
215 self.init()?;
216 }
217 let triggered = self.triggered();
218 self.write(register::CONFIGURATION, triggered.to_register())?;
219 self.delay.delay_us(triggered.update_microseconds());
220
221 let mut status = None;
222 for _ in 0..STATUS_POLLS {
223 let mask = MaskEnable::from_register(self.read(register::MASK_ENABLE)?);
224 if mask.conversion_ready_flag {
225 status = Some(mask);
226 break;
227 }
228 self.delay.delay_ms(1);
229 }
230 let status = status.ok_or(DriverError::Timeout)?;
231
232 let shunt = self.read(register::SHUNT_VOLTAGE)? as i16;
233 let bus = self.read(register::BUS_VOLTAGE)?;
234 let current = self.read(register::CURRENT)? as i16;
235 let power = self.read(register::POWER)?;
236 Ok(Reading {
237 shunt,
238 bus,
239 current,
240 power,
241 current_lsb_microamps: self.current_lsb_microamps,
242 math_overflow: status.math_overflow,
243 })
244 }
245
246 pub fn set_alert(
258 &mut self,
259 mask: MaskEnable,
260 limit: u16,
261 ) -> Result<(), DriverError<I2C::Error>> {
262 self.write(register::ALERT_LIMIT, limit)?;
263 self.write(register::MASK_ENABLE, mask.to_register())
264 }
265
266 fn read(&mut self, register: u8) -> Result<u16, DriverError<I2C::Error>> {
267 read_word(&mut self.bus, self.address, register).map_err(DriverError::Bus)
268 }
269
270 fn write(&mut self, register: u8, value: u16) -> Result<(), DriverError<I2C::Error>> {
271 write_word(&mut self.bus, self.address, register, value).map_err(DriverError::Bus)
272 }
273}
274
275impl<I2C, D> Sensor for Ina226<I2C, D>
276where
277 I2C: I2c + Send,
278 I2C::Error: core::fmt::Debug,
279 D: DelayNs + Send,
280{
281 type Reading = Reading;
282
283 async fn read(&mut self) -> pamoja_core::Result<Reading> {
284 self.measure().map_err(pamoja_core::Error::from)
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use crate::ina226::{
292 bus_register, current_register, power_register, shunt_register, Averaging, ConversionTime,
293 BASE_ADDRESS, CONFIG_RESET, MANUFACTURER_ID,
294 };
295 use alloc::vec;
296 use alloc::vec::Vec;
297 use embedded_hal::i2c::{ErrorKind, NoAcknowledgeSource};
298 use pamoja_hal::script::{block_on, DelayLog, I2cScript, I2cStep};
299
300 const LSB: u32 = 98;
301 const DIE: u16 = 0x2260;
302
303 fn word(address: u8, register: u8, value: u16) -> I2cStep {
304 let [high, low] = value.to_be_bytes();
305 I2cStep::write(address, [register, high, low])
306 }
307
308 fn reply(address: u8, register: u8, value: u16) -> I2cStep {
309 I2cStep::write_read(address, [register], value.to_be_bytes())
310 }
311
312 fn init_steps(address: u8, settings: u16, calibration: u16) -> Vec<I2cStep> {
313 vec![
314 word(address, register::CONFIGURATION, CONFIG_RESET | 0x8000),
315 reply(address, register::MANUFACTURER_ID, MANUFACTURER_ID),
316 reply(address, register::DIE_ID, DIE),
317 word(address, register::CONFIGURATION, settings),
318 word(address, register::CALIBRATION, calibration),
319 reply(address, register::CALIBRATION, calibration),
320 ]
321 }
322
323 fn measure_steps(address: u8, triggered: u16, mask: u16) -> Vec<I2cStep> {
324 vec![
325 word(address, register::CONFIGURATION, triggered),
326 reply(address, register::MASK_ENABLE, mask),
327 reply(
328 address,
329 register::SHUNT_VOLTAGE,
330 shunt_register(50_000_000) as u16,
331 ),
332 reply(address, register::BUS_VOLTAGE, bus_register(12_000_000)),
333 reply(
334 address,
335 register::CURRENT,
336 current_register(1_000_000, LSB) as u16,
337 ),
338 reply(address, register::POWER, power_register(12_000_000, LSB)),
339 ]
340 }
341
342 #[test]
343 fn init_resets_identifies_configures_and_calibrates_then_measure_reads_all_four() {
344 let calibration = calibration(LSB, 100);
345 let mut steps = init_steps(BASE_ADDRESS, CONFIG_RESET, calibration);
346 steps.extend(measure_steps(BASE_ADDRESS, 0x4123, 0x0008));
347 let mut monitor = Ina226::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new())
348 .with_shunt(100, 3_200_000);
349 assert_eq!(monitor.die_id(), None);
350 monitor.init().unwrap();
351 assert_eq!(monitor.die_id().map(|die| die.device), Some(0x226));
352
353 let reading = monitor.measure().unwrap();
354 assert_eq!(reading.bus_microvolts(), 12_000_000);
355 assert_eq!(reading.shunt_nanovolts(), 50_000_000);
356 assert!(!reading.math_overflow);
357 let (bus, delay) = monitor.release();
358 assert!(bus.done());
359 assert_eq!(
360 delay.waits_ns(),
361 [2_200_000],
362 "two 1.1 ms conversions, one sample"
363 );
364 }
365
366 #[test]
367 fn averaging_and_conversion_times_set_the_wait_and_the_overflow_flag_is_kept() {
368 let configuration = Configuration {
369 averaging: Averaging::Samples16,
370 bus_conversion_time: ConversionTime::Us8244,
371 shunt_conversion_time: ConversionTime::Us8244,
372 ..Configuration::RESET
373 };
374 let settings = configuration.to_register();
375 let triggered = (settings & !0x0007) | 0x0003;
376 let mut steps = init_steps(0x45, settings, calibration(100, 100));
377 steps.extend(measure_steps(0x45, triggered, 0x0008 | 0x0004));
378 let mut monitor = Ina226::new(I2cScript::new(steps), 0x45, DelayLog::new())
379 .with_shunt(100, 3_200_000)
380 .with_current_lsb(100)
381 .with_configuration(configuration);
382 let reading = monitor.measure().unwrap();
383 assert!(reading.math_overflow);
384 let (bus, delay) = monitor.release();
385 assert!(bus.done());
386 let expected = Configuration {
387 mode: Mode::ShuntAndBusTriggered,
388 ..configuration
389 }
390 .update_microseconds();
391 assert_eq!(delay.waits_ns(), [expected * 1_000]);
392 assert_eq!(expected, 2 * 8_244 * 16);
393 }
394
395 #[test]
396 fn a_part_that_is_not_an_ina226_is_refused_at_the_id_registers() {
397 let steps = [
398 word(BASE_ADDRESS, register::CONFIGURATION, CONFIG_RESET | 0x8000),
399 reply(BASE_ADDRESS, register::MANUFACTURER_ID, MANUFACTURER_ID),
400 reply(BASE_ADDRESS, register::DIE_ID, 0x2190),
401 ];
402 let mut monitor = Ina226::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new());
403 assert_eq!(
404 monitor.init(),
405 Err(DriverError::Sensor(SensorError::Identity))
406 );
407 assert!(monitor.release().0.done());
408 }
409
410 #[test]
411 fn a_conversion_that_never_finishes_is_a_timeout() {
412 let mut steps = init_steps(BASE_ADDRESS, CONFIG_RESET, calibration(LSB, 100));
413 steps.push(word(BASE_ADDRESS, register::CONFIGURATION, 0x4123));
414 for _ in 0..STATUS_POLLS {
415 steps.push(reply(BASE_ADDRESS, register::MASK_ENABLE, 0x0000));
416 }
417 let mut monitor = Ina226::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new());
418 assert_eq!(monitor.measure(), Err(DriverError::Timeout));
419 assert!(monitor.release().0.done());
420 }
421
422 #[test]
423 fn a_bus_fault_surfaces_as_a_bus_error() {
424 let kind = ErrorKind::NoAcknowledge(NoAcknowledgeSource::Address);
425 let mut monitor = Ina226::new(
426 I2cScript::new([I2cStep::fault(BASE_ADDRESS, kind)]),
427 BASE_ADDRESS,
428 DelayLog::new(),
429 );
430 assert!(matches!(monitor.init(), Err(DriverError::Bus(_))));
431 }
432
433 #[test]
434 fn an_alert_writes_the_limit_then_the_mask() {
435 let mut steps = init_steps(BASE_ADDRESS, CONFIG_RESET, calibration(LSB, 100));
436 let mask = MaskEnable {
437 bus_over_limit: true,
438 ..MaskEnable::default()
439 };
440 steps.push(word(
441 BASE_ADDRESS,
442 register::ALERT_LIMIT,
443 bus_register(13_000_000),
444 ));
445 steps.push(word(
446 BASE_ADDRESS,
447 register::MASK_ENABLE,
448 mask.to_register(),
449 ));
450 let mut monitor = Ina226::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new());
451 monitor.init().unwrap();
452 monitor.set_alert(mask, bus_register(13_000_000)).unwrap();
453 assert!(monitor.release().0.done());
454 }
455
456 #[test]
457 fn as_a_sensor_it_reads_and_maps_to_one_quantity() {
458 let mut steps = init_steps(BASE_ADDRESS, CONFIG_RESET, calibration(LSB, 100));
459 steps.extend(measure_steps(BASE_ADDRESS, 0x4123, 0x0008));
460 let monitor = Ina226::new(I2cScript::new(steps), BASE_ADDRESS, DelayLog::new());
461 let mut volts = monitor.map(|reading| reading.bus_volts());
462 assert!((block_on(volts.read()).unwrap() - 12.0).abs() < 0.001);
463 }
464
465 #[test]
466 fn calibration_and_one_reading_make_the_transfers_the_datasheet_gives() {
467 use crate::ina226::{bus_register, current_register, power_register, shunt_register};
468
469 const PART: u8 = crate::ina226::BASE_ADDRESS;
470 const LSB: u32 = 98;
471 let bus = I2cScript::new([
472 I2cStep::write(PART, [0x00, 0xC1, 0x27]),
473 I2cStep::write_read(PART, [0xFE], [0x54, 0x49]),
474 I2cStep::write_read(PART, [0xFF], [0x22, 0x60]),
475 I2cStep::write(PART, [0x00, 0x41, 0x27]),
476 I2cStep::write(PART, [0x05, 0x02, 0x0A]),
477 I2cStep::write_read(PART, [0x05], [0x02, 0x0A]),
478 I2cStep::write(PART, [0x00, 0x41, 0x23]),
479 I2cStep::write_read(PART, [0x06], [0x00, 0x08]),
480 I2cStep::write_read(PART, [0x01], shunt_register(50_000_000).to_be_bytes()),
481 I2cStep::write_read(PART, [0x02], bus_register(12_000_000).to_be_bytes()),
482 I2cStep::write_read(PART, [0x04], current_register(1_000_000, LSB).to_be_bytes()),
483 I2cStep::write_read(PART, [0x03], power_register(12_000_000, LSB).to_be_bytes()),
484 ]);
485
486 let mut monitor = Ina226::new(bus, PART, DelayLog::new()).with_shunt(100, 3_200_000);
487 let reading = block_on(Sensor::read(&mut monitor)).expect("the scripted part answers");
488 assert_eq!(reading.bus_microvolts(), 12_000_000);
489 assert!((reading.current_amps() - 1.0).abs() < 0.001);
490 }
491}