Skip to content

Commit 7e089bc

Browse files
committed
Add SPI ManagedCS marker, SpiWithCs helper wrapper
ManagedCS indicates the CS pin is managed by the underlying driver, enabling safe bus sharing between SPI devices. SpiWithCs provides a wrapper around blocking SPI traits and an output pin to simplify use of this trait in unshared contexts.
1 parent 683e41e commit 7e089bc

File tree

2 files changed

+158
-5
lines changed

2 files changed

+158
-5
lines changed

src/spi/blocking.rs

Lines changed: 146 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
11
//! Blocking SPI API
2-
//!
3-
//! In some cases it's possible to implement these blocking traits on top of one of the core HAL
4-
//! traits. To save boilerplate when that's the case a `Default` marker trait may be provided.
5-
//! Implementing that marker trait will opt in your type into a blanket implementation.
6-
72
/// Blocking transfer
83
pub trait Transfer<W> {
94
/// Error type
@@ -90,3 +85,149 @@ impl<T: Transactional<W>, W: 'static> Transactional<W> for &mut T {
9085
T::exec(self, operations)
9186
}
9287
}
88+
89+
90+
91+
/// Provides SpiWithCS wrapper using for spi::* types combined with an OutputPin
92+
pub use spi_with_cs::{SpiWithCs, SpiWithCsError};
93+
mod spi_with_cs {
94+
95+
use core::fmt::Debug;
96+
97+
use super::{Transfer, Write, WriteIter};
98+
use crate::digital::blocking::OutputPin;
99+
use crate::spi::{ManagedChipSelect, ErrorKind};
100+
101+
/// [`SpiWithCs`] wraps an SPI implementation with Chip Select (CS)
102+
/// pin management for exclusive (non-shared) use.
103+
/// For sharing SPI between peripherals, see [shared-bus](https://crates.io/crates/shared-bus)
104+
pub struct SpiWithCs<Spi, Pin> {
105+
spi: Spi,
106+
cs: Pin,
107+
}
108+
109+
/// Wrapper for errors returned by [`SpiWithCs`]
110+
#[derive(Clone, Debug, PartialEq)]
111+
pub enum SpiWithCsError<SpiError, PinError> {
112+
/// Underlying SPI communication error
113+
Spi(SpiError),
114+
/// Underlying chip-select pin state setting error
115+
Pin(PinError),
116+
}
117+
118+
/// Implement [`crate::spi::Error`] for wrapped types
119+
impl<SpiError, PinError> crate::spi::Error for SpiWithCsError<SpiError, PinError>
120+
where
121+
SpiError: crate::spi::Error + Debug,
122+
PinError: Debug,
123+
{
124+
fn kind(&self) -> ErrorKind {
125+
match self {
126+
SpiWithCsError::Spi(e) => e.kind(),
127+
SpiWithCsError::Pin(_e) => ErrorKind::Other,
128+
}
129+
}
130+
}
131+
132+
/// [`ManagedChipSelect`] marker trait indicates Chip Select (CS) pin management is automatic
133+
impl<Spi, Pin> ManagedChipSelect for SpiWithCs<Spi, Pin> {}
134+
135+
impl<Spi, Pin> SpiWithCs<Spi, Pin>
136+
where
137+
Pin: OutputPin,
138+
{
139+
/// Create a new SpiWithCS wrapper with the provided Spi and Pin
140+
pub fn new(spi: Spi, cs: Pin) -> Self {
141+
Self { spi, cs }
142+
}
143+
144+
/// Fetch references to the inner Spi and Pin types.
145+
/// Note that using these directly will violate the `ManagedChipSelect` constraint.
146+
pub fn inner(&mut self) -> (&mut Spi, &mut Pin) {
147+
(&mut self.spi, &mut self.cs)
148+
}
149+
150+
/// Destroy the SpiWithCs wrapper, returning the bus and pin objects
151+
pub fn destroy(self) -> (Spi, Pin) {
152+
(self.spi, self.cs)
153+
}
154+
}
155+
156+
impl<Spi, Pin, W> Transfer<W> for SpiWithCs<Spi, Pin>
157+
where
158+
Spi: Transfer<W>,
159+
Pin: OutputPin,
160+
<Spi as Transfer<W>>::Error: Debug,
161+
<Pin as OutputPin>::Error: Debug,
162+
{
163+
type Error = SpiWithCsError<<Spi as Transfer<W>>::Error, <Pin as OutputPin>::Error>;
164+
165+
/// Attempt an SPI transfer with automated CS assert/deassert
166+
fn transfer<'w>(&mut self, data: &'w mut [W]) -> Result<(), Self::Error> {
167+
// First assert CS
168+
self.cs.set_low().map_err(SpiWithCsError::Pin)?;
169+
170+
// Attempt the transfer, storing the result for later
171+
let spi_result = self.spi.transfer(data).map_err(SpiWithCsError::Spi);
172+
173+
// Deassert CS
174+
self.cs.set_high().map_err(SpiWithCsError::Pin)?;
175+
176+
// Return failures
177+
spi_result
178+
}
179+
}
180+
181+
impl<Spi, Pin, W> Write<W> for SpiWithCs<Spi, Pin>
182+
where
183+
Spi: Write<W>,
184+
Pin: OutputPin,
185+
<Spi as Write<W>>::Error: Debug,
186+
<Pin as OutputPin>::Error: Debug,
187+
{
188+
type Error = SpiWithCsError<<Spi as Write<W>>::Error, <Pin as OutputPin>::Error>;
189+
190+
/// Attempt an SPI write with automated CS assert/deassert
191+
fn write<'w>(&mut self, data: &'w [W]) -> Result<(), Self::Error> {
192+
// First assert CS
193+
self.cs.set_low().map_err(SpiWithCsError::Pin)?;
194+
195+
// Attempt the transfer, storing the result for later
196+
let spi_result = self.spi.write(data).map_err(SpiWithCsError::Spi);
197+
198+
// Deassert CS
199+
self.cs.set_high().map_err(SpiWithCsError::Pin)?;
200+
201+
// Return failures
202+
spi_result
203+
}
204+
}
205+
206+
impl<Spi, Pin, W> WriteIter<W> for SpiWithCs<Spi, Pin>
207+
where
208+
Spi: WriteIter<W>,
209+
Pin: OutputPin,
210+
<Spi as WriteIter<W>>::Error: Debug,
211+
<Pin as OutputPin>::Error: Debug,
212+
{
213+
type Error = SpiWithCsError<<Spi as WriteIter<W>>::Error, <Pin as OutputPin>::Error>;
214+
215+
/// Attempt an SPI write_iter with automated CS assert/deassert
216+
fn write_iter<WI>(&mut self, words: WI) -> Result<(), Self::Error>
217+
where
218+
WI: IntoIterator<Item = W>,
219+
{
220+
// First assert CS
221+
self.cs.set_low().map_err(SpiWithCsError::Pin)?;
222+
223+
// Attempt the transfer, storing the result for later
224+
let spi_result = self.spi.write_iter(words).map_err(SpiWithCsError::Spi);
225+
226+
// Deassert CS
227+
self.cs.set_high().map_err(SpiWithCsError::Pin)?;
228+
229+
// Return failures
230+
spi_result
231+
}
232+
}
233+
}

src/spi/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,15 @@ impl core::fmt::Display for ErrorKind {
107107
}
108108
}
109109
}
110+
111+
/// ManagedChipSelect marker trait indicates the CS pin is managed by the underlying driver.
112+
///
113+
/// This specifies that `spi` operations will be grouped:
114+
/// preceded by asserting the CS pin, and followed by
115+
/// de-asserting the CS pin, prior to returning from the method.
116+
///
117+
/// This is important for shared bus access to ensure that only one CS can be asserted at a given time.
118+
/// To support shared use, drivers should require this (and not manage their own CS pins).
119+
///
120+
/// For usage examples see: [`crate::spi::blocking::SpiWithCs`].
121+
pub trait ManagedChipSelect {}

0 commit comments

Comments
 (0)