embedded_registers/i2c/codecs/
crc_codec.rs

1use core::marker::PhantomData;
2
3use crate::{ReadableRegister, WritableRegister};
4use arrayvec::ArrayVec;
5use bytemuck::Zeroable;
6use crc::Algorithm;
7
8/// This codec implements an I2C codec utilizing crc checksums for data
9/// The main variables are:
10/// - the size of register addresses in bytes.
11/// - the crc algorithm in use
12/// - set chunk size that get checksummed
13///
14/// This implements the codec only for crc outputs of single byte size.
15/// If your device has larger crc sums, you cannot use this implementation as is.
16///
17/// This codec has no information over register sizes or contents.
18/// It will always send one header and then receive/send the associated register data,
19/// interspersed with crc sums each CHUNK_SIZE bytes.
20/// This makes the codec unsuited for cases where the device has
21/// registers that require interspersing the crc between fields of differing sizes.
22///
23/// The following generic parameters are available:
24///
25/// | Parameter | Type | Description |
26/// |---|---|---|
27/// | `HEADER_SIZE` | `usize` | The size of the command header (register address) in bytes |
28/// | `CHUNK_SIZE` | `usize` | The size of a chunk that has a singular crc sum attached in bytes |
29/// | `C` | `Crc8Algorithm` | A static reference to the crc algorithm to be used |
30/// Example implemenation for a basic CRC Algorithm:
31///
32/// ```
33/// #[derive(Default)]
34/// struct MyCrc {}
35///
36/// impl Crc8Algorithm for MyCrc {
37///     fn instance() -> &'static Algorithm<u8> {
38///         const CUSTOM_ALG: crc::Algorithm<u8> = CRC_8_NRSC_5;
39///         &CUSTOM_ALG
40///     }
41/// }
42/// ```
43#[derive(Default)]
44pub struct Crc8Codec<const HEADER_SIZE: usize, const CHUNK_SIZE: usize, C: Crc8Algorithm> {
45    _algo: PhantomData<C>,
46}
47
48pub trait Crc8Algorithm: Default {
49    /// Return reference to global static CRC Algorithm
50    fn instance() -> &'static Algorithm<u8>;
51}
52
53#[maybe_async_cfg::maybe(
54    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec, I2cBoundBus),
55    sync(feature = "sync"),
56    async(feature = "async"),
57    keep_self
58)]
59impl<const HEADER_SIZE: usize, const CHUNK_SIZE: usize, C: Crc8Algorithm + 'static> crate::i2c::Codec
60    for Crc8Codec<HEADER_SIZE, CHUNK_SIZE, C>
61{
62    #[inline]
63    async fn read_register<R, I, A>(bound_bus: &mut crate::i2c::I2cBoundBus<I, A>) -> Result<R, I::Error>
64    where
65        R: ReadableRegister,
66        I: hal::i2c::I2c<A> + hal::i2c::ErrorType,
67        A: hal::i2c::AddressMode + Copy,
68    {
69        let crc = crc::Crc::<u8>::new(C::instance());
70        let header = &R::ADDRESS.to_be_bytes()[core::mem::size_of_val(&R::ADDRESS) - HEADER_SIZE..];
71
72        let mut array = ArrayVec::<_, 64>::new();
73        unsafe {
74            array.set_len(R::REGISTER_SIZE + R::REGISTER_SIZE / CHUNK_SIZE);
75        }
76
77        bound_bus
78            .interface
79            .write_read(bound_bus.address, header, &mut array)
80            .await?;
81
82        let mut register = R::zeroed();
83        let data = bytemuck::bytes_of_mut(&mut register);
84        for (i, x) in array.chunks(CHUNK_SIZE + 1).enumerate() {
85            let value = &x[0..CHUNK_SIZE];
86            data[i..i + CHUNK_SIZE].copy_from_slice(value);
87            let crc_val = crc.checksum(value);
88            let crc_real = x[CHUNK_SIZE];
89            if crc_real != crc_val {
90                panic!("crc failed")
91            }
92        }
93
94        Ok(register)
95    }
96
97    #[inline]
98    async fn write_register<R, I, A>(
99        bound_bus: &mut crate::i2c::I2cBoundBus<I, A>,
100        register: impl AsRef<R>,
101    ) -> Result<(), I::Error>
102    where
103        R: WritableRegister,
104        I: hal::i2c::I2c<A> + hal::i2c::ErrorType,
105        A: hal::i2c::AddressMode + Copy,
106    {
107        #[repr(C, packed(1))]
108        #[derive(Copy, Clone, bytemuck::Pod, Zeroable)]
109        struct Buffer<const HEADER_SIZE: usize, R> {
110            header: [u8; HEADER_SIZE],
111            register: R,
112        }
113        let crc = crc::Crc::<u8>::new(C::instance());
114
115        let mut buffer = Buffer::<{ HEADER_SIZE }, R> {
116            header: R::ADDRESS.to_be_bytes()[core::mem::size_of_val(&R::ADDRESS) - HEADER_SIZE..]
117                .try_into()
118                .expect("Unexpected compile-time header address size. This is a bug in the chosen Codec or embedded-registers."),
119            register: *register.as_ref(),
120        };
121        let data = bytemuck::bytes_of_mut(&mut buffer);
122
123        let mut array = ArrayVec::<_, 64>::new();
124        for x in data.chunks(CHUNK_SIZE) {
125            array.try_extend_from_slice(x).unwrap();
126            let crc_val = crc.checksum(x);
127            array.push(crc_val);
128        }
129
130        bound_bus.interface.write(bound_bus.address, &array).await
131    }
132}