Skip to content
Draft
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
1 change: 1 addition & 0 deletions embassy-embedded-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [
] }
embedded-hal-1 = { package = "embedded-hal", version = "1.0" }
embedded-hal-async = { version = "1.0" }
embedded-hal-bus = { version = "0.3.0" }
embedded-storage = "0.3.1"
embedded-storage-async = { version = "0.4.1" }
nb = "1.0.0"
Expand Down
1 change: 1 addition & 0 deletions embassy-embedded-hal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

pub mod adapter;
pub mod flash;
pub mod qspi;
pub mod shared_bus;

/// Set the configuration of a peripheral driver.
Expand Down
38 changes: 38 additions & 0 deletions embassy-embedded-hal/src/qspi/device_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! Module for QSPI device error
use core::fmt::{self, Debug, Display, Formatter};
use embedded_hal_async::spi::{Error, ErrorKind};

/// Error type for [`ExclusiveDevice`] operations.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum DeviceError<BUS, CS> {
/// An inner QSPI bus operation failed.
Qspi(BUS),
/// Asserting or deasserting CS failed.
Cs(CS),
}

impl<BUS: Display, CS: Display> Display for DeviceError<BUS, CS> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Qspi(bus) => write!(f, "SPI bus error: {}", bus),
Self::Cs(cs) => write!(f, "SPI CS error: {}", cs),
}
}
}

impl<BUS: Debug + Display, CS: Debug + Display> core::error::Error for DeviceError<BUS, CS> {}

impl<BUS, CS> Error for DeviceError<BUS, CS>
where
BUS: Error + Debug,
CS: Debug,
{
#[inline]
fn kind(&self) -> ErrorKind {
match self {
Self::Qspi(e) => e.kind(),
Self::Cs(_) => ErrorKind::ChipSelectFault,
}
}
}
135 changes: 135 additions & 0 deletions embassy-embedded-hal/src/qspi/exclusive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//! QSPI bus sharing mechanisms.

use super::traits::{Operation, QspiBus, QspiDevice};
use embedded_hal_1::digital::OutputPin;
use embedded_hal_async::delay::DelayNs;
use embedded_hal_async::spi::ErrorType;

use super::device_error::DeviceError;

/// Dummy [`DelayNs`](embedded_hal::delay::DelayNs) implementation that panics on use.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct NoDelay;

/// [`QspiDevice`] implementation with exclusive access to the bus (not shared).
///
/// This is the most straightforward way of obtaining an [`QspiDevice`] from an [`QspiBus`],
/// ideal for when no sharing is required (only one SPI device is present on the bus).
pub struct ExclusiveDevice<BUS, CS, D> {
bus: BUS,
cs: CS,
delay: D,
}

impl<BUS, CS, D> ExclusiveDevice<BUS, CS, D> {
/// Create a new [`ExclusiveDevice`].
///
/// This sets the `cs` pin high, and returns an error if that fails. It is recommended
/// to set the pin high the moment it's configured as an output, to avoid glitches.
#[inline]
pub fn new(bus: BUS, mut cs: CS, delay: D) -> Result<Self, CS::Error>
where
CS: OutputPin,
{
cs.set_high()?;
Ok(Self { bus, cs, delay })
}

/// Returns a reference to the underlying bus object.
#[inline]
pub fn bus(&self) -> &BUS {
&self.bus
}

/// Returns a mutable reference to the underlying bus object.
#[inline]
pub fn bus_mut(&mut self) -> &mut BUS {
&mut self.bus
}
}

impl<BUS, CS> ExclusiveDevice<BUS, CS, NoDelay> {
/// Create a new [`ExclusiveDevice`] without support for in-transaction delays.
///
/// This sets the `cs` pin high, and returns an error if that fails. It is recommended
/// to set the pin high the moment it's configured as an output, to avoid glitches.
///
/// **Warning**: The returned instance *technically* doesn't comply with the `QspiDevice`
/// contract, which mandates delay support. It is relatively rare for drivers to use
/// in-transaction delays, so you might still want to use this method because it's more practical.
///
/// Note that a future version of the driver might start using delays, causing your
/// code to panic. This wouldn't be considered a breaking change from the driver side, because
/// drivers are allowed to assume `QspiDevice` implementations comply with the contract.
/// If you feel this risk outweighs the convenience of having `cargo` automatically upgrade
/// the driver crate, you might want to pin the driver's version.
///
/// # Panics
///
/// The returned device will panic if you try to execute a transaction
/// that contains any operations of type [`Operation::DelayNs`].
#[inline]
pub fn new_no_delay(bus: BUS, mut cs: CS) -> Result<Self, CS::Error>
where
CS: OutputPin,
{
cs.set_high()?;
Ok(Self {
bus,
cs,
delay: 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, D> QspiDevice<Word> for ExclusiveDevice<BUS, CS, D>
where
BUS: QspiBus<Word>,
CS: OutputPin,
D: DelayNs,
{
#[inline]
async fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> {
self.cs.set_low().map_err(DeviceError::Cs)?;

let op_res = 'ops: {
for op in operations {
let res = match op {
Operation::Read(buf) => self.bus.read(buf).await,
Operation::Write(buf) => self.bus.write(buf).await,
Operation::WriteSingleLine(buf) => self.bus.write_single_line(buf).await,
Operation::DelayNs(ns) => match self.bus.flush().await {
Err(e) => Err(e),
Ok(()) => {
self.delay.delay_ns(*ns).await;
Ok(())
}
},
};
if let Err(e) = res {
break 'ops Err(e);
}
}
Ok(())
};

// On failure, it's important to still flush and deassert CS.
let flush_res = self.bus.flush().await;
let cs_res = self.cs.set_high();

op_res.map_err(DeviceError::Qspi)?;
flush_res.map_err(DeviceError::Qspi)?;
cs_res.map_err(DeviceError::Cs)?;

Ok(())
}
}
4 changes: 4 additions & 0 deletions embassy-embedded-hal/src/qspi/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! Utilities for Quad SPI
pub mod device_error;
pub mod exclusive;
pub mod traits;
Loading