#![warn(unsafe_code, missing_docs)]
#![no_std]
use core::result::Result;
use bitflags::bitflags;
use byteorder::{BigEndian, ByteOrder};
use embedded_hal::{
blocking::spi::{Transfer, Write},
digital::v2::OutputPin,
spi::{Mode, MODE_1},
};
bitflags! {
#[repr(C)]
pub struct Configuration: u16 {
const RST = 0b1000_0000_0000_0000;
const RSTACC = 0b0100_0000_0000_0000;
const CONVDLY = 0b0011_1111_1100_0000;
const TEMPCOMP = 0b0000_0000_0010_0000;
const ADCRANGE = 0b0000_0000_0001_0000;
const RESERVED = 0b0000_0000_0000_1111;
}
}
pub const MODE: Mode = MODE_1;
#[repr(u8)]
enum Register {
Configuration = 0x00,
ShuntCalibration = 0x02,
ShuntVoltage = 0x04,
BusVoltage = 0x05,
DieTemperature = 0x06,
Current = 0x07,
Power = 0x08,
ManufacturerID = 0x3E,
DeviceID = 0x3F,
}
enum Command {
Read,
Write,
}
#[derive(Debug)]
pub enum Error<SPIError, CSError> {
NotConfigured,
SPIError(SPIError),
ChipSelectError(CSError),
}
const BUS_VOLTAGE_UV_PER_LSB: f64 = 195.3125;
const SHUNT_VOLTAGE_NV_PER_LSB_MODE_0: f64 = 312.5;
const SHUNT_VOLTAGE_NV_PER_LSB_MODE_1: f64 = 78.125;
const TEMPERATURE_MC_PER_LSB: f64 = 7.8125;
const POWER_SCALING_FACTOR: f64 = 3.2;
const DENOMINATOR: f64 = (1 << 19) as f64; const INTERNAL_SCALING: f64 = 13107200000.0;
#[inline(always)]
fn calculate_calibration_value(
configuration: Configuration,
shunt_resistance: f64,
current_expected_max: f64,
) -> (f64, u16) {
let scale = if configuration.contains(Configuration::ADCRANGE) {
4.0
} else {
1.0
};
let current_lsb = calculate_current_lsb(current_expected_max);
let shunt_cal = INTERNAL_SCALING * current_lsb * shunt_resistance * scale;
(current_lsb, shunt_cal as u16)
}
#[inline(always)]
fn calculate_current_lsb(current_expected_max: f64) -> f64 {
current_expected_max / DENOMINATOR
}
pub struct INA229<SPI, NCS> {
spi: SPI,
ncs: NCS,
config: Option<Configuration>,
current_lsb: Option<f64>,
}
impl<SPI, NCS, SPIError, CSError> INA229<SPI, NCS>
where
SPI: Transfer<u8, Error = SPIError> + Write<u8, Error = SPIError>,
NCS: OutputPin<Error = CSError>,
{
pub fn new(spi: SPI, ncs: NCS) -> Self {
INA229 {
spi,
ncs,
config: None,
current_lsb: None,
}
}
pub fn release(self) -> (SPI, NCS) {
(self.spi, self.ncs)
}
fn read_register_u16(&mut self, register: Register) -> Result<u16, Error<SPIError, CSError>> {
let mut buffer = [get_frame(register, Command::Read), 0x00, 0x00];
self.ncs.set_low().map_err(Error::ChipSelectError)?;
self.spi.transfer(&mut buffer).map_err(Error::SPIError)?;
self.ncs.set_high().map_err(Error::ChipSelectError)?;
let value = BigEndian::read_u16(&buffer[1..3]);
Ok(value)
}
fn write_register_u16(
&mut self,
register: Register,
value: u16,
) -> Result<(), Error<SPIError, CSError>> {
let mut buffer = [get_frame(register, Command::Write), 0x00, 0x00];
BigEndian::write_u16_into(&[value], &mut buffer[1..3]);
self.ncs.set_low().map_err(Error::ChipSelectError)?;
self.spi.write(&buffer).map_err(Error::SPIError)?;
self.ncs.set_high().map_err(Error::ChipSelectError)?;
Ok(())
}
fn read_register_i16(&mut self, register: Register) -> Result<i16, Error<SPIError, CSError>> {
let mut buffer = [get_frame(register, Command::Read), 0x00, 0x00];
self.ncs.set_low().map_err(Error::ChipSelectError)?;
self.spi.transfer(&mut buffer).map_err(Error::SPIError)?;
self.ncs.set_high().map_err(Error::ChipSelectError)?;
let value = BigEndian::read_i16(&buffer[1..3]);
Ok(value)
}
fn read_register_u24(&mut self, register: Register) -> Result<u32, Error<SPIError, CSError>> {
let mut buffer = [get_frame(register, Command::Read), 0x00, 0x00, 0x00];
self.ncs.set_low().map_err(Error::ChipSelectError)?;
self.spi.transfer(&mut buffer).map_err(Error::SPIError)?;
self.ncs.set_high().map_err(Error::ChipSelectError)?;
let value = BigEndian::read_u24(&buffer[1..4]);
Ok(value)
}
fn read_register_i24(&mut self, register: Register) -> Result<i32, Error<SPIError, CSError>> {
let mut buffer = [get_frame(register, Command::Read), 0x00, 0x00, 0x00];
self.ncs.set_low().map_err(Error::ChipSelectError)?;
self.spi.transfer(&mut buffer).map_err(Error::SPIError)?;
self.ncs.set_high().map_err(Error::ChipSelectError)?;
let value = BigEndian::read_i24(&buffer[1..4]);
Ok(value)
}
pub fn set_configuration(
&mut self,
configuration: Configuration,
) -> Result<(), Error<SPIError, CSError>> {
self.write_register_u16(Register::Configuration, configuration.bits())?;
self.config = Some(configuration);
Ok(())
}
pub fn configuration(&mut self) -> Result<Configuration, Error<SPIError, CSError>> {
self.read_register_u16(Register::Configuration)
.map(Configuration::from_bits_truncate)
.map(|config| {
self.config = Some(config);
config
})
}
pub fn shunt_calibration(&mut self) -> Result<u16, Error<SPIError, CSError>> {
self.read_register_u16(Register::ShuntCalibration)
}
pub fn set_shunt_calibration(&mut self, value: u16) -> Result<(), Error<SPIError, CSError>> {
self.write_register_u16(Register::ShuntCalibration, value)
}
pub fn calibrate(
&mut self,
shunt_resistance: f64,
current_expected_max: f64,
) -> Result<(), Error<SPIError, CSError>> {
if let Some(config) = self.config {
let (current_lsb, value) =
calculate_calibration_value(config, shunt_resistance, current_expected_max);
self.set_shunt_calibration(value)?;
self.current_lsb = Some(current_lsb);
Ok(())
} else {
Err(Error::NotConfigured)
}
}
pub fn configure_and_calibrate(
&mut self,
configuration: Configuration,
shunt_resistance: f64,
current_expected_max: f64,
) -> Result<(), Error<SPIError, CSError>> {
self.set_configuration(configuration)
.and_then(|_| self.calibrate(shunt_resistance, current_expected_max))
}
pub fn bus_voltage_raw(&mut self) -> Result<i32, Error<SPIError, CSError>> {
self.read_register_i24(Register::BusVoltage).map(|x| x >> 4) }
pub fn bus_voltage_microvolts(&mut self) -> Result<f64, Error<SPIError, CSError>> {
self.bus_voltage_raw()
.map(|x| (x as f64) * BUS_VOLTAGE_UV_PER_LSB)
}
pub fn shunt_voltage_raw(&mut self) -> Result<i32, Error<SPIError, CSError>> {
self.read_register_i24(Register::ShuntVoltage)
.map(|x| x >> 4)
}
pub fn shunt_voltage_nanovolts(&mut self) -> Result<f64, Error<SPIError, CSError>> {
if let Some(config) = self.config {
self.shunt_voltage_raw().map(|value| {
if config.contains(Configuration::ADCRANGE) {
(value as f64) * SHUNT_VOLTAGE_NV_PER_LSB_MODE_1
} else {
(value as f64) * SHUNT_VOLTAGE_NV_PER_LSB_MODE_0
}
})
} else {
Err(Error::NotConfigured)
}
}
pub fn temperature_raw(&mut self) -> Result<i16, Error<SPIError, CSError>> {
self.read_register_i16(Register::DieTemperature)
}
pub fn temperature_millidegrees_celsius(&mut self) -> Result<f64, Error<SPIError, CSError>> {
self.temperature_raw()
.map(|x| (x as f64) * TEMPERATURE_MC_PER_LSB)
}
pub fn current_raw(&mut self) -> Result<i32, Error<SPIError, CSError>> {
self.read_register_i24(Register::Current).map(|x| x >> 4) }
pub fn current_amps(&mut self) -> Result<f64, Error<SPIError, CSError>> {
if let Some(current_lsb) = self.current_lsb {
self.current_raw().map(|x| (x as f64) * current_lsb)
} else {
Err(Error::NotConfigured)
}
}
pub fn power_raw(&mut self) -> Result<u32, Error<SPIError, CSError>> {
self.read_register_u24(Register::Power)
}
pub fn power_watts(&mut self) -> Result<f64, Error<SPIError, CSError>> {
if let Some(current_lsb) = self.current_lsb {
self.power_raw()
.map(|x| (x as f64) * current_lsb * POWER_SCALING_FACTOR)
} else {
Err(Error::NotConfigured)
}
}
pub fn manufacturer_id(&mut self) -> Result<u16, Error<SPIError, CSError>> {
self.read_register_u16(Register::ManufacturerID)
}
pub fn device_id(&mut self) -> Result<u16, Error<SPIError, CSError>> {
self.read_register_u16(Register::DeviceID)
}
}
fn get_frame(register: Register, command: Command) -> u8 {
let frame = (register as u8) << 2u8;
match command {
Command::Write => frame & !0b00000001,
Command::Read => frame | 0b00000001,
}
}
#[cfg(test)]
mod tests {
use super::{
calculate_calibration_value, calculate_current_lsb, get_frame, Command, Configuration,
Register,
};
use approx::assert_relative_eq;
#[test]
fn get_frame_manufacturer_read() {
let result = get_frame(Register::ManufacturerID, Command::Read);
assert_eq!(result, 0b1111_1001);
}
#[test]
fn get_frame_device_read() {
let result = get_frame(Register::DeviceID, Command::Read);
assert_eq!(result, 0b1111_1101);
}
#[test]
fn calculate_current_lsb_works() {
let lsb = calculate_current_lsb(10.0); assert_relative_eq!(lsb, 0.0000190735, max_relative = 0.00001);
}
#[test]
fn calculate_calibration_value_works() {
let (_, value) =
calculate_calibration_value(Configuration::from_bits_truncate(0), 0.0162, 10.0);
assert_eq!(value, 4050);
}
}