Skip to content

bus/spi: add RefCell, CriticalSection and Mutex shared bus implementations. #444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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