From 8448d94d46ed51d9ab0760f78daae938b6bddc65 Mon Sep 17 00:00:00 2001 From: ju6ge Date: Fri, 6 Mar 2026 19:54:55 +0100 Subject: [PATCH] document design choices and implement hemming (26, 31) with basic tests --- lib-bms-protocol/src/lib.rs | 5 + lib-bms-protocol/src/types.rs | 197 ++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 lib-bms-protocol/src/types.rs diff --git a/lib-bms-protocol/src/lib.rs b/lib-bms-protocol/src/lib.rs index 95f8d90..3704cd9 100644 --- a/lib-bms-protocol/src/lib.rs +++ b/lib-bms-protocol/src/lib.rs @@ -1,5 +1,10 @@ #![no_std] +#[cfg(test)] +extern crate std; + +mod types; + use embedded_hal::i2c::{I2c, Operation, SevenBitAddress}; use thiserror::Error; diff --git a/lib-bms-protocol/src/types.rs b/lib-bms-protocol/src/types.rs new file mode 100644 index 0000000..b25775f --- /dev/null +++ b/lib-bms-protocol/src/types.rs @@ -0,0 +1,197 @@ +//! # Base Value Types +//! +//! This module implements multiples types used for storing and transmitting/receiving data with +//! build in error detection/correction. +//! +//! For the purposes in the context of this project the following assumptions where made to determine the required data size. +//! +//! The largest value that could be stored or transmitted would be the running counter for the total amount of charge +//! or discharge that has occured since the beginning of recording. The intended use here is the monitoring of single +//! 12V LiFePO4 batteries which come in sizes up to 300 Ah. Many of the larger scale ones already come with a more advanced +//! BMS, so this is targetting lower tier ones with sizes up to 15 Ah. +//! +//! Modern LiFePO4 battery can easily withstand past 3000 charge cycles with about 365 charge cycles per year correspoding to +//! a charge cycle per day with solar energy. +//! +//! Targeting a 10 year lifespan would result in an absolute maxium of +//! 10,000 mAh * 10 years * 365 days = 53,400,000 mAh of total charge or discharge. +//! +//! Using a u26 would give us a counter for up to 2**26 = 67,108,863 mAh which is sufficient and allows using up to 6 bits for +//! redundancy within the 32bit word width. +//! +//! ## Use cases: +//! +//! ### Storage +//! +//! Storing data on flash for long term history retention might yield errors when flash cells are faulty leading most probably to on +//! bit errors. Ussing a hemming code for storing data (giving us 1bit error correction) requires 5 bits which is easily fits in the 6 bit redundancy data. For extra +//! redundancy a crc sum could be stored additionally to non recoverable errors. +//! +//! ### Transmission +//! +//! During transmission of data via serial interfaces, on bit errors ar less of a concern, here burst errors are more common du to +//! possible interfence on the transmission line. Using hemming codes would be waistul here. Instead a crc code is used for error +//! detection. If an error is detected the client device is expected to rerequest the data. +//! +//! ## CRC Selection +//! +//! Selecting a good crc polynomial requires careful consideration on the use data length and possible room within the code desired +//! code word lenght. For a good summary on CRC tradeoffs refer to this [paper](https://users.ece.cmu.edu/~koopman/roses/dsn04/koopman04_crc_poly_embedded.pdf). +//! For the purposes of this project the following selection was made. +//! +//! CRC-6 using the generator polynomial 0x21 is the best choice for a data length of 26 bits and giving us a hemming distance of +//! 3 for every valid code word. Meaning it is guaranteed that any error up to 3 bits will always be deteced. Beyond that more +//! errors random errors in the transmission have a probabilty of 2**-6 = 0.015625 of matching the original messages CRC code. +//! Meaning the chance of errors going undetected is <2%. + + +#[derive(Debug)] +/// this type is used for storing data on memory devices the concern here is mostly protecting against memory corruption +/// du to faulty memory cells. Here one bit errors are the most probabl cause of errors so this types uses a build in +/// hemming code for one mit error correction. +/// +/// additionally it is advised to store an extra checks sum to protect against multi bit errors +/// +/// this type is stored in u32 in the following form: +/// | 26 bits u26 value | 5 hemming bits | 1 unused extrabit | +pub struct Stored_U26(u32); + +#[derive(Debug)] +/// this type is used for transmitting data, the concern here is protection against transmission errors which most likely occur +/// as burst errors effecting multiple bits, hemming codes would be a waist of space here so instead a 6 bit crc code is used +/// +/// this type is stored in u32 in the following form: +/// | 26 bits u26 value | 6 bits crc value | +pub struct Transit_U26(u32); + +// only calc hemming code for the first 24 data bits including the 5 hemming bits +// pos | | p1 | p2 | p3 | p4 | p5 | +// 00001 | p1 | o | | | | | +// 00010 | p2 | | o | | | | +// 00011 | d1 | x | x | | | | +// 00100 | p3 | | | o | | | +// 00101 | d2 | x | | x | | | +// 00110 | d3 | | x | x | | | +// 00111 | d4 | x | x | x | | | +// 01000 | p4 | | | | o | | +// 01001 | d5 | x | | | x | | +// 01010 | d6 | | x | | x | | +// 01011 | d7 | x | x | | x | | +// 01100 | d8 | | | x | x | | +// 01101 | d9 | x | | x | x | | +// 01110 | d10 | | x | x | x | | +// 01111 | d11 | x | x | x | x | | +// 10000 | p5 | | | | | o | +// 10001 | d12 | x | | | | x | +// 10010 | d13 | | x | | | x | +// 10011 | d14 | x | x | | | x | +// 10100 | d15 | | | x | | x | +// 10101 | d16 | x | | x | | x | +// 10110 | d17 | | x | x | | x | +// 10111 | d18 | x | x | x | | x | +// 11000 | d19 | | | | x | x | +// 11001 | d20 | x | | | x | x | +// 11010 | d21 | | x | | x | x | +// 11011 | d22 | x | x | | x | x | +// 11100 | d23 | | | x | x | x | +// 11101 | d24 | x | | x | x | x | +// 11110 | d25 | | x | x | x | x | +// 11111 | d26 | x | x | x | x | x | +fn calc_hemming(data_bits: u32) -> u8 { + let p1 = ((data_bits) + ^ (data_bits >> 1) + ^ (data_bits >> 3) + ^ (data_bits >> 4) + ^ (data_bits >> 6) + ^ (data_bits >> 8) + ^ (data_bits >> 10) + ^ (data_bits >> 11) + ^ (data_bits >> 13) + ^ (data_bits >> 15) + ^ (data_bits >> 17) + ^ (data_bits >> 19) + ^ (data_bits >> 21) + ^ (data_bits >> 23) + ^ (data_bits >> 25)) + & 0x0001; + let p2 = ((data_bits) + ^ (data_bits >> 2) + ^ (data_bits >> 3) + ^ (data_bits >> 5) + ^ (data_bits >> 6) + ^ (data_bits >> 9) + ^ (data_bits >> 10) + ^ (data_bits >> 12) + ^ (data_bits >> 13) + ^ (data_bits >> 16) + ^ (data_bits >> 17) + ^ (data_bits >> 20) + ^ (data_bits >> 21) + ^ (data_bits >> 24) + ^ (data_bits >> 25)) + & 0x0001; + let p3 = ((data_bits >> 1) + ^ (data_bits >> 2) + ^ (data_bits >> 3) + ^ (data_bits >> 7) + ^ (data_bits >> 8) + ^ (data_bits >> 9) + ^ (data_bits >> 10) + ^ (data_bits >> 14) + ^ (data_bits >> 15) + ^ (data_bits >> 16) + ^ (data_bits >> 17) + ^ (data_bits >> 22) + ^ (data_bits >> 23) + ^ (data_bits >> 24) + ^ (data_bits >> 25)) + & 0x0001; + let p4 = ((data_bits >> 4) + ^ (data_bits >> 5) + ^ (data_bits >> 6) + ^ (data_bits >> 7) + ^ (data_bits >> 8) + ^ (data_bits >> 9) + ^ (data_bits >> 10) + ^ (data_bits >> 18) + ^ (data_bits >> 19) + ^ (data_bits >> 20) + ^ (data_bits >> 21) + ^ (data_bits >> 22) + ^ (data_bits >> 23) + ^ (data_bits >> 24) + ^ (data_bits >> 25)) + & 0x001; + let p5 = ((data_bits >> 11) + ^ (data_bits >> 12) + ^ (data_bits >> 13) + ^ (data_bits >> 14) + ^ (data_bits >> 15) + ^ (data_bits >> 16) + ^ (data_bits >> 17) + ^ (data_bits >> 18) + ^ (data_bits >> 19) + ^ (data_bits >> 20) + ^ (data_bits >> 21) + ^ (data_bits >> 22) + ^ (data_bits >> 23) + ^ (data_bits >> 24) + ^ (data_bits >> 25)) + & 0x0001; + + 0x00 | (p1 as u8) << 0 | (p2 as u8) << 1 | (p3 as u8) << 2 | (p4 as u8) << 3 | (p5 as u8) << 5 +} + + +#[cfg(test)] +mod base_type_tests { + use super::calc_hemming; + + #[test] + fn hemming_code_generation() { + assert_eq!(calc_hemming(0x000), 0b00000); + assert_eq!(calc_hemming(0x001), 0b00011); + assert_eq!(calc_hemming(0x002), 0b00101); + assert_eq!(calc_hemming(0x003), 0b00110); + } +}