Skip to content

Commit 678a5eb

Browse files
committed
SPI: split into device/bus, allows sharing buses with automatic CS management.
1 parent ddf4375 commit 678a5eb

File tree

2 files changed

+246
-65
lines changed

2 files changed

+246
-65
lines changed

src/spi/blocking.rs

+244-65
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,287 @@
11
//! Blocking SPI API
2+
//!
3+
//! # Bus vs Device
4+
//!
5+
//! SPI allows sharing a single bus between many SPI devices. The SCK, MOSI and MISO lines are
6+
//! wired in parallel to all the devices, and each device gets a dedicated CS line from the MCU, like this:
7+
//!
8+
#![doc=include_str!("shared-bus.svg")]
9+
//!
10+
//! CS is usually active-low. When CS is high (not asserted), SPI devices ignore all incoming data, and
11+
//! don't drive MISO. When CS is low (asserted), the device is active: reacts to incoming data on MOSI and
12+
//! drives MISO with the response data. By asserting one CS or another, the MCU can choose to which
13+
//! SPI device it "talks" to on the shared bus.
14+
//!
15+
//! This bus sharing is common when having multiple SPI devices in the same board, since it uses fewer MCU
16+
//! pins (n+3 instead of 4*n), and fewer MCU SPI peripherals (1 instead of n).
17+
//!
18+
//! However, it poses a challenge when building portable drivers for SPI devices. The driver needs to
19+
//! be able to talk to its device on the bus, while not interfering with other drivers talking to other
20+
//! devices.
21+
//!
22+
//! To solve this, `embedded-hal` has two kinds of SPI traits: **SPI bus** and **SPI device**.
23+
//!
24+
//! ## Bus
25+
//!
26+
//! SPI bus traits represent **exclusive ownership** over the whole SPI bus. This is usually the entire
27+
//! SPI MCU peripheral, plus the SCK, MOSI and MISO pins.
28+
//!
29+
//! Owning an instance of an SPI bus guarantees exclusive access, this is, we have the guarantee no other
30+
//! piece of code will try to use the bus while we own it.
31+
//!
32+
//! There's 3 bus traits, depending on the bus capabilities.
33+
//!
34+
//! - [`SpiBus`]: Read-write access. This is the most commonly used.
35+
//! - [`SpiBusRead`]: Read-only access, for example a bus with a MISO pin but no MOSI pin.
36+
//! - [`SpiBusWrite`]: Read-write access, for example a bus with a MOSI pin but no MISO pin.
37+
//!
38+
//! ## Device
39+
//!
40+
//! [`SpiDevice`] represents **ownership over a single SPI device selected by a CS pin** in a (possibly shared) bus. This is typically:
41+
//!
42+
//! - Exclusive ownership of the **CS pin**.
43+
//! - Access to the **underlying SPI bus**. If shared, it'll be behind some kind of lock/mutex.
44+
//!
45+
//! An [`SpiDevice`] allows initiating [transactions](SpiDevice::transaction) against the target device on the bus. A transaction
46+
//! consists in asserting CS, then doing one or more transfers, then deasserting CS. For the entire duration of the transaction, the [`SpiDevice`]
47+
//! impl will ensure no other transaction can be opened on the same bus. This is the key that allows correct sharing of the bus.
48+
//!
49+
//! The capabilities of the bus (read-write, read-only or read-write) are determined by which of the [`SpiBus`], [`SpiBusRead`] [`SpiBusWrite`]
50+
//! are implemented for the [`Bus`](SpiDevice::Bus) associated type.
51+
//!
52+
//! # For driver authors
53+
//!
54+
//! When implementing a driver, it's crucial to pick the right trait, to ensure correct operation
55+
//! with maximum interoperability. Here are some guidelines depending on the device you're implementing a driver for:
56+
//!
57+
//! If your device **has a CS pin**, use [`SpiDevice`]. Do not manually manage the CS pin, the [`SpiDevice`] impl will do it for you.
58+
//! Add bounds like `where T::Bus: SpiBus`, `where T::Bus: SpiBusRead`, `where T::Bus: SpiBusWrite` to specify the kind of access you need.
59+
//! By using [`SpiDevice`], your driver will cooperate nicely with other drivers for other devices in the same shared SPI bus.
60+
//!
61+
//! ```
62+
//! # use embedded_hal::spi::blocking::{SpiBus, SpiBusRead, SpiBusWrite, SpiDevice};
63+
//! pub struct MyDriver<SPI> {
64+
//! spi: SPI,
65+
//! }
66+
//!
67+
//! impl<SPI> MyDriver<SPI>
68+
//! where
69+
//! SPI: SpiDevice,
70+
//! SPI::Bus: SpiBus, // or SpiBusRead/SpiBusWrite if you only need to read or only write.
71+
//! {
72+
//! pub fn new(spi: SPI) -> Self {
73+
//! Self { spi }
74+
//! }
75+
//!
76+
//! pub fn read_foo(&mut self) -> [u8; 2] {
77+
//! let mut buf = [0; 2];
78+
//!
79+
//! // `transaction` asserts and deasserts CS for us. No need to do it manually!
80+
//! self.spi.transaction(|bus| {
81+
//! bus.write(&[0x90]).unwrap();
82+
//! bus.read(&mut buf).unwrap();
83+
//! }).unwrap();
84+
//!
85+
//! buf
86+
//! }
87+
//! }
88+
//! ```
89+
//!
90+
//! If your device **does not have a CS pin**, use [`SpiBus`] (or [`SpiBusRead`], [`SpiBusWrite`]). This will ensure
91+
//! your driver has exclusive access to the bus, so no other drivers can interfere. It's not possible to safely share
92+
//! a bus without CS pins. By requiring [`SpiBus`] you disallow sharing, ensuring correct operation.
93+
//!
94+
//! ```
95+
//! # use embedded_hal::spi::blocking::{SpiBus, SpiBusRead, SpiBusWrite};
96+
//! pub struct MyDriver<SPI> {
97+
//! spi: SPI,
98+
//! }
99+
//!
100+
//! impl<SPI> MyDriver<SPI>
101+
//! where
102+
//! SPI: SpiBus, // or SpiBusRead/SpiBusWrite if you only need to read or only write.
103+
//! {
104+
//! pub fn new(spi: SPI) -> Self {
105+
//! Self { spi }
106+
//! }
107+
//!
108+
//! pub fn read_foo(&mut self) -> [u8; 2] {
109+
//! let mut buf = [0; 2];
110+
//! self.spi.write(&[0x90]).unwrap();
111+
//! self.spi.read(&mut buf).unwrap();
112+
//! buf
113+
//! }
114+
//! }
115+
//! ```
116+
//!
117+
//! If you're (ab)using SPI to **implement other protocols** by bitbanging (WS2812B, onewire, generating arbitrary waveforms...), use [`SpiBus`].
118+
//! SPI bus sharing doesn't make sense at all in this case. By requiring [`SpiBus`] you disallow sharing, ensuring correct operation.
119+
//!
120+
//! # For HAL authors
121+
//!
122+
//! HALs **must** implement [`SpiBus`], [`SpiBusRead`], [`SpiBusWrite`]. Users can combine the bus together with the CS pin (which should
123+
//! impl [`OutputPin`]) using HAL-independent [`SpiDevice`] impls such as [`ExclusiveDevice`].
124+
//!
125+
//! HALs may additionally implement [`SpiDevice`] to **take advantage of hardware CS management**, which may provide some performance
126+
//! benefits. (There's no point in a HAL implementing [`SpiDevice`] if the CS management is software-only, this task is better left to
127+
//! the HAL-independent implementations).
128+
//!
129+
//! HALs **must not** add infrastructure for sharing at the [`SpiBus`] level. User code owning a [`SpiBus`] must have the guarantee
130+
//! of exclusive access.
2131
3-
use super::ErrorType;
132+
use core::fmt::Debug;
4133

