Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async: add SPI #347

Merged
merged 2 commits into from
Mar 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)?;
ryankurte marked this conversation as resolved.
Show resolved Hide resolved

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