embedded_interfaces/registers/spi/codecs/
standard_codec.rs

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