spi: add Operation::DelayUs(u32).

This commit is contained in:
Dario Nieuwenhuis 2023-05-23 21:26:15 +02:00
parent 30a8190703
commit d07d39e359
8 changed files with 160 additions and 31 deletions

View File

@ -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<BUS, CS> {
pub struct ExclusiveDevice<BUS, CS, D> {
bus: BUS,
cs: CS,
delay: D,
}
impl<BUS, CS> ExclusiveDevice<BUS, CS> {
impl<BUS, CS, D> ExclusiveDevice<BUS, CS, D> {
/// 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<BUS, CS> ErrorType for ExclusiveDevice<BUS, CS>
impl<BUS, CS, D> ErrorType for ExclusiveDevice<BUS, CS, D>
where
BUS: ErrorType,
CS: OutputPin,
@ -215,10 +218,11 @@ where
type Error = ExclusiveDeviceError<BUS::Error, CS::Error>;
}
impl<Word: Copy + 'static, BUS, CS> blocking::SpiDevice<Word> for ExclusiveDevice<BUS, CS>
impl<Word: Copy + 'static, BUS, CS, D> blocking::SpiDevice<Word> for ExclusiveDevice<BUS, CS, D>
where
BUS: blocking::SpiBus<Word>,
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<Word: Copy + 'static, BUS, CS> SpiDevice<Word> for ExclusiveDevice<BUS, CS>
impl<Word: Copy + 'static, BUS, CS, D> SpiDevice<Word> for ExclusiveDevice<BUS, CS, D>
where
BUS: SpiBus<Word>,
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);

View File

@ -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<RefCell<BUS>>,
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<RefCell<BUS>>, cs: CS) -> Self {
Self { bus, cs }
pub fn new(bus: &'a Mutex<RefCell<BUS>>, 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<RefCell<BUS>>, 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<BUS::Error, CS::Error>;
}
impl<'a, Word: Copy + 'static, BUS, CS> SpiDevice<Word> for CriticalSectionDevice<'a, BUS, CS>
impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice<Word> for CriticalSectionDevice<'a, BUS, CS, D>
where
BUS: SpiBus<Word>,
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.

View File

@ -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<BUS, CS> {
pub struct ExclusiveDevice<BUS, CS, D> {
bus: BUS,
cs: CS,
delay: D,
}
impl<BUS, CS> ExclusiveDevice<BUS, CS> {
impl<BUS, CS, D> ExclusiveDevice<BUS, CS, D> {
/// 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<BUS, CS> ErrorType for ExclusiveDevice<BUS, CS>
impl<BUS, CS> ExclusiveDevice<BUS, CS, super::NoDelay> {
/// 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<BUS, CS, D> ErrorType for ExclusiveDevice<BUS, CS, D>
where
BUS: ErrorType,
CS: OutputPin,
@ -29,10 +47,11 @@ where
type Error = DeviceError<BUS::Error, CS::Error>;
}
impl<Word: Copy + 'static, BUS, CS> SpiDevice<Word> for ExclusiveDevice<BUS, CS>
impl<Word: Copy + 'static, BUS, CS, D> SpiDevice<Word> for ExclusiveDevice<BUS, CS, D>
where
BUS: SpiBus<Word>,
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.

View File

@ -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.")
}
}

View File

@ -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<BUS>,
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<BUS>, cs: CS) -> Self {
Self { bus, cs }
pub fn new(bus: &'a Mutex<BUS>, 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<BUS>, 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<BUS::Error, CS::Error>;
}
impl<'a, Word: Copy + 'static, BUS, CS> SpiDevice<Word> for MutexDevice<'a, BUS, CS>
impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice<Word> for MutexDevice<'a, BUS, CS, D>
where
BUS: SpiBus<Word>,
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.

View File

@ -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<BUS>,
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<BUS>, cs: CS) -> Self {
Self { bus, cs }
pub fn new(bus: &'a RefCell<BUS>, 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<BUS>, 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<BUS::Error, CS::Error>;
}
impl<'a, Word: Copy + 'static, BUS, CS> SpiDevice<Word> for RefCellDevice<'a, BUS, CS>
impl<'a, Word: Copy + 'static, BUS, CS, D> SpiDevice<Word> for RefCellDevice<'a, BUS, CS, D>
where
BUS: SpiBus<Word>,
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.

View File

@ -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.

View File

@ -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