Skip to content

Commit 762b421

Browse files
committed
bus/spi: add RefCell, CriticalSection and Mutex shared bus implementations.
1 parent 08f7e13 commit 762b421

File tree

9 files changed

+513
-44
lines changed

9 files changed

+513
-44
lines changed

.github/workflows/clippy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ jobs:
1515
# Use a pinned version to avoid spontaneous breakages (new clippy lints are added often)
1616
toolchain: nightly-2022-11-22
1717
components: clippy
18-
- run: cargo clippy -- --deny=warnings
18+
- run: cargo clippy --features=embedded-hal-bus/std -- --deny=warnings

.github/workflows/test.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ jobs:
1313
runs-on: ubuntu-latest
1414
strategy:
1515
matrix:
16-
# All generated code should be running on stable now
1716
rust:
1817
- stable
1918
- 1.59.0 # MSRV
2019
- nightly
21-
22-
# The default target we're compiling on and for
2320
target:
2421
- x86_64-unknown-linux-gnu
2522
- thumbv6m-none-eabi
2623
- thumbv7m-none-eabi
24+
include:
25+
- target: x86_64-unknown-linux-gnu
26+
features: embedded-hal-bus/std
2727

2828
steps:
2929
- uses: actions/checkout@v3
@@ -35,7 +35,7 @@ jobs:
3535
- run: sed -i '/nightly-only/d' Cargo.toml
3636
if: matrix.rust != 'nightly'
3737

38-
- run: cargo check --target=${{ matrix.target }}
38+
- run: cargo check --target=${{ matrix.target }} --features=${{ matrix.features }}
3939

40-
- run: cargo test --target=${{ matrix.target }}
40+
- run: cargo test --target=${{ matrix.target }} --features=${{ matrix.features }}
4141
if: contains(matrix.target, 'linux')

embedded-hal-bus/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,9 @@ readme = "README.md"
1313
repository = "https://github.com/rust-embedded/embedded-hal"
1414
version = "0.1.0-alpha.1"
1515

16+
[features]
17+
std = []
18+
1619
[dependencies]
1720
embedded-hal = { version = "=1.0.0-alpha.9", path = "../embedded-hal" }
21+
critical-section = { version = "1.0" }

embedded-hal-bus/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
//! For further details on these traits, please consult the [`embedded-hal` documentation](https://docs.rs/embedded-hal).
1313
1414
#![warn(missing_docs)]
15-
#![no_std]
15+
#![cfg_attr(not(feature = "std"), no_std)]
1616

