Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: rust-embedded/embedded-hal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 156e4a8c0d0cfcb1ac4bd0fe1c47e4ed1efcccd2
Choose a base ref
..
head repository: rust-embedded/embedded-hal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 66774e9bac9d51523412563caa7e1c30d1ea5139
Choose a head ref
Showing with 56 additions and 47 deletions.
  1. +0 −4 embedded-hal-bus/CHANGELOG.md
  2. +29 −20 embedded-hal-bus/src/i2c/atomic.rs
  3. +1 −0 embedded-hal-bus/src/lib.rs
  4. +1 −23 embedded-hal-bus/src/spi/atomic.rs
  5. +25 −0 embedded-hal-bus/src/util.rs
4 changes: 0 additions & 4 deletions embedded-hal-bus/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -10,10 +10,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Added
- Added a new `AtomicDevice` for I2C and SPI to enable bus sharing across multiple contexts.


### Fixed
- Fixed an issue with SPI `ExclusiveDevice` builds when the `async` feature was enabled.

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

- Updated `embedded-hal` to version `1.0.0`.
49 changes: 29 additions & 20 deletions embedded-hal-bus/src/i2c/atomic.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
use core::cell::UnsafeCell;
use embedded_hal::i2c::{Error, ErrorKind, ErrorType, I2c};

/// `UnsafeCell`-based shared bus [`I2c`] implementation.
use crate::util::AtomicCell;

/// Atomics-based shared bus [`I2c`] implementation.
///
/// Sharing is implemented with a [`AtomicDevice`], which consists of an `UnsafeCell` and an `AtomicBool` "locked" flag.
/// This means it has low overhead, like [`RefCellDevice`](crate::i2c::RefCellDevice). Aditionally, it is `Send`,
/// which allows sharing a single bus across multiple threads (interrupt priority level), like [`CriticalSectionDevice`](crate::i2c::CriticalSectionDevice),
/// while not using critical sections and therefore impacting real-time performance less.
///
/// Sharing is implemented with a `UnsafeCell`. This means it has low overhead, similar to [`crate::i2c::RefCellDevice`] instances, but they are `Send`.
/// so it only allows sharing across multiple threads (interrupt priority levels). When attempting
/// to preempt usage of the bus, a `AtomicError::Busy` error is returned.
/// The downside is using a simple `AtomicBool` for locking doesn't prevent two threads from trying to lock it at once.
/// For example, the main thread can be doing an I2C transaction, and an interrupt fires and tries to do another. In this
/// case, a `Busy` error is returned that must be handled somehow, usually dropping the data or trying again later.
///
/// Note that retrying in a loop on `Busy` errors usually leads to deadlocks. In the above example, it'll prevent the
/// interrupt handler from returning, which will starve the main thread and prevent it from releasing the lock. If
/// this is an issue for your use case, you most likely should use [`CriticalSectionDevice`](crate::i2c::CriticalSectionDevice) instead.
///
/// This primitive is particularly well-suited for applications that have external arbitration
/// rules, such as the RTIC framework.
/// rules that prevent `Busy` errors in the first place, such as the RTIC framework.
///
/// # Examples
///
@@ -18,7 +28,7 @@ use embedded_hal::i2c::{Error, ErrorKind, ErrorType, I2c};
///
/// ```
/// use embedded_hal_bus::i2c;
/// use core::cell::UnsafeCell;
/// use embedded_hal_bus::util::AtomicCell;
/// # use embedded_hal::i2c::{self as hali2c, SevenBitAddress, TenBitAddress, I2c, Operation, ErrorKind};
/// # pub struct Sensor<I2C> {
/// # i2c: I2C,
@@ -56,19 +66,18 @@ use embedded_hal::i2c::{Error, ErrorKind, ErrorType, I2c};
/// # let hal = Hal;
///
/// let i2c = hal.i2c();
/// let i2c_unsafe_cell = UnsafeCell::new(i2c);
/// let i2c_cell = AtomicCell::new(i2c);
/// let mut temperature_sensor = TemperatureSensor::new(
/// i2c::AtomicDevice::new(&i2c_unsafe_cell),
/// i2c::AtomicDevice::new(&i2c_cell),
/// 0x20,
/// );
/// let mut pressure_sensor = PressureSensor::new(
/// i2c::AtomicDevice::new(&i2c_unsafe_cell),
/// i2c::AtomicDevice::new(&i2c_cell),
/// 0x42,
/// );
/// ```
pub struct AtomicDevice<'a, T> {
bus: &'a UnsafeCell<T>,
busy: portable_atomic::AtomicBool,
bus: &'a AtomicCell<T>,
}

