embedded_hal_bus/i2c/atomic.rs
1use embedded_hal::i2c::{Error, ErrorKind, ErrorType, I2c};
2
3use crate::util::AtomicCell;
4
5/// Atomics-based shared bus [`I2c`] implementation.
6///
7/// Sharing is implemented with a [`AtomicDevice`], which consists of an `UnsafeCell` and an `AtomicBool` "locked" flag.
8/// This means it has low overhead, like [`RefCellDevice`](crate::i2c::RefCellDevice). Aditionally, it is `Send`,
9/// which allows sharing a single bus across multiple threads (interrupt priority level), like [`CriticalSectionDevice`](crate::i2c::CriticalSectionDevice),
10/// while not using critical sections and therefore impacting real-time performance less.
11///
12/// The downside is using a simple `AtomicBool` for locking doesn't prevent two threads from trying to lock it at once.
13/// For example, the main thread can be doing an I2C transaction, and an interrupt fires and tries to do another. In this
14/// case, a `Busy` error is returned that must be handled somehow, usually dropping the data or trying again later.
15///
16/// Note that retrying in a loop on `Busy` errors usually leads to deadlocks. In the above example, it'll prevent the
17/// interrupt handler from returning, which will starve the main thread and prevent it from releasing the lock. If
18/// this is an issue for your use case, you most likely should use [`CriticalSectionDevice`](crate::i2c::CriticalSectionDevice) instead.
19///
20/// This primitive is particularly well-suited for applications that have external arbitration
21/// rules that prevent `Busy` errors in the first place, such as the RTIC framework.
22///
23/// # Examples
24///
25/// Assuming there is a pressure sensor with address `0x42` on the same bus as a temperature sensor
26/// with address `0x20`; [`AtomicDevice`] can be used to give access to both of these sensors
27/// from a single `i2c` instance.
28///
29/// ```
30/// use embedded_hal_bus::i2c;
31/// use embedded_hal_bus::util::AtomicCell;
32/// # use embedded_hal::i2c::{self as hali2c, SevenBitAddress, TenBitAddress, I2c, Operation, ErrorKind};
33/// # pub struct Sensor<I2C> {
34/// # i2c: I2C,
35/// # address: u8,
36/// # }
37/// # impl<I2C: I2c> Sensor<I2C> {
38/// # pub fn new(i2c: I2C, address: u8) -> Self {
39/// # Self { i2c, address }
40/// # }
41/// # }
42/// # type PressureSensor<I2C> = Sensor<I2C>;
43/// # type TemperatureSensor<I2C> = Sensor<I2C>;
44/// # pub struct I2c0;
45/// # #[derive(Debug, Copy, Clone, Eq, PartialEq)]
46/// # pub enum Error { }
47/// # impl hali2c::Error for Error {
48/// # fn kind(&self) -> hali2c::ErrorKind {
49/// # ErrorKind::Other
50/// # }
51/// # }
52/// # impl hali2c::ErrorType for I2c0 {
53/// # type Error = Error;
54/// # }
55/// # impl I2c<SevenBitAddress> for I2c0 {
56/// # fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> {
57/// # Ok(())
58/// # }
59/// # }
60/// # struct Hal;
61/// # impl Hal {
62/// # fn i2c(&self) -> I2c0 {
63/// # I2c0
64/// # }
65/// # }
66/// # let hal = Hal;
67///
68/// let i2c = hal.i2c();
69/// let i2c_cell = AtomicCell::new(i2c);
70/// let mut temperature_sensor = TemperatureSensor::new(
71/// i2c::AtomicDevice::new(&i2c_cell),
72/// 0x20,
73/// );
74/// let mut pressure_sensor = PressureSensor::new(
75/// i2c::AtomicDevice::new(&i2c_cell),
76/// 0x42,
77/// );
78/// ```
79pub struct AtomicDevice<'a, T> {
80 bus: &'a AtomicCell<T>,
81}
82
83#[derive(Debug, Copy, Clone)]
84/// Wrapper type for errors originating from the atomically-checked I2C bus manager.
85pub enum AtomicError<T: Error> {
86 /// This error is returned if the I2C bus was already in use when an operation was attempted,
87 /// which indicates that the driver requirements are not being met with regard to
88 /// synchronization.
89 Busy,
90
91 /// An I2C-related error occurred, and the internal error should be inspected.
92 Other(T),
93}
94
95impl<T: Error> Error for AtomicError<T> {
96 fn kind(&self) -> ErrorKind {
97 match self {
98 AtomicError::Other(e) => e.kind(),
99 _ => ErrorKind::Other,
100 }
101 }
102}
103
104unsafe impl<'a, T> Send for AtomicDevice<'a, T> {}
105
106impl<'a, T> AtomicDevice<'a, T>
107where
108 T: I2c,
109{
110 /// Create a new `AtomicDevice`.
111 #[inline]
112 pub fn new(bus: &'a AtomicCell<T>) -> Self {
113 Self { bus }
114 }
115
116 fn lock<R, F>(&self, f: F) -> Result<R, AtomicError<T::Error>>
117 where
118 F: FnOnce(&mut T) -> Result<R, <T as ErrorType>::Error>,
119 {
120 self.bus
121 .busy
122 .compare_exchange(
123 false,
124 true,
125 core::sync::atomic::Ordering::SeqCst,
126 core::sync::atomic::Ordering::SeqCst,
127 )
128 .map_err(|_| AtomicError::<T::Error>::Busy)?;
129
130 let result = f(unsafe { &mut *self.bus.bus.get() });
131
132 self.bus
133 .busy
134 .store(false, core::sync::atomic::Ordering::SeqCst);
135
136 result.map_err(AtomicError::Other)
137 }
138}
139
140impl<'a, T> ErrorType for AtomicDevice<'a, T>
141where
142 T: I2c,
143{
144 type Error = AtomicError<T::Error>;
145}
146
147impl<'a, T> I2c for AtomicDevice<'a, T>
148where
149 T: I2c,
150{
151 #[inline]
152 fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
153 self.lock(|bus| bus.read(address, read))
154 }
155
156 #[inline]
157 fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
158 self.lock(|bus| bus.write(address, write))
159 }
160
161 #[inline]
162 fn write_read(
163 &mut self,
164 address: u8,
165 write: &[u8],
166 read: &mut [u8],
167 ) -> Result<(), Self::Error> {
168 self.lock(|bus| bus.write_read(address, write, read))
169 }
170
171 #[inline]
172 fn transaction(
173 &mut self,
174 address: u8,
175 operations: &mut [embedded_hal::i2c::Operation<'_>],
176 ) -> Result<(), Self::Error> {
177 self.lock(|bus| bus.transaction(address, operations))
178 }
179}