1717
pub mod spi;
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
use core::cell::RefCell;
2+
use critical_section::Mutex;
3+
use embedded_hal::digital::OutputPin;
4+
use embedded_hal::spi::{
5+
ErrorType, Operation, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice, SpiDeviceRead, SpiDeviceWrite,
6+
};
7+
8+
use super::DeviceError;
9+
10+
/// `critical-section`-based shared bus [`SpiDevice`] implementation.
11+
///
12+
/// This allows for sharing an [`SpiBus`](embedded_hal::spi::blocking::SpiBus), obtaining multiple [`SpiDevice`] instances,
13+
/// each with its own `CS` pin.
14+
///
15+
/// Sharing is implemented with a `critical-section` [`Mutex`](critical_section::Mutex). A critical section is taken for
16+
/// the entire duration of a transaction. This allows sharing a single bus across multiple threads (interrupt priority levels).
17+
/// The downside is critical sections typically require globally disabling interrupts, so `CriticalSectionDevice` will likely
18+
/// negatively impact real-time properties, such as interrupt latency. If you can, prefer using
19+
/// [`RefCellDevice`](super::RefCellDevice) instead, which does not require taking critical sections.
20+
pub struct CriticalSectionDevice<'a, BUS, CS> {
21+
bus: &'a Mutex<RefCell<BUS>>,
22+
cs: CS,
23+
}
24+
25+
impl<'a, BUS, CS> CriticalSectionDevice<'a, BUS, CS> {
26+
/// Create a new ExclusiveDevice
27+
pub fn new(bus: &'a Mutex<RefCell<BUS>>, cs: CS) -> Self {
28+
Self { bus, cs }
29+
}
30+
}
31+
32+
impl<'a, BUS, CS> ErrorType for CriticalSectionDevice<'a, BUS, CS>
33+
where
34+
BUS: ErrorType,
35+
CS: OutputPin,
36+
{
37+
type Error = DeviceError<BUS::Error, CS::Error>;
38+
}
39+
40+
impl<'a, Word: Copy + 'static, BUS, CS> SpiDeviceRead<Word> for CriticalSectionDevice<'a, BUS, CS>
41+
where
42+
BUS: SpiBusRead<Word>,
43+
CS: OutputPin,
44+
{
45+
fn read_transaction(&mut self, operations: &mut [&mut [Word]]) -> Result<(), Self::Error> {
46+
critical_section::with(|cs| {
47+
let bus = &mut *self.bus.borrow_ref_mut(cs);
48+
49+
self.cs.set_low().map_err(DeviceError::Cs)?;
50+
51+
let mut op_res = Ok(());
52+
for buf in operations {
53+
if let Err(e) = bus.read(buf) {
54+
op_res = Err(e);
55+
break;
56+
}
57+
}
58+
59+
// On failure, it's important to still flush and deassert CS.
60+
let flush_res = bus.flush();
61+
let cs_res = self.cs.set_high();
62+
63+
op_res.map_err(DeviceError::Spi)?;
64+
flush_res.map_err(DeviceError::Spi)?;
65+
cs_res.map_err(DeviceError::Cs)?;
66+
67+
Ok(())
68+
})
69+
}
70+
}
71+
72+
impl<'a, Word: Copy + 'static, BUS, CS> SpiDeviceWrite<Word> for CriticalSectionDevice<'a, BUS, CS>
73+
where
74+
BUS: SpiBusWrite<Word>,
75+
CS: OutputPin,
76+
{
77+
fn write_transaction(&mut self, operations: &[&[Word]]) -> Result<(), Self::Error> {
78+
critical_section::with(|cs| {
79+
let bus = &mut *self.bus.borrow_ref_mut(cs);
80+
81+
self.cs.set_low().map_err(DeviceError::Cs)?;
82+
83+
let mut op_res = Ok(());
84+
for buf in operations {
85+
if let Err(e) = bus.write(buf) {
86+
op_res = Err(e);
87+
break;
88+
}
89+
}
90+
91+
// On failure, it's important to still flush and deassert CS.
92+
let flush_res = bus.flush();
93+
let cs_res = self.cs.set_high();
94+
95+
op_res.map_err(DeviceError::Spi)?;
96+
flush_res.map_err(DeviceError::Spi)?;
97+
cs_res.map_err(DeviceError::Cs)?;
98+
99+
Ok(())
100+
})
101+
}
102+
}
103+
104+
impl<'a, Word: Copy + 'static, BUS, CS> SpiDevice<Word> for CriticalSectionDevice<'a, BUS, CS>
105+
where
106+
BUS: SpiBus<Word>,
107+
CS: OutputPin,
108+
{
109+
fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> {
110+
critical_section::with(|cs| {
111+
let bus = &mut *self.bus.borrow_ref_mut(cs);
112+
113+
self.cs.set_low().map_err(DeviceError::Cs)?;
114+
115+
let mut op_res = Ok(());
116+
for op in operations {
117+
match op {
118+
Operation::Read(buf) => {
119+
if let Err(e) = bus.read(buf) {
120+
op_res = Err(e);
121+
break;
122+
}
123+
}
124+
Operation::Write(buf) => {
125+
if let Err(e) = bus.write(buf) {
126+
op_res = Err(e);
127+
break;
128+
}
129+
}
130+
Operation::Transfer(read, write) => {
131+
if let Err(e) = bus.transfer(read, write) {
132+
op_res = Err(e);
133+
break;
134+
}
135+
}
136+
Operation::TransferInPlace(buf) => {
137+
if let Err(e) = bus.transfer_in_place(buf) {
138+
op_res = Err(e);
139+
break;
140+
}
141+
}
142+
}
143+
}
144+
145+
// On failure, it's important to still flush and deassert CS.
146+
let flush_res = bus.flush();
147+
let cs_res = self.cs.set_high();
148+
149+
op_res.map_err(DeviceError::Spi)?;
150+
flush_res.map_err(DeviceError::Spi)?;
151+
cs_res.map_err(DeviceError::Cs)?;
152+
153+
Ok(())
154+
})
155+
}
156+
}

embedded-hal-bus/src/spi.rs renamed to embedded-hal-bus/src/spi/exclusive.rs

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,11 @@
11
//! SPI bus sharing mechanisms.
22
3-
use core::fmt::Debug;
43
use embedded_hal::digital::OutputPin;
54
use embedded_hal::spi::{
6-
Error, ErrorKind, ErrorType, Operation, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice,
7-
SpiDeviceRead, SpiDeviceWrite,
5+
ErrorType, Operation, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice, SpiDeviceRead, SpiDeviceWrite,
86
};
97

10-
/// Error type for [`ExclusiveDevice`] operations.
11-
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
12-
pub enum ExclusiveDeviceError<BUS, CS> {
13-
/// An inner SPI bus operation failed
14-
Spi(BUS),
15-
/// Asserting or deasserting CS failed
16-
Cs(CS),
17-
}
18-
19-
impl<BUS, CS> Error for ExclusiveDeviceError<BUS, CS>
20-
where
21-
BUS: Error + Debug,
22-
CS: Debug,
23-
{
24-
fn kind(&self) -> ErrorKind {
25-
match self {
26-
Self::Spi(e) => e.kind(),
27-
Self::Cs(_) => ErrorKind::ChipSelectFault,
28-
}
29-
}
30-
}
8+
use super::DeviceError;
319

