Skip to content

Commit

Permalink
Merge #347
Browse files Browse the repository at this point in the history
347: async: add SPI r=eldruin a=Dirbaio

This is an exact mirror of the blocking SPI trait (including the proposed changes in #351 ), except the following differences:

- `W: 'static` is required everywhere, otherwise complicated lifetime bounds are required in the future GATs.


Co-authored-by: Dario Nieuwenhuis <[email protected]>
  • Loading branch information
bors[bot] and Dirbaio authored Mar 10, 2022
2 parents c2830f1 + 62b177c commit b5c29b3
Show file tree
Hide file tree
Showing 3 changed files with 337 additions and 11 deletions.
2 changes: 2 additions & 0 deletions embedded-hal-async/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
324 changes: 324 additions & 0 deletions embedded-hal-async/src/spi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
//! 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 (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 {
/// SPI Bus type for this device.
type Bus: ErrorType;

/// Future returned by the `transaction` method.
type TransactionFuture<'a, R, F, Fut>: Future<Output = Result<R, Self::Error>> + 'a
where
Self: 'a,
R: 'a,
F: FnOnce(&'a mut Self::Bus) -> Fut + 'a,
Fut: Future<
Output = (
&'a mut Self::Bus,
Result<R, <Self::Bus as ErrorType>::Error>,
),
> + 'a;

/// 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.
///
/// 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, 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<R, <Self::Bus as ErrorType>::Error>,
),
> + 'a;
}

impl<T: SpiDevice> 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<Output = (&'a mut Self::Bus, Result<R, <Self::Bus as ErrorType>::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<R, <Self::Bus as ErrorType>::Error>,
),
> + 'a,
{
T::transaction(self, f)
}
}

/// Flush support for SPI bus
pub trait SpiBusFlush: ErrorType {
/// Future returned by the `flush` method.
type FlushFuture<'a>: Future<Output = Result<(), Self::Error>> + 'a
where
Self: 'a;

/// 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>;
}

impl<T: SpiBusFlush> 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<Word: 'static + Copy = u8>: SpiBusFlush {
/// Future returned by the `read` method.
type ReadFuture<'a>: Future<Output = Result<(), Self::Error>> + '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.
///
/// 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>;
}

impl<T: SpiBusRead<Word>, Word: 'static + Copy> SpiBusRead<Word> 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<Word: 'static + Copy = u8>: SpiBusFlush {
/// Future returned by the `write` method.
type WriteFuture<'a>: Future<Output = Result<(), Self::Error>> + 'a
where
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>;
}

impl<T: SpiBusWrite<Word>, Word: 'static + Copy> SpiBusWrite<Word> 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<Word: 'static + Copy = u8>: SpiBusRead<Word> + SpiBusWrite<Word> {
/// Future returned by the `transfer` method.
type TransferFuture<'a>: Future<Output = Result<(), Self::Error>> + '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.
///
/// 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 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<Output = Result<(), Self::Error>> + '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.
///
/// 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 transfer_in_place<'a>(
&'a mut self,
words: &'a mut [Word],
) -> Self::TransferInPlaceFuture<'a>;
}

impl<T: SpiBus<Word>, Word: 'static + Copy> SpiBus<Word> 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<BUS, CS> {
/// An inner SPI bus operation failed
Spi(BUS),
/// Asserting or deasserting CS failed
Cs(CS),
}

impl<BUS, CS> Error for ExclusiveDeviceError<BUS, CS>
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, CS> {
bus: BUS,
cs: CS,
}

impl<BUS, CS> ExclusiveDevice<BUS, CS> {
/// Create a new ExclusiveDevice
pub fn new(bus: BUS, cs: CS) -> Self {
Self { bus, cs }
}
}

impl<BUS, CS> ErrorType for ExclusiveDevice<BUS, CS>
where
BUS: ErrorType,
CS: OutputPin,
{
type Error = ExclusiveDeviceError<BUS::Error, CS::Error>;
}

impl<BUS, CS> blocking::SpiDevice for ExclusiveDevice<BUS, CS>
where
BUS: blocking::SpiBusFlush,
CS: OutputPin,
{
type Bus = BUS;

fn transaction<R>(
&mut self,
f: impl FnOnce(&mut Self::Bus) -> Result<R, <Self::Bus as ErrorType>::Error>,
) -> Result<R, Self::Error> {
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<BUS, CS> SpiDevice for ExclusiveDevice<BUS, CS>
where
BUS: SpiBusFlush,
CS: OutputPin,
{
type Bus = BUS;

type TransactionFuture<'a, R, F, Fut> = impl Future<Output = Result<R, Self::Error>> + 'a
where
Self: 'a, R: 'a, F: FnOnce(&'a mut Self::Bus) -> Fut + 'a,
Fut: Future<Output = (&'a mut Self::Bus, Result<R, <Self::Bus as ErrorType>::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<R, <Self::Bus as ErrorType>::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)
}
}
}
Loading

0 comments on commit b5c29b3

Please sign in to comment.