Skip to main content

ina233_rs/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3
4use embedded_hal::i2c::I2c;
5use paste::paste;
6
7/// A list of possible errors that can occur within this crate.
8#[derive(Debug)]
9pub enum Error<E> {
10    /// Represents an I²C communication error.
11    I2C(E),
12
13    /// Signifies that the provided input data was invalid.
14    InvalidInputData,
15}
16
17// SI units
18/// Voltage measurement in Volts.
19pub type Voltage = f32;
20
21/// Current measurement in Amperes.
22pub type Current = f32;
23
24/// Power measurement in Watts.
25pub type Power = f32;
26
27/// Resistance measurement in Ohms.
28pub type Ohms = f32;
29/// Driver for the Texas Instruments INA233 High-Side or Low-Side Measurement, 
30/// Bidirectional Current and Power Monitor.
31///
32/// The INA233 is a current, voltage, and power monitor with an I2C-, SMBus-, 
33/// and PMBus-compatible interface. It monitors current, voltage, and power with 
34/// programmable calibration, conversion times, and averaging.
35///
36/// # Features
37/// - Bus voltage range: 0V to 36V
38/// - Supply voltage: 2.7V to 5.5V  
39/// - Operating temperature: -40°C to +125°C
40/// - Up to 16 programmable I2C addresses
41/// - Integrated power accumulator
42/// - Low-power standby mode (2μA typical)
43///
44/// # Example
45/// ```no_run
46/// use embedded_hal::delay::DelayNs;
47/// use linux_embedded_hal::{Delay, I2cdev};
48/// use ina233_rs::Ina233;
49///
50/// // Initialize I2C interface
51/// let dev = I2cdev::new("/dev/i2c-1").unwrap();
52/// 
53/// // Create INA233 driver instance
54/// // Address: 0x45, Shunt resistance: 8 milliohms  
55/// let mut ina233 = Ina233::new(dev, 0x45, 0.008);
56/// 
57/// // Calibrate for expected maximum current of 10A
58/// ina233.calibrate(10.0, 0.008).unwrap();
59///
60/// // Read measurements
61/// let current = ina233.read_mfr_vshunt_current().unwrap();
62/// let voltage = ina233.read_vin().unwrap(); 
63/// let power = current * voltage;
64/// 
65/// println!("Current: {:.3}A, Voltage: {:.2}V, Power: {:.3}W", 
66///          current, voltage, power);
67/// ```
68pub struct Ina233<I2C> {
69    /// The I2C interface.
70    i2c: I2C,
71
72    /// The I2C address of the INA233 device.
73    address: u8,
74
75    ///Shunt resistance value in Ohms
76    shunt_resistance_ohms: Ohms,
77
78    /// Maximum expected current in Amperes
79    /// Used to calcultate the Current_LSB value and the Power_LSB value
80    max_expected_current: Current,
81
82    /// Current_LSB value in Amperes
83    /// Calculated from the Maximum_Expected_Current/2^15 as defined in the datasheet
84    current_lsb: Current,
85
86    /// Power_LSB value in Watts
87    /// Calculated from the Current_LSB value as defined in the datasheet
88    power_lsb: Power,
89}
90
91impl<I2C, E> Ina233<I2C>
92where
93    I2C: I2c<Error = E>,
94{
95    /// Writes a value to a register of the INA233 device.
96    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    /// Writes a 16-bit value to a register of the INA233 device.
108    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    /// Reads a 16-bit value from a register of the INA233 device.
119    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    /// Reads an 8-bit value from a register of the INA233 device.
126    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    /// Generic read data function for the INA233 device.
132    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    // Match for commands with no data (SendByte)
143    ($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    // Match for read-only commands with 1-byte data
151    ($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    // Match for read-only commands with 1-byte data
161    ($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    // Match for read-only commands with 2-byte data
191    ($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    // Match for read-only commands with 2-byte data
201    ($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     // Match for read-only commands with 6-byte data
213     ($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    // Match for read/write commands with 1-byte data
225    ($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    // Match for read/write commands with 2-byte data
241    ($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(); // Convert to big-endian byte array
247                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    // Match for read/write commands with 2-byte data
259    ($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(); // Convert to big-endian byte array
267                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; // Convert to signed 16-bit integer value
276                Ok(value as $unit * $convertion_factor)
277            }
278        }
279    };
280
281    // Add patterns for other combinations of mode and data size if needed
282}
283
284// Example usage of the macro
285impl<I2C, E> Ina233<I2C>
286where
287    I2C: I2c<Error = E>,
288{
289    // Conversion factors for VIN, VIN_OV_WARN_LIMIT, VIN_UV_WARN_LIMIT as defined in the datasheet
290    pub const VIN_CONVERTION_COEFFICIENT: f32 = 0.00125;
291
292    pub const MFR_READ_SHUNT_CONVERTION_COEFFICIENT: f32 = 0.0000025;
293
294    // Internal constant for MFR_CALIBRATION as defined in the datasheet
295    pub const MFR_CALIBRATION_SCALING_CONSTANTE: f32 = 0.00512;
296
297    pub const DEFAULT_MAX_EXPECTED_CURRENT: f32 = 20971.52;
298    // Conversion factor for PIN, PIN_OP_WARN_LIMIT from Current_LSB as defined in the datasheet
299    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    // SendByte Commands
306    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    // ReadOnly Commands
329    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    // ReadWriteClear Commands
430    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    // ReadWrite Commands
460    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    /// Creates a new INA233 driver instance.
527    /// 
528    /// # Parameters
529    /// * `i2c` - I2C interface implementing embedded-hal I2c trait
530    /// * `address` - I2C address of the INA233 device (0x40-0x4F)
531    /// * `shunt_resistance_ohms` - Shunt resistance value in Ohms
532    /// 
533    /// # Example
534    /// ```no_run
535    /// # use linux_embedded_hal::I2cdev;
536    /// # use ina233_rs::Ina233;
537    /// let i2c = I2cdev::new("/dev/i2c-1").unwrap();
538    /// let ina233 = Ina233::new(i2c, 0x45, 0.008); // 8 milliohm shunt
539    /// ```
540    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    /// Returns the configured shunt resistance value in Ohms.
552    pub fn get_shunt_resistance(&self) -> Ohms {
553        self.shunt_resistance_ohms
554    }
555
556    /// Returns the maximum expected current value in Amperes used for calibration.
557    pub fn get_max_expected_current(&self) -> Current {
558        self.max_expected_current
559    }
560
561    /// Returns the current LSB value in Amperes calculated from the maximum expected current.
562    pub fn get_current_lsb(&self) -> f32 {
563        self.current_lsb
564    }
565
566    /// Calibrates the INA233 device.
567    /// The calibration value is calculated from the maximum expected current and the shunt resistance.
568    /// During the calibration process, the MFR_CALIBRATION register is written with the calculated value.<br>
569    /// **Also the Current_LSB value is calculated and stored.**
570    /// # Parameters
571    /// * `max_expected_current` - Maximum expected current in Amperes
572    /// * `shunt_resistance_ohms` - Shunt resistance value in Ohms
573    /// # Return
574    /// * `calibration_value` - Calibration value written to the MFR_CALIBRATION register
575    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; // 32768 = 2^15
583        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    /// Reads the input voltage from the INA233 device and converts it to current (in Amperes).
591    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) // I = V/R
594    }
595
596    /// Reads the iin register value and converts it to current (in Amperes) using calibration
597    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    /// Reads the pin register value and converts it to power (in Watts) using calibration (instantaneous power)
603    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    /// set the iout_oc_warn_limit register value with the calibration
609    /// # Parameters
610    /// * `limit` - Current limit in Amperes
611    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    /// Reads the iout_oc_warn_limit register value and converts it to current (in Amperes).
621    /// # Return
622    /// * `limit` - Current limit in Amperes
623    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    /// Sets the power warning limit in Watts.
629    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    /// Gets the power warning limit in Watts.
639    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    /// Checks if input current overcurrent warning limit has been exceeded.
645    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    /// Reads the average power from the energy accumulator in Watts.
651    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}