Skip to main content

xpanse_api/bus/
i2c.rs

1//! I2C bus abstraction.
2//!
3//! Wraps a concrete backend (hardware, PIO, or bit-banged) behind a single
4//! boxed trait object so that drivers can operate on an `I2cBusHandle`
5//! without knowing which backend was selected.
6//!
7//! [`BusAllocator`](crate::bus::allocator::BusAllocator) is the entry point used to
8//! allocate I2C buses at startup.
9
10use alloc::boxed::Box;
11use core::future::Future;
12use core::pin::Pin;
13
14use embedded_hal::i2c::{ErrorKind, Operation, SevenBitAddress};
15
16/// Error returned by I2C operations.
17///
18/// # Example
19///
20/// ```ignore
21/// use xpanse_api::bus::i2c::{I2cBusHandle, I2cError};
22///
23/// async fn read_register(bus: &mut I2cBusHandle, dev: u8, reg: u8) -> Result<u8, I2cError> {
24///     let mut buf = [0u8; 1];
25///     bus.write_read(dev, &[reg], &mut buf).await?;
26///     Ok(buf[0])
27/// }
28/// ```
29#[derive(Debug, Clone, Copy, PartialEq, Eq, defmt::Format)]
30pub enum I2cError {
31    Abort,
32    ArbitrationLoss,
33    InvalidBufferLength,
34    AddressOutOfRange,
35    UnsupportedTransaction,
36    /// Any other error reported by the underlying backend.
37    Other,
38}
39
40impl core::fmt::Display for I2cError {
41    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42        write!(f, "{:?}", self)
43    }
44}
45
46impl core::error::Error for I2cError {}
47
48impl embedded_hal::i2c::Error for I2cError {
49    fn kind(&self) -> ErrorKind {
50        match self {
51            I2cError::Abort => {
52                ErrorKind::NoAcknowledge(embedded_hal::i2c::NoAcknowledgeSource::Unknown)
53            }
54            I2cError::ArbitrationLoss => ErrorKind::ArbitrationLoss,
55            I2cError::InvalidBufferLength => ErrorKind::Other,
56            I2cError::AddressOutOfRange => ErrorKind::Other,
57            I2cError::UnsupportedTransaction => ErrorKind::Other,
58            I2cError::Other => ErrorKind::Other,
59        }
60    }
61}
62
63impl From<embassy_rp::i2c::Error> for I2cError {
64    fn from(e: embassy_rp::i2c::Error) -> Self {
65        match e {
66            embassy_rp::i2c::Error::Abort(embassy_rp::i2c::AbortReason::ArbitrationLoss) => {
67                I2cError::ArbitrationLoss
68            }
69            embassy_rp::i2c::Error::Abort(_) => I2cError::Abort,
70            embassy_rp::i2c::Error::InvalidReadBufferLength
71            | embassy_rp::i2c::Error::InvalidWriteBufferLength => I2cError::InvalidBufferLength,
72            embassy_rp::i2c::Error::AddressOutOfRange(_) => I2cError::AddressOutOfRange,
73            _ => I2cError::Other,
74        }
75    }
76}
77
78/// Backend variant of an [`I2cBusHandle`].
79/// No PIO yet
80#[derive(Debug, Clone, Copy, PartialEq, Eq, defmt::Format)]
81pub enum I2cBusVersion {
82    /// Hardware I2C peripheral.
83    Hardware,
84    /// Bit-banged via GPIO.
85    BitBang,
86    // PIO variant reserved for when embassy adds a PIO I2C program.
87}
88
89/// Trait-object-safe I2C bus operating on 7-bit addresses.
90pub trait DynI2cBus: Send {
91    fn read<'a>(
92        &'a mut self,
93        address: u8,
94        read: &'a mut [u8],
95    ) -> Pin<Box<dyn Future<Output = Result<(), I2cError>> + 'a>>;
96
97    fn write<'a>(
98        &'a mut self,
99        address: u8,
100        write: &'a [u8],
101    ) -> Pin<Box<dyn Future<Output = Result<(), I2cError>> + 'a>>;
102
103    fn write_read<'a>(
104        &'a mut self,
105        address: u8,
106        write: &'a [u8],
107        read: &'a mut [u8],
108    ) -> Pin<Box<dyn Future<Output = Result<(), I2cError>> + 'a>>;
109
110    fn transaction<'a, 'op>(
111        &'a mut self,
112        address: u8,
113        operations: &'a mut [Operation<'op>],
114    ) -> Pin<Box<dyn Future<Output = Result<(), I2cError>> + 'a>>
115    where
116        'op: 'a;
117}
118
119/// Owned handle to an async I2C bus.
120///
121/// Dropping this handle does not return its startup resources, so keep it alive
122/// for as long as you need I2C access.
123#[must_use = "dropping a bus handle does not return its startup resources"]
124pub struct I2cBusHandle {
125    inner: Box<dyn DynI2cBus>,
126    version: I2cBusVersion,
127}
128
129impl I2cBusHandle {
130    /// Wraps a boxed backend and records its [`I2cBusVersion`].
131    pub fn new(inner: Box<dyn DynI2cBus>, version: I2cBusVersion) -> Self {
132        Self { inner, version }
133    }
134
135    /// Returns the backend variant.
136    pub fn version(&self) -> I2cBusVersion {
137        self.version
138    }
139
140    /// Read bytes from a 7-bit address.
141    pub async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), I2cError> {
142        self.inner.read(address, read).await
143    }
144
145    /// Write bytes to a 7-bit address.
146    pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), I2cError> {
147        self.inner.write(address, write).await
148    }
149
150    /// Write the `write` slice, then read into the `read` slice without releasing the bus.
151    pub async fn write_read(
152        &mut self,
153        address: u8,
154        write: &[u8],
155        read: &mut [u8],
156    ) -> Result<(), I2cError> {
157        self.inner.write_read(address, write, read).await
158    }
159
160    /// Run a sequence of read/write operations atomically on the bus.
161    pub async fn transaction(
162        &mut self,
163        address: u8,
164        operations: &mut [Operation<'_>],
165    ) -> Result<(), I2cError> {
166        self.inner.transaction(address, operations).await
167    }
168}
169
170impl embedded_hal::i2c::ErrorType for I2cBusHandle {
171    type Error = I2cError;
172}
173
174impl embedded_hal_async::i2c::I2c<SevenBitAddress> for I2cBusHandle {
175    async fn read(&mut self, address: SevenBitAddress, read: &mut [u8]) -> Result<(), I2cError> {
176        self.inner.read(address, read).await
177    }
178
179    async fn write(&mut self, address: SevenBitAddress, write: &[u8]) -> Result<(), I2cError> {
180        self.inner.write(address, write).await
181    }
182
183    async fn write_read(
184        &mut self,
185        address: SevenBitAddress,
186        write: &[u8],
187        read: &mut [u8],
188    ) -> Result<(), I2cError> {
189        self.inner.write_read(address, write, read).await
190    }
191
192    async fn transaction(
193        &mut self,
194        address: SevenBitAddress,
195        operations: &mut [Operation<'_>],
196    ) -> Result<(), I2cError> {
197        self.inner.transaction(address, operations).await
198    }
199}
200
201// ── blanket impl: anything that impls embedded-hal-async I2c<SevenBitAddress>
202//    with a compatible error gets DynI2cBus for free. ──
203
204impl<T> DynI2cBus for T
205where
206    T: embedded_hal_async::i2c::I2c<SevenBitAddress>,
207    T::Error: Into<I2cError>,
208    T: Send,
209{
210    fn read<'a>(
211        &'a mut self,
212        address: u8,
213        read: &'a mut [u8],
214    ) -> Pin<Box<dyn Future<Output = Result<(), I2cError>> + 'a>> {
215        Box::pin(async move {
216            if address > 0x7f {
217                return Err(I2cError::AddressOutOfRange);
218            }
219            embedded_hal_async::i2c::I2c::read(self, address, read)
220                .await
221                .map_err(Into::into)
222        })
223    }
224
225    fn write<'a>(
226        &'a mut self,
227        address: u8,
228        write: &'a [u8],
229    ) -> Pin<Box<dyn Future<Output = Result<(), I2cError>> + 'a>> {
230        Box::pin(async move {
231            if address > 0x7f {
232                return Err(I2cError::AddressOutOfRange);
233            }
234            embedded_hal_async::i2c::I2c::write(self, address, write)
235                .await
236                .map_err(Into::into)
237        })
238    }
239
240    fn write_read<'a>(
241        &'a mut self,
242        address: u8,
243        write: &'a [u8],
244        read: &'a mut [u8],
245    ) -> Pin<Box<dyn Future<Output = Result<(), I2cError>> + 'a>> {
246        Box::pin(async move {
247            if address > 0x7f {
248                return Err(I2cError::AddressOutOfRange);
249            }
250            embedded_hal_async::i2c::I2c::write_read(self, address, write, read)
251                .await
252                .map_err(Into::into)
253        })
254    }
255
256    fn transaction<'a, 'op>(
257        &'a mut self,
258        address: u8,
259        operations: &'a mut [Operation<'op>],
260    ) -> Pin<Box<dyn Future<Output = Result<(), I2cError>> + 'a>>
261    where
262        'op: 'a,
263    {
264        Box::pin(async move {
265            if address > 0x7f {
266                return Err(I2cError::AddressOutOfRange);
267            }
268            embedded_hal_async::i2c::I2c::transaction(self, address, operations)
269                .await
270                .map_err(Into::into)
271        })
272    }
273}