diff --git a/CHANGELOG.md b/CHANGELOG.md index 145c9a223..620295924 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). implement these `Error` traits, which implies providing a conversion to a common set of error kinds. Generic drivers using these interfaces can then convert the errors to this common set to act upon them. +- `ManagedCS` trait for SPI, signals that CS is managed by the driver to allow + shared use of an SPI bus, as well as an `SpiWithCs` wrapper implementing this + over an Spi and Pin implementation. ### Removed - Removed `DelayMs` in favor of `DelayUs` with `u32` as type for clarity. diff --git a/src/spi/blocking.rs b/src/spi/blocking.rs index b98207277..a7f85cd10 100644 --- a/src/spi/blocking.rs +++ b/src/spi/blocking.rs @@ -1,9 +1,4 @@ //! Blocking SPI API -//! -//! In some cases it's possible to implement these blocking traits on top of one of the core HAL -//! traits. To save boilerplate when that's the case a `Default` marker trait may be provided. -//! Implementing that marker trait will opt in your type into a blanket implementation. - /// Blocking transfer pub trait Transfer { /// Error type @@ -90,3 +85,147 @@ impl, W: 'static> Transactional for &mut T { T::exec(self, operations) } } + +/// Provides SpiWithCS wrapper using for spi::* types combined with an OutputPin +pub use spi_with_cs::{SpiWithCs, SpiWithCsError}; +mod spi_with_cs { + + use core::fmt::Debug; + + use super::{Transfer, Write, WriteIter}; + use crate::digital::blocking::OutputPin; + use crate::spi::{ErrorKind, ManagedChipSelect}; + + /// [`SpiWithCs`] wraps an SPI implementation with Chip Select (CS) + /// pin management for exclusive (non-shared) use. + /// For sharing SPI between peripherals, see [shared-bus](https://crates.io/crates/shared-bus) + pub struct SpiWithCs { + spi: Spi, + cs: Pin, + } + + /// Wrapper for errors returned by [`SpiWithCs`] + #[derive(Clone, Debug, PartialEq)] + pub enum SpiWithCsError { + /// Underlying SPI communication error + Spi(SpiError), + /// Underlying chip-select pin state setting error + Pin(PinError), + } + + /// Implement [`crate::spi::Error`] for wrapped types + impl crate::spi::Error for SpiWithCsError + where + SpiError: crate::spi::Error + Debug, + PinError: Debug, + { + fn kind(&self) -> ErrorKind { + match self { + SpiWithCsError::Spi(e) => e.kind(), + SpiWithCsError::Pin(_e) => ErrorKind::Other, + } + } + } + + /// [`ManagedChipSelect`] marker trait indicates Chip Select (CS) pin management is automatic + impl ManagedChipSelect for SpiWithCs {} + + impl SpiWithCs + where + Pin: OutputPin, + { + /// Create a new SpiWithCS wrapper with the provided Spi and Pin + pub fn new(spi: Spi, cs: Pin) -> Self { + Self { spi, cs } + } + + /// Fetch references to the inner Spi and Pin types. + /// Note that using these directly will violate the `ManagedChipSelect` constraint. + pub fn inner(&mut self) -> (&mut Spi, &mut Pin) { + (&mut self.spi, &mut self.cs) + } + + /// Destroy the SpiWithCs wrapper, returning the bus and pin objects + pub fn destroy(self) -> (Spi, Pin) { + (self.spi, self.cs) + } + } + + impl Transfer for SpiWithCs + where + Spi: Transfer, + Pin: OutputPin, + >::Error: Debug, + ::Error: Debug, + { + type Error = SpiWithCsError<>::Error, ::Error>; + + /// Attempt an SPI transfer with automated CS assert/deassert + fn transfer<'w>(&mut self, data: &'w mut [W]) -> Result<(), Self::Error> { + // First assert CS + self.cs.set_low().map_err(SpiWithCsError::Pin)?; + + // Attempt the transfer, storing the result for later + let spi_result = self.spi.transfer(data).map_err(SpiWithCsError::Spi); + + // Deassert CS + self.cs.set_high().map_err(SpiWithCsError::Pin)?; + + // Return failures + spi_result + } + } + + impl Write for SpiWithCs + where + Spi: Write, + Pin: OutputPin, + >::Error: Debug, + ::Error: Debug, + { + type Error = SpiWithCsError<>::Error, ::Error>; + + /// Attempt an SPI write with automated CS assert/deassert + fn write<'w>(&mut self, data: &'w [W]) -> Result<(), Self::Error> { + // First assert CS + self.cs.set_low().map_err(SpiWithCsError::Pin)?; + + // Attempt the transfer, storing the result for later + let spi_result = self.spi.write(data).map_err(SpiWithCsError::Spi); + + // Deassert CS + self.cs.set_high().map_err(SpiWithCsError::Pin)?; + + // Return failures + spi_result + } + } + + impl WriteIter for SpiWithCs + where + Spi: WriteIter, + Pin: OutputPin, + >::Error: Debug, + ::Error: Debug, + { + type Error = SpiWithCsError<>::Error, ::Error>; + + /// Attempt an SPI write_iter with automated CS assert/deassert + fn write_iter(&mut self, words: WI) -> Result<(), Self::Error> + where + WI: IntoIterator, + { + // First assert CS + self.cs.set_low().map_err(SpiWithCsError::Pin)?; + + // Attempt the transfer, storing the result for later + let spi_result = self.spi.write_iter(words).map_err(SpiWithCsError::Spi); + + // Deassert CS + self.cs.set_high().map_err(SpiWithCsError::Pin)?; + + // Return failures + spi_result + } + } +} diff --git a/src/spi/mod.rs b/src/spi/mod.rs index c367e70d8..a769d083b 100644 --- a/src/spi/mod.rs +++ b/src/spi/mod.rs @@ -107,3 +107,15 @@ impl core::fmt::Display for ErrorKind { } } } + +/// ManagedChipSelect marker trait indicates the CS pin is managed by the underlying driver. +/// +/// This specifies that `spi` operations will be grouped: +/// preceded by asserting the CS pin, and followed by +/// de-asserting the CS pin, prior to returning from the method. +/// +/// This is important for shared bus access to ensure that only one CS can be asserted at a given time. +/// To support shared use, drivers should require this (and not manage their own CS pins). +/// +/// For usage examples see: [`crate::spi::blocking::SpiWithCs`]. +pub trait ManagedChipSelect {}