3210
/// [`SpiDevice`] implementation with exclusive access to the bus (not shared).
3311
///
@@ -50,7 +28,7 @@ where
5028
BUS: ErrorType,
5129
CS: OutputPin,
5230
{
53-
type Error = ExclusiveDeviceError<BUS::Error, CS::Error>;
31+
type Error = DeviceError<BUS::Error, CS::Error>;
5432
}
5533

5634
impl<Word: Copy + 'static, BUS, CS> SpiDeviceRead<Word> for ExclusiveDevice<BUS, CS>
@@ -59,7 +37,7 @@ where
5937
CS: OutputPin,
6038
{
6139
fn read_transaction(&mut self, operations: &mut [&mut [Word]]) -> Result<(), Self::Error> {
62-
self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?;
40+
self.cs.set_low().map_err(DeviceError::Cs)?;
6341

6442
let mut op_res = Ok(());
6543

@@ -74,9 +52,9 @@ where
7452
let flush_res = self.bus.flush();
7553
let cs_res = self.cs.set_high();
7654

77-
op_res.map_err(ExclusiveDeviceError::Spi)?;
78-
flush_res.map_err(ExclusiveDeviceError::Spi)?;
79-
cs_res.map_err(ExclusiveDeviceError::Cs)?;
55+
op_res.map_err(DeviceError::Spi)?;
56+
flush_res.map_err(DeviceError::Spi)?;
57+
cs_res.map_err(DeviceError::Cs)?;
8058

8159
Ok(())
8260
}
@@ -88,7 +66,7 @@ where
8866
CS: OutputPin,
8967
{
9068
fn write_transaction(&mut self, operations: &[&[Word]]) -> Result<(), Self::Error> {
91-
self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?;
69+
self.cs.set_low().map_err(DeviceError::Cs)?;
9270

9371
let mut op_res = Ok(());
9472

@@ -103,9 +81,9 @@ where
10381
let flush_res = self.bus.flush();
10482
let cs_res = self.cs.set_high();
10583

106-
op_res.map_err(ExclusiveDeviceError::Spi)?;
107-
flush_res.map_err(ExclusiveDeviceError::Spi)?;
108-
cs_res.map_err(ExclusiveDeviceError::Cs)?;
84+
op_res.map_err(DeviceError::Spi)?;
85+
flush_res.map_err(DeviceError::Spi)?;
86+
cs_res.map_err(DeviceError::Cs)?;
10987

11088
Ok(())
11189
}
@@ -117,7 +95,7 @@ where
11795
CS: OutputPin,
11896
{
11997
fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> {
120-
self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?;
98+
self.cs.set_low().map_err(DeviceError::Cs)?;
12199

122100
let mut op_res = Ok(());
123101

@@ -154,9 +132,9 @@ where
154132
let flush_res = self.bus.flush();
155133
let cs_res = self.cs.set_high();
156134

157-
op_res.map_err(ExclusiveDeviceError::Spi)?;
158-
flush_res.map_err(ExclusiveDeviceError::Spi)?;
159-
cs_res.map_err(ExclusiveDeviceError::Cs)?;
135+
op_res.map_err(DeviceError::Spi)?;
136+
flush_res.map_err(DeviceError::Spi)?;
137+
cs_res.map_err(DeviceError::Cs)?;
160138

161139
Ok(())
162140
}

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//! `SpiDevice` implementations.
2+
3+
use core::fmt::Debug;
4+
use embedded_hal::spi::{Error, ErrorKind};
5+
6+
mod exclusive;
7+
pub use exclusive::*;
8+
mod refcell;
9+
pub use refcell::*;
10+
#[cfg(feature = "std")]
11+
mod mutex;
12+
#[cfg(feature = "std")]
13+
pub use mutex::*;
14+
mod critical_section;
15+
pub use self::critical_section::*;
16+
17+
/// Error type for [`ExclusiveDevice`] operations.
18+
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
19+
pub enum DeviceError<BUS, CS> {
20+
/// An inner SPI bus operation failed
21+
Spi(BUS),
22+
/// Asserting or deasserting CS failed
23+
Cs(CS),
24+
}
25+
26+
impl<BUS, CS> Error for DeviceError<BUS, CS>
27+
where
28+
BUS: Error + Debug,
29+
CS: Debug,
30+
{
31+
fn kind(&self) -> ErrorKind {
32+
match self {
33+
Self::Spi(e) => e.kind(),
34+
Self::Cs(_) => ErrorKind::ChipSelectFault,
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)