From ad66fa9ca82735994a6195ba2186fe6aac11c56d Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 16 Feb 2022 03:06:17 +0100 Subject: [PATCH 1/2] async: add SPI. --- embedded-hal-async/src/lib.rs | 2 + embedded-hal-async/src/spi.rs | 309 ++++++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 embedded-hal-async/src/spi.rs diff --git a/embedded-hal-async/src/lib.rs b/embedded-hal-async/src/lib.rs index 0fe8c86b8..e3f3b705a 100644 --- a/embedded-hal-async/src/lib.rs +++ b/embedded-hal-async/src/lib.rs @@ -10,7 +10,9 @@ #![deny(missing_docs)] #![no_std] #![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] pub mod delay; pub mod digital; pub mod i2c; +pub mod spi; diff --git a/embedded-hal-async/src/spi.rs b/embedded-hal-async/src/spi.rs new file mode 100644 index 000000000..a1cd90df1 --- /dev/null +++ b/embedded-hal-async/src/spi.rs @@ -0,0 +1,309 @@ +//! Serial Peripheral Interface + +use core::{fmt::Debug, future::Future}; + +pub use embedded_hal::spi::{ + Error, ErrorKind, ErrorType, Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3, +}; +use embedded_hal::{digital::blocking::OutputPin, spi::blocking}; + +/// SPI device trait +/// +/// SpiDevice represents ownership over a single SPI device on a (possibly shared) bus, selected +/// with a CS pin. +/// +/// See (the docs on embedded-hal)[embedded_hal::spi::blocking] for important information on SPI Bus vs Device traits. +pub trait SpiDevice: ErrorType { + /// SPI Bus type for this device. + type Bus: ErrorType; + + /// Future returned by the `transaction` method. + type TransactionFuture<'a, R, F, Fut>: Future> + 'a + where + Self: 'a, + R: 'a, + F: FnOnce(&'a mut Self::Bus) -> Fut + 'a, + Fut: Future< + Output = ( + &'a mut Self::Bus, + Result::Error>, + ), + > + 'a; + + /// Start a transaction against the device. + /// + /// - Locks the bus + /// - Asserts the CS (Chip Select) pin. + /// - Calls `f` with an exclusive reference to the bus, which can then be used to do transfers against the device. + /// - Deasserts the CS pin. + /// - Unlocks the bus, + /// + /// The lock mechanism is implementation-defined. The only requirement is it must prevent two + /// transactions from executing concurrently against the same bus. Examples of implementations are: + /// critical sections, blocking mutexes, async mutexes, or returning an error or panicking if the bus is already busy. + fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut> + where + F: FnOnce(&'a mut Self::Bus) -> Fut + 'a, + Fut: Future< + Output = ( + &'a mut Self::Bus, + Result::Error>, + ), + > + 'a; +} + +impl SpiDevice for &mut T { + type Bus = T::Bus; + + type TransactionFuture<'a, R, F, Fut> = T::TransactionFuture<'a, R, F, Fut> + where + Self: 'a, R: 'a, F: FnOnce(&'a mut Self::Bus) -> Fut + 'a, + Fut: Future::Error>)> + 'a; + + fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut> + where + F: FnOnce(&'a mut Self::Bus) -> Fut + 'a, + Fut: Future< + Output = ( + &'a mut Self::Bus, + Result::Error>, + ), + > + 'a, + { + T::transaction(self, f) + } +} + +/// Flush for SPI bus +pub trait SpiBusFlush: ErrorType { + /// Future returned by the `flush` method. + type FlushFuture<'a>: Future> + 'a + where + Self: 'a; + + /// Flush + fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a>; +} + +impl SpiBusFlush for &mut T { + type FlushFuture<'a> = T::FlushFuture<'a> where Self: 'a; + + fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { + T::flush(self) + } +} + +/// Read-only SPI bus +pub trait SpiBusRead: SpiBusFlush { + /// Future returned by the `read` method. + type ReadFuture<'a>: Future> + 'a + where + Self: 'a; + + /// Read `words` from the slave. + /// + /// The word value sent on MOSI during reading is implementation-defined, + /// typically `0x00`, `0xFF`, or configurable. + fn read<'a>(&'a mut self, words: &'a mut [Word]) -> Self::ReadFuture<'a>; +} + +impl, Word: 'static + Copy> SpiBusRead for &mut T { + type ReadFuture<'a> = T::ReadFuture<'a> where Self: 'a; + + fn read<'a>(&'a mut self, words: &'a mut [Word]) -> Self::ReadFuture<'a> { + T::read(self, words) + } +} + +/// Write-only SPI +pub trait SpiBusWrite: SpiBusFlush { + /// Future returned by the `write` method. + type WriteFuture<'a>: Future> + 'a + where + Self: 'a; + + /// Write `words` to the slave, ignoring all the incoming words + fn write<'a>(&'a mut self, words: &'a [Word]) -> Self::WriteFuture<'a>; +} + +impl, Word: 'static + Copy> SpiBusWrite for &mut T { + type WriteFuture<'a> = T::WriteFuture<'a> where Self: 'a; + + fn write<'a>(&'a mut self, words: &'a [Word]) -> Self::WriteFuture<'a> { + T::write(self, words) + } +} + +/// Read-write SPI bus +/// +/// SpiBus represents **exclusive ownership** over the whole SPI bus, with SCK, MOSI and MISO pins. +/// +/// See (the docs on embedded-hal)[embedded_hal::spi::blocking] for important information on SPI Bus vs Device traits. +pub trait SpiBus: SpiBusRead + SpiBusWrite { + /// Future returned by the `transfer` method. + type TransferFuture<'a>: Future> + 'a + where + Self: 'a; + + /// Write and read simultaneously. `write` is written to the slave on MOSI and + /// words received on MISO are stored in `read`. + /// + /// It is allowed for `read` and `write` to have different lengths, even zero length. + /// The transfer runs for `max(read.len(), write.len())` words. If `read` is shorter, + /// incoming words after `read` has been filled will be discarded. If `write` is shorter, + /// the value of words sent in MOSI after all `write` has been sent is implementation-defined, + /// typically `0x00`, `0xFF`, or configurable. + fn transfer<'a>( + &'a mut self, + read: &'a mut [Word], + write: &'a [Word], + ) -> Self::TransferFuture<'a>; + + /// Future returned by the `transfer_in_place` method. + type TransferInPlaceFuture<'a>: Future> + 'a + where + Self: 'a; + + /// Write and read simultaneously. The contents of `words` are + /// written to the slave, and the received words are stored into the same + /// `words` buffer, overwriting it. + fn transfer_in_place<'a>( + &'a mut self, + words: &'a mut [Word], + ) -> Self::TransferInPlaceFuture<'a>; +} + +impl, Word: 'static + Copy> SpiBus for &mut T { + type TransferFuture<'a> = T::TransferFuture<'a> where Self: 'a; + + fn transfer<'a>( + &'a mut self, + read: &'a mut [Word], + write: &'a [Word], + ) -> Self::TransferFuture<'a> { + T::transfer(self, read, write) + } + + type TransferInPlaceFuture<'a> = T::TransferInPlaceFuture<'a> where Self: 'a; + + fn transfer_in_place<'a>( + &'a mut self, + words: &'a mut [Word], + ) -> Self::TransferInPlaceFuture<'a> { + T::transfer_in_place(self, words) + } +} + +/// Error type for [`ExclusiveDevice`] operations. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum ExclusiveDeviceError { + /// An inner SPI bus operation failed + Spi(BUS), + /// Asserting or deasserting CS failed + Cs(CS), +} + +impl Error for ExclusiveDeviceError +where + BUS: Error + Debug, + CS: Debug, +{ + fn kind(&self) -> ErrorKind { + match self { + Self::Spi(e) => e.kind(), + Self::Cs(_) => ErrorKind::ChipSelectFault, + } + } +} + +/// [`SpiDevice`] implementation with exclusive access to the bus (not shared). +/// +/// This is the most straightforward way of obtaining an [`SpiDevice`] from an [`SpiBus`], +/// ideal for when no sharing is required (only one SPI device is present on the bus). +pub struct ExclusiveDevice { + bus: BUS, + cs: CS, +} + +impl ExclusiveDevice { + /// Create a new ExclusiveDevice + pub fn new(bus: BUS, cs: CS) -> Self { + Self { bus, cs } + } +} + +impl ErrorType for ExclusiveDevice +where + BUS: ErrorType, + CS: OutputPin, +{ + type Error = ExclusiveDeviceError; +} + +impl blocking::SpiDevice for ExclusiveDevice +where + BUS: blocking::SpiBusFlush, + CS: OutputPin, +{ + type Bus = BUS; + + fn transaction( + &mut self, + f: impl FnOnce(&mut Self::Bus) -> Result::Error>, + ) -> Result { + self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; + + let f_res = f(&mut self.bus); + + // On failure, it's important to still flush and deassert CS. + let flush_res = self.bus.flush(); + let cs_res = self.cs.set_high(); + + let f_res = f_res.map_err(ExclusiveDeviceError::Spi)?; + flush_res.map_err(ExclusiveDeviceError::Spi)?; + cs_res.map_err(ExclusiveDeviceError::Cs)?; + + Ok(f_res) + } +} + +impl SpiDevice for ExclusiveDevice +where + BUS: SpiBusFlush, + CS: OutputPin, +{ + type Bus = BUS; + + type TransactionFuture<'a, R, F, Fut> = impl Future> + 'a + where + Self: 'a, R: 'a, F: FnOnce(&'a mut Self::Bus) -> Fut + 'a, + Fut: Future::Error>)> + 'a; + + fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut> + where + R: 'a, + F: FnOnce(&'a mut Self::Bus) -> Fut + 'a, + Fut: Future< + Output = ( + &'a mut Self::Bus, + Result::Error>, + ), + > + 'a, + { + async move { + self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; + + let (bus, f_res) = f(&mut self.bus).await; + + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush().await; + let cs_res = self.cs.set_high(); + + let f_res = f_res.map_err(ExclusiveDeviceError::Spi)?; + flush_res.map_err(ExclusiveDeviceError::Spi)?; + cs_res.map_err(ExclusiveDeviceError::Cs)?; + + Ok(f_res) + } + } +} From 62b177cd061f5abd64a8cd0d0b95d3bf805a693a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Thu, 10 Mar 2022 06:13:59 +0100 Subject: [PATCH 2/2] spi: Small doc fixes --- embedded-hal-async/src/spi.rs | 33 ++++++++++++++++++++++++--------- src/spi/blocking.rs | 22 +++++++++++----------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/embedded-hal-async/src/spi.rs b/embedded-hal-async/src/spi.rs index a1cd90df1..c6f4c13a1 100644 --- a/embedded-hal-async/src/spi.rs +++ b/embedded-hal-async/src/spi.rs @@ -9,8 +9,8 @@ use embedded_hal::{digital::blocking::OutputPin, spi::blocking}; /// SPI device trait /// -/// SpiDevice represents ownership over a single SPI device on a (possibly shared) bus, selected -/// with a CS pin. +/// `SpiDevice` represents ownership over a single SPI device on a (possibly shared) bus, selected +/// with a CS (Chip Select) pin. /// /// See (the docs on embedded-hal)[embedded_hal::spi::blocking] for important information on SPI Bus vs Device traits. pub trait SpiDevice: ErrorType { @@ -30,17 +30,18 @@ pub trait SpiDevice: ErrorType { ), > + 'a; - /// Start a transaction against the device. + /// Perform a transaction against the device. /// /// - Locks the bus /// - Asserts the CS (Chip Select) pin. /// - Calls `f` with an exclusive reference to the bus, which can then be used to do transfers against the device. + /// - [Flushes](SpiBusFlush::flush) the bus. /// - Deasserts the CS pin. - /// - Unlocks the bus, + /// - Unlocks the bus. /// - /// The lock mechanism is implementation-defined. The only requirement is it must prevent two + /// The locking mechanism is implementation-defined. The only requirement is it must prevent two /// transactions from executing concurrently against the same bus. Examples of implementations are: - /// critical sections, blocking mutexes, async mutexes, or returning an error or panicking if the bus is already busy. + /// critical sections, blocking mutexes, async mutexes, returning an error or panicking if the bus is already busy. fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut> where F: FnOnce(&'a mut Self::Bus) -> Fut + 'a, @@ -74,14 +75,16 @@ impl SpiDevice for &mut T { } } -/// Flush for SPI bus +/// Flush support for SPI bus pub trait SpiBusFlush: ErrorType { /// Future returned by the `flush` method. type FlushFuture<'a>: Future> + 'a where Self: 'a; - /// Flush + /// Wait until all operations have completed and the bus is idle. + /// + /// See (the docs on embedded-hal)[embedded_hal::spi::blocking] for information on flushing. fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a>; } @@ -104,6 +107,9 @@ pub trait SpiBusRead: SpiBusFlush { /// /// The word value sent on MOSI during reading is implementation-defined, /// typically `0x00`, `0xFF`, or configurable. + /// + /// Implementations are allowed to return before the operation is + /// complete. See (the docs on embedded-hal)[embedded_hal::spi::blocking] for details on flushing. fn read<'a>(&'a mut self, words: &'a mut [Word]) -> Self::ReadFuture<'a>; } @@ -123,6 +129,9 @@ pub trait SpiBusWrite: SpiBusFlush { Self: 'a; /// Write `words` to the slave, ignoring all the incoming words + /// + /// Implementations are allowed to return before the operation is + /// complete. See (the docs on embedded-hal)[embedded_hal::spi::blocking] for details on flushing. fn write<'a>(&'a mut self, words: &'a [Word]) -> Self::WriteFuture<'a>; } @@ -136,7 +145,7 @@ impl, Word: 'static + Copy> SpiBusWrite for &mut T { /// Read-write SPI bus /// -/// SpiBus represents **exclusive ownership** over the whole SPI bus, with SCK, MOSI and MISO pins. +/// `SpiBus` represents **exclusive ownership** over the whole SPI bus, with SCK, MOSI and MISO pins. /// /// See (the docs on embedded-hal)[embedded_hal::spi::blocking] for important information on SPI Bus vs Device traits. pub trait SpiBus: SpiBusRead + SpiBusWrite { @@ -153,6 +162,9 @@ pub trait SpiBus: SpiBusRead + SpiBusWrite( &'a mut self, read: &'a mut [Word], @@ -167,6 +179,9 @@ pub trait SpiBus: SpiBusRead + SpiBusWrite( &'a mut self, words: &'a mut [Word], diff --git a/src/spi/blocking.rs b/src/spi/blocking.rs index 4d16cbe1c..80d611e4f 100644 --- a/src/spi/blocking.rs +++ b/src/spi/blocking.rs @@ -175,8 +175,8 @@ use super::{Error, ErrorKind}; /// SPI device trait /// -/// SpiDevice represents ownership over a single SPI device on a (possibly shared) bus, selected -/// with a CS pin. +/// `SpiDevice` represents ownership over a single SPI device on a (possibly shared) bus, selected +/// with a CS (Chip Select) pin. /// /// See the [module-level documentation](self) for important usage information. pub trait SpiDevice: ErrorType { @@ -192,9 +192,9 @@ pub trait SpiDevice: ErrorType { /// - Deasserts the CS pin. /// - Unlocks the bus. /// - /// The lock mechanism is implementation-defined. The only requirement is it must prevent two + /// The locking mechanism is implementation-defined. The only requirement is it must prevent two /// transactions from executing concurrently against the same bus. Examples of implementations are: - /// critical sections, blocking mutexes, or returning an error or panicking if the bus is already busy. + /// critical sections, blocking mutexes, returning an error or panicking if the bus is already busy. fn transaction( &mut self, f: impl FnOnce(&mut Self::Bus) -> Result::Error>, @@ -265,7 +265,7 @@ impl SpiDevice for &mut T { /// Flush support for SPI bus pub trait SpiBusFlush: ErrorType { - /// Blocks until all operations have completed and the bus is idle. + /// Wait until all operations have completed and the bus is idle. /// /// See the [module-level documentation](self) for important usage information. fn flush(&mut self) -> Result<(), Self::Error>; @@ -279,7 +279,7 @@ impl SpiBusFlush for &mut T { /// Read-only SPI bus pub trait SpiBusRead: SpiBusFlush { - /// Reads `words` from the slave. + /// Read `words` from the slave. /// /// The word value sent on MOSI during reading is implementation-defined, /// typically `0x00`, `0xFF`, or configurable. @@ -297,7 +297,7 @@ impl, Word: Copy> SpiBusRead for &mut T { /// Write-only SPI bus pub trait SpiBusWrite: SpiBusFlush { - /// Writes `words` to the slave, ignoring all the incoming words + /// Write `words` to the slave, ignoring all the incoming words /// /// Implementations are allowed to return before the operation is /// complete. See the [module-level documentation](self) for details. @@ -312,11 +312,11 @@ impl, Word: Copy> SpiBusWrite for &mut T { /// Read-write SPI bus /// -/// SpiBus represents **exclusive ownership** over the whole SPI bus, with SCK, MOSI and MISO pins. +/// `SpiBus` represents **exclusive ownership** over the whole SPI bus, with SCK, MOSI and MISO pins. /// /// See the [module-level documentation](self) for important information on SPI Bus vs Device traits. pub trait SpiBus: SpiBusRead + SpiBusWrite { - /// Writes and reads simultaneously. `write` is written to the slave on MOSI and + /// Write and read simultaneously. `write` is written to the slave on MOSI and /// words received on MISO are stored in `read`. /// /// It is allowed for `read` and `write` to have different lengths, even zero length. @@ -329,7 +329,7 @@ pub trait SpiBus: SpiBusRead + SpiBusWrite { /// complete. See the [module-level documentation](self) for details. fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error>; - /// Writes and reads simultaneously. The contents of `words` are + /// Write and read simultaneously. The contents of `words` are /// written to the slave, and the received words are stored into the same /// `words` buffer, overwriting it. /// @@ -388,7 +388,7 @@ impl ExclusiveDevice { impl ErrorType for ExclusiveDevice where - BUS: SpiBusFlush, + BUS: ErrorType, CS: OutputPin, { type Error = ExclusiveDeviceError;