Skip to content

Commit 20b533e

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

File tree

2 files changed

+245
-65
lines changed

2 files changed

+245
-65
lines changed

src/spi/blocking.rs

Lines changed: 243 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,286 @@
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+
//!
60+
//! ```
61+
//! # use embedded_hal::spi::blocking::{SpiBus, SpiBusRead, SpiBusWrite, SpiDevice};
62+
//! pub struct MyDriver<SPI> {
63+
//! spi: SPI,
64+
//! }
65+
//!
66+
//! impl<SPI> MyDriver<SPI>
67+
//! where
68+
//! SPI: SpiDevice,
69+
//! SPI::Bus: SpiBus, // or SpiBusRead/SpiBusWrite if you only need to read or only write.
70+
//! {
71+
//! pub fn new(spi: SPI) -> Self {
72+
//! Self { spi }
73+
//! }
74+
//!
75+
//! pub fn read_foo(&mut self) -> [u8; 2] {
76+
//! let mut buf = [0; 2];
77+
//!
78+
//! // `transaction` asserts and deasserts CS for us. No need to do it manually!
79+
//! self.spi.transaction(|bus| {
80+
//! bus.write(&[0x90]).unwrap();
81+
//! bus.read(&mut buf).unwrap();
82+
//! }).unwrap();
83+
//!
84+
//! buf
85+
//! }
86+
//! }
87+
//! ```
88+
//!
89+
//! If your device **does not have a CS pin**, use [`SpiBus`] (or [`SpiBusRead`], [`SpiBusWrite`]). This will ensure
90+
//! your driver has exclusive access to the bus, so no other drivers can interfere. It's not possible to safely share
91+
//! a bus without CS pins. By requiring [`SpiBus`] you disallow sharing, ensuring correct operation.
92+
//!
93+
//! ```
94+
//! # use embedded_hal::spi::blocking::{SpiBus, SpiBusRead, SpiBusWrite};
95+
//! pub struct MyDriver<SPI> {
96+
//! spi: SPI,
97+
//! }
98+
//!
99+
//! impl<SPI> MyDriver<SPI>
100+
//! where
101+
//! SPI: SpiBus, // or SpiBusRead/SpiBusWrite if you only need to read or only write.
102+
//! {
103+
//! pub fn new(spi: SPI) -> Self {
104+
//! Self { spi }
105+
//! }
106+
//!
107+
//! pub fn read_foo(&mut self) -> [u8; 2] {
108+
//! let mut buf = [0; 2];
109+
//! self.spi.write(&[0x90]).unwrap();
110+
//! self.spi.read(&mut buf).unwrap();
111+
//! buf
112+
//! }
113+
//! }
114+
//! ```
115+
//!
116+
//! If you're (ab)using SPI to **implement other protocols** by bitbanging (WS2812B, onewire, generating arbitrary waveforms...), use [`SpiBus`].
117+
//! SPI bus sharing doesn't make sense at all in this case. By requiring [`SpiBus`] you disallow sharing, ensuring correct operation.
118+
//!
119+
//! # For HAL authors
120+
//!
121+
//! HALs **must** implement [`SpiBus`], [`SpiBusRead`], [`SpiBusWrite`]. Users can combine the bus together with the CS pin (which should
122+
//! impl [`OutputPin`]) using HAL-independent [`SpiDevice`] impls such as [`ExclusiveDevice`].
123+
//!
124+
//! HALs may additionally implement [`SpiDevice`] to **take advantage of hardware CS management**, which may provide some performance
125+
//! benefits. (There's no point in a HAL implementing [`SpiDevice`] if the CS management is software-only, this task is better left to
126+
//! the HAL-independent implementations).
127+
//!
128+
//! HALs **must not** add infrastructure for sharing at the [`SpiBus`] level. User code owning a [`SpiBus`] must have the guarantee
129+
//! of exclusive access.
2130
3-
use super::ErrorType;
131+
use core::fmt::Debug;
4132

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-
}
133+
use crate::{digital::blocking::OutputPin, spi::ErrorType};
17134

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-
}
135+
use super::{Error, ErrorKind};
23136

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>;
137+
/// SPI device trait
138+
///
139+
/// SpiDevice represents ownership over a single SPI device on a (possibly shared) bus, selected
140+
/// with a CS pin.
141+
///
142+
/// See the [module-level documentation](self) for important usage information.
143+
pub trait SpiDevice: ErrorType {
144+
/// SPI Bus type for this device.
145+
type Bus: ErrorType;
146+
147+
/// Start a transaction against the device.
148+
///
149+
/// - Locks the bus
150+
/// - Asserts the CS (Chip Select) pin.
151+
/// - Calls `f` with an exclusive reference to the bus, which can then be used to do transfers against the device.
152+
/// - Deasserts the CS pin.
153+
/// - Unlocks the bus,
154+
///
155+
/// The lock mechanism is implementation-defined. The only requirement is it must prevent two
156+
/// transactions from executing concurrently against the same bus. Examples of implementations are:
157+
/// critical sections, blocking mutexes, or returning an error or panicking if the bus is already busy.
158+
fn transaction<R>(&mut self, f: impl FnOnce(&mut Self::Bus) -> R) -> Result<R, Self::Error>;
30159
}
31160

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)
161+
impl<T: SpiDevice> SpiDevice for &mut T {
162+
type Bus = T::Bus;
163+
fn transaction<R>(&mut self, f: impl FnOnce(&mut Self::Bus) -> R) -> Result<R, Self::Error> {
164+
T::transaction(self, f)
35165
}
36166
}
37167

