use embedded_hal::i2c::I2c;
const REG_CONFIG: u8 = 0x00;
const REG_SHUNT1: u8 = 0x01;
const REG_BUS1: u8 = 0x02;
const REG_SHUNT2: u8 = 0x03;
const REG_BUS2: u8 = 0x04;
const REG_SHUNT3: u8 = 0x05;
const REG_BUS3: u8 = 0x06;
const REG_CH1_CRIT: u8 = 0x07;
const REG_CH1_WARN: u8 = 0x08;
const REG_CH2_CRIT: u8 = 0x09;
const REG_CH2_WARN: u8 = 0x0A;
const REG_CH3_CRIT: u8 = 0x0B;
const REG_CH3_WARN: u8 = 0x0C;
const REG_SUM: u8 = 0x0D;
const REG_SUM_LIMIT: u8 = 0x0E;
const REG_MASK_EN: u8 = 0x0F;
const REG_PV_UPPER: u8 = 0x10;
const REG_PV_LOWER: u8 = 0x11;
const REG_MFR_ID: u8 = 0xFE;
const REG_DIE_ID: u8 = 0xFF;
const SHUNT_REGS: [u8; 3] = [REG_SHUNT1, REG_SHUNT2, REG_SHUNT3];
const BUS_REGS: [u8; 3] = [REG_BUS1, REG_BUS2, REG_BUS3];
const CRIT_REGS: [u8; 3] = [REG_CH1_CRIT, REG_CH2_CRIT, REG_CH3_CRIT];
const WARN_REGS: [u8; 3] = [REG_CH1_WARN, REG_CH2_WARN, REG_CH3_WARN];
pub const CF1: u16 = 0x0200;
pub const CF2: u16 = 0x0100;
pub const CF3: u16 = 0x0080;
pub const SF: u16 = 0x0040;
pub const WF1: u16 = 0x0020;
pub const WF2: u16 = 0x0010;
pub const WF3: u16 = 0x0008;
pub const PVF: u16 = 0x0004;
pub const TCF: u16 = 0x0002;
pub const CVRF: u16 = 0x0001;
pub const MODE_POWERDOWN: u8 = 0;
pub const MODE_SHUNT_TRIG: u8 = 1;
pub const MODE_BUS_TRIG: u8 = 2;
pub const MODE_SHUNT_BUS_TRIG: u8 = 3;
pub const MODE_SHUNT_CONT: u8 = 5;
pub const MODE_BUS_CONT: u8 = 6;
pub const MODE_SHUNT_BUS_CONT: u8 = 7;
fn channel_valid(ch: u8) -> u8 {
if ch < 1 || ch > 3 {
1
} else {
ch
}
}
fn write_reg<I2C: I2c>(i2c: &mut I2C, addr: u8, reg: u8, value: u16) -> Result<(), I2C::Error> {
let buf = [reg, (value >> 8) as u8, (value & 0xFF) as u8];
i2c.write(addr, &buf)
}
fn read_reg<I2C: I2c>(i2c: &mut I2C, addr: u8, reg: u8) -> Result<u16, I2C::Error> {
let mut buf = [0u8; 2];
i2c.write_read(addr, &[reg], &mut buf)?;
Ok(((buf[0] as u16) << 8) | buf[1] as u16)
}
fn read_reg_signed<I2C: I2c>(i2c: &mut I2C, addr: u8, reg: u8) -> Result<i16, I2C::Error> {
Ok(read_reg(i2c, addr, reg)? as i16)
}
pub struct Ina3221Minimal<I2C> {
i2c: I2C,
addr: u8,
r_shunt: [f32; 3],
}
impl<I2C: I2c> Ina3221Minimal<I2C> {
pub fn new(i2c: I2C, addr: u8, r_shunt: f32) -> Self {
Self {
i2c,
addr,
r_shunt: [r_shunt, r_shunt, r_shunt],
}
}
pub fn with_r_shunt_per_channel(i2c: I2C, addr: u8, r_shunt: &[f32; 3]) -> Self {
Self {
i2c,
addr,
r_shunt: *r_shunt,
}
}
pub fn voltage(&mut self, channel: u8) -> Result<f32, I2C::Error> {
let ch = channel_valid(channel);
let raw = read_reg(&mut self.i2c, self.addr, BUS_REGS[ch as usize - 1])?;
Ok((raw >> 3) as f32 * 8e-3)
}
pub fn shunt_voltage(&mut self, channel: u8) -> Result<f32, I2C::Error> {
let ch = channel_valid(channel);
let raw = read_reg_signed(&mut self.i2c, self.addr, SHUNT_REGS[ch as usize - 1])?;
Ok(raw as f32 * 5e-6)
}
pub fn current(&mut self, channel: u8) -> Result<f32, I2C::Error> {
let ch = channel_valid(channel);
let sv = self.shunt_voltage(ch)?;
Ok(sv / self.r_shunt[ch as usize - 1])
}
pub fn power(&mut self, channel: u8) -> Result<f32, I2C::Error> {
let ch = channel_valid(channel);
Ok(self.voltage(ch)? * self.current(ch)?)
}
}
pub struct Ina3221Full<I2C> {
inner: Ina3221Minimal<I2C>,
mode: u8,
}
impl<I2C: I2c> Ina3221Full<I2C> {
pub fn new(i2c: I2C, addr: u8, r_shunt: f32) -> Self {
Self {
inner: Ina3221Minimal::new(i2c, addr, r_shunt),
mode: 0x07,
}
}
pub fn with_r_shunt_per_channel(i2c: I2C, addr: u8, r_shunt: &[f32; 3]) -> Self {
Self {
inner: Ina3221Minimal::with_r_shunt_per_channel(i2c, addr, r_shunt),
mode: 0x07,
}
}
pub fn voltage(&mut self, channel: u8) -> Result<f32, I2C::Error> {
self.inner.voltage(channel)
}
pub fn shunt_voltage(&mut self, channel: u8) -> Result<f32, I2C::Error> {
self.inner.shunt_voltage(channel)
}
pub fn current(&mut self, channel: u8) -> Result<f32, I2C::Error> {
self.inner.current(channel)
}
pub fn power(&mut self, channel: u8) -> Result<f32, I2C::Error> {
self.inner.power(channel)
}
pub fn configure(&mut self, avg: u8, vbus_ct: u8, vsh_ct: u8, mode: u8) -> Result<(), I2C::Error> {
let cfg = read_reg(&mut self.inner.i2c, self.inner.addr, REG_CONFIG)?;
let config = ((avg as u16 & 0x07) << 9)
| ((vbus_ct as u16 & 0x07) << 6)
| ((vsh_ct as u16 & 0x07) << 3)
| (mode as u16 & 0x07)
| (cfg & 0x7000);
self.mode = mode & 0x07;
write_reg(&mut self.inner.i2c, self.inner.addr, REG_CONFIG, config)
}
pub fn enable_channel(&mut self, channel: u8, enabled: bool) -> Result<(), I2C::Error> {
let ch = channel_valid(channel);
let cfg = read_reg(&mut self.inner.i2c, self.inner.addr, REG_CONFIG)?;
let bit = 14 - (ch as u8 - 1);
let cfg = if enabled {
cfg | (1u16 << bit)
} else {
cfg & !(1u16 << bit)
};
write_reg(&mut self.inner.i2c, self.inner.addr, REG_CONFIG, cfg)
}
pub fn channel_enabled(&mut self, channel: u8) -> Result<bool, I2C::Error> {
let ch = channel_valid(channel);
let cfg = read_reg(&mut self.inner.i2c, self.inner.addr, REG_CONFIG)?;
let bit = 14 - (ch as u8 - 1);
Ok((cfg & (1u16 << bit)) != 0)
}
pub fn conversion_ready(&mut self) -> Result<bool, I2C::Error> {
Ok(read_reg(&mut self.inner.i2c, self.inner.addr, REG_MASK_EN)? & CVRF != 0)
}
pub fn set_critical_alert(&mut self, channel: u8, limit_v: f32, latch: bool) -> Result<(), I2C::Error> {
let ch = channel_valid(channel);
let raw = (((limit_v / 40e-6) as i32) << 3) as u16 & 0xFFF8;
write_reg(&mut self.inner.i2c, self.inner.addr, CRIT_REGS[ch as usize - 1], raw)?;
let cfg = read_reg(&mut self.inner.i2c, self.inner.addr, REG_MASK_EN)?;
let cfg = if latch { cfg | 0x0400 } else { cfg & !0x0400 };
write_reg(&mut self.inner.i2c, self.inner.addr, REG_MASK_EN, cfg)
}
pub fn set_warning_alert(&mut self, channel: u8, limit_v: f32, latch: bool) -> Result<(), I2C::Error> {
let ch = channel_valid(channel);
let raw = (((limit_v / 40e-6) as i32) << 3) as u16 & 0xFFF8;
write_reg(&mut self.inner.i2c, self.inner.addr, WARN_REGS[ch as usize - 1], raw)?;
let cfg = read_reg(&mut self.inner.i2c, self.inner.addr, REG_MASK_EN)?;
let cfg = if latch { cfg | 0x0800 } else { cfg & !0x0800 };
write_reg(&mut self.inner.i2c, self.inner.addr, REG_MASK_EN, cfg)
}
pub fn alert_flags(&mut self) -> Result<u16, I2C::Error> {
read_reg(&mut self.inner.i2c, self.inner.addr, REG_MASK_EN)
}
pub fn set_summation_channels(&mut self, channels: &[u8], limit_v: f32) -> Result<(), I2C::Error> {
let mut cfg = read_reg(&mut self.inner.i2c, self.inner.addr, REG_MASK_EN)? & !0xE000;
for &ch in channels {
let _ = channel_valid(ch);
cfg |= 1u16 << (15 - (ch - 1));
}
write_reg(&mut self.inner.i2c, self.inner.addr, REG_MASK_EN, cfg)?;
let raw = (((limit_v / 40e-6) as i32) << 1) as u16 & 0xFFFE;
write_reg(&mut self.inner.i2c, self.inner.addr, REG_SUM_LIMIT, raw)
}
pub fn summation_value(&mut self) -> Result<f32, I2C::Error> {
let raw = read_reg_signed(&mut self.inner.i2c, self.inner.addr, REG_SUM)?;
Ok(raw as f32 * 5e-6)
}
pub fn set_power_valid_limits(&mut self, upper_v: f32, lower_v: f32) -> Result<(), I2C::Error> {
let raw_upper = (((upper_v / 8e-3) as i32) << 3) as u16 & 0xFFF8;
let raw_lower = (((lower_v / 8e-3) as i32) << 3) as u16 & 0xFFF8;
write_reg(&mut self.inner.i2c, self.inner.addr, REG_PV_UPPER, raw_upper)?;
write_reg(&mut self.inner.i2c, self.inner.addr, REG_PV_LOWER, raw_lower)
}
pub fn power_valid(&mut self) -> Result<bool, I2C::Error> {
Ok(read_reg(&mut self.inner.i2c, self.inner.addr, REG_MASK_EN)? & PVF != 0)
}
pub fn shutdown(&mut self) -> Result<(), I2C::Error> {
let cfg = read_reg(&mut self.inner.i2c, self.inner.addr, REG_CONFIG)?;
self.mode = (cfg & 0x07) as u8;
write_reg(&mut self.inner.i2c, self.inner.addr, REG_CONFIG, cfg & 0xFFF8)
}
pub fn wake(&mut self) -> Result<(), I2C::Error> {
let cfg = read_reg(&mut self.inner.i2c, self.inner.addr, REG_CONFIG)?;
write_reg(&mut self.inner.i2c, self.inner.addr, REG_CONFIG, (cfg & 0xFFF8) | self.mode as u16)
}
pub fn reset(&mut self) -> Result<(), I2C::Error> {
write_reg(&mut self.inner.i2c, self.inner.addr, REG_CONFIG, 0x8000)
}
pub fn manufacturer_id(&mut self) -> Result<u16, I2C::Error> {
read_reg(&mut self.inner.i2c, self.inner.addr, REG_MFR_ID)
}
pub fn die_id(&mut self) -> Result<u16, I2C::Error> {
read_reg(&mut self.inner.i2c, self.inner.addr, REG_DIE_ID)
}
}