Skip to content

Commit 4a15a6f

Browse files
committed
bus/spi: add RefCell, CriticalSection and Mutex shared bus implementations.
1 parent b8b5489 commit 4a15a6f

File tree

11 files changed

+548
-171
lines changed

11 files changed

+548
-171
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/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-
...
10+
### Added
11+
- spi: add bus sharing implementations.
1112

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

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;

embedded-hal-bus/src/spi.rs

Lines changed: 0 additions & 163 deletions
This file was deleted.
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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 op_res = operations.iter_mut().try_for_each(|op| match op {
116+
Operation::Read(buf) => bus.read(buf),
117+
Operation::Write(buf) => bus.write(buf),
118+
Operation::Transfer(read, write) => bus.transfer(read, write),
119+
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
120+
});
121+
122+
// On failure, it's important to still flush and deassert CS.
123+
let flush_res = bus.flush();
124+
let cs_res = self.cs.set_high();
125+
126+
op_res.map_err(DeviceError::Spi)?;
127+
flush_res.map_err(DeviceError::Spi)?;
128+
cs_res.map_err(DeviceError::Cs)?;
129+
130+
Ok(())
131+
})
132+
}
133+
}

0 commit comments

Comments
 (0)