38-
/// Blocking read
39-
pub trait Read<Word: Copy = u8>: ErrorType {
168+
/// Read-only SPI bus
169+
pub trait SpiBusRead<Word: Copy = u8>: ErrorType {
40170
/// Reads `words` from the slave.
41171
///
42172
/// The word value sent on MOSI during reading is implementation-defined,
43173
/// typically `0x00`, `0xFF`, or configurable.
44174
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>;
45175
}
46176

47-
impl<T: Read<Word>, Word: Copy> Read<Word> for &mut T {
177+
impl<T: SpiBusRead<Word>, Word: Copy> SpiBusRead<Word> for &mut T {
48178
fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
49179
T::read(self, words)
50180
}
51181
}
52182

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

59-
impl<T: Write<Word>, Word: Copy> Write<Word> for &mut T {
189+
impl<T: SpiBusWrite<Word>, Word: Copy> SpiBusWrite<Word> for &mut T {
60190
fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> {
61191
T::write(self, words)
62192
}
63193
}
64194

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

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)
217+
impl<T: SpiBus<Word>, Word: Copy> SpiBus<Word> for &mut T {
218+
fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> {
219+
T::transfer(self, read, write)
220+
}
221+
222+
fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> {
223+
T::transfer_in_place(self, words)
224+
}
225+
}
226+
227+
/// Error type for [`ExclusiveDevice`] operations.
228+
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
229+
pub enum ExclusiveDeviceError<BUS, CS> {
230+
/// An inner SPI bus operation failed
231+
Spi(BUS),
232+
/// Asserting or deasserting CS failed
233+
Cs(CS),
234+
}
235+
236+
impl<BUS, CS> Error for ExclusiveDeviceError<BUS, CS>
237+
where
238+
BUS: Error + Debug,
239+
CS: Debug,
240+
{
241+
fn kind(&self) -> ErrorKind {
242+
match self {
243+
Self::Spi(e) => e.kind(),
244+
Self::Cs(_) => ErrorKind::Other,
245+
}
79246
}
80247
}
81248

82-
/// Operation for transactional SPI trait
249+
/// [`SpiDevice`] implementation with exclusive access to the bus (not shared).
83250
///
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]),
251+
/// This is the most straightforward way of obtaining an [`SpiDevice`] from an [`SpiBus`],
252+
/// ideal for when no sharing is required (only one SPI device is present on the bus).
253+
pub struct ExclusiveDevice<BUS, CS> {
254+
bus: BUS,
255+
cs: CS,
256+
}
257+
258+
impl<BUS, CS> ExclusiveDevice<BUS, CS> {
259+
/// Create a new ExclusiveDevice
260+
pub fn new(bus: BUS, cs: CS) -> Self {
261+
Self { bus, cs }
262+
}
95263
}
96264

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>;
265+
impl<BUS, CS> ErrorType for ExclusiveDevice<BUS, CS>
266+
where
267+
BUS: ErrorType,
268+
CS: OutputPin,
269+
{
270+
type Error = ExclusiveDeviceError<BUS::Error, CS::Error>;
102271
}
103272

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)
273+
impl<BUS, CS> SpiDevice for ExclusiveDevice<BUS, CS>
274+
where
275+
BUS: ErrorType,
276+
CS: OutputPin,
277+
{
278+
type Bus = BUS;
279+
280+
fn transaction<R>(&mut self, f: impl FnOnce(&mut Self::Bus) -> R) -> Result<R, Self::Error> {
281+
self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?;
282+
let res = f(&mut self.bus);
283+
self.cs.set_high().map_err(ExclusiveDeviceError::Cs)?;
284+
Ok(res)
107285
}
108286
}

0 commit comments

Comments
 (0)