embedded_registers/spi/codecs/
simple_codec.rs

1use crate::{ReadableRegister, Register, WritableRegister};
2use bytemuck::Zeroable;
3
4/// This codec represents the most commonly found codecs for SPI devices.
5/// It consists of an N-bit big-endian register address, a 1-bit R/W indicator
6/// and uses zero initializion for reserved bits. The header is always a multiple of 8-bit
7/// in width.
8///
9/// This codec has no information over register sizes or the existence of
10/// read/write address-auto-increment which some devices support.
11/// It will always send one header and then receive/send the associated register data,
12/// so it's compatible with auto-increment usage, but cannot be used to read or write
13/// registers that require interspersing the address between bytes.
14///
15/// Devices often use a mixed mode, where some registers allow auto-increment while others
16/// don't, or where the address is directly associated with a specific, but varying, register size.
17/// Therefore, it is up to the user to make sure that accessing a register via this codec
18/// is supported by the hardware.
19///
20/// The following generic parameters are available:
21///
22/// | Parameter | Type | Description |
23/// |---|---|---|
24/// | `HEADER_SIZE`              | `usize` | The size of the command header in bytes |
25/// | `ADDR_MSB`                 | `u8`    | The bit index of the MSB of the register-address (inclusive) |
26/// | `ADDR_LSB`                 | `u8`    | The bit index of the LSB of the register-address (inclusive) |
27/// | `RW_BIT`                   | `u8`    | The bit index of the RW bit when interpreting the struct in big-endian |
28/// | `RW_1_IS_READ`             | `bool`  | whether the setting the RW bit signals read-mode (true) or write-mode (false) |
29/// | `READ_DELAY`               | `usize` | Number of bytes that we have to wait (send additional zeros) after sending the header until data arrives |
30pub struct SimpleCodec<
31    const HEADER_SIZE: usize,
32    const ADDR_MSB: u8,
33    const ADDR_LSB: u8,
34    const RW_BIT: u8,
35    const RW_1_IS_READ: bool,
36    const READ_DELAY: usize,
37> {}
38
39impl<
40        const HEADER_SIZE: usize,
41        const ADDR_MSB: u8,
42        const ADDR_LSB: u8,
43        const RW_BIT: u8,
44        const RW_1_IS_READ: bool,
45        const READ_DELAY: usize,
46    > SimpleCodec<HEADER_SIZE, ADDR_MSB, ADDR_LSB, RW_BIT, RW_1_IS_READ, READ_DELAY>
47{
48    #[inline]
49    pub fn fill_addr_header<R>(header: &mut [u8])
50    where
51        R: Register,
52    {
53        // create a mask with ADDR_MSB + 1 ones (it is inclusive).
54        // It doesn't matter if ADDR_LSB is > 0, since the shift below already guarantees
55        // that there will only ever be zeros.
56        let addr_mask = u64::checked_shl(1, ADDR_MSB as u32 + 1).unwrap_or(0).wrapping_sub(1);
57        // Shift the address to the correct place
58        let addr_shifted = (R::ADDRESS << ADDR_LSB) & addr_mask;
59        // incorporate addess
60        let addr_bytes = addr_shifted.to_le_bytes();
61        let affected_bytes = ((ADDR_MSB - ADDR_LSB) / 8) as usize;
62        for i in 0..=affected_bytes {
63            header[HEADER_SIZE - 1 - i] |= addr_bytes[i];
64        }
65    }
66}
67
68#[maybe_async_cfg::maybe(
69    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec),
70    sync(feature = "sync"),
71    async(feature = "async"),
72    keep_self
73)]
74impl<
75        const HEADER_SIZE: usize,
76        const ADDR_MSB: u8,
77        const ADDR_LSB: u8,
78        const RW_BIT: u8,
79        const RW_1_IS_READ: bool,
80        const READ_DELAY: usize,
81    > crate::spi::Codec for SimpleCodec<HEADER_SIZE, ADDR_MSB, ADDR_LSB, RW_BIT, RW_1_IS_READ, READ_DELAY>
82{
83    #[inline]
84    async fn read_register<R, I>(interface: &mut I) -> Result<R, I::Error>
85    where
86        R: ReadableRegister,
87        I: hal::spi::r#SpiDevice,
88    {
89        #[repr(C, packed(1))]
90        #[derive(Copy, Clone, bytemuck::Pod, Zeroable)]
91        struct Buffer<const HEADER_SIZE: usize, const READ_DELAY: usize, R> {
92            header: [u8; HEADER_SIZE],
93            delay: [u8; READ_DELAY],
94            register: R,
95        }
96
97        let mut buffer = Buffer::<{ HEADER_SIZE }, { READ_DELAY }, R>::zeroed();
98        let data = bytemuck::bytes_of_mut(&mut buffer);
99        // Set RW_BIT if necessary
100        data[HEADER_SIZE - 1 - (RW_BIT as usize) / 8] |= (RW_1_IS_READ as u8) << (RW_BIT % 8);
101        Self::fill_addr_header::<R>(data);
102        interface.transfer_in_place(data).await?;
103        Ok(buffer.register)
104    }
105
106    #[inline]
107    async fn write_register<R, I>(interface: &mut I, register: impl AsRef<R>) -> Result<(), I::Error>
108    where
109        R: WritableRegister,
110        I: hal::spi::r#SpiDevice,
111    {
112        #[repr(C, packed(1))]
113        #[derive(Copy, Clone, bytemuck::Pod, Zeroable)]
114        struct Buffer<const HEADER_SIZE: usize, R> {
115            header: [u8; HEADER_SIZE],
116            register: R,
117        }
118
119        let mut buffer = Buffer::<{ HEADER_SIZE }, R> {
120            header: [0u8; HEADER_SIZE],
121            register: *register.as_ref(),
122        };
123
124        let data = bytemuck::bytes_of_mut(&mut buffer);
125        // Set RW_BIT if necessary
126        data[HEADER_SIZE - 1 - (RW_BIT as usize) / 8] |= ((!RW_1_IS_READ) as u8) << (RW_BIT % 8);
127        Self::fill_addr_header::<R>(data);
128        interface.write(data).await
129    }
130}