document design choices and implement hemming (26, 31) with basic tests

This commit is contained in:
2026-03-10 00:16:07 +01:00
parent 5b0251f4c9
commit 8448d94d46
2 changed files with 202 additions and 0 deletions
+5
View File
@@ -1,5 +1,10 @@
#![no_std]
#[cfg(test)]
extern crate std;
mod types;
use embedded_hal::i2c::{I2c, Operation, SevenBitAddress};
use thiserror::Error;
+197
View File
@@ -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);
}
}