Files
ch32-bms/lib-bms-protocol/src/lib.rs
T
judge 5b0251f4c9 add new types for charge window info to bms protocol
these type add support for reading how much the charge state of the
battery has changed according to the configured charge window
2026-02-26 20:45:34 +01:00

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,
}