lc709203 0.3.0

Platform-agnostic Rust driver for the LC709302 battery gauge sensor.
Documentation
//! Low level device access.

use embedded_hal::i2c::I2c;

/// default 7bit I2C address
pub const DEFAULT_I2C_ADDRESS: u8 = 0x0b;

pub mod registers {
    #![allow(dead_code)]
    //! Known I2C registers of the LC709203 chip.

    pub const TIME_TO_EMPTY: u8 = 0x03;
    pub const BEFORE_RSOC: u8 = 0x04;
    pub const TIME_TO_FULL: u8 = 0x05;
    pub const TSENSE1_THERMISTOR_B: u8 = 0x06;
    pub const INIT_RSOC: u8 = 0x07;
    pub const CELL_TEMPERATURE: u8 = 0x08;
    pub const CELL_VOLTAGE: u8 = 0x09;
    pub const CURRENT_DIRECTION: u8 = 0x0a;
    pub const APA: u8 = 0x0b;
    pub const APT: u8 = 0x0c;
    pub const RSOC: u8 = 0x0d;
    pub const TSENSE2_THERMISTOR_B: u8 = 0x0e;
    pub const ITE: u8 = 0x0f;
    pub const IC_VERSION: u8 = 0x11;
    pub const BATTERY_PROFILE: u8 = 0x12;
    pub const ALARM_LOW_RSOC: u8 = 0x13;
    pub const ALARM_LOW_CELL_VOLTAGE: u8 = 0x14;
    pub const POWER_MODE: u8 = 0x15;
    pub const STATUS_BIT: u8 = 0x16;
    pub const CYCLE_COUNT: u8 = 0x17;
    pub const BATTERY_STATUS: u8 = 0x19;
    pub const BATTERY_PROFILE_CODE: u8 = 0x1a;
    pub const TERMINATION_CURRENT_RATE: u8 = 0x1c;
    pub const EMPTY_CELL_VOLTAGE: u8 = 0x1d;
    pub const ITE_OFFSET: u8 = 0x1e;
    pub const ALARM_HIGH_CELL_VOLTAGE: u8 = 0x1f;
    pub const ALARM_LOW_TEMPERATURE: u8 = 0x20;
    pub const ALARM_HIGH_TEMPERATURE: u8 = 0x21;
    pub const TOTAL_RUNTIME_LOW: u8 = 0x24;
    pub const TOTAL_RUNTIME_HIGH: u8 = 0x25;
    pub const ACCUMULATED_TEMPERATURE_LOW: u8 = 0x26;
    pub const ACCUMULATED_TEMPERATURE_HIGH: u8 = 0x27;
    pub const ACCUMULATED_RSOC_LOW: u8 = 0x28;
    pub const ACCUMULATED_RSOC_HIGH: u8 = 0x29;
    pub const MAXIMUM_CELL_VOLTAGE: u8 = 0x2a;
    pub const MINIMUM_CELL_VOLTAGE: u8 = 0x2b;
    pub const MAXIMUM_CELL_TEMPERATURE: u8 = 0x2c;
    pub const MINIMUM_CELL_TEMPERATURE: u8 = 0x2d;
    pub const AMBIENT_TEMPERATURE: u8 = 0x30;
    pub const BATTERY_HEALTH: u8 = 0x32;
    pub const USER_ID_LOW: u8 = 0x36;
    pub const USER_ID_HIGH: u8 = 0x37;
}

pub mod constants {
    #![allow(dead_code)]
    //! Some specific constants for interfacing with the LC709203 chip.

    pub const BEFORE_RSOC_1ST_SAMPLE: u16 = 0xaa55;
    pub const BEFORE_RSOC_2ND_SAMPLE: u16 = 0xaa56;
    pub const BEFORE_RSOC_3RD_SAMPLE: u16 = 0xaa57;
    pub const BEFORE_RSOC_4TH_SAMPLE: u16 = 0xaa58;
    pub const INIT_RSOC: u16 = 0xaa55;
    pub const BATTERY_TYPE01: u16 = 0x00;
    pub const BATTERY_TYPE04: u16 = 0x01;
    pub const BATTERY_TYPE05: u16 = 0x02;
    pub const BATTERY_TYPE06: u16 = 0x03;
    pub const BATTERY_TYPE07: u16 = 0x04;
    pub const IC_POWER_MODE_OPERATION: u16 = 0x01;
    pub const IC_POWER_MODE_SLEEP: u16 = 0x02;
    pub const BATTERY_STATUS_HIGH_CELL_VOLTAGE: u16 = 1 << 15;
    pub const BATTERY_STATUS_HIGH_TEMPERATURE: u16 = 1 << 12;
    pub const BATTERY_STATUS_LOW_CELL_VOLTAGE: u16 = 1 << 11;
    pub const BATTERY_STATUS_LOW_RSOC: u16 = 1 << 9;
    pub const BATTERY_STATUS_LOW_TEMPERATURE: u16 = 1 << 8;
    pub const BATTERY_STATUS_INITIALIZED: u16 = 1 << 7;
    pub const BATTERY_STATUS_DISCHARGING: u16 = 1 << 6;
    pub const STATUS_BIT_TSENSE1: u16 = 1 << 0;
    pub const STATUS_BIT_TSENSE2: u16 = 1 << 1;
}

