Skip to content

Commit

Permalink
Merge #444
Browse files Browse the repository at this point in the history
444: bus/spi: add RefCell, CriticalSection and Mutex shared bus implementations. r=eldruin a=Dirbaio

Requires #443 

This adds a few bus sharing implementations, with varying tradeoffs:
- `RefCellDevice`: single thread only
- `CriticalSectionDevice`: thread-safe, coarse locking, nostd.
- `MutexDevice`: thread-safe, fine-grained locking, std only.

Co-authored-by: Dario Nieuwenhuis <[email protected]>
  • Loading branch information
bors[bot] and Dirbaio authored Apr 1, 2023
2 parents a8ea55f + 756b055 commit 0c98b79
Show file tree
Hide file tree
Showing 9 changed files with 538 additions and 163 deletions.
1 change: 1 addition & 0 deletions embedded-hal-async/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- delay: make infallible.
- i2c: remove `_iter()` methods.
- i2c: add default implementations for all methods based on `transaction()`.
- spi: SpiDevice transaction now takes an operation slice instead of a closure

## [v0.2.0-alpha.0] - 2022-11-23

Expand Down
1 change: 1 addition & 0 deletions embedded-hal-bus/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

### Added
- i2c: add bus sharing implementations.
- spi: add bus sharing implementations.

## [v0.1.0-alpha.1] - 2022-09-28

Expand Down
163 changes: 0 additions & 163 deletions embedded-hal-bus/src/spi.rs

This file was deleted.

133 changes: 133 additions & 0 deletions embedded-hal-bus/src/spi/critical_section.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use core::cell::RefCell;
use critical_section::Mutex;
use embedded_hal::digital::OutputPin;
use embedded_hal::spi::{
ErrorType, Operation, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice, SpiDeviceRead, SpiDeviceWrite,
};

use super::DeviceError;

/// `critical-section`-based shared bus [`SpiDevice`] implementation.
///
/// This allows for sharing an [`SpiBus`](embedded_hal::spi::SpiBus), obtaining multiple [`SpiDevice`] instances,
/// each with its own `CS` pin.
///
/// Sharing is implemented with a `critical-section` [`Mutex`](critical_section::Mutex). A critical section is taken for
/// the entire duration of a transaction. This allows sharing a single bus across multiple threads (interrupt priority levels).
/// The downside is critical sections typically require globally disabling interrupts, so `CriticalSectionDevice` will likely
/// negatively impact real-time properties, such as interrupt latency. If you can, prefer using
/// [`RefCellDevice`](super::RefCellDevice) instead, which does not require taking critical sections.
pub struct CriticalSectionDevice<'a, BUS, CS> {
bus: &'a Mutex<RefCell<BUS>>,
cs: CS,
}

impl<'a, BUS, CS> CriticalSectionDevice<'a, BUS, CS> {
/// Create a new ExclusiveDevice
pub fn new(bus: &'a Mutex<RefCell<BUS>>, cs: CS) -> Self {
Self { bus, cs }
}
}

impl<'a, BUS, CS> ErrorType for CriticalSectionDevice<'a, BUS, CS>
where
BUS: ErrorType,
CS: OutputPin,
{
type Error = DeviceError<BUS::Error, CS::Error>;
}

impl<'a, Word: Copy + 'static, BUS, CS> SpiDeviceRead<Word> for CriticalSectionDevice<'a, BUS, CS>
where
BUS: SpiBusRead<Word>,
CS: OutputPin,
{
fn read_transaction(&mut self, operations: &mut [&mut [Word]]) -> Result<(), Self::Error> {
critical_section::with(|cs| {
let bus = &mut *self.bus.borrow_ref_mut(cs);

self.cs.set_low().map_err(DeviceError::Cs)?;

let mut op_res = Ok(());
for buf in operations {
if let Err(e) = bus.read(buf) {
op_res = Err(e);
break;
}
}

// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush();
let cs_res = self.cs.set_high();

op_res.map_err(DeviceError::Spi)?;
flush_res.map_err(DeviceError::Spi)?;
cs_res.map_err(DeviceError::Cs)?;

Ok(())
})
}
}

impl<'a, Word: Copy + 'static, BUS, CS> SpiDeviceWrite<Word> for CriticalSectionDevice<'a, BUS, CS>
where
BUS: SpiBusWrite<Word>,
CS: OutputPin,
{
fn write_transaction(&mut self, operations: &[&[Word]]) -> Result<(), Self::Error> {
critical_section::with(|cs| {
let bus = &mut *self.bus.borrow_ref_mut(cs);

self.cs.set_low().map_err(DeviceError::Cs)?;

let mut op_res = Ok(());
for buf in operations {
if let Err(e) = bus.write(buf) {
op_res = Err(e);
break;
}
}

// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush();
let cs_res = self.cs.set_high();

op_res.map_err(DeviceError::Spi)?;
flush_res.map_err(DeviceError::Spi)?;
cs_res.map_err(DeviceError::Cs)?;

Ok(())
})
}
}

impl<'a, Word: Copy + 'static, BUS, CS> SpiDevice<Word> for CriticalSectionDevice<'a, BUS, CS>
where
BUS: SpiBus<Word>,
CS: OutputPin,
{
fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> {
critical_section::with(|cs| {
let bus = &mut *self.bus.borrow_ref_mut(cs);

self.cs.set_low().map_err(DeviceError::Cs)?;

let op_res = operations.iter_mut().try_for_each(|op| match op {
Operation::Read(buf) => bus.read(buf),
Operation::Write(buf) => bus.write(buf),
Operation::Transfer(read, write) => bus.transfer(read, write),
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
});

// On failure, it's important to still flush and deassert CS.
let flush_res = bus.flush();
let cs_res = self.cs.set_high();

op_res.map_err(DeviceError::Spi)?;
flush_res.map_err(DeviceError::Spi)?;
cs_res.map_err(DeviceError::Cs)?;

Ok(())
})
}
}
Loading

0 comments on commit 0c98b79

Please sign in to comment.