1#![no_std]
2#![doc = include_str!("../README.md")]
3
4use embedded_hal::i2c::I2c;
5use paste::paste;
6
7#[derive(Debug)]
9pub enum Error<E> {
10 I2C(E),
12
13 InvalidInputData,
15}
16
17pub type Voltage = f32;
20
21pub type Current = f32;
23
24pub type Power = f32;
26
27pub type Ohms = f32;
29pub struct Ina233<I2C> {
69 i2c: I2C,
71
72 address: u8,
74
75 shunt_resistance_ohms: Ohms,
77
78 max_expected_current: Current,
81
82 current_lsb: Current,
85
86 power_lsb: Power,
89}
90
91impl<I2C, E> Ina233<I2C>
92where
93 I2C: I2c<Error = E>,
94{
95 pub(crate) fn write_register(&mut self, register: u8, data: u8) -> Result<(), Error<E>> {
97 let payload: [u8; 2] = [register, data];
98 let addr = self.address;
99 self.i2c.write(addr, &payload).map_err(Error::I2C)
100 }
101
102 pub(crate) fn send_byte(&mut self, register: u8) -> Result<(), Error<E>> {
103 let addr = self.address;
104 self.i2c.write(addr, &[register]).map_err(Error::I2C)
105 }
106
107 pub(crate) fn write_double_register(
109 &mut self,
110 register: u8,
111 data: &[u8; 2],
112 ) -> Result<(), Error<E>> {
113 let payload: [u8; 3] = [register, data[0], data[1]];
114 let addr = self.address;
115 self.i2c.write(addr, &payload).map_err(Error::I2C)
116 }
117
118 pub(crate) fn read_double_register(&mut self, register: u8) -> Result<i16, Error<E>> {
120 let mut data = [0, 0];
121 self.read_data(register, &mut data)
122 .and(Ok((u16::from(data[0]) | (u16::from(data[1]) << 8)) as i16))
123 }
124
125 pub(crate) fn read_register(&mut self, register: u8) -> Result<u8, Error<E>> {
127 let mut data = [0];
128 self.read_data(register, &mut data).and(Ok(data[0]))
129 }
130
131 pub(crate) fn read_data(&mut self, register: u8, data: &mut [u8]) -> Result<(), Error<E>> {
133 let addr = self.address;
134 self.i2c
135 .write_read(addr, &[register], data)
136 .map_err(Error::I2C)
137 }
138}
139
140#[macro_export]
141macro_rules! pmbus_command {
142 ($name:ident, $cmd:expr, SendByte, 0, $comment:tt) => {
144 #[doc = $comment]
145 pub fn $name(&mut self) -> Result<(), Error<E>> {
146 self.send_byte($cmd)
147 }
148 };
149
150 ($name:ident, $cmd:expr, Read, 1, $comment:tt) => {
152 paste!{
153 #[doc = $comment]
154 pub fn [<read_ $name>](&mut self) -> Result<u8, Error<E>> {
155 self.read_register($cmd)
156 }
157 }
158 };
159
160 ($name:ident, $cmd:expr, ReadWriteClear, 1, $comment:tt) => {
162 paste!{
163 #[doc = $comment]
164 #[doc = "Reading from this register clears the status bits"]
165 pub fn [<read_ $name>](&mut self) -> Result<u8, Error<E>> {
166 self.read_register($cmd)
167 }
168 }
169
170 paste!{
171 #[doc = $comment]
172 #[doc = "Read the register value and then clears this register by writing 0xFF"]
173 pub fn [<read_n_clear_ $name>](&mut self) -> Result<u8, Error<E>> {
174 let value = self.read_register($cmd)?;
175 self.write_register($cmd, 0xFF)?;
176 return Ok(value);
177 }
178 }
179
180 paste! {
181 #[doc = "\n**WARNING** Writing to this register might not be supported as it's a Status register"]
182 #[doc = "\nPlease refer to the datasheet for more information\n"]
183 #[doc = $comment]
184 pub fn [<write_ $name>](&mut self, data: u8) -> Result<(), Error<E>> {
185 self.write_register($cmd, data)
186 }
187 }
188 };
189
190 ($name:ident, $cmd:expr, Read, 2, $comment:tt) => {
192 paste!{
193 #[doc = $comment]
194 pub fn [<read_ $name>](&mut self) -> Result<i16, Error<E>> {
195 self.read_double_register($cmd)
196 }
197 }
198 };
199
200 ($name:ident, $cmd:expr, Read, 2, $comment:tt, $convertion_factor:expr, $unit:expr) => {
202 paste!{
203 #[doc = $comment]
204 #[doc = "\nReturns the value converted in the SI unit"]
205 pub fn [<read_ $name>](&mut self) -> Result<$unit, Error<E>> {
206 let result = self.read_double_register($cmd)?;
207 Ok(result as $unit * $convertion_factor)
208 }
209 }
210 };
211
212 ($name:ident, $cmd:expr, Read, 6, $comment:tt) => {
214 paste!{
215 #[doc = $comment]
216 pub fn [<read_ $name>](&mut self) -> Result<[u8;6], Error<E>> {
217 let mut data = [0; 6];
218 self.read_data($cmd, &mut data)?;
219 Ok(data)
220 }
221 }
222 };
223
224 ($name:ident, $cmd:expr, ReadWrite, 1, $comment:tt) => {
226 paste! {
227 #[doc = $comment]
228 pub fn [<write_ $name>](&mut self, data: u8) -> Result<(), Error<E>> {
229 self.write_register($cmd, data)
230 }
231 }
232 paste! {
233 #[doc = $comment]
234 pub fn [<read_ $name>](&mut self) -> Result<u8, Error<E>> {
235 self.read_register($cmd)
236 }
237 }
238 };
239
240 ($name:ident, $cmd:expr, ReadWrite, 2, $comment:tt) => {
242
243 paste!{
244 #[doc = $comment]
245 pub fn [<write_ $name>](&mut self, data: u16) -> Result<(), Error<E>> {
246 let data_bytes = data.to_le_bytes(); self.write_double_register($cmd, &data_bytes)
248 }
249 }
250 paste!{
251 #[doc = $comment]
252 pub fn [<read_ $name>](&mut self) -> Result<i16, Error<E>> {
253 self.read_double_register($cmd)
254 }
255 }
256 };
257
258 ($name:ident, $cmd:expr, ReadWrite, 2, $comment:tt, $convertion_factor:expr, $unit:expr) => {
260
261 paste!{
262 #[doc = $comment]
263 #[doc = "\nReturns the value converted in the SI unit"]
264 pub fn [<write_ $name>](&mut self, value: $unit) -> Result<(), Error<E>> {
265 let data = (value / $convertion_factor) as u16;
266 let data_bytes = data.to_le_bytes(); self.write_double_register($cmd, &data_bytes)
268 }
269 }
270 paste!{
271 #[doc = $comment]
272 #[doc = "\nReturns the value converted in the SI unit"]
273 pub fn [<read_ $name>](&mut self) -> Result<$unit, Error<E>> {
274 let result = self.read_double_register($cmd)?;
275 let value = result; Ok(value as $unit * $convertion_factor)
277 }
278 }
279 };
280
281 }
283
284impl<I2C, E> Ina233<I2C>
286where
287 I2C: I2c<Error = E>,
288{
289 pub const VIN_CONVERTION_COEFFICIENT: f32 = 0.00125;
291
292 pub const MFR_READ_SHUNT_CONVERTION_COEFFICIENT: f32 = 0.0000025;
293
294 pub const MFR_CALIBRATION_SCALING_CONSTANTE: f32 = 0.00512;
296
297 pub const DEFAULT_MAX_EXPECTED_CURRENT: f32 = 20971.52;
298 pub const CURRENT_LSB_TO_POWER_LSB: f32 = 25.0;
300
301 pub const DEFAULT_CURRENT_LSB: f32 = 0.64;
302
303 pub const DEFAULT_POWER_LSB: f32 = Self::DEFAULT_CURRENT_LSB * Self::CURRENT_LSB_TO_POWER_LSB;
304
305 pmbus_command!(
307 clear_faults,
308 0x03,
309 SendByte,
310 0,
311 "Clears the status registers and rearms the black box registers for updating"
312 );
313 pmbus_command!(
314 restore_default_all,
315 0x12,
316 SendByte,
317 0,
318 "Restores internal registers to the default values"
319 );
320 pmbus_command!(
321 clear_ein,
322 0xD6,
323 SendByte,
324 0,
325 "Clears the energy accumulator"
326 );
327
328 pmbus_command!(capability, 0x19, Read, 1, "Retrieves the device capability");
330 pmbus_command!(
331 status_byte,
332 0x78,
333 Read,
334 1,
335 "Retrieves information about the device operating status"
336 );
337 pmbus_command!(
338 status_word,
339 0x79,
340 Read,
341 2,
342 "Retrieves information about the device operating status"
343 );
344 pmbus_command!(
345 ein,
346 0x86,
347 Read,
348 6,
349 "Retrieves the energy reading measurement"
350 );
351 pmbus_command!(
352 vin,
353 0x88,
354 Read,
355 2,
356 "Retrieves the measurement for the VBUS voltage",
357 Self::VIN_CONVERTION_COEFFICIENT,
358 Voltage
359 );
360 pmbus_command!(
361 iin,
362 0x89,
363 Read,
364 2,
365 "Retrieves the input current measurement, supports both positive and negative currents"
366 );
367 pmbus_command!(vout, 0x8B, Read, 2, "Mirrors READ_VIN");
368 pmbus_command!(iout, 0x8C, Read, 2, "Mirror of READ_IN for compatibility");
369 pmbus_command!(
370 pout,
371 0x96,
372 Read,
373 2,
374 "Mirror of READ_PIN for compatibility with possible VBUS connections"
375 );
376 pmbus_command!(pin, 0x97, Read, 2, "Retrieves the input power measurement");
377 pmbus_command!(
378 mfr_id,
379 0x99,
380 Read,
381 2,
382 "Retrieves the manufacturer ID in ASCII characters (TI)"
383 );
384 pmbus_command!(
385 mfr_model,
386 0x9A,
387 Read,
388 6,
389 "Retrieves the device number in ASCII characters (INA233)"
390 );
391 pmbus_command!(
392 mfr_revision,
393 0x9B,
394 Read,
395 2,
396 "Retrieves the device revision letter and number in ASCII (for instance, A0)"
397 );
398 pmbus_command!(
399 mfr_read_vshunt,
400 0xD1,
401 Read,
402 2,
403 "Retrieves the shunt voltage measurement",
404 Self::MFR_READ_SHUNT_CONVERTION_COEFFICIENT,
405 Voltage
406 );
407 pmbus_command!(
408 ti_mfr_id,
409 0xE0,
410 Read,
411 2,
412 "Returns a unique word for the manufacturer ID is ASCII (TI)"
413 );
414 pmbus_command!(
415 ti_mfr_model,
416 0xE1,
417 Read,
418 2,
419 "Returns a unique word for the manufacturer model"
420 );
421 pmbus_command!(
422 ti_mfr_revision,
423 0xE2,
424 Read,
425 2,
426 "Returns a unique word for the manufacturer revision"
427 );
428
429 pmbus_command!(
431 status_iout,
432 0x7B,
433 ReadWriteClear,
434 1,
435 "Retrieves information about the output current status"
436 );
437 pmbus_command!(
438 status_input,
439 0x7C,
440 ReadWriteClear,
441 1,
442 "Retrieves information about the input status"
443 );
444 pmbus_command!(
445 status_cml,
446 0x7E,
447 ReadWriteClear,
448 1,
449 "Retrieves information about the communications status"
450 );
451 pmbus_command!(
452 status_mfr_specific,
453 0x80,
454 ReadWriteClear,
455 1,
456 "Retrieves information about the manufacturer specific device status"
457 );
458
459 pmbus_command!(
461 iout_oc_warn_limit,
462 0x4A,
463 ReadWrite,
464 2,
465 "Retrieves or stores the output overcurrent warn limit threshold"
466 );
467 pmbus_command!(
468 vin_ov_warn_limit,
469 0x57,
470 ReadWrite,
471 2,
472 "Retrieves or stores the input overvoltage warn limit threshold",
473 Self::VIN_CONVERTION_COEFFICIENT,
474 Voltage
475 );
476 pmbus_command!(
477 vin_uv_warn_limit,
478 0x58,
479 ReadWrite,
480 2,
481 "Retrieves or stores the input undervoltage warn limit threshold",
482 Self::VIN_CONVERTION_COEFFICIENT,
483 Voltage
484 );
485 pmbus_command!(
486 pin_op_warn_limit,
487 0x6B,
488 ReadWrite,
489 2,
490 "Retrieves or stores the output overpower warn limit threshold"
491 );
492 pmbus_command!(
493 mfr_adc_config,
494 0xD0,
495 ReadWrite,
496 2,
497 "Configures the ADC averaging modes, conversion times, and operating modes"
498 );
499 pmbus_command!(
500 mfr_alert_mask,
501 0xD2,
502 ReadWrite,
503 1,
504 "Allows masking of device warnings"
505 );
506 pmbus_command!(
507 mfr_calibration,
508 0xD4,
509 ReadWrite,
510 2,
511 "Allows the value of the current-sense resistor calibration value to be input.\nMust be programed at power-up.\nDefault value is set to 1."
512 );
513 pmbus_command!(
514 mfr_device_config,
515 0xD5,
516 ReadWrite,
517 1,
518 "Allows the ALERT pin polarity to be changed"
519 );
520}
521
522impl<I2C, E> Ina233<I2C>
523where
524 I2C: I2c<Error = E>,
525{
526 pub fn new(i2c: I2C, address: u8, shunt_resistance_ohms: f32) -> Self {
541 Ina233 {
542 i2c,
543 address,
544 shunt_resistance_ohms,
545 max_expected_current: Self::DEFAULT_MAX_EXPECTED_CURRENT,
546 current_lsb: Self::DEFAULT_CURRENT_LSB,
547 power_lsb: Self::DEFAULT_POWER_LSB,
548 }
549 }
550
551 pub fn get_shunt_resistance(&self) -> Ohms {
553 self.shunt_resistance_ohms
554 }
555
556 pub fn get_max_expected_current(&self) -> Current {
558 self.max_expected_current
559 }
560
561 pub fn get_current_lsb(&self) -> f32 {
563 self.current_lsb
564 }
565
566 pub fn calibrate(
576 &mut self,
577 max_expected_current: f32,
578 shunt_resistance_ohms: f32,
579 ) -> Result<u16, Error<E>> {
580 self.max_expected_current = max_expected_current;
581 self.shunt_resistance_ohms = shunt_resistance_ohms;
582 self.current_lsb = self.max_expected_current / 32768.0; self.power_lsb = self.current_lsb * Self::CURRENT_LSB_TO_POWER_LSB;
584 let calibration_value = ((Self::MFR_CALIBRATION_SCALING_CONSTANTE)
585 / (self.current_lsb * self.shunt_resistance_ohms))
586 as u16;
587 self.write_mfr_calibration(calibration_value)?;
588 Ok(calibration_value)
589 }
590 pub fn read_mfr_vshunt_current(&mut self) -> Result<Current, Error<E>> {
592 let result = self.read_mfr_read_vshunt()?;
593 Ok(result / self.shunt_resistance_ohms) }
595
596 pub fn read_calibrated_iin(&mut self) -> Result<Current, Error<E>> {
598 let result = self.read_iin()?;
599 Ok(result as f32 * self.current_lsb)
600 }
601
602 pub fn read_calibrated_pin(&mut self) -> Result<Power, Error<E>> {
604 let result = self.read_pin()?;
605 Ok(result as f32 * self.power_lsb)
606 }
607
608 pub fn set_iout_oc_warn_limit(&mut self, limit: Current) -> Result<(), Error<E>> {
612 if limit > self.max_expected_current {
613 return Err(Error::InvalidInputData);
614 }
615 let mut limit = (limit / self.current_lsb) as u16;
616 limit &= 0x7FF8;
617 self.write_iout_oc_warn_limit(limit)
618 }
619
620 pub fn get_iout_oc_warn_limit(&mut self) -> Result<Current, Error<E>> {
624 let limit = self.read_iout_oc_warn_limit()?;
625 Ok(limit as f32 * self.current_lsb)
626 }
627
628 pub fn set_pin_op_warn_limit(&mut self, limit: Power) -> Result<(), Error<E>> {
630 if limit > self.max_expected_current * Self::CURRENT_LSB_TO_POWER_LSB {
631 return Err(Error::InvalidInputData);
632 }
633 let mut limit = (limit / self.power_lsb) as u16;
634 limit &= 0x7FF8;
635 self.write_pin_op_warn_limit(limit)
636 }
637
638 pub fn get_pin_op_warn_limit(&mut self) -> Result<Power, Error<E>> {
640 let limit = self.read_pin_op_warn_limit()?;
641 Ok(limit as f32 * self.power_lsb)
642 }
643
644 pub fn is_iin_oc_warn_limit(&mut self) -> Result<bool, Error<E>> {
646 let status = self.read_n_clear_status_mfr_specific()?;
647 Ok((status & 0x04) != 0)
648 }
649
650 pub fn read_average_power(&mut self) -> Result<Power, Error<E>> {
652 let result = self.read_ein()?;
653 let accumulator = (result[0] as u32) << 8 | (result[1] as u32);
654 let roll_over = result[2] as u32;
655 let sample_count =
656 (result[3] as u32) | ((result[4] as u32) << 8) | ((result[5] as u32) << 16);
657
658 let accumulator_24 = roll_over * 65536 + accumulator;
659 let average_power = (accumulator_24 as f32 / sample_count as f32) * self.power_lsb;
660 self.clear_ein()?;
661 Ok(average_power)
662 }
663}