diff --git a/embedded-hal-async/src/spi.rs b/embedded-hal-async/src/spi.rs index 0c99d79bf..2c05f56e4 100644 --- a/embedded-hal-async/src/spi.rs +++ b/embedded-hal-async/src/spi.rs @@ -8,6 +8,8 @@ pub use embedded_hal::spi::{ Error, ErrorKind, ErrorType, Mode, Operation, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3, }; +use crate::delay::DelayUs; + /// SPI device trait /// /// `SpiDevice` represents ownership over a single SPI device on a (possibly shared) bus, selected @@ -195,19 +197,20 @@ where /// /// 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 { +pub struct ExclusiveDevice { bus: BUS, cs: CS, + delay: D, } -impl ExclusiveDevice { +impl ExclusiveDevice { /// Create a new ExclusiveDevice - pub fn new(bus: BUS, cs: CS) -> Self { - Self { bus, cs } + pub fn new(bus: BUS, cs: CS, delay: D) -> Self { + Self { bus, cs, delay } } } -impl ErrorType for ExclusiveDevice +impl ErrorType for ExclusiveDevice where BUS: ErrorType, CS: OutputPin, @@ -215,10 +218,11 @@ where type Error = ExclusiveDeviceError; } -impl blocking::SpiDevice for ExclusiveDevice +impl blocking::SpiDevice for ExclusiveDevice where BUS: blocking::SpiBus, CS: OutputPin, + D: embedded_hal::delay::DelayUs, { fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; @@ -230,6 +234,13 @@ where Operation::Write(buf) => self.bus.write(buf), Operation::Transfer(read, write) => self.bus.transfer(read, write), Operation::TransferInPlace(buf) => self.bus.transfer_in_place(buf), + Operation::DelayUs(us) => match self.bus.flush() { + Err(e) => Err(e), + Ok(()) => { + self.delay.delay_us(*us); + Ok(()) + } + }, }; if let Err(e) = res { break 'ops Err(e); @@ -250,10 +261,11 @@ where } } -impl SpiDevice for ExclusiveDevice +impl SpiDevice for ExclusiveDevice where BUS: SpiBus, CS: OutputPin, + D: DelayUs, { async fn transaction( &mut self, @@ -268,6 +280,13 @@ where Operation::Write(buf) => self.bus.write(buf).await, Operation::Transfer(read, write) => self.bus.transfer(read, write).await, Operation::TransferInPlace(buf) => self.bus.transfer_in_place(buf).await, + Operation::DelayUs(us) => match self.bus.flush().await { + Err(e) => Err(e), + Ok(()) => { + self.delay.delay_us(*us).await; + Ok(()) + } + }, }; if let Err(e) = res { break 'ops Err(e); diff --git a/embedded-hal-bus/src/spi/critical_section.rs b/embedded-hal-bus/src/spi/critical_section.rs index c3e8cf699..2a4c09788 100644 --- a/embedded-hal-bus/src/spi/critical_section.rs +++ b/embedded-hal-bus/src/spi/critical_section.rs @@ -1,5 +1,6 @@ use core::cell::RefCell; use critical_section::Mutex; +use embedded_hal::delay::DelayUs; use embedded_hal::digital::OutputPin; use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice}; @@ -15,19 +16,36 @@ use super::DeviceError; /// The downside is critical sections typically require globally disabling interrupts, so `CriticalSectionDevice` will likely /// negatively impact real-time properties, such as interrupt latency. If you can, prefer using /// [`RefCellDevice`](super::RefCellDevice) instead, which does not require taking critical sections. -pub struct CriticalSectionDevice<'a, BUS, CS> { +pub struct CriticalSectionDevice<'a, BUS, CS, D> { bus: &'a Mutex>, cs: CS, + delay: D, } -impl<'a, BUS, CS> CriticalSectionDevice<'a, BUS, CS> { +impl<'a, BUS, CS, D> CriticalSectionDevice<'a, BUS, CS, D> { /// Create a new ExclusiveDevice - pub fn new(bus: &'a Mutex>, cs: CS) -> Self { - Self { bus, cs } + pub fn new(bus: &'a Mutex>, cs: CS, delay: D) -> Self { + Self { bus, cs, delay } } } -impl<'a, BUS, CS> ErrorType for CriticalSectionDevice<'a, BUS, CS> +impl<'a, BUS, CS> CriticalSectionDevice<'a, BUS, CS, super::NoDelay> { + /// Create a new CriticalSectionDevice without support for in-transaction delays. + /// + /// # Panics + /// + /// The returned device will panic if you try to execute a transaction + /// that contains any operations of type `Operation::DelayUs`. + pub fn new_no_delay(bus: &'a Mutex>, cs: CS) -> Self { + Self { + bus, + cs, + delay: super::NoDelay, + } + } +} + +impl<'a, BUS, CS, D> ErrorType for CriticalSectionDevice<'a, BUS, CS, D> where BUS: ErrorType, CS: OutputPin, @@ -35,10 +53,11 @@ where type Error = DeviceError; } -impl<'a, Word: Copy + 'static, BUS, CS> SpiDevice for CriticalSectionDevice<'a, BUS, CS> +impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice for CriticalSectionDevice<'a, BUS, CS, D> where BUS: SpiBus, CS: OutputPin, + D: DelayUs, { fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { critical_section::with(|cs| { @@ -51,6 +70,11 @@ where Operation::Write(buf) => bus.write(buf), Operation::Transfer(read, write) => bus.transfer(read, write), Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), + Operation::DelayUs(us) => { + bus.flush()?; + self.delay.delay_us(*us); + Ok(()) + } }); // On failure, it's important to still flush and deassert CS. diff --git a/embedded-hal-bus/src/spi/exclusive.rs b/embedded-hal-bus/src/spi/exclusive.rs index 9d4d002c9..cbec194dc 100644 --- a/embedded-hal-bus/src/spi/exclusive.rs +++ b/embedded-hal-bus/src/spi/exclusive.rs @@ -1,5 +1,6 @@ //! SPI bus sharing mechanisms. +use embedded_hal::delay::DelayUs; use embedded_hal::digital::OutputPin; use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice}; @@ -9,19 +10,36 @@ use super::DeviceError; /// /// This is the most straightforward way of obtaining an [`SpiDevice`] from an [`SpiBus`](embedded_hal::spi::SpiBus), /// ideal for when no sharing is required (only one SPI device is present on the bus). -pub struct ExclusiveDevice { +pub struct ExclusiveDevice { bus: BUS, cs: CS, + delay: D, } -impl ExclusiveDevice { +impl ExclusiveDevice { /// Create a new ExclusiveDevice - pub fn new(bus: BUS, cs: CS) -> Self { - Self { bus, cs } + pub fn new(bus: BUS, cs: CS, delay: D) -> Self { + Self { bus, cs, delay } } } -impl ErrorType for ExclusiveDevice +impl ExclusiveDevice { + /// Create a new ExclusiveDevice without support for in-transaction delays. + /// + /// # Panics + /// + /// The returned device will panic if you try to execute a transaction + /// that contains any operations of type `Operation::DelayUs`. + pub fn new_no_delay(bus: BUS, cs: CS) -> Self { + Self { + bus, + cs, + delay: super::NoDelay, + } + } +} + +impl ErrorType for ExclusiveDevice where BUS: ErrorType, CS: OutputPin, @@ -29,10 +47,11 @@ where type Error = DeviceError; } -impl SpiDevice for ExclusiveDevice +impl SpiDevice for ExclusiveDevice where BUS: SpiBus, CS: OutputPin, + D: DelayUs, { fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { self.cs.set_low().map_err(DeviceError::Cs)?; @@ -42,6 +61,11 @@ where Operation::Write(buf) => self.bus.write(buf), Operation::Transfer(read, write) => self.bus.transfer(read, write), Operation::TransferInPlace(buf) => self.bus.transfer_in_place(buf), + Operation::DelayUs(us) => { + self.bus.flush()?; + self.delay.delay_us(*us); + Ok(()) + } }); // On failure, it's important to still flush and deassert CS. diff --git a/embedded-hal-bus/src/spi/mod.rs b/embedded-hal-bus/src/spi/mod.rs index 6ae803865..10f74e8d6 100644 --- a/embedded-hal-bus/src/spi/mod.rs +++ b/embedded-hal-bus/src/spi/mod.rs @@ -35,3 +35,12 @@ where } } } + +/// Dummy `DelayUs` implementation that panics on use. +pub struct NoDelay; + +impl embedded_hal::delay::DelayUs for NoDelay { + fn delay_us(&mut self, _us: u32) { + panic!("You've tried to execute a SPI transaction containing a `Operation::Delay` in a `SpiDevice` created with `new_no_delay()`. Create it with `new()` instead, passing a `DelayUs` implementation.") + } +} diff --git a/embedded-hal-bus/src/spi/mutex.rs b/embedded-hal-bus/src/spi/mutex.rs index 59f8dfe95..bdf329217 100644 --- a/embedded-hal-bus/src/spi/mutex.rs +++ b/embedded-hal-bus/src/spi/mutex.rs @@ -1,3 +1,4 @@ +use embedded_hal::delay::DelayUs; use embedded_hal::digital::OutputPin; use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice}; use std::sync::Mutex; @@ -12,19 +13,36 @@ use super::DeviceError; /// Sharing is implemented with a `std` [`Mutex`](std::sync::Mutex). It allows a single bus across multiple threads, /// with finer-grained locking than [`CriticalSectionDevice`](super::CriticalSectionDevice). The downside is /// it is only available in `std` targets. -pub struct MutexDevice<'a, BUS, CS> { +pub struct MutexDevice<'a, BUS, CS, D> { bus: &'a Mutex, cs: CS, + delay: D, } -impl<'a, BUS, CS> MutexDevice<'a, BUS, CS> { +impl<'a, BUS, CS, D> MutexDevice<'a, BUS, CS, D> { /// Create a new ExclusiveDevice - pub fn new(bus: &'a Mutex, cs: CS) -> Self { - Self { bus, cs } + pub fn new(bus: &'a Mutex, cs: CS, delay: D) -> Self { + Self { bus, cs, delay } } } -impl<'a, BUS, CS> ErrorType for MutexDevice<'a, BUS, CS> +impl<'a, BUS, CS> MutexDevice<'a, BUS, CS, super::NoDelay> { + /// Create a new MutexDevice without support for in-transaction delays. + /// + /// # Panics + /// + /// The returned device will panic if you try to execute a transaction + /// that contains any operations of type `Operation::DelayUs`. + pub fn new_no_delay(bus: &'a Mutex, cs: CS) -> Self { + Self { + bus, + cs, + delay: super::NoDelay, + } + } +} + +impl<'a, BUS, CS, D> ErrorType for MutexDevice<'a, BUS, CS, D> where BUS: ErrorType, CS: OutputPin, @@ -32,10 +50,11 @@ where type Error = DeviceError; } -impl<'a, Word: Copy + 'static, BUS, CS> SpiDevice for MutexDevice<'a, BUS, CS> +impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice for MutexDevice<'a, BUS, CS, D> where BUS: SpiBus, CS: OutputPin, + D: DelayUs, { fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { let bus = &mut *self.bus.lock().unwrap(); @@ -47,6 +66,11 @@ where Operation::Write(buf) => bus.write(buf), Operation::Transfer(read, write) => bus.transfer(read, write), Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), + Operation::DelayUs(us) => { + bus.flush()?; + self.delay.delay_us(*us); + Ok(()) + } }); // On failure, it's important to still flush and deassert CS. diff --git a/embedded-hal-bus/src/spi/refcell.rs b/embedded-hal-bus/src/spi/refcell.rs index 9fd5d3e03..a08e8a262 100644 --- a/embedded-hal-bus/src/spi/refcell.rs +++ b/embedded-hal-bus/src/spi/refcell.rs @@ -1,4 +1,5 @@ use core::cell::RefCell; +use embedded_hal::delay::DelayUs; use embedded_hal::digital::OutputPin; use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice}; @@ -12,19 +13,36 @@ use super::DeviceError; /// Sharing is implemented with a `RefCell`. This means it has low overhead, but `RefCellDevice` instances are not `Send`, /// so it only allows sharing within a single thread (interrupt priority level). If you need to share a bus across several /// threads, use [`CriticalSectionDevice`](super::CriticalSectionDevice) instead. -pub struct RefCellDevice<'a, BUS, CS> { +pub struct RefCellDevice<'a, BUS, CS, D> { bus: &'a RefCell, cs: CS, + delay: D, } -impl<'a, BUS, CS> RefCellDevice<'a, BUS, CS> { +impl<'a, BUS, CS, D> RefCellDevice<'a, BUS, CS, D> { /// Create a new ExclusiveDevice - pub fn new(bus: &'a RefCell, cs: CS) -> Self { - Self { bus, cs } + pub fn new(bus: &'a RefCell, cs: CS, delay: D) -> Self { + Self { bus, cs, delay } } } -impl<'a, BUS, CS> ErrorType for RefCellDevice<'a, BUS, CS> +impl<'a, BUS, CS> RefCellDevice<'a, BUS, CS, super::NoDelay> { + /// Create a new RefCellDevice without support for in-transaction delays. + /// + /// # Panics + /// + /// The returned device will panic if you try to execute a transaction + /// that contains any operations of type `Operation::DelayUs`. + pub fn new_no_delay(bus: &'a RefCell, cs: CS) -> Self { + Self { + bus, + cs, + delay: super::NoDelay, + } + } +} + +impl<'a, BUS, CS, D> ErrorType for RefCellDevice<'a, BUS, CS, D> where BUS: ErrorType, CS: OutputPin, @@ -32,10 +50,11 @@ where type Error = DeviceError; } -impl<'a, Word: Copy + 'static, BUS, CS> SpiDevice for RefCellDevice<'a, BUS, CS> +impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice for RefCellDevice<'a, BUS, CS, D> where BUS: SpiBus, CS: OutputPin, + D: DelayUs, { fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { let bus = &mut *self.bus.borrow_mut(); @@ -47,6 +66,11 @@ where Operation::Write(buf) => bus.write(buf), Operation::Transfer(read, write) => bus.transfer(read, write), Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), + Operation::DelayUs(us) => { + bus.flush()?; + self.delay.delay_us(*us); + Ok(()) + } }); // On failure, it's important to still flush and deassert CS. diff --git a/embedded-hal/CHANGELOG.md b/embedded-hal/CHANGELOG.md index 3c25f2dd6..afd99a7a7 100644 --- a/embedded-hal/CHANGELOG.md +++ b/embedded-hal/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- spi: added `Operation::DelayUs(u32)`. + ### Removed - spi: removed read-only and write-only traits. diff --git a/embedded-hal/src/spi.rs b/embedded-hal/src/spi.rs index ea77bf982..3bcdda795 100644 --- a/embedded-hal/src/spi.rs +++ b/embedded-hal/src/spi.rs @@ -313,6 +313,8 @@ pub enum Operation<'a, Word: 'static> { /// /// Equivalent to [`SpiBus::transfer_in_place`]. TransferInPlace(&'a mut [Word]), + /// Delay for at least the specified number of microseconds + DelayUs(u32), } /// SPI device trait