5-
/// Blocking transfer with separate buffers
6-
pub trait Transfer<Word = u8>: ErrorType {
7-
/// Writes and reads simultaneously. `write` is written to the slave on MOSI and
8-
/// words received on MISO are stored in `read`.
9-
///
10-
/// It is allowed for `read` and `write` to have different lengths, even zero length.
11-
/// The transfer runs for `max(read.len(), write.len())` words. If `read` is shorter,
12-
/// incoming words after `read` has been filled will be discarded. If `write` is shorter,
13-
/// the value of words sent in MOSI after all `write` has been sent is implementation-defined,
14-
/// typically `0x00`, `0xFF`, or configurable.
15-
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>;
16-
}
134+
use crate::{digital::blocking::OutputPin, spi::ErrorType};
17135

18-
impl<T: Transfer<Word>, Word: Copy> Transfer<Word> for &mut T {
19-
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
20-
T::transfer(self, read, write)
21-
}
22-
}
136+
use super::{Error, ErrorKind};
23137

24-
/// Blocking transfer with single buffer (in-place)
25-
pub trait TransferInplace<Word: Copy = u8>: ErrorType {
26-
/// Writes and reads simultaneously. The contents of `words` are
27-
/// written to the slave, and the received words are stored into the same
28-
/// `words` buffer, overwriting it.
29-
fn transfer_inplace(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
138+
/// SPI device trait
139+
///
140+
/// SpiDevice represents ownership over a single SPI device on a (possibly shared) bus, selected
141+
/// with a CS pin.
142+
///
143+
/// See the [module-level documentation](self) for important usage information.
144+
pub trait SpiDevice: ErrorType {
145+
/// SPI Bus type for this device.
146+
type Bus: ErrorType;
147+
148+
/// Start a transaction against the device.
149+
///
150+
/// - Locks the bus
151+
/// - Asserts the CS (Chip Select) pin.
152+
/// - Calls `f` with an exclusive reference to the bus, which can then be used to do transfers against the device.
153+
/// - Deasserts the CS pin.
154+
/// - Unlocks the bus,
155+
///
156+
/// The lock mechanism is implementation-defined. The only requirement is it must prevent two
157+
/// transactions from executing concurrently against the same bus. Examples of implementations are:
158+
/// critical sections, blocking mutexes, or returning an error or panicking if the bus is already busy.
159+
fn transaction<R>(&mut self, f: impl FnOnce(&mut Self::Bus) -> R) -> Result<R, Self::Error>;
30160
}
31161

32-
impl<T: TransferInplace<Word>, Word: Copy> TransferInplace<Word> for &mut T {
33-
fn transfer_inplace(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
34-
T::transfer_inplace(self, words)
162+
impl<T: SpiDevice> SpiDevice for &mut T {
163+
type Bus = T::Bus;
164+
fn transaction<R>(&mut self, f: impl FnOnce(&mut Self::Bus) -> R) -> Result<R, Self::Error> {
165+
T::transaction(self, f)
35166
}
36167
}
37168

38-
/// Blocking read
39-
pub trait Read<Word: Copy = u8>: ErrorType {
169+
/// Read-only SPI bus
170+
pub trait SpiBusRead<Word: Copy = u8>: ErrorType {
40171
/// Reads `words` from the slave.
41172
///
42173
/// The word value sent on MOSI during reading is implementation-defined,
43174
/// typically `0x00`, `0xFF`, or configurable.
44175
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
45176
}
46177

47-
impl<T: Read<Word>, Word: Copy> Read<Word> for &mut T {
178+
impl<T: SpiBusRead<Word>, Word: Copy> SpiBusRead<Word> for &mut T {
48179
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
49180
T::read(self, words)
50181
}
51182
}
52183

53-
/// Blocking write
54-
pub trait Write<Word: Copy = u8>: ErrorType {
184+
/// Write-only SPI bus
185+
pub trait SpiBusWrite<Word: Copy = u8>: ErrorType {
55186
/// Writes `words` to the slave, ignoring all the incoming words
56187
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>;
57188
}
58189

59-
impl<T: Write<Word>, Word: Copy> Write<Word> for &mut T {
190+
impl<T: SpiBusWrite<Word>, Word: Copy> SpiBusWrite<Word> for &mut T {
60191
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
61192
T::write(self, words)
62193
}
63194
}
64195

65-
/// Blocking write (iterator version)
66-
pub trait WriteIter<Word: Copy = u8>: ErrorType {
67-
/// Writes `words` to the slave, ignoring all the incoming words
68-
fn write_iter<WI>(&mut self, words: WI) -> Result<(), Self::Error>
69-
where
70-
WI: IntoIterator<Item = Word>;
196+
/// Read-write SPI bus
197+
///
198+
/// SpiBus represents **exclusive ownership** over the whole SPI bus, with SCK, MOSI and MISO pins.
199+
///
200+
/// See the [module-level documentation](self) for important information on SPI Bus vs Device traits.
201+
pub trait SpiBus<Word: Copy = u8>: SpiBusRead<Word> + SpiBusWrite<Word> {
202+
/// Writes and reads simultaneously. `write` is written to the slave on MOSI and
203+
/// words received on MISO are stored in `read`.
204+
///
205+
/// It is allowed for `read` and `write` to have different lengths, even zero length.
206+
/// The transfer runs for `max(read.len(), write.len())` words. If `read` is shorter,
207+
/// incoming words after `read` has been filled will be discarded. If `write` is shorter,
208+
/// the value of words sent in MOSI after all `write` has been sent is implementation-defined,
209+
/// typically `0x00`, `0xFF`, or configurable.
210+
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>;
211+
212+
/// Writes and reads simultaneously. The contents of `words` are
213+
/// written to the slave, and the received words are stored into the same
214+
/// `words` buffer, overwriting it.
215+
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
71216
}
72217

73-
impl<T: WriteIter<Word>, Word: Copy> WriteIter<Word> for &mut T {
74-
fn write_iter<WI>(&mut self, words: WI) -> Result<(), Self::Error>
75-
where
76-
WI: IntoIterator<Item = Word>,
77-
{
78-
T::write_iter(self, words)
218+
impl<T: SpiBus<Word>, Word: Copy> SpiBus<Word> for &mut T {
219+
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
220+
T::transfer(self, read, write)
221+
}
222+
223+
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
224+
T::transfer_in_place(self, words)
225+
}
226+
}
227+
228+
/// Error type for [`ExclusiveDevice`] operations.
229+
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
230+
pub enum ExclusiveDeviceError<BUS, CS> {
231+
/// An inner SPI bus operation failed
232+
Spi(BUS),
233+
/// Asserting or deasserting CS failed
234+
Cs(CS),
235+
}
236+
237+
impl<BUS, CS> Error for ExclusiveDeviceError<BUS, CS>
238+
where
239+
BUS: Error + Debug,
240+
CS: Debug,
241+
{
242+
fn kind(&self) -> ErrorKind {
243+
match self {
244+
Self::Spi(e) => e.kind(),
245+
Self::Cs(_) => ErrorKind::Other,
246+
}
79247
}
80248
}
81249

82-
/// Operation for transactional SPI trait
250+
/// [`SpiDevice`] implementation with exclusive access to the bus (not shared).
83251
///
84-
/// This allows composition of SPI operations into a single bus transaction
85-
#[derive(Debug, PartialEq)]
86-
pub enum Operation<'a, Word: 'static + Copy = u8> {
87-
/// Read data into the provided buffer.
88-
Read(&'a mut [Word]),
89-
/// Write data from the provided buffer, discarding read data
90-
Write(&'a [Word]),
91-
/// Write data out while reading data into the provided buffer
92-
Transfer(&'a mut [Word], &'a [Word]),
93-
/// Write data out while reading data into the provided buffer
94-
TransferInplace(&'a mut [Word]),
252+
/// This is the most straightforward way of obtaining an [`SpiDevice`] from an [`SpiBus`],
253+
/// ideal for when no sharing is required (only one SPI device is present on the bus).
254+
pub struct ExclusiveDevice<BUS, CS> {
255+
bus: BUS,
256+
cs: CS,
257+
}
258+
259+
impl<BUS, CS> ExclusiveDevice<BUS, CS> {
260+
/// Create a new ExclusiveDevice
261+
pub fn new(bus: BUS, cs: CS) -> Self {
262+
Self { bus, cs }
263+
}
95264
}
96265

97-
/// Transactional trait allows multiple actions to be executed
98-
/// as part of a single SPI transaction
99-
pub trait Transactional<Word: 'static + Copy = u8>: ErrorType {
100-
/// Execute the provided transactions
101-
fn exec<'a>(&mut self, operations: &mut [Operation<'a, Word>]) -> Result<(), Self::Error>;
266+
impl<BUS, CS> ErrorType for ExclusiveDevice<BUS, CS>
267+
where
268+
BUS: ErrorType,
269+
CS: OutputPin,
270+
{
271+
type Error = ExclusiveDeviceError<BUS::Error, CS::Error>;
102272
}
103273

104-
impl<T: Transactional<Word>, Word: 'static + Copy> Transactional<Word> for &mut T {
105-
fn exec<'a>(&mut self, operations: &mut [Operation<'a, Word>]) -> Result<(), Self::Error> {
106-
T::exec(self, operations)
274+
impl<BUS, CS> SpiDevice for ExclusiveDevice<BUS, CS>
275+
where
276+
BUS: ErrorType,
277+
CS: OutputPin,
278+
{
279+
type Bus = BUS;
280+
281+
fn transaction<R>(&mut self, f: impl FnOnce(&mut Self::Bus) -> R) -> Result<R, Self::Error> {
282+
self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?;
283+
let res = f(&mut self.bus);
284+
self.cs.set_high().map_err(ExclusiveDeviceError::Cs)?;
285+
Ok(res)
107286
}
108287
}

0 commit comments

Comments
 (0)