#[derive(Debug, Copy, Clone)]
@@ -100,18 +109,16 @@ where
{
/// Create a new `AtomicDevice`.
#[inline]
pub fn new(bus: &'a UnsafeCell<T>) -> Self {
Self {
bus,
busy: portable_atomic::AtomicBool::from(false),
}
pub fn new(bus: &'a AtomicCell<T>) -> Self {
Self { bus }
}

fn lock<R, F>(&self, f: F) -> Result<R, AtomicError<T::Error>>
where
F: FnOnce(&mut T) -> Result<R, <T as ErrorType>::Error>,
{
self.busy
self.bus
.busy
.compare_exchange(
false,
true,
@@ -120,9 +127,11 @@ where
)
.map_err(|_| AtomicError::<T::Error>::Busy)?;

let result = f(unsafe { &mut *self.bus.get() });
let result = f(unsafe { &mut *self.bus.bus.get() });

self.busy.store(false, core::sync::atomic::Ordering::SeqCst);
self.bus
.busy
.store(false, core::sync::atomic::Ordering::SeqCst);

result.map_err(AtomicError::Other)
}
1 change: 1 addition & 0 deletions embedded-hal-bus/src/lib.rs
Original file line number Diff line number Diff line change
@@ -18,3 +18,4 @@ use defmt_03 as defmt;

pub mod i2c;
pub mod spi;
pub mod util;
24 changes: 1 addition & 23 deletions embedded-hal-bus/src/spi/atomic.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,10 @@
use core::cell::UnsafeCell;
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;
use embedded_hal::spi::{Error, ErrorKind, ErrorType, Operation, SpiBus, SpiDevice};

use super::DeviceError;
use crate::spi::shared::transaction;

/// Cell type used by [`AtomicDevice`].
///
/// To use [`AtomicDevice`], you must wrap the bus with this struct, and then
/// construct multiple [`AtomicDevice`] instances with references to it.
pub struct AtomicCell<BUS> {
bus: UnsafeCell<BUS>,
busy: portable_atomic::AtomicBool,
}

unsafe impl<BUS: Send> Send for AtomicCell<BUS> {}
unsafe impl<BUS: Send> Sync for AtomicCell<BUS> {}

impl<BUS> AtomicCell<BUS> {
/// Create a new `AtomicCell`
pub fn new(bus: BUS) -> Self {
Self {
bus: UnsafeCell::new(bus),
busy: portable_atomic::AtomicBool::from(false),
}
}
}
use crate::util::AtomicCell;

/// Atomics-based shared bus [`SpiDevice`] implementation.
///
25 changes: 25 additions & 0 deletions embedded-hal-bus/src/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! Utilities shared by all bus types.
use core::cell::UnsafeCell;

/// Cell type used by [`spi::AtomicDevice`](crate::spi::AtomicDevice) and [`i2c::AtomicDevice`](crate::i2c::AtomicDevice).
///
/// To use [`AtomicDevice`], you must wrap the bus with this struct, and then
/// construct multiple [`AtomicDevice`] instances with references to it.
pub struct AtomicCell<BUS> {
pub(crate) bus: UnsafeCell<BUS>,
pub(crate) busy: portable_atomic::AtomicBool,
}

unsafe impl<BUS: Send> Send for AtomicCell<BUS> {}
unsafe impl<BUS: Send> Sync for AtomicCell<BUS> {}

impl<BUS> AtomicCell<BUS> {
/// Create a new `AtomicCell`
pub fn new(bus: BUS) -> Self {
Self {
bus: UnsafeCell::new(bus),
busy: portable_atomic::AtomicBool::from(false),
}
}
}