Skip to content

spi: add Operation::DelayUs(u32). #462

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

Merged
merged 1 commit into from
Jun 20, 2023
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
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