embedded_devices/devices/belling/
mod.rs

1use core::num::Wrapping;
2
3use embedded_interfaces::TransportError;
4use embedded_interfaces::registers::{ReadableRegister, Register, RegisterCodec, WritableRegister};
5
6#[cfg(feature = "belling-bl0942")]
7pub mod bl0942;
8
9/// An error representing checksum errors.
10#[cfg_attr(feature = "defmt", derive(defmt::Format))]
11#[derive(Debug, thiserror::Error)]
12pub enum ChecksumError {
13    #[error("the calculated checksum {calculated:#02x} did not match the expected value {expected:#02x}")]
14    Mismatch { calculated: u8, expected: u8 },
15}
16
17/// This represents the SPI codec most commonly used in Belling devices. It consists of a static
18/// command (read or write), a 8 bit register address, 24 bits of a data and an additive checksum.
19///
20/// ```text
21/// # Read
22/// MOSI | CMD_READ[7:0]  | ADDR[7:0] |     0               0      |
23/// MISO |        0            0      | DATA[23:0] | CHECKSUM[7:0] |
24///
25/// # Write
26/// MOSI | CMD_WRITE[7:0] | ADDR[7:0] | DATA[23:0] | CHECKSUM[7:0] |
27/// ```
28pub struct BellingSpiCodec<const CMD_READ: u8, const CMD_WRITE: u8> {}
29
30impl<const CMD_READ: u8, const CMD_WRITE: u8> RegisterCodec for BellingSpiCodec<CMD_READ, CMD_WRITE> {
31    type Error = ChecksumError;
32}
33
34#[maybe_async_cfg::maybe(
35    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Codec),
36    sync(feature = "sync"),
37    async(feature = "async"),
38    keep_self
39)]
40impl<const CMD_READ: u8, const CMD_WRITE: u8> embedded_interfaces::registers::spi::Codec
41    for BellingSpiCodec<CMD_READ, CMD_WRITE>
42{
43    #[inline]
44    async fn read_register<R, I>(interface: &mut I) -> Result<R, TransportError<Self::Error, I::Error>>
45    where
46        R: Register<CodecError = Self::Error> + ReadableRegister,
47        I: hal::spi::r#SpiDevice,
48    {
49        assert_eq!(R::REGISTER_SIZE, 3);
50        let mut buffer = [CMD_READ, R::ADDRESS as u8, 0, 0, 0, 0];
51        interface.transfer_in_place(&mut buffer).await?;
52
53        buffer[0] = CMD_READ;
54        buffer[1] = R::ADDRESS as u8;
55        let data = &buffer[2..5];
56        let calculated = !(buffer[0..5].iter().map(|&x| Wrapping(x)).sum::<Wrapping<u8>>().0);
57        let expected = buffer[5];
58        if expected != calculated {
59            return Err(TransportError::r#Codec(ChecksumError::Mismatch {
60                calculated,
61                expected,
62            }));
63        }
64
65        // Moving it into a variable ensures alignment.
66        let mut register = R::zeroed();
67        bytemuck::bytes_of_mut(&mut register).copy_from_slice(data);
68
69        #[cfg(feature = "trace-communication")]
70        log::trace!(
71            "SPI read register_addr={:08x}:\n{}",
72            R::ADDRESS,
73            register.bitdump()
74        );
75
76        Ok(register)
77    }
78
79    #[inline]
80    async fn write_register<R, I>(
81        interface: &mut I,
82        register: impl AsRef<R>,
83    ) -> Result<(), TransportError<Self::Error, I::Error>>
84    where
85        R: Register<CodecError = Self::Error> + WritableRegister,
86        I: hal::spi::r#SpiDevice,
87    {
88        assert_eq!(R::REGISTER_SIZE, 3);
89
90        #[cfg(feature = "trace-communication")]
91        log::trace!(
92            "SPI write register_addr={:08x}:\n{}",
93            R::ADDRESS,
94            register.as_ref().bitdump()
95        );
96
97        let mut buffer = [CMD_WRITE, R::ADDRESS as u8, 0, 0, 0, 0];
98        buffer[2..5].copy_from_slice(bytemuck::bytes_of(register.as_ref()));
99
100        // Checksum
101        let checksum = !(buffer[0..5].iter().map(|&x| Wrapping(x)).sum::<Wrapping<u8>>().0);
102        buffer[5] = checksum;
103
104        Ok(interface.write(&buffer).await?)
105    }
106}