icm20948/
interface.rs

1//! Bus interface implementations for the ICM-20948
2//!
3//! This module provides implementations of the `device-driver` traits for
4//! I2C and SPI communication with the ICM-20948.
5
6use crate::I2C_ADDRESS_AD0_LOW;
7
8use crate::Error;
9use device_driver::RegisterInterface;
10
11/// I2C interface for the ICM-20948
12pub struct I2cInterface<I2C> {
13    i2c: I2C,
14    #[allow(dead_code)]
15    address: u8,
16}
17
18impl<I2C> I2cInterface<I2C> {
19    /// Create a new I2C interface with the default address (0x68, AD0 pin LOW)
20    ///
21    /// This is the most common configuration where the AD0 pin is pulled low
22    /// or left floating (has internal pull-down on most breakout boards).
23    ///
24    /// # Arguments
25    /// * `i2c` - The I2C peripheral
26    ///
27    /// # Example
28    /// ```ignore
29    /// let interface = I2cInterface::default(i2c);
30    /// let mut imu = Icm20948Driver::new(interface)?;
31    /// ```
32    pub const fn default(i2c: I2C) -> Self {
33        Self {
34            i2c,
35            address: I2C_ADDRESS_AD0_LOW,
36        }
37    }
38
39    /// Create a new I2C interface with the alternative address (0x69, AD0 pin HIGH)
40    ///
41    /// Use this when the AD0 pin is explicitly pulled high to VDD.
42    ///
43    /// # Arguments
44    /// * `i2c` - The I2C peripheral
45    ///
46    /// # Example
47    /// ```ignore
48    /// let interface = I2cInterface::alternative(i2c);
49    /// let mut imu = Icm20948Driver::new(interface)?;
50    /// ```
51    pub const fn alternative(i2c: I2C) -> Self {
52        Self {
53            i2c,
54            address: crate::I2C_ADDRESS_AD0_HIGH,
55        }
56    }
57
58    /// Create a new I2C interface with a custom device address
59    ///
60    /// This allows specifying any I2C address for advanced use cases.
61    /// For standard ICM-20948 configurations, prefer [`default()`](Self::default)
62    /// or [`alternative()`](Self::alternative).
63    ///
64    /// # Arguments
65    /// * `i2c` - The I2C peripheral
66    /// * `address` - The I2C device address
67    pub const fn new(i2c: I2C, address: u8) -> Self {
68        Self { i2c, address }
69    }
70
71    /// Consume the interface and return the I2C peripheral
72    pub fn release(self) -> I2C {
73        self.i2c
74    }
75}
76
77impl<I2C, E> RegisterInterface for I2cInterface<I2C>
78where
79    I2C: embedded_hal::i2c::I2c<Error = E>,
80{
81    type Error = E;
82    type AddressType = u8;
83
84    fn read_register(
85        &mut self,
86        address: Self::AddressType,
87        size_bits: u32,
88        read_data: &mut [u8],
89    ) -> Result<(), Self::Error> {
90        let _ = size_bits; // Size is implicit in read_data.len() for I2C
91        self.i2c.write_read(self.address, &[address], read_data)
92    }
93
94    fn write_register(
95        &mut self,
96        address: Self::AddressType,
97        size_bits: u32,
98        write_data: &[u8],
99    ) -> Result<(), Self::Error> {
100        let _ = size_bits; // Size is implicit in write_data.len() for I2C
101        // Create a buffer with address + data
102        let mut buffer = [0u8; 33]; // Max: 1 address + 32 data bytes
103        buffer[0] = address;
104        let len = write_data.len().min(32);
105        buffer[1..=len].copy_from_slice(&write_data[..len]);
106
107        self.i2c.write(self.address, &buffer[..=len])
108    }
109}
110
111#[cfg(feature = "async")]
112impl<I2C, E> device_driver::AsyncRegisterInterface for I2cInterface<I2C>
113where
114    I2C: embedded_hal_async::i2c::I2c<Error = E>,
115{
116    type Error = E;
117    type AddressType = u8;
118
119    async fn read_register(
120        &mut self,
121        address: Self::AddressType,
122        size_bits: u32,
123        read_data: &mut [u8],
124    ) -> Result<(), Self::Error> {
125        let _ = size_bits; // Size is implicit in read_data.len() for I2C
126        self.i2c
127            .write_read(self.address, &[address], read_data)
128            .await
129    }
130
131    async fn write_register(
132        &mut self,
133        address: Self::AddressType,
134        size_bits: u32,
135        write_data: &[u8],
136    ) -> Result<(), Self::Error> {
137        let _ = size_bits; // Size is implicit in write_data.len() for I2C
138        // Create a buffer with address + data
139        let mut buffer = [0u8; 33]; // Max: 1 address + 32 data bytes
140        buffer[0] = address;
141        let len = write_data.len().min(32);
142        buffer[1..=len].copy_from_slice(&write_data[..len]);
143
144        self.i2c.write(self.address, &buffer[..=len]).await
145    }
146}
147
148/// SPI interface for the ICM-20948
149///
150/// # Note on Chip Select
151///
152/// This interface uses the `SpiDevice` trait from `embedded-hal`, which manages
153/// the chip select (CS) pin automatically. The CS pin is handled internally by
154/// the SPI device implementation you provide, so you don't need to pass it separately.
155///
156/// If using `embedded-hal-bus`, you would typically create an `SpiDevice` like:
157/// ```ignore
158/// let spi_device = embedded_hal_bus::spi::ExclusiveDevice::new(spi_bus, cs_pin, delay);
159/// let interface = SpiInterface::new(spi_device);
160/// ```
161pub struct SpiInterface<SPI> {
162    spi: SPI,
163}
164
165impl<SPI> SpiInterface<SPI> {
166    /// Create a new SPI interface with the given SPI device
167    ///
168    /// The SPI device should already include chip select management via the
169    /// `SpiDevice` trait (e.g., using `embedded_hal_bus::spi::ExclusiveDevice`).
170    pub const fn new(spi: SPI) -> Self {
171        Self { spi }
172    }
173
174    /// Consume the interface and return the SPI device
175    pub fn release(self) -> SPI {
176        self.spi
177    }
178}
179
180impl<SPI, E> RegisterInterface for SpiInterface<SPI>
181where
182    SPI: embedded_hal::spi::SpiDevice<Error = E>,
183{
184    type Error = Error<E>;
185    type AddressType = u8;
186
187    fn read_register(
188        &mut self,
189        address: Self::AddressType,
190        size_bits: u32,
191        read_data: &mut [u8],
192    ) -> Result<(), Self::Error> {
193        let _ = size_bits; // Size is implicit in read_data.len() for SPI
194        // For SPI reads, set MSB to 1
195        let read_address = address | 0x80;
196
197        let mut operations = [
198            embedded_hal::spi::Operation::Write(&[read_address]),
199            embedded_hal::spi::Operation::Read(read_data),
200        ];
201
202        self.spi.transaction(&mut operations).map_err(Error::Bus)
203    }
204
205    fn write_register(
206        &mut self,
207        address: Self::AddressType,
208        size_bits: u32,
209        write_data: &[u8],
210    ) -> Result<(), Self::Error> {
211        let _ = size_bits; // Size is implicit in write_data.len() for SPI
212        // For SPI writes, MSB should be 0 (clear it just in case)
213        let write_address = address & 0x7F;
214
215        // Create buffer with address + data
216        let mut buffer = [0u8; 33];
217        buffer[0] = write_address;
218        let len = write_data.len().min(32);
219        buffer[1..=len].copy_from_slice(&write_data[..len]);
220
221        self.spi.write(&buffer[..=len]).map_err(Error::Bus)
222    }
223}
224
225#[cfg(feature = "async")]
226impl<SPI, E> device_driver::AsyncRegisterInterface for SpiInterface<SPI>
227where
228    SPI: embedded_hal_async::spi::SpiDevice<Error = E>,
229{
230    type Error = Error<E>;
231    type AddressType = u8;
232
233    async fn read_register(
234        &mut self,
235        address: Self::AddressType,
236        size_bits: u32,
237        read_data: &mut [u8],
238    ) -> Result<(), Self::Error> {
239        let _ = size_bits; // Size is implicit in read_data.len() for SPI
240        // For SPI reads, set MSB to 1
241        let read_address = address | 0x80;
242
243        let mut operations = [
244            embedded_hal_async::spi::Operation::Write(&[read_address]),
245            embedded_hal_async::spi::Operation::Read(read_data),
246        ];
247
248        self.spi
249            .transaction(&mut operations)
250            .await
251            .map_err(Error::Bus)
252    }
253
254    async fn write_register(
255        &mut self,
256        address: Self::AddressType,
257        size_bits: u32,
258        write_data: &[u8],
259    ) -> Result<(), Self::Error> {
260        let _ = size_bits; // Size is implicit in write_data.len() for SPI
261        // For SPI writes, MSB should be 0 (clear it just in case)
262        let write_address = address & 0x7F;
263
264        // Create buffer with address + data
265        let mut buffer = [0u8; 33];
266        buffer[0] = write_address;
267        let len = write_data.len().min(32);
268        buffer[1..=len].copy_from_slice(&write_data[..len]);
269
270        self.spi.write(&buffer[..=len]).await.map_err(Error::Bus)
271    }
272}