/// Struct for low level device access. Functions usually don't do anything more than read/write a
/// 16-bit chip register.
pub struct Device<'a, I2C> {
    address: u8,
    i2c: &'a mut I2C,
}

/// Error during communication with the chip.
#[derive(Debug)]
pub enum ChipError<E> {
    /// Error from the I2C implementation.
    I2CError(E),
    /// CRC mismatch during read. The read value is still returned.
    CrcMismatch(u16),
}

impl<E> From<E> for ChipError<E> {
    fn from(e: E) -> Self {
        Self::I2CError(e)
    }
}

impl<'a, I2C, E> Device<'a, I2C>
where
    I2C: I2c<Error = E>,
{
    pub fn new(address: u8, i2c: &'a mut I2C) -> Self {
        Self { address, i2c }
    }

    fn compute_crc_8(bytes: &[u8]) -> u8 {
        let mut crc = 0;
        for byte in bytes {
            let byte = *byte;
            crc ^= byte;
            for _ in 0..8 {
                if (crc & 0x80) == 0 {
                    crc = crc << 1;
                } else {
                    crc = (crc << 1) ^ 0x07;
                }
            }
        }
        crc
    }

    pub fn read_register(&mut self, register: u8) -> Result<u16, ChipError<E>> {
        // read protocol: slave address, register, slave address (read), data byte low, data byte high, crc
        let mut data: [u8; 6] = [
            self.address << 1,
            register,
            (self.address << 1) | 0x01,
            0,
            0,
            0,
        ];
        self.i2c
            .write_read(self.address, &[register], &mut data[3..])?;
        #[cfg(feature = "log")]
        log::trace!("Read bytes: {:2x?}", &data[..]);
        let value = (data[4] as u16) << 8 | (data[3] as u16);
        let crc = Self::compute_crc_8(&data[0..5]);
        if crc != data[5] {
            #[cfg(feature = "log")]
            log::warn!(
                "CRC didn't match! Got {:2x} but expected {:2x}",
                data[5],
                crc
            );
            return Err(ChipError::CrcMismatch(value));
        }
        Ok(value)
    }

    pub fn write_register(&mut self, register: u8, value: u16) -> Result<(), ChipError<E>> {
        // write protocol: slave address, register, data byte low, data byte high, crc
        let mut data: [u8; 5] = [
            self.address << 1,
            register,
            (value & 0xff) as u8,
            ((value & 0xff00) >> 8) as u8,
            0,
        ];
        data[4] = Self::compute_crc_8(&data[0..4]);
        #[cfg(feature = "log")]
        log::trace!("Writing data: {:2x?}", data);
        self.i2c.write(self.address, &data[1..])?;
        Ok(())
    }

    pub fn time_to_empty(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::TIME_TO_EMPTY)
    }

    pub fn time_to_full(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::TIME_TO_FULL)
    }

    pub fn write_before_rsoc(&mut self, value: u16) -> Result<(), ChipError<E>> {
        self.write_register(registers::BEFORE_RSOC, value)
    }

    pub fn write_init_rsoc(&mut self, value: u16) -> Result<(), ChipError<E>> {
        self.write_register(registers::INIT_RSOC, value)
    }

    pub fn write_battery_type(&mut self, value: u16) -> Result<(), ChipError<E>> {
        self.write_register(registers::BATTERY_PROFILE, value)
    }

    pub fn write_apa(&mut self, value: u16) -> Result<(), ChipError<E>> {
        self.write_register(registers::APA, value)
    }

    pub fn write_power_mode(&mut self, value: u16) -> Result<(), ChipError<E>> {
        self.write_register(registers::POWER_MODE, value)
    }

    pub fn battery_status(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::BATTERY_STATUS)
    }

    pub fn write_battery_status(&mut self, value: u16) -> Result<(), ChipError<E>> {
        self.write_register(registers::BATTERY_STATUS, value)
    }

    pub fn rsoc(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::RSOC)
    }

    pub fn battery_health(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::BATTERY_HEALTH)
    }

    pub fn cell_voltage(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::CELL_VOLTAGE)
    }

    pub fn ic_version(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::IC_VERSION)
    }

    pub fn ite(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::ITE)
    }

    pub fn cycle_count(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::CYCLE_COUNT)
    }

    pub fn status_bit(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::STATUS_BIT)
    }

    pub fn write_status_bit(&mut self, value: u16) -> Result<(), ChipError<E>> {
        self.write_register(registers::STATUS_BIT, value)
    }

    pub fn tsense1_thermistor_b(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::TSENSE1_THERMISTOR_B)
    }

    pub fn write_tsense1_thermistor_b(&mut self, value: u16) -> Result<(), ChipError<E>> {
        self.write_register(registers::TSENSE1_THERMISTOR_B, value)
    }

    pub fn cell_temperature(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::CELL_TEMPERATURE)
    }

    pub fn write_cell_temperature(&mut self, value: u16) -> Result<(), ChipError<E>> {
        self.write_register(registers::CELL_TEMPERATURE, value)
    }

    pub fn tsense2_thermistor_b(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::TSENSE2_THERMISTOR_B)
    }

    pub fn write_tsense2_thermistor_b(&mut self, value: u16) -> Result<(), ChipError<E>> {
        self.write_register(registers::TSENSE2_THERMISTOR_B, value)
    }

    pub fn ambient_temperature(&mut self) -> Result<u16, ChipError<E>> {
        self.read_register(registers::AMBIENT_TEMPERATURE)
    }
}