these type add support for reading how much the charge state of the battery has changed according to the configured charge window
285 lines
8.6 KiB
Rust
285 lines
8.6 KiB
Rust
#![no_std]
|
|
|
|
use embedded_hal::i2c::{I2c, Operation, SevenBitAddress};
|
|
use thiserror::Error;
|
|
|
|
pub const BMS_I2C_ADDRESS: u8 = 0x55;
|
|
|
|
pub trait BmsReadable {
|
|
fn read_from_i2c<I>(i2c_dev: &mut I) -> Result<Self, BmsProtocolError>
|
|
where
|
|
Self: Sized,
|
|
I: I2c<SevenBitAddress>;
|
|
}
|
|
|
|
pub trait BmsWriteable {
|
|
fn write_to_device<I>(&self, i2c_dev: &mut I) -> Result<(), BmsProtocolError>
|
|
where
|
|
I: I2c<SevenBitAddress>;
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum BmsProtocolError {
|
|
#[error("i2c communication failed")]
|
|
I2cCommunicationError,
|
|
}
|
|
|
|
impl<E> From<E> for BmsProtocolError
|
|
where
|
|
E: embedded_hal::i2c::Error,
|
|
{
|
|
fn from(_value: E) -> Self {
|
|
Self::I2cCommunicationError
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct BmsSoftwareReset;
|
|
|
|
impl BmsWriteable for BmsSoftwareReset {
|
|
fn write_to_device<I>(&self, i2c_dev: &mut I) -> Result<(), BmsProtocolError>
|
|
where
|
|
I: I2c<SevenBitAddress>,
|
|
{
|
|
i2c_dev.write(
|
|
BMS_I2C_ADDRESS,
|
|
&(BmsRegisterMap::Reset as u32).to_be_bytes(),
|
|
)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(transparent)]
|
|
pub struct ProtocolVersion(pub u32);
|
|
|
|
impl BmsReadable for ProtocolVersion {
|
|
fn read_from_i2c<I>(i2c_dev: &mut I) -> Result<Self, BmsProtocolError>
|
|
where
|
|
Self: Sized,
|
|
I: I2c<SevenBitAddress>,
|
|
{
|
|
let cmd = BmsRegisterMap::ProtocolVersion;
|
|
let mut version: [u8; 4] = [0x00; 4];
|
|
i2c_dev.transaction(
|
|
BMS_I2C_ADDRESS,
|
|
&mut [
|
|
Operation::Write(&(cmd as u32).to_be_bytes()),
|
|
Operation::Read(&mut version),
|
|
],
|
|
)?;
|
|
Ok(ProtocolVersion(u32::from_be_bytes(version)))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[repr(transparent)]
|
|
pub struct FirmwareVersion(pub u32);
|
|
|
|
impl BmsReadable for FirmwareVersion {
|
|
fn read_from_i2c<I>(i2c_dev: &mut I) -> Result<Self, BmsProtocolError>
|
|
where
|
|
Self: Sized,
|
|
I: I2c<SevenBitAddress>,
|
|
{
|
|
let cmd = BmsRegisterMap::FirmwareVersion;
|
|
let mut version: [u8; 4] = [0x00; 4];
|
|
i2c_dev.transaction(
|
|
BMS_I2C_ADDRESS,
|
|
&mut [
|
|
Operation::Write(&(cmd as u32).to_be_bytes()),
|
|
Operation::Read(&mut version),
|
|
],
|
|
)?;
|
|
Ok(FirmwareVersion(u32::from_be_bytes(version)))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Config {
|
|
pub capacity_mah: u32,
|
|
pub v_full_mv: u32,
|
|
pub v_empty_mv: u32,
|
|
}
|
|
|
|
impl BmsReadable for Config {
|
|
fn read_from_i2c<I>(i2c_dev: &mut I) -> Result<Self, BmsProtocolError>
|
|
where
|
|
Self: Sized,
|
|
I: I2c<SevenBitAddress>,
|
|
{
|
|
let cmd = BmsRegisterMap::ConfigCapacityMilliAmpereHours;
|
|
let mut capacity_mah: [u8; 4] = [0x00; 4];
|
|
let mut v_full_mv: [u8; 4] = [0x00; 4];
|
|
let mut v_empty_mv: [u8; 4] = [0x00; 4];
|
|
i2c_dev.transaction(
|
|
BMS_I2C_ADDRESS,
|
|
&mut [
|
|
Operation::Write(&(cmd as u32).to_be_bytes()),
|
|
Operation::Read(&mut capacity_mah),
|
|
Operation::Read(&mut v_full_mv),
|
|
Operation::Read(&mut v_empty_mv),
|
|
],
|
|
)?;
|
|
Ok(Config {
|
|
capacity_mah: u32::from_be_bytes(capacity_mah),
|
|
v_full_mv: u32::from_be_bytes(v_full_mv),
|
|
v_empty_mv: u32::from_be_bytes(v_empty_mv),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct BatteryState {
|
|
pub lifetime_capacity_mah: u32,
|
|
pub remaining_capacity_mah: u32,
|
|
pub current_mv: u32,
|
|
pub temperature_celcius: i32,
|
|
pub health_percent: u32,
|
|
}
|
|
|
|
impl BmsReadable for BatteryState {
|
|
fn read_from_i2c<I>(i2c_dev: &mut I) -> Result<Self, BmsProtocolError>
|
|
where
|
|
Self: Sized,
|
|
I: I2c<SevenBitAddress>,
|
|
{
|
|
let cmd = BmsRegisterMap::BatteryLifetimeCapacityMilliAmpereHours;
|
|
let mut lifetime_capacity_mah: [u8; 4] = [0x00; 4];
|
|
let mut remaining_capacity_mah: [u8; 4] = [0x00; 4];
|
|
let mut current_mv: [u8; 4] = [0x00; 4];
|
|
let mut temperature_celcius: [u8; 4] = [0x00; 4];
|
|
let mut health_percent: [u8; 4] = [0x00; 4];
|
|
i2c_dev.transaction(
|
|
BMS_I2C_ADDRESS,
|
|
&mut [
|
|
Operation::Write(&(cmd as u32).to_be_bytes()),
|
|
Operation::Read(&mut lifetime_capacity_mah),
|
|
Operation::Read(&mut remaining_capacity_mah),
|
|
Operation::Read(&mut current_mv),
|
|
Operation::Read(&mut temperature_celcius),
|
|
Operation::Read(&mut health_percent),
|
|
],
|
|
)?;
|
|
Ok(BatteryState {
|
|
lifetime_capacity_mah: u32::from_be_bytes(lifetime_capacity_mah),
|
|
remaining_capacity_mah: u32::from_be_bytes(remaining_capacity_mah),
|
|
current_mv: u32::from_be_bytes(current_mv),
|
|
temperature_celcius: i32::from_be_bytes(temperature_celcius),
|
|
health_percent: u32::from_be_bytes(health_percent),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ChargeInfoWindowSec(pub u32);
|
|
|
|
impl BmsReadable for ChargeInfoWindowSec {
|
|
fn read_from_i2c<I>(i2c_dev: &mut I) -> Result<Self, BmsProtocolError>
|
|
where
|
|
Self: Sized,
|
|
I: I2c<SevenBitAddress>,
|
|
{
|
|
let cmd = BmsRegisterMap::ChargoInfoWindowTotalSecs;
|
|
let mut charge_info_window_sec: [u8; 4] = [0x00; 4];
|
|
i2c_dev.transaction(
|
|
BMS_I2C_ADDRESS,
|
|
&mut [
|
|
Operation::Write(&(cmd as u32).to_be_bytes()),
|
|
Operation::Read(&mut charge_info_window_sec),
|
|
],
|
|
)?;
|
|
Ok(ChargeInfoWindowSec(u32::from_be_bytes(
|
|
charge_info_window_sec,
|
|
)))
|
|
}
|
|
}
|
|
|
|
impl BmsWriteable for ChargeInfoWindowSec {
|
|
fn write_to_device<I>(&self, i2c_dev: &mut I) -> Result<(), BmsProtocolError>
|
|
where
|
|
I: I2c<SevenBitAddress>,
|
|
{
|
|
let cmd = BmsRegisterMap::ChargoInfoWindowTotalSecs;
|
|
i2c_dev.transaction(
|
|
BMS_I2C_ADDRESS,
|
|
&mut [
|
|
Operation::Write(&(cmd as u32).to_be_bytes()),
|
|
Operation::Write(&self.0.to_be_bytes()),
|
|
],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct ChargeInfo {
|
|
pub total_charge_ma: u32,
|
|
pub total_discharge_ma: u32,
|
|
pub max_charge_mw: u32,
|
|
pub max_discharge_mw: u32,
|
|
pub avg_voltage_mv: u32,
|
|
}
|
|
|
|
impl BmsReadable for ChargeInfo {
|
|
fn read_from_i2c<I>(i2c_dev: &mut I) -> Result<Self, BmsProtocolError>
|
|
where
|
|
Self: Sized,
|
|
I: I2c<SevenBitAddress>,
|
|
{
|
|
let cmd = BmsRegisterMap::ChargoInfoTotalChargeMilliAmpere;
|
|
let mut total_charge_ma: [u8; 4] = [0x00; 4];
|
|
let mut total_discharge_ma: [u8; 4] = [0x00; 4];
|
|
let mut max_charge_mw: [u8; 4] = [0x00; 4];
|
|
let mut max_discharge_mw: [u8; 4] = [0x00; 4];
|
|
let mut avg_voltage_mv: [u8; 4] = [0x00; 4];
|
|
i2c_dev.transaction(
|
|
BMS_I2C_ADDRESS,
|
|
&mut [
|
|
Operation::Write(&(cmd as u32).to_be_bytes()),
|
|
Operation::Read(&mut total_charge_ma),
|
|
Operation::Read(&mut total_discharge_ma),
|
|
Operation::Read(&mut max_charge_mw),
|
|
Operation::Read(&mut max_discharge_mw),
|
|
Operation::Read(&mut avg_voltage_mv),
|
|
],
|
|
)?;
|
|
Ok(ChargeInfo {
|
|
total_charge_ma: u32::from_be_bytes(total_charge_ma),
|
|
total_discharge_ma: u32::from_be_bytes(total_discharge_ma),
|
|
max_charge_mw: u32::from_be_bytes(max_charge_mw),
|
|
max_discharge_mw: u32::from_be_bytes(max_discharge_mw),
|
|
avg_voltage_mv: u32::from_be_bytes(avg_voltage_mv),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[repr(u32)]
|
|
#[non_exhaustive]
|
|
pub enum BmsRegisterMap {
|
|
// software reset register
|
|
Reset = 0x00000000,
|
|
// bms protocol revesion as u32 value
|
|
ProtocolVersion = 0x00000001,
|
|
// bms firmware revesion as u32 value
|
|
FirmwareVersion = 0x00000002,
|
|
// Configuration Values Start Here
|
|
ConfigCapacityMilliAmpereHours = 0x00010000,
|
|
ConfigFullMilliVolt = 0x00010004,
|
|
ConfigEmptyMilliVolt = 0x00010008,
|
|
// Battery State Values Start Here
|
|
BatteryLifetimeCapacityMilliAmpereHours = 0x00020000,
|
|
BatteryRemainingCapacityMilliAmpereHours = 0x00020004,
|
|
BatteryCurrentVoltageMilliVolt = 0x00020008,
|
|
BatteryTemperatureCelcius = 0x0002000c,
|
|
BatteryHealthPercent = 0x00020010,
|
|
// Charge Window Read/Write Address
|
|
ChargoInfoWindowTotalSecs = 0x0003000,
|
|
// Charge Info Value Start Here
|
|
ChargoInfoTotalChargeMilliAmpere = 0x0003004,
|
|
ChargoInfoTotalDischargeMilliAmpere = 0x0003008,
|
|
ChargoInfoMaxChargeMilliWatt = 0x000300c,
|
|
ChargoInfoMaxDischargeMilliWatt = 0x0003010,
|
|
ChargoInfoAvgVoltageMilliVolt = 0x0003014,
|
|
}
|