Skip to content

Commit e00ffa0

Browse files
ryan-summersDirbaio
authored andcommitted
Adding AtomicDevice for I2C bus sharing
1 parent 88f63d4 commit e00ffa0

File tree

6 files changed

+306
-1
lines changed

6 files changed

+306
-1
lines changed

embedded-hal-bus/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
99

10-
No unreleased changes
10+
### Added
11+
- Added a new `AtomicDevice` for I2C and SPI to enable bus sharing across multiple contexts.
1112

1213
## [v0.1.0] - 2023-12-28
1314

embedded-hal-bus/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ embedded-hal = { version = "1.0.0", path = "../embedded-hal" }
2424
embedded-hal-async = { version = "1.0.0", path = "../embedded-hal-async", optional = true }
2525
critical-section = { version = "1.0" }
2626
defmt-03 = { package = "defmt", version = "0.3", optional = true }
27+
portable-atomic = {version = "1", default-features = false}
2728

2829
[package.metadata.docs.rs]
2930
features = ["std", "async"]

embedded-hal-bus/src/i2c/atomic.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use core::cell::UnsafeCell;
2+
use embedded_hal::i2c::{Error, ErrorKind, ErrorType, I2c};
3+
4+
/// `UnsafeCell`-based shared bus [`I2c`] implementation.
5+
///
6+
/// Sharing is implemented with a `UnsafeCell`. This means it has low overhead, similar to [`crate::i2c::RefCellDevice`] instances, but they are `Send`.
7+
/// so it only allows sharing across multiple threads (interrupt priority levels). When attempting
8+
/// to preempt usage of the bus, a `AtomicError::Busy` error is returned.
9+
///
10+
/// This primitive is particularly well-suited for applications that have external arbitration
11+
/// rules, such as the RTIC framework.
12+
///
13+
/// # Examples
14+
///
15+
/// Assuming there is a pressure sensor with address `0x42` on the same bus as a temperature sensor
16+
/// with address `0x20`; [`AtomicDevice`] can be used to give access to both of these sensors
17+
/// from a single `i2c` instance.
18+
///
19+
/// ```
20+
/// use embedded_hal_bus::i2c;
21+
/// use core::cell::UnsafeCell;
22+
/// # use embedded_hal::i2c::{self as hali2c, SevenBitAddress, TenBitAddress, I2c, Operation, ErrorKind};
23+
/// # pub struct Sensor<I2C> {
24+
/// # i2c: I2C,
25+
/// # address: u8,
26+
/// # }
27+
/// # impl<I2C: I2c> Sensor<I2C> {
28+
/// # pub fn new(i2c: I2C, address: u8) -> Self {
29+
/// # Self { i2c, address }
30+
/// # }
31+
/// # }
32+
/// # type PressureSensor<I2C> = Sensor<I2C>;
33+
/// # type TemperatureSensor<I2C> = Sensor<I2C>;
34+
/// # pub struct I2c0;
35+
/// # #[derive(Debug, Copy, Clone, Eq, PartialEq)]
36+
/// # pub enum Error { }
37+
/// # impl hali2c::Error for Error {
38+
/// # fn kind(&self) -> hali2c::ErrorKind {
39+
/// # ErrorKind::Other
40+
/// # }
41+
/// # }
42+
/// # impl hali2c::ErrorType for I2c0 {
43+
/// # type Error = Error;
44+
/// # }
45+
/// # impl I2c<SevenBitAddress> for I2c0 {
46+
/// # fn transaction(&mut self, address: u8, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> {
47+
/// # Ok(())
48+
/// # }
49+
/// # }
50+
/// # struct Hal;
51+
/// # impl Hal {
52+
/// # fn i2c(&self) -> I2c0 {
53+
/// # I2c0
54+
/// # }
55+
/// # }
56+
/// # let hal = Hal;
57+
///
58+
/// let i2c = hal.i2c();
59+
/// let i2c_unsafe_cell = UnsafeCell::new(i2c);
60+
/// let mut temperature_sensor = TemperatureSensor::new(
61+
/// i2c::AtomicDevice::new(&i2c_unsafe_cell),
62+
/// 0x20,
63+
/// );
64+
/// let mut pressure_sensor = PressureSensor::new(
65+
/// i2c::AtomicDevice::new(&i2c_unsafe_cell),
66+
/// 0x42,
67+
/// );
68+
/// ```
69+
pub struct AtomicDevice<'a, T> {
70+
bus: &'a UnsafeCell<T>,
71+
busy: portable_atomic::AtomicBool,
72+
}
73+
74+
#[derive(Debug, Copy, Clone)]
75+
/// Wrapper type for errors originating from the atomically-checked I2C bus manager.
76+
pub enum AtomicError<T: Error> {
77+
/// This error is returned if the I2C bus was already in use when an operation was attempted,
78+
/// which indicates that the driver requirements are not being met with regard to
79+
/// synchronization.
80+
Busy,
81+
82+
/// An I2C-related error occurred, and the internal error should be inspected.
83+
Other(T),
84+
}
85+
86+
impl<T: Error> Error for AtomicError<T> {
87+
fn kind(&self) -> ErrorKind {
88+
match self {
89+
AtomicError::Other(e) => e.kind(),
90+
_ => ErrorKind::Other,
91+
}
92+
}
93+
}
94+
95+
unsafe impl<'a, T> Send for AtomicDevice<'a, T> {}
96+
97+
impl<'a, T> AtomicDevice<'a, T>
98+
where
99+
T: I2c,
100+
{
101+
/// Create a new `AtomicDevice`.
102+
#[inline]
103+
pub fn new(bus: &'a UnsafeCell<T>) -> Self {
104+
Self {
105+
bus,
106+
busy: portable_atomic::AtomicBool::from(false),
107+
}
108+
}
109+
110+
fn lock<R, F>(&self, f: F) -> Result<R, AtomicError<T::Error>>
111+
where
112+
F: FnOnce(&mut T) -> Result<R, <T as ErrorType>::Error>,
113+
{
114+
self.busy
115+
.compare_exchange(
116+
false,
117+
true,
118+
core::sync::atomic::Ordering::SeqCst,
119+
core::sync::atomic::Ordering::SeqCst,
120+
)
121+
.map_err(|_| AtomicError::<T::Error>::Busy)?;
122+
123+
let result = f(unsafe { &mut *self.bus.get() });
124+
125+
self.busy.store(false, core::sync::atomic::Ordering::SeqCst);
126+
127+
result.map_err(AtomicError::Other)
128+
}
129+
}
130+
131+
impl<'a, T> ErrorType for AtomicDevice<'a, T>
132+
where
133+
T: I2c,
134+
{
135+
type Error = AtomicError<T::Error>;
136+
}
137+
138+
impl<'a, T> I2c for AtomicDevice<'a, T>
139+
where
140+
T: I2c,
141+
{
142+
#[inline]
143+
fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
144+
self.lock(|bus| bus.read(address, read))
145+
}
146+
147+
#[inline]
148+
fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
149+
self.lock(|bus| bus.write(address, write))
150+
}
151+
152+
#[inline]
153+
fn write_read(
154+
&mut self,
155+
address: u8,
156+
write: &[u8],
157+
read: &mut [u8],
158+
) -> Result<(), Self::Error> {
159+
self.lock(|bus| bus.write_read(address, write, read))
160+
}
161+
162+
#[inline]
163+
fn transaction(
164+
&mut self,
165+
address: u8,
166+
operations: &mut [embedded_hal::i2c::Operation<'_>],
167+
) -> Result<(), Self::Error> {
168+
self.lock(|bus| bus.transaction(address, operations))
169+
}
170+
}

