Skip to content

Commit

Permalink
Merge pull request #462 from Dirbaio/spi-delay
Browse files Browse the repository at this point in the history
spi: add Operation::DelayUs(u32).
  • Loading branch information
Dirbaio committed Jun 20, 2023
2 parents 3620083 + d07d39e commit a8ff64f
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 31 deletions.
33 changes: 26 additions & 7 deletions embedded-hal-async/src/spi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -195,30 +197,32 @@ 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,
{
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)?;
Expand All @@ -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);
Expand All @@ -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,
Expand All @@ -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);
Expand Down
36 changes: 30 additions & 6 deletions embedded-hal-bus/src/spi/critical_section.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -15,30 +16,48 @@ 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,
{
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| {
Expand All @@ -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.
Expand Down
36 changes: 30 additions & 6 deletions embedded-hal-bus/src/spi/exclusive.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -9,30 +10,48 @@ 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,
{
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)?;
Expand All @@ -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.
Expand Down
9 changes: 9 additions & 0 deletions embedded-hal-bus/src/spi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.")
}
}
36 changes: 30 additions & 6 deletions embedded-hal-bus/src/spi/mutex.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -12,30 +13,48 @@ 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,
{
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();
Expand All @@ -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.
Expand Down
36 changes: 30 additions & 6 deletions embedded-hal-bus/src/spi/refcell.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -12,30 +13,48 @@ 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,
{
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();
Expand All @@ -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.
Expand Down
Loading

0 comments on commit a8ff64f

Please sign in to comment.