SPI: Implement interrupt-driven transfer_in_place_async (#2691)

* Implement transfer_async

* The start of our shiny future

* Add State

* Wake from interrupt

* Rename and remove return value

* Fix register write

* Fix S2

* Rename traits

* Async flushes

* Flush before async operations

* Fix comments

* Rename start fn, place async handler in RAM

* Explicitly stop listening before async operations
This commit is contained in:
Dániel Buga 2024-12-18 08:32:12 +01:00 committed by GitHub
parent f990957f21
commit 2ca1545b50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 538 additions and 87 deletions

View File

@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `BurstConfig`, a device-specific configuration for configuring DMA transfers in burst mode (#2543)
- `{DmaRxBuf, DmaTxBuf, DmaRxTxBuf}::set_burst_config` (#2543)
- ESP32-S2: DMA support for AES (#2699)
- Added `transfer_in_place_async` and embedded-hal-async implementation to `Spi` (#2691)
### Changed

View File

@ -71,17 +71,21 @@ use procmacros::ram;
use super::{DmaError, Error, SpiBitOrder, SpiDataMode, SpiMode};
use crate::{
asynch::AtomicWaker,
clock::Clocks,
dma::{DmaChannelFor, DmaEligible, DmaRxBuffer, DmaTxBuffer, Rx, Tx},
gpio::{interconnect::PeripheralOutput, InputSignal, NoPin, OutputSignal},
interrupt::InterruptHandler,
peripheral::{Peripheral, PeripheralRef},
peripherals::spi2::RegisterBlock,
prelude::InterruptConfigurable,
private,
private::Sealed,
spi::AnySpi,
system::PeripheralGuard,
Async,
Blocking,
Cpu,
Mode,
};
@ -463,12 +467,18 @@ pub struct Spi<'d, Dm, T = AnySpi> {
guard: PeripheralGuard,
}
impl<Dm: Mode, T: Instance> Sealed for Spi<'_, Dm, T> {}
impl<Dm, T> Spi<'_, Dm, T>
where
T: Instance,
Dm: Mode,
{
fn driver(&self) -> &'static Info {
self.spi.info()
fn driver(&self) -> Driver {
Driver {
info: self.spi.info(),
state: self.spi.state(),
}
}
/// Read a byte from SPI.
@ -502,7 +512,7 @@ where
impl<'d> Spi<'d, Blocking> {
/// Constructs an SPI instance in 8bit dataframe mode.
pub fn new(
spi: impl Peripheral<P = impl Instance> + 'd,
spi: impl Peripheral<P = impl PeripheralInstance> + 'd,
config: Config,
) -> Result<Self, ConfigError> {
Self::new_typed(spi.map_into(), config)
@ -514,7 +524,8 @@ where
T: Instance,
{
/// Converts the SPI instance into async mode.
pub fn into_async(self) -> Spi<'d, Async, T> {
pub fn into_async(mut self) -> Spi<'d, Async, T> {
self.set_interrupt_handler(self.spi.handler());
Spi {
spi: self.spi,
_mode: PhantomData,
@ -536,23 +547,63 @@ where
}
}
impl<T> InterruptConfigurable for Spi<'_, Blocking, T>
where
T: Instance,
{
/// Sets the interrupt handler
///
/// Interrupts are not enabled at the peripheral level here.
fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
let interrupt = self.driver().info.interrupt;
for core in Cpu::other() {
crate::interrupt::disable(core, interrupt);
}
unsafe { crate::interrupt::bind_interrupt(interrupt, handler.handler()) };
unwrap!(crate::interrupt::enable(interrupt, handler.priority()));
}
}
impl<'d, T> Spi<'d, Async, T>
where
T: Instance,
{
/// Converts the SPI instance into blocking mode.
pub fn into_blocking(self) -> Spi<'d, Blocking, T> {
crate::interrupt::disable(Cpu::current(), self.driver().info.interrupt);
Spi {
spi: self.spi,
_mode: PhantomData,
guard: self.guard,
}
}
/// Waits for the completion of previous operations.
pub async fn flush_async(&mut self) -> Result<(), Error> {
let driver = self.driver();
if !driver.busy() {
return Ok(());
}
SpiFuture::new(&driver).await;
Ok(())
}
/// Sends `words` to the slave. Returns the `words` received from the slave
pub async fn transfer_in_place_async(&mut self, words: &mut [u8]) -> Result<(), Error> {
// We need to flush because the blocking transfer functions may return while a
// transfer is still in progress.
self.flush_async().await?;
self.driver().transfer_in_place_async(words).await
}
}
impl<'d, Dm, T> Spi<'d, Dm, T>
where
T: Instance,
Dm: Mode,
{
/// Constructs an SPI instance in 8bit dataframe mode.
pub fn new_typed(
@ -578,12 +629,12 @@ where
.with_sck(NoPin)
.with_cs(NoPin);
let is_qspi = this.driver().sio2_input.is_some();
let is_qspi = this.driver().info.sio2_input.is_some();
if is_qspi {
unwrap!(this.driver().sio2_input).connect_to(NoPin);
unwrap!(this.driver().sio2_output).connect_to(NoPin);
unwrap!(this.driver().sio3_input).connect_to(NoPin);
unwrap!(this.driver().sio3_output).connect_to(NoPin);
unwrap!(this.driver().info.sio2_input).connect_to(NoPin);
unwrap!(this.driver().info.sio2_output).connect_to(NoPin);
unwrap!(this.driver().info.sio3_input).connect_to(NoPin);
unwrap!(this.driver().info.sio3_output).connect_to(NoPin);
}
Ok(this)
@ -598,8 +649,8 @@ where
mosi.enable_output(true, private::Internal);
mosi.enable_input(true, private::Internal);
self.driver().mosi.connect_to(&mut mosi);
self.driver().sio0_input.connect_to(&mut mosi);
self.driver().info.mosi.connect_to(&mut mosi);
self.driver().info.sio0_input.connect_to(&mut mosi);
self
}
@ -613,8 +664,8 @@ where
miso.enable_input(true, private::Internal);
miso.enable_output(true, private::Internal);
self.driver().miso.connect_to(&mut miso);
self.driver().sio1_output.connect_to(&mut miso);
self.driver().info.miso.connect_to(&mut miso);
self.driver().info.sio1_output.connect_to(&mut miso);
self
}
@ -626,7 +677,7 @@ where
pub fn with_sck<SCK: PeripheralOutput>(self, sclk: impl Peripheral<P = SCK> + 'd) -> Self {
crate::into_mapped_ref!(sclk);
sclk.set_to_push_pull_output(private::Internal);
self.driver().sclk.connect_to(sclk);
self.driver().info.sclk.connect_to(sclk);
self
}
@ -639,7 +690,7 @@ where
pub fn with_cs<CS: PeripheralOutput>(self, cs: impl Peripheral<P = CS> + 'd) -> Self {
crate::into_mapped_ref!(cs);
cs.set_to_push_pull_output(private::Internal);
self.driver().cs.connect_to(cs);
self.driver().info.cs.connect_to(cs);
self
}
@ -667,7 +718,8 @@ where
impl<'d, Dm, T> Spi<'d, Dm, T>
where
T: QspiInstance,
T: Instance + QspiInstance,
Dm: Mode,
{
/// Assign the SIO2 pin for the SPI instance.
///
@ -681,8 +733,8 @@ where
sio2.enable_input(true, private::Internal);
sio2.enable_output(true, private::Internal);
unwrap!(self.driver().sio2_input).connect_to(&mut sio2);
unwrap!(self.driver().sio2_output).connect_to(&mut sio2);
unwrap!(self.driver().info.sio2_input).connect_to(&mut sio2);
unwrap!(self.driver().info.sio2_output).connect_to(&mut sio2);
self
}
@ -699,8 +751,8 @@ where
sio3.enable_input(true, private::Internal);
sio3.enable_output(true, private::Internal);
unwrap!(self.driver().sio3_input).connect_to(&mut sio3);
unwrap!(self.driver().sio3_output).connect_to(&mut sio3);
unwrap!(self.driver().info.sio3_input).connect_to(&mut sio3);
unwrap!(self.driver().info.sio3_output).connect_to(&mut sio3);
self
}
@ -709,6 +761,7 @@ where
impl<Dm, T> Spi<'_, Dm, T>
where
T: Instance,
Dm: Mode,
{
/// Half-duplex read.
#[instability::unstable]
@ -910,7 +963,7 @@ mod dma {
///
/// Interrupts are not enabled at the peripheral level here.
fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
let interrupt = self.driver().interrupt;
let interrupt = self.driver().info.interrupt;
for core in crate::Cpu::other() {
crate::interrupt::disable(core, interrupt);
}
@ -991,16 +1044,19 @@ mod dma {
impl<'d, Dm, T> SpiDma<'d, Dm, T>
where
Dm: Mode,
T: Instance,
Dm: Mode,
{
fn driver(&self) -> &'static Info {
self.spi.info()
fn driver(&self) -> Driver {
Driver {
info: self.spi.info(),
state: self.spi.state(),
}
}
fn dma_driver(&self) -> DmaDriver {
DmaDriver {
info: self.driver(),
driver: self.driver(),
dma_peripheral: self.spi.dma_peripheral(),
}
}
@ -1522,7 +1578,6 @@ mod dma {
pub struct SpiDmaBus<'d, Dm, T = AnySpi>
where
T: Instance,
Dm: Mode,
{
spi_dma: SpiDma<'d, Dm, T>,
@ -2070,6 +2125,7 @@ mod dma {
mod ehal1 {
use embedded_hal::spi::SpiBus;
use embedded_hal_async::spi::SpiBus as SpiBusAsync;
use embedded_hal_nb::spi::FullDuplex;
use super::*;
@ -2081,6 +2137,7 @@ mod ehal1 {
impl<Dm, T> FullDuplex for Spi<'_, Dm, T>
where
T: Instance,
Dm: Mode,
{
fn read(&mut self) -> nb::Result<u8, Self::Error> {
self.driver().read_byte()
@ -2094,6 +2151,7 @@ mod ehal1 {
impl<Dm, T> SpiBus for Spi<'_, Dm, T>
where
T: Instance,
Dm: Mode,
{
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
self.driver().read_bytes(words)
@ -2150,29 +2208,95 @@ mod ehal1 {
}
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
// Since we have the traits so neatly implemented above, use them!
for chunk in words.chunks_mut(FIFO_SIZE) {
SpiBus::write(self, chunk)?;
SpiBus::flush(self)?;
self.driver().read_bytes_from_fifo(chunk)?;
}
Ok(())
self.driver().transfer(words).map(|_| ())
}
fn flush(&mut self) -> Result<(), Self::Error> {
self.driver().flush()
}
}
impl<T> SpiBusAsync for Spi<'_, Async, T>
where
T: Instance,
{
async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
// We need to flush because the blocking transfer functions may return while a
// transfer is still in progress.
self.flush_async().await?;
self.driver().read_bytes_async(words).await
}
async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
// We need to flush because the blocking transfer functions may return while a
// transfer is still in progress.
self.flush_async().await?;
self.driver().write_bytes_async(words).await
}
async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> {
// Optimizations
if read.is_empty() {
return SpiBusAsync::write(self, write).await;
} else if write.is_empty() {
return SpiBusAsync::read(self, read).await;
}
let mut write_from = 0;
let mut read_from = 0;
loop {
// How many bytes we write in this chunk
let write_inc = core::cmp::min(FIFO_SIZE, write.len() - write_from);
let write_to = write_from + write_inc;
// How many bytes we read in this chunk
let read_inc = core::cmp::min(FIFO_SIZE, read.len() - read_from);
let read_to = read_from + read_inc;
if (write_inc == 0) && (read_inc == 0) {
break;
}
// No need to flush here, `SpiBusAsync::write` will do it for us
if write_to < read_to {
// Read more than we write, must pad writing part with zeros
let mut empty = [EMPTY_WRITE_PAD; FIFO_SIZE];
empty[0..write_inc].copy_from_slice(&write[write_from..write_to]);
SpiBusAsync::write(self, &empty).await?;
} else {
SpiBusAsync::write(self, &write[write_from..write_to]).await?;
}
if read_inc > 0 {
self.driver()
.read_bytes_from_fifo(&mut read[read_from..read_to])?;
}
write_from = write_to;
read_from = read_to;
}
Ok(())
}
async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
self.transfer_in_place_async(words).await
}
async fn flush(&mut self) -> Result<(), Self::Error> {
self.flush_async().await
}
}
}
/// SPI peripheral instance.
pub trait Instance: private::Sealed + Into<AnySpi> + DmaEligible + 'static {
pub trait PeripheralInstance: private::Sealed + Into<AnySpi> + DmaEligible + 'static {
/// Returns the peripheral data describing this SPI instance.
fn info(&self) -> &'static Info;
}
/// Marker trait for QSPI-capable SPI peripherals.
pub trait QspiInstance: Instance {}
pub trait QspiInstance: PeripheralInstance {}
/// Peripheral data describing a particular SPI instance.
#[non_exhaustive]
@ -2220,14 +2344,14 @@ pub struct Info {
}
struct DmaDriver {
info: &'static Info,
driver: Driver,
dma_peripheral: crate::dma::DmaPeripheral,
}
impl DmaDriver {
fn abort_transfer(&self) {
self.info.configure_datalen(1, 1);
self.info.update();
self.driver.configure_datalen(1, 1);
self.driver.update();
}
#[allow(clippy::too_many_arguments)]
@ -2242,7 +2366,7 @@ impl DmaDriver {
rx: &mut RX,
tx: &mut TX,
) -> Result<(), Error> {
let reg_block = self.info.register_block();
let reg_block = self.driver.register_block();
#[cfg(esp32s2)]
{
@ -2251,7 +2375,7 @@ impl DmaDriver {
reg_block.dma_in_link().write(|w| w.bits(0));
}
self.info.configure_datalen(rx_len, tx_len);
self.driver.configure_datalen(rx_len, tx_len);
// enable the MISO and MOSI if needed
reg_block
@ -2286,7 +2410,7 @@ impl DmaDriver {
#[cfg(gdma)]
self.reset_dma();
self.info.start_operation();
self.driver.start_operation();
Ok(())
}
@ -2295,7 +2419,7 @@ impl DmaDriver {
#[cfg(gdma)]
{
// for non GDMA this is done in `assign_tx_device` / `assign_rx_device`
let reg_block = self.info.register_block();
let reg_block = self.driver.register_block();
reg_block.dma_conf().modify(|_, w| {
w.dma_tx_ena().set_bit();
w.dma_rx_ena().set_bit()
@ -2321,7 +2445,7 @@ impl DmaDriver {
w.dma_afifo_rst().bit(bit)
});
}
let reg_block = self.info.register_block();
let reg_block = self.driver.register_block();
set_reset_bit(reg_block, true);
set_reset_bit(reg_block, false);
self.clear_dma_interrupts();
@ -2329,7 +2453,7 @@ impl DmaDriver {
#[cfg(gdma)]
fn clear_dma_interrupts(&self) {
let reg_block = self.info.register_block();
let reg_block = self.driver.register_block();
reg_block.dma_int_clr().write(|w| {
w.dma_infifo_full_err().clear_bit_by_one();
w.dma_outfifo_empty_err().clear_bit_by_one();
@ -2341,7 +2465,7 @@ impl DmaDriver {
#[cfg(pdma)]
fn clear_dma_interrupts(&self) {
let reg_block = self.info.register_block();
let reg_block = self.driver.register_block();
reg_block.dma_int_clr().write(|w| {
w.inlink_dscr_empty().clear_bit_by_one();
w.outlink_dscr_error().clear_bit_by_one();
@ -2356,12 +2480,17 @@ impl DmaDriver {
}
}
struct Driver {
info: &'static Info,
state: &'static State,
}
// private implementation bits
// FIXME: split this up into peripheral versions
impl Info {
impl Driver {
/// Returns the register block for this SPI instance.
pub fn register_block(&self) -> &RegisterBlock {
unsafe { &*self.register_block }
unsafe { &*self.info.register_block }
}
/// Initialize for full-duplex 1 bit mode
@ -2731,14 +2860,37 @@ impl Info {
Ok(())
}
#[cfg_attr(place_spi_driver_in_ram, ram)]
fn fill_fifo(&self, chunk: &[u8]) {
// TODO: replace with `array_chunks` and `from_le_bytes`
let mut c_iter = chunk.chunks_exact(4);
let mut w_iter = self.register_block().w_iter();
for c in c_iter.by_ref() {
if let Some(w_reg) = w_iter.next() {
let word =
(c[0] as u32) | (c[1] as u32) << 8 | (c[2] as u32) << 16 | (c[3] as u32) << 24;
w_reg.write(|w| w.buf().set(word));
}
}
let rem = c_iter.remainder();
if !rem.is_empty() {
if let Some(w_reg) = w_iter.next() {
let word = match rem.len() {
3 => (rem[0] as u32) | (rem[1] as u32) << 8 | (rem[2] as u32) << 16,
2 => (rem[0] as u32) | (rem[1] as u32) << 8,
1 => rem[0] as u32,
_ => unreachable!(),
};
w_reg.write(|w| w.buf().set(word));
}
}
}
/// Write bytes to SPI.
///
/// Copies the content of `words` in chunks of 64 bytes into the SPI
/// transmission FIFO. If `words` is longer than 64 bytes, multiple
/// sequential transfers are performed. This function will return before
/// all bytes of the last chunk to transmit have been sent to the wire. If
/// you must ensure that the whole messages was written correctly, use
/// [`Self::flush`].
/// This function will return before all bytes of the last chunk to transmit
/// have been sent to the wire. If you must ensure that the whole
/// messages was written correctly, use [`Self::flush`].
#[cfg_attr(place_spi_driver_in_ram, ram)]
fn write_bytes(&self, words: &[u8]) -> Result<(), Error> {
let num_chunks = words.len() / FIFO_SIZE;
@ -2751,33 +2903,7 @@ impl Info {
// transmitted
for (i, chunk) in words.chunks(FIFO_SIZE).enumerate() {
self.configure_datalen(0, chunk.len());
{
// TODO: replace with `array_chunks` and `from_le_bytes`
let mut c_iter = chunk.chunks_exact(4);
let mut w_iter = self.register_block().w_iter();
for c in c_iter.by_ref() {
if let Some(w_reg) = w_iter.next() {
let word = (c[0] as u32)
| (c[1] as u32) << 8
| (c[2] as u32) << 16
| (c[3] as u32) << 24;
w_reg.write(|w| w.buf().set(word));
}
}
let rem = c_iter.remainder();
if !rem.is_empty() {
if let Some(w_reg) = w_iter.next() {
let word = match rem.len() {
3 => (rem[0] as u32) | (rem[1] as u32) << 8 | (rem[2] as u32) << 16,
2 => (rem[0] as u32) | (rem[1] as u32) << 8,
1 => rem[0] as u32,
_ => unreachable!(),
};
w_reg.write(|w| w.buf().set(word));
}
}
}
self.fill_fifo(chunk);
self.start_operation();
@ -2791,6 +2917,19 @@ impl Info {
Ok(())
}
/// Write bytes to SPI.
#[cfg_attr(place_spi_driver_in_ram, ram)]
async fn write_bytes_async(&self, words: &[u8]) -> Result<(), Error> {
// The fifo has a limited fixed size, so the data must be chunked and then
// transmitted
for chunk in words.chunks(FIFO_SIZE) {
self.configure_datalen(0, chunk.len());
self.fill_fifo(chunk);
self.execute_operation_async().await;
}
Ok(())
}
/// Read bytes from SPI.
///
/// Sends out a stuffing byte for every byte to read. This function doesn't
@ -2808,6 +2947,22 @@ impl Info {
Ok(())
}
/// Read bytes from SPI.
///
/// Sends out a stuffing byte for every byte to read. This function doesn't
/// perform flushing. If you want to read the response to something you
/// have written before, consider using [`Self::transfer`] instead.
#[cfg_attr(place_spi_driver_in_ram, ram)]
async fn read_bytes_async(&self, words: &mut [u8]) -> Result<(), Error> {
let empty_array = [EMPTY_WRITE_PAD; FIFO_SIZE];
for chunk in words.chunks_mut(FIFO_SIZE) {
self.write_bytes_async(&empty_array[0..chunk.len()]).await?;
self.read_bytes_from_fifo(chunk)?;
}
Ok(())
}
/// Read received bytes from SPI FIFO.
///
/// Copies the contents of the SPI receive FIFO into `words`. This function
@ -2833,6 +2988,59 @@ impl Info {
Ok(())
}
// FIXME EnumSet flag
fn clear_done_interrupt(&self) {
cfg_if::cfg_if! {
if #[cfg(any(esp32, esp32s2))] {
self.register_block().slave().modify(|_, w| {
w.trans_done().clear_bit()
});
} else {
self.register_block().dma_int_clr().write(|w| {
w.trans_done().clear_bit_by_one()
});
}
}
}
fn is_transfer_done(&self) -> bool {
let reg_block = self.register_block();
cfg_if::cfg_if! {
if #[cfg(any(esp32, esp32s2))] {
reg_block.slave().read().trans_done().bit_is_set()
} else {
reg_block.dma_int_raw().read().trans_done().bit_is_set()
}
}
}
fn enable_listen_for_trans_done(&self, enable: bool) {
cfg_if::cfg_if! {
if #[cfg(esp32)] {
self.register_block().slave().modify(|_, w| {
w.trans_inten().bit(enable)
});
} else if #[cfg(esp32s2)] {
self.register_block().slave().modify(|_, w| {
w.int_trans_done_en().bit(enable)
});
} else {
self.register_block().dma_int_ena().write(|w| {
w.trans_done().bit(enable)
});
}
}
}
fn start_listening(&self) {
self.enable_listen_for_trans_done(true);
}
fn stop_listening(&self) {
self.enable_listen_for_trans_done(false);
}
fn busy(&self) -> bool {
let reg_block = self.register_block();
reg_block.cmd().read().usr().bit_is_set()
@ -2857,12 +3065,30 @@ impl Info {
Ok(words)
}
#[cfg_attr(place_spi_driver_in_ram, ram)]
async fn transfer_in_place_async(&self, words: &mut [u8]) -> Result<(), Error> {
for chunk in words.chunks_mut(FIFO_SIZE) {
self.write_bytes_async(chunk).await?;
self.read_bytes_from_fifo(chunk)?;
}
Ok(())
}
fn start_operation(&self) {
let reg_block = self.register_block();
self.update();
reg_block.cmd().modify(|_, w| w.usr().set_bit());
}
/// Starts the operation and waits for it to complete.
async fn execute_operation_async(&self) {
self.stop_listening();
self.clear_done_interrupt();
self.start_operation();
SpiFuture::new(self).await;
}
#[allow(clippy::too_many_arguments)]
fn setup_half_duplex(
&self,
@ -3012,10 +3238,13 @@ impl PartialEq for Info {
unsafe impl Sync for Info {}
// TODO: this macro needs to move to one level up, and it needs to describe the
// hardware fully. The master module should extend it with the master specific
// details.
macro_rules! spi_instance {
($num:literal, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident $(, $sio2:ident, $sio3:ident)?) => {
paste::paste! {
impl Instance for crate::peripherals::[<SPI $num>] {
impl PeripheralInstance for crate::peripherals::[<SPI $num>] {
#[inline(always)]
fn info(&self) -> &'static Info {
static INFO: Info = Info {
@ -3069,7 +3298,7 @@ cfg_if::cfg_if! {
}
}
impl Instance for super::AnySpi {
impl PeripheralInstance for super::AnySpi {
delegate::delegate! {
to match &self.0 {
super::AnySpiInner::Spi2(spi) => spi,
@ -3082,3 +3311,104 @@ impl Instance for super::AnySpi {
}
impl QspiInstance for super::AnySpi {}
#[doc(hidden)]
pub struct State {
waker: AtomicWaker,
}
#[cfg_attr(place_spi_driver_in_ram, ram)]
fn handle_async<I: Instance>(instance: I) {
let state = instance.state();
let info = instance.info();
let driver = Driver { info, state };
if driver.is_transfer_done() {
state.waker.wake();
driver.stop_listening();
}
}
#[doc(hidden)]
pub trait Instance: PeripheralInstance {
fn state(&self) -> &'static State;
fn handler(&self) -> InterruptHandler;
}
macro_rules! master_instance {
($peri:ident) => {
impl Instance for $crate::peripherals::$peri {
fn state(&self) -> &'static State {
static STATE: State = State {
waker: AtomicWaker::new(),
};
&STATE
}
fn handler(&self) -> InterruptHandler {
#[$crate::macros::handler]
#[cfg_attr(place_spi_driver_in_ram, ram)]
fn handle() {
handle_async(unsafe { $crate::peripherals::$peri::steal() })
}
handle
}
}
};
}
master_instance!(SPI2);
#[cfg(spi3)]
master_instance!(SPI3);
impl Instance for super::AnySpi {
delegate::delegate! {
to match &self.0 {
super::AnySpiInner::Spi2(spi) => spi,
#[cfg(spi3)]
super::AnySpiInner::Spi3(spi) => spi,
} {
fn state(&self) -> &'static State;
fn handler(&self) -> InterruptHandler;
}
}
}
struct SpiFuture<'a> {
driver: &'a Driver,
}
impl<'a> SpiFuture<'a> {
pub fn new(driver: &'a Driver) -> Self {
Self { driver }
}
}
use core::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
impl Future for SpiFuture<'_> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.driver.is_transfer_done() {
self.driver.clear_done_interrupt();
return Poll::Ready(());
}
self.driver.state.waker.register(cx.waker());
self.driver.start_listening();
Poll::Pending
}
}
impl Drop for SpiFuture<'_> {
fn drop(&mut self) {
self.driver.stop_listening();
}
}

View File

@ -9,7 +9,6 @@
#![no_main]
use embedded_hal::spi::SpiBus;
#[cfg(pcnt)]
use embedded_hal_async::spi::SpiBus as SpiBusAsync;
use esp_hal::{
dma::{DmaDescriptor, DmaRxBuf, DmaTxBuf},
@ -107,6 +106,18 @@ mod tests {
assert_eq!(write, read);
}
#[test]
async fn test_async_symmetric_transfer(ctx: Context) {
let write = [0xde, 0xad, 0xbe, 0xef];
let mut read: [u8; 4] = [0x00u8; 4];
let mut spi = ctx.spi.into_async();
SpiBusAsync::transfer(&mut spi, &mut read[..], &write[..])
.await
.expect("Symmetric transfer failed");
assert_eq!(write, read);
}
#[test]
fn test_asymmetric_transfer(mut ctx: Context) {
let write = [0xde, 0xad, 0xbe, 0xef];
@ -118,6 +129,19 @@ mod tests {
assert_eq!(read[2], 0x00u8);
}
#[test]
async fn test_async_asymmetric_transfer(ctx: Context) {
let write = [0xde, 0xad, 0xbe, 0xef];
let mut read: [u8; 4] = [0x00; 4];
let mut spi = ctx.spi.into_async();
SpiBusAsync::transfer(&mut spi, &mut read[0..2], &write[..])
.await
.expect("Asymmetric transfer failed");
assert_eq!(write[0], read[0]);
assert_eq!(read[2], 0x00u8);
}
#[test]
#[cfg(pcnt)]
fn test_asymmetric_write(mut ctx: Context) {
@ -136,6 +160,50 @@ mod tests {
assert_eq!(unit.value(), 9);
}
#[test]
#[cfg(pcnt)]
async fn test_async_asymmetric_write(ctx: Context) {
let write = [0xde, 0xad, 0xbe, 0xef];
let unit = ctx.pcnt_unit;
unit.channel0.set_edge_signal(ctx.pcnt_source);
unit.channel0
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
let mut spi = ctx.spi.into_async();
SpiBusAsync::write(&mut spi, &write[..])
.await
.expect("Asymmetric write failed");
assert_eq!(unit.value(), 9);
}
#[test]
#[cfg(pcnt)]
async fn async_write_after_sync_write_waits_for_flush(ctx: Context) {
let write = [0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef];
let unit = ctx.pcnt_unit;
unit.channel0.set_edge_signal(ctx.pcnt_source);
unit.channel0
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
let mut spi = ctx.spi.into_async();
// Slow down SCLK so that transferring the buffer takes a while.
spi.apply_config(&Config::default().with_frequency(80.kHz()))
.expect("Apply config failed");
SpiBus::write(&mut spi, &write[..]).expect("Sync write failed");
SpiBusAsync::write(&mut spi, &write[..])
.await
.expect("Async write failed");
assert_eq!(unit.value(), 34);
}
#[test]
#[cfg(pcnt)]
fn test_asymmetric_write_transfer(mut ctx: Context) {
@ -154,21 +222,57 @@ mod tests {
assert_eq!(unit.value(), 9);
}
#[test]
#[cfg(pcnt)]
async fn test_async_asymmetric_write_transfer(ctx: Context) {
let write = [0xde, 0xad, 0xbe, 0xef];
let unit = ctx.pcnt_unit;
unit.channel0.set_edge_signal(ctx.pcnt_source);
unit.channel0
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
let mut spi = ctx.spi.into_async();
SpiBusAsync::transfer(&mut spi, &mut [], &write[..])
.await
.expect("Asymmetric transfer failed");
assert_eq!(unit.value(), 9);
}
#[test]
fn test_symmetric_transfer_huge_buffer(mut ctx: Context) {
let mut write = [0x55u8; 4096];
let write = &mut ctx.tx_buffer[0..4096];
for byte in 0..write.len() {
write[byte] = byte as u8;
}
let mut read = [0x00u8; 4096];
let read = &mut ctx.rx_buffer[0..4096];
SpiBus::transfer(&mut ctx.spi, &mut read[..], &write[..]).expect("Huge transfer failed");
assert_eq!(write, read);
}
#[test]
fn test_symmetric_transfer_huge_buffer_no_alloc(mut ctx: Context) {
let mut write = [0x55u8; 4096];
async fn test_async_symmetric_transfer_huge_buffer(ctx: Context) {
let write = &mut ctx.tx_buffer[0..4096];
for byte in 0..write.len() {
write[byte] = byte as u8;
}
let read = &mut ctx.rx_buffer[0..4096];
let mut spi = ctx.spi.into_async();
SpiBusAsync::transfer(&mut spi, &mut read[..], &write[..])
.await
.expect("Huge transfer failed");
for idx in 0..write.len() {
assert_eq!(write[idx], read[idx], "Mismatch at index {}", idx);
}
}
#[test]
fn test_symmetric_transfer_huge_buffer_in_place(mut ctx: Context) {
let write = &mut ctx.tx_buffer[0..4096];
for byte in 0..write.len() {
write[byte] = byte as u8;
}
@ -181,6 +285,22 @@ mod tests {
}
}
#[test]
async fn test_async_symmetric_transfer_huge_buffer_in_place(ctx: Context) {
let write = &mut ctx.tx_buffer[0..4096];
for byte in 0..write.len() {
write[byte] = byte as u8;
}
let mut spi = ctx.spi.into_async();
SpiBusAsync::transfer_in_place(&mut spi, &mut write[..])
.await
.expect("Huge transfer failed");
for byte in 0..write.len() {
assert_eq!(write[byte], byte as u8);
}
}
#[test]
#[cfg(pcnt)]
fn test_dma_read_dma_write_pcnt(ctx: Context) {