embedded-hal-bus/src/i2c/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ mod mutex;
88
pub use mutex::*;
99
mod critical_section;
1010
pub use self::critical_section::*;
11+
mod atomic;
12+
pub use atomic::*;

embedded-hal-bus/src/spi/atomic.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use core::cell::UnsafeCell;
2+
use embedded_hal::delay::DelayNs;
3+
use embedded_hal::digital::OutputPin;
4+
use embedded_hal::spi::{Error, ErrorKind, ErrorType, Operation, SpiBus, SpiDevice};
5+
6+
use super::DeviceError;
7+
use crate::spi::shared::transaction;
8+
9+
/// `UnsafeCell`-based shared bus [`SpiDevice`] implementation.
10+
///
11+
/// This allows for sharing an [`SpiBus`], obtaining multiple [`SpiDevice`] instances,
12+
/// each with its own `CS` pin.
13+
///
14+
/// Sharing is implemented with a `UnsafeCell`. This means it has low overhead, and, unlike [`crate::spi::RefCellDevice`], instances are `Send`,
15+
/// so it only allows sharing across multiple threads (interrupt priority level). When attempting
16+
/// to preempt usage of the bus, a `AtomicError::Busy` error is returned.
17+
///
18+
/// This primitive is particularly well-suited for applications that have external arbitration
19+
/// rules, such as the RTIC framework.
20+
///
21+
pub struct AtomicDevice<'a, BUS, CS, D> {
22+
bus: &'a UnsafeCell<BUS>,
23+
cs: CS,
24+
delay: D,
25+
busy: portable_atomic::AtomicBool,
26+
}
27+
28+
#[derive(Debug, Copy, Clone)]
29+
/// Wrapper type for errors originating from the atomically-checked SPI bus manager.
30+
pub enum AtomicError<T: Error> {
31+
/// This error is returned if the SPI bus was already in use when an operation was attempted,
32+
/// which indicates that the driver requirements are not being met with regard to
33+
/// synchronization.
34+
Busy,
35+
36+
/// An SPI-related error occurred, and the internal error should be inspected.
37+
Other(T),
38+
}
39+
40+
impl<'a, BUS, CS, D> AtomicDevice<'a, BUS, CS, D> {
41+
/// Create a new [`AtomicDevice`].
42+
#[inline]
43+
pub fn new(bus: &'a UnsafeCell<BUS>, cs: CS, delay: D) -> Self {
44+
Self {
45+
bus,
46+
cs,
47+
delay,
48+
busy: portable_atomic::AtomicBool::from(false),
49+
}
50+
}
51+
}
52+
53+
impl<'a, BUS, CS> AtomicDevice<'a, BUS, CS, super::NoDelay>
54+
where
55+
BUS: ErrorType,
56+
CS: OutputPin,
57+
{
58+
/// Create a new [`AtomicDevice`] without support for in-transaction delays.
59+
///
60+
/// **Warning**: The returned instance *technically* doesn't comply with the `SpiDevice`
61+
/// contract, which mandates delay support. It is relatively rare for drivers to use
62+
/// in-transaction delays, so you might still want to use this method because it's more practical.
63+
///
64+
/// Note that a future version of the driver might start using delays, causing your
65+
/// code to panic. This wouldn't be considered a breaking change from the driver side, because
66+
/// drivers are allowed to assume `SpiDevice` implementations comply with the contract.
67+
/// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade
68+
/// the driver crate, you might want to pin the driver's version.
69+
///
70+
/// # Panics
71+
///
72+
/// The returned device will panic if you try to execute a transaction
73+
/// that contains any operations of type [`Operation::DelayNs`].
74+
#[inline]
75+
pub fn new_no_delay(bus: &'a UnsafeCell<BUS>, cs: CS) -> Self {
76+
Self {
77+
bus,
78+
cs,
79+
delay: super::NoDelay,
80+
busy: portable_atomic::AtomicBool::from(false),
81+
}
82+
}
83+
}
84+
85+
unsafe impl<'a, BUS, CS, D> Send for AtomicDevice<'a, BUS, CS, D> {}
86+
87+
impl<T: Error> Error for AtomicError<T> {
88+
fn kind(&self) -> ErrorKind {
89+
match self {
90+
AtomicError::Other(e) => e.kind(),
91+
_ => ErrorKind::Other,
92+
}
93+
}
94+
}
95+
96+
impl<'a, BUS, CS, D> ErrorType for AtomicDevice<'a, BUS, CS, D>
97+
where
98+
BUS: ErrorType,
99+
CS: OutputPin,
100+
{
101+
type Error = AtomicError<DeviceError<BUS::Error, CS::Error>>;
102+
}
103+
104+
impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice<Word> for AtomicDevice<'a, BUS, CS, D>
105+
where
106+
BUS: SpiBus<Word>,
107+
CS: OutputPin,
108+
D: DelayNs,
109+
{
110+
#[inline]
111+
fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> {
112+
self.busy
113+
.compare_exchange(
114+
false,
115+
true,
116+
core::sync::atomic::Ordering::SeqCst,
117+
core::sync::atomic::Ordering::SeqCst,
118+
)
119+
.map_err(|_| AtomicError::Busy)?;
120+
121+
let bus = unsafe { &mut *self.bus.get() };
122+
123+
let result = transaction(operations, bus, &mut self.delay, &mut self.cs);
124+
125+
self.busy.store(false, core::sync::atomic::Ordering::SeqCst);
126+
127+
result.map_err(AtomicError::Other)
128+
}
129+
}

embedded-hal-bus/src/spi/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ pub use refcell::*;
1111
mod mutex;
1212
#[cfg(feature = "std")]
1313
pub use mutex::*;
14+
mod atomic;
1415
mod critical_section;
1516
mod shared;
17+
pub use atomic::*;
1618

1719
pub use self::critical_section::*;
1820

0 commit comments

Comments
 (0)