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:
parent
f990957f21
commit
2ca1545b50
@ -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)
|
- `BurstConfig`, a device-specific configuration for configuring DMA transfers in burst mode (#2543)
|
||||||
- `{DmaRxBuf, DmaTxBuf, DmaRxTxBuf}::set_burst_config` (#2543)
|
- `{DmaRxBuf, DmaTxBuf, DmaRxTxBuf}::set_burst_config` (#2543)
|
||||||
- ESP32-S2: DMA support for AES (#2699)
|
- ESP32-S2: DMA support for AES (#2699)
|
||||||
|
- Added `transfer_in_place_async` and embedded-hal-async implementation to `Spi` (#2691)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@ -71,17 +71,21 @@ use procmacros::ram;
|
|||||||
|
|
||||||
use super::{DmaError, Error, SpiBitOrder, SpiDataMode, SpiMode};
|
use super::{DmaError, Error, SpiBitOrder, SpiDataMode, SpiMode};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
asynch::AtomicWaker,
|
||||||
clock::Clocks,
|
clock::Clocks,
|
||||||
dma::{DmaChannelFor, DmaEligible, DmaRxBuffer, DmaTxBuffer, Rx, Tx},
|
dma::{DmaChannelFor, DmaEligible, DmaRxBuffer, DmaTxBuffer, Rx, Tx},
|
||||||
gpio::{interconnect::PeripheralOutput, InputSignal, NoPin, OutputSignal},
|
gpio::{interconnect::PeripheralOutput, InputSignal, NoPin, OutputSignal},
|
||||||
interrupt::InterruptHandler,
|
interrupt::InterruptHandler,
|
||||||
peripheral::{Peripheral, PeripheralRef},
|
peripheral::{Peripheral, PeripheralRef},
|
||||||
peripherals::spi2::RegisterBlock,
|
peripherals::spi2::RegisterBlock,
|
||||||
|
prelude::InterruptConfigurable,
|
||||||
private,
|
private,
|
||||||
|
private::Sealed,
|
||||||
spi::AnySpi,
|
spi::AnySpi,
|
||||||
system::PeripheralGuard,
|
system::PeripheralGuard,
|
||||||
Async,
|
Async,
|
||||||
Blocking,
|
Blocking,
|
||||||
|
Cpu,
|
||||||
Mode,
|
Mode,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -463,12 +467,18 @@ pub struct Spi<'d, Dm, T = AnySpi> {
|
|||||||
guard: PeripheralGuard,
|
guard: PeripheralGuard,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Dm: Mode, T: Instance> Sealed for Spi<'_, Dm, T> {}
|
||||||
|
|
||||||
impl<Dm, T> Spi<'_, Dm, T>
|
impl<Dm, T> Spi<'_, Dm, T>
|
||||||
where
|
where
|
||||||
T: Instance,
|
T: Instance,
|
||||||
|
Dm: Mode,
|
||||||
{
|
{
|
||||||
fn driver(&self) -> &'static Info {
|
fn driver(&self) -> Driver {
|
||||||
self.spi.info()
|
Driver {
|
||||||
|
info: self.spi.info(),
|
||||||
|
state: self.spi.state(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a byte from SPI.
|
/// Read a byte from SPI.
|
||||||
@ -502,7 +512,7 @@ where
|
|||||||
impl<'d> Spi<'d, Blocking> {
|
impl<'d> Spi<'d, Blocking> {
|
||||||
/// Constructs an SPI instance in 8bit dataframe mode.
|
/// Constructs an SPI instance in 8bit dataframe mode.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
spi: impl Peripheral<P = impl Instance> + 'd,
|
spi: impl Peripheral<P = impl PeripheralInstance> + 'd,
|
||||||
config: Config,
|
config: Config,
|
||||||
) -> Result<Self, ConfigError> {
|
) -> Result<Self, ConfigError> {
|
||||||
Self::new_typed(spi.map_into(), config)
|
Self::new_typed(spi.map_into(), config)
|
||||||
@ -514,7 +524,8 @@ where
|
|||||||
T: Instance,
|
T: Instance,
|
||||||
{
|
{
|
||||||
/// Converts the SPI instance into async mode.
|
/// 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 {
|
||||||
spi: self.spi,
|
spi: self.spi,
|
||||||
_mode: PhantomData,
|
_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>
|
impl<'d, T> Spi<'d, Async, T>
|
||||||
where
|
where
|
||||||
T: Instance,
|
T: Instance,
|
||||||
{
|
{
|
||||||
/// Converts the SPI instance into blocking mode.
|
/// Converts the SPI instance into blocking mode.
|
||||||
pub fn into_blocking(self) -> Spi<'d, Blocking, T> {
|
pub fn into_blocking(self) -> Spi<'d, Blocking, T> {
|
||||||
|
crate::interrupt::disable(Cpu::current(), self.driver().info.interrupt);
|
||||||
Spi {
|
Spi {
|
||||||
spi: self.spi,
|
spi: self.spi,
|
||||||
_mode: PhantomData,
|
_mode: PhantomData,
|
||||||
guard: self.guard,
|
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>
|
impl<'d, Dm, T> Spi<'d, Dm, T>
|
||||||
where
|
where
|
||||||
T: Instance,
|
T: Instance,
|
||||||
|
Dm: Mode,
|
||||||
{
|
{
|
||||||
/// Constructs an SPI instance in 8bit dataframe mode.
|
/// Constructs an SPI instance in 8bit dataframe mode.
|
||||||
pub fn new_typed(
|
pub fn new_typed(
|
||||||
@ -578,12 +629,12 @@ where
|
|||||||
.with_sck(NoPin)
|
.with_sck(NoPin)
|
||||||
.with_cs(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 {
|
if is_qspi {
|
||||||
unwrap!(this.driver().sio2_input).connect_to(NoPin);
|
unwrap!(this.driver().info.sio2_input).connect_to(NoPin);
|
||||||
unwrap!(this.driver().sio2_output).connect_to(NoPin);
|
unwrap!(this.driver().info.sio2_output).connect_to(NoPin);
|
||||||
unwrap!(this.driver().sio3_input).connect_to(NoPin);
|
unwrap!(this.driver().info.sio3_input).connect_to(NoPin);
|
||||||
unwrap!(this.driver().sio3_output).connect_to(NoPin);
|
unwrap!(this.driver().info.sio3_output).connect_to(NoPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(this)
|
Ok(this)
|
||||||
@ -598,8 +649,8 @@ where
|
|||||||
mosi.enable_output(true, private::Internal);
|
mosi.enable_output(true, private::Internal);
|
||||||
mosi.enable_input(true, private::Internal);
|
mosi.enable_input(true, private::Internal);
|
||||||
|
|
||||||
self.driver().mosi.connect_to(&mut mosi);
|
self.driver().info.mosi.connect_to(&mut mosi);
|
||||||
self.driver().sio0_input.connect_to(&mut mosi);
|
self.driver().info.sio0_input.connect_to(&mut mosi);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -613,8 +664,8 @@ where
|
|||||||
miso.enable_input(true, private::Internal);
|
miso.enable_input(true, private::Internal);
|
||||||
miso.enable_output(true, private::Internal);
|
miso.enable_output(true, private::Internal);
|
||||||
|
|
||||||
self.driver().miso.connect_to(&mut miso);
|
self.driver().info.miso.connect_to(&mut miso);
|
||||||
self.driver().sio1_output.connect_to(&mut miso);
|
self.driver().info.sio1_output.connect_to(&mut miso);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -626,7 +677,7 @@ where
|
|||||||
pub fn with_sck<SCK: PeripheralOutput>(self, sclk: impl Peripheral<P = SCK> + 'd) -> Self {
|
pub fn with_sck<SCK: PeripheralOutput>(self, sclk: impl Peripheral<P = SCK> + 'd) -> Self {
|
||||||
crate::into_mapped_ref!(sclk);
|
crate::into_mapped_ref!(sclk);
|
||||||
sclk.set_to_push_pull_output(private::Internal);
|
sclk.set_to_push_pull_output(private::Internal);
|
||||||
self.driver().sclk.connect_to(sclk);
|
self.driver().info.sclk.connect_to(sclk);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -639,7 +690,7 @@ where
|
|||||||
pub fn with_cs<CS: PeripheralOutput>(self, cs: impl Peripheral<P = CS> + 'd) -> Self {
|
pub fn with_cs<CS: PeripheralOutput>(self, cs: impl Peripheral<P = CS> + 'd) -> Self {
|
||||||
crate::into_mapped_ref!(cs);
|
crate::into_mapped_ref!(cs);
|
||||||
cs.set_to_push_pull_output(private::Internal);
|
cs.set_to_push_pull_output(private::Internal);
|
||||||
self.driver().cs.connect_to(cs);
|
self.driver().info.cs.connect_to(cs);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -667,7 +718,8 @@ where
|
|||||||
|
|
||||||
impl<'d, Dm, T> Spi<'d, Dm, T>
|
impl<'d, Dm, T> Spi<'d, Dm, T>
|
||||||
where
|
where
|
||||||
T: QspiInstance,
|
T: Instance + QspiInstance,
|
||||||
|
Dm: Mode,
|
||||||
{
|
{
|
||||||
/// Assign the SIO2 pin for the SPI instance.
|
/// Assign the SIO2 pin for the SPI instance.
|
||||||
///
|
///
|
||||||
@ -681,8 +733,8 @@ where
|
|||||||
sio2.enable_input(true, private::Internal);
|
sio2.enable_input(true, private::Internal);
|
||||||
sio2.enable_output(true, private::Internal);
|
sio2.enable_output(true, private::Internal);
|
||||||
|
|
||||||
unwrap!(self.driver().sio2_input).connect_to(&mut sio2);
|
unwrap!(self.driver().info.sio2_input).connect_to(&mut sio2);
|
||||||
unwrap!(self.driver().sio2_output).connect_to(&mut sio2);
|
unwrap!(self.driver().info.sio2_output).connect_to(&mut sio2);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -699,8 +751,8 @@ where
|
|||||||
sio3.enable_input(true, private::Internal);
|
sio3.enable_input(true, private::Internal);
|
||||||
sio3.enable_output(true, private::Internal);
|
sio3.enable_output(true, private::Internal);
|
||||||
|
|
||||||
unwrap!(self.driver().sio3_input).connect_to(&mut sio3);
|
unwrap!(self.driver().info.sio3_input).connect_to(&mut sio3);
|
||||||
unwrap!(self.driver().sio3_output).connect_to(&mut sio3);
|
unwrap!(self.driver().info.sio3_output).connect_to(&mut sio3);
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -709,6 +761,7 @@ where
|
|||||||
impl<Dm, T> Spi<'_, Dm, T>
|
impl<Dm, T> Spi<'_, Dm, T>
|
||||||
where
|
where
|
||||||
T: Instance,
|
T: Instance,
|
||||||
|
Dm: Mode,
|
||||||
{
|
{
|
||||||
/// Half-duplex read.
|
/// Half-duplex read.
|
||||||
#[instability::unstable]
|
#[instability::unstable]
|
||||||
@ -910,7 +963,7 @@ mod dma {
|
|||||||
///
|
///
|
||||||
/// Interrupts are not enabled at the peripheral level here.
|
/// Interrupts are not enabled at the peripheral level here.
|
||||||
fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
|
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() {
|
for core in crate::Cpu::other() {
|
||||||
crate::interrupt::disable(core, interrupt);
|
crate::interrupt::disable(core, interrupt);
|
||||||
}
|
}
|
||||||
@ -991,16 +1044,19 @@ mod dma {
|
|||||||
|
|
||||||
impl<'d, Dm, T> SpiDma<'d, Dm, T>
|
impl<'d, Dm, T> SpiDma<'d, Dm, T>
|
||||||
where
|
where
|
||||||
Dm: Mode,
|
|
||||||
T: Instance,
|
T: Instance,
|
||||||
|
Dm: Mode,
|
||||||
{
|
{
|
||||||
fn driver(&self) -> &'static Info {
|
fn driver(&self) -> Driver {
|
||||||
self.spi.info()
|
Driver {
|
||||||
|
info: self.spi.info(),
|
||||||
|
state: self.spi.state(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dma_driver(&self) -> DmaDriver {
|
fn dma_driver(&self) -> DmaDriver {
|
||||||
DmaDriver {
|
DmaDriver {
|
||||||
info: self.driver(),
|
driver: self.driver(),
|
||||||
dma_peripheral: self.spi.dma_peripheral(),
|
dma_peripheral: self.spi.dma_peripheral(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1522,7 +1578,6 @@ mod dma {
|
|||||||
pub struct SpiDmaBus<'d, Dm, T = AnySpi>
|
pub struct SpiDmaBus<'d, Dm, T = AnySpi>
|
||||||
where
|
where
|
||||||
T: Instance,
|
T: Instance,
|
||||||
|
|
||||||
Dm: Mode,
|
Dm: Mode,
|
||||||
{
|
{
|
||||||
spi_dma: SpiDma<'d, Dm, T>,
|
spi_dma: SpiDma<'d, Dm, T>,
|
||||||
@ -2070,6 +2125,7 @@ mod dma {
|
|||||||
|
|
||||||
mod ehal1 {
|
mod ehal1 {
|
||||||
use embedded_hal::spi::SpiBus;
|
use embedded_hal::spi::SpiBus;
|
||||||
|
use embedded_hal_async::spi::SpiBus as SpiBusAsync;
|
||||||
use embedded_hal_nb::spi::FullDuplex;
|
use embedded_hal_nb::spi::FullDuplex;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -2081,6 +2137,7 @@ mod ehal1 {
|
|||||||
impl<Dm, T> FullDuplex for Spi<'_, Dm, T>
|
impl<Dm, T> FullDuplex for Spi<'_, Dm, T>
|
||||||
where
|
where
|
||||||
T: Instance,
|
T: Instance,
|
||||||
|
Dm: Mode,
|
||||||
{
|
{
|
||||||
fn read(&mut self) -> nb::Result<u8, Self::Error> {
|
fn read(&mut self) -> nb::Result<u8, Self::Error> {
|
||||||
self.driver().read_byte()
|
self.driver().read_byte()
|
||||||
@ -2094,6 +2151,7 @@ mod ehal1 {
|
|||||||
impl<Dm, T> SpiBus for Spi<'_, Dm, T>
|
impl<Dm, T> SpiBus for Spi<'_, Dm, T>
|
||||||
where
|
where
|
||||||
T: Instance,
|
T: Instance,
|
||||||
|
Dm: Mode,
|
||||||
{
|
{
|
||||||
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
||||||
self.driver().read_bytes(words)
|
self.driver().read_bytes(words)
|
||||||
@ -2150,29 +2208,95 @@ mod ehal1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
|
||||||
// Since we have the traits so neatly implemented above, use them!
|
self.driver().transfer(words).map(|_| ())
|
||||||
for chunk in words.chunks_mut(FIFO_SIZE) {
|
|
||||||
SpiBus::write(self, chunk)?;
|
|
||||||
SpiBus::flush(self)?;
|
|
||||||
self.driver().read_bytes_from_fifo(chunk)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flush(&mut self) -> Result<(), Self::Error> {
|
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||||
self.driver().flush()
|
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.
|
/// 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.
|
/// Returns the peripheral data describing this SPI instance.
|
||||||
fn info(&self) -> &'static Info;
|
fn info(&self) -> &'static Info;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marker trait for QSPI-capable SPI peripherals.
|
/// Marker trait for QSPI-capable SPI peripherals.
|
||||||
pub trait QspiInstance: Instance {}
|
pub trait QspiInstance: PeripheralInstance {}
|
||||||
|
|
||||||
/// Peripheral data describing a particular SPI instance.
|
/// Peripheral data describing a particular SPI instance.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
@ -2220,14 +2344,14 @@ pub struct Info {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct DmaDriver {
|
struct DmaDriver {
|
||||||
info: &'static Info,
|
driver: Driver,
|
||||||
dma_peripheral: crate::dma::DmaPeripheral,
|
dma_peripheral: crate::dma::DmaPeripheral,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DmaDriver {
|
impl DmaDriver {
|
||||||
fn abort_transfer(&self) {
|
fn abort_transfer(&self) {
|
||||||
self.info.configure_datalen(1, 1);
|
self.driver.configure_datalen(1, 1);
|
||||||
self.info.update();
|
self.driver.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@ -2242,7 +2366,7 @@ impl DmaDriver {
|
|||||||
rx: &mut RX,
|
rx: &mut RX,
|
||||||
tx: &mut TX,
|
tx: &mut TX,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let reg_block = self.info.register_block();
|
let reg_block = self.driver.register_block();
|
||||||
|
|
||||||
#[cfg(esp32s2)]
|
#[cfg(esp32s2)]
|
||||||
{
|
{
|
||||||
@ -2251,7 +2375,7 @@ impl DmaDriver {
|
|||||||
reg_block.dma_in_link().write(|w| w.bits(0));
|
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
|
// enable the MISO and MOSI if needed
|
||||||
reg_block
|
reg_block
|
||||||
@ -2286,7 +2410,7 @@ impl DmaDriver {
|
|||||||
#[cfg(gdma)]
|
#[cfg(gdma)]
|
||||||
self.reset_dma();
|
self.reset_dma();
|
||||||
|
|
||||||
self.info.start_operation();
|
self.driver.start_operation();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -2295,7 +2419,7 @@ impl DmaDriver {
|
|||||||
#[cfg(gdma)]
|
#[cfg(gdma)]
|
||||||
{
|
{
|
||||||
// for non GDMA this is done in `assign_tx_device` / `assign_rx_device`
|
// 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| {
|
reg_block.dma_conf().modify(|_, w| {
|
||||||
w.dma_tx_ena().set_bit();
|
w.dma_tx_ena().set_bit();
|
||||||
w.dma_rx_ena().set_bit()
|
w.dma_rx_ena().set_bit()
|
||||||
@ -2321,7 +2445,7 @@ impl DmaDriver {
|
|||||||
w.dma_afifo_rst().bit(bit)
|
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, true);
|
||||||
set_reset_bit(reg_block, false);
|
set_reset_bit(reg_block, false);
|
||||||
self.clear_dma_interrupts();
|
self.clear_dma_interrupts();
|
||||||
@ -2329,7 +2453,7 @@ impl DmaDriver {
|
|||||||
|
|
||||||
#[cfg(gdma)]
|
#[cfg(gdma)]
|
||||||
fn clear_dma_interrupts(&self) {
|
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| {
|
reg_block.dma_int_clr().write(|w| {
|
||||||
w.dma_infifo_full_err().clear_bit_by_one();
|
w.dma_infifo_full_err().clear_bit_by_one();
|
||||||
w.dma_outfifo_empty_err().clear_bit_by_one();
|
w.dma_outfifo_empty_err().clear_bit_by_one();
|
||||||
@ -2341,7 +2465,7 @@ impl DmaDriver {
|
|||||||
|
|
||||||
#[cfg(pdma)]
|
#[cfg(pdma)]
|
||||||
fn clear_dma_interrupts(&self) {
|
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| {
|
reg_block.dma_int_clr().write(|w| {
|
||||||
w.inlink_dscr_empty().clear_bit_by_one();
|
w.inlink_dscr_empty().clear_bit_by_one();
|
||||||
w.outlink_dscr_error().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
|
// private implementation bits
|
||||||
// FIXME: split this up into peripheral versions
|
// FIXME: split this up into peripheral versions
|
||||||
impl Info {
|
impl Driver {
|
||||||
/// Returns the register block for this SPI instance.
|
/// Returns the register block for this SPI instance.
|
||||||
pub fn register_block(&self) -> &RegisterBlock {
|
pub fn register_block(&self) -> &RegisterBlock {
|
||||||
unsafe { &*self.register_block }
|
unsafe { &*self.info.register_block }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize for full-duplex 1 bit mode
|
/// Initialize for full-duplex 1 bit mode
|
||||||
@ -2731,37 +2860,15 @@ impl Info {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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`].
|
|
||||||
#[cfg_attr(place_spi_driver_in_ram, ram)]
|
#[cfg_attr(place_spi_driver_in_ram, ram)]
|
||||||
fn write_bytes(&self, words: &[u8]) -> Result<(), Error> {
|
fn fill_fifo(&self, chunk: &[u8]) {
|
||||||
let num_chunks = words.len() / FIFO_SIZE;
|
|
||||||
|
|
||||||
// Flush in case previous writes have not completed yet, required as per
|
|
||||||
// embedded-hal documentation (#1369).
|
|
||||||
self.flush()?;
|
|
||||||
|
|
||||||
// The fifo has a limited fixed size, so the data must be chunked and then
|
|
||||||
// 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`
|
// TODO: replace with `array_chunks` and `from_le_bytes`
|
||||||
let mut c_iter = chunk.chunks_exact(4);
|
let mut c_iter = chunk.chunks_exact(4);
|
||||||
let mut w_iter = self.register_block().w_iter();
|
let mut w_iter = self.register_block().w_iter();
|
||||||
for c in c_iter.by_ref() {
|
for c in c_iter.by_ref() {
|
||||||
if let Some(w_reg) = w_iter.next() {
|
if let Some(w_reg) = w_iter.next() {
|
||||||
let word = (c[0] as u32)
|
let word =
|
||||||
| (c[1] as u32) << 8
|
(c[0] as u32) | (c[1] as u32) << 8 | (c[2] as u32) << 16 | (c[3] as u32) << 24;
|
||||||
| (c[2] as u32) << 16
|
|
||||||
| (c[3] as u32) << 24;
|
|
||||||
w_reg.write(|w| w.buf().set(word));
|
w_reg.write(|w| w.buf().set(word));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2779,6 +2886,25 @@ impl Info {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write bytes to SPI.
|
||||||
|
///
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
// Flush in case previous writes have not completed yet, required as per
|
||||||
|
// embedded-hal documentation (#1369).
|
||||||
|
self.flush()?;
|
||||||
|
|
||||||
|
// The fifo has a limited fixed size, so the data must be chunked and then
|
||||||
|
// transmitted
|
||||||
|
for (i, chunk) in words.chunks(FIFO_SIZE).enumerate() {
|
||||||
|
self.configure_datalen(0, chunk.len());
|
||||||
|
self.fill_fifo(chunk);
|
||||||
|
|
||||||
self.start_operation();
|
self.start_operation();
|
||||||
|
|
||||||
// Wait for all chunks to complete except the last one.
|
// Wait for all chunks to complete except the last one.
|
||||||
@ -2791,6 +2917,19 @@ impl Info {
|
|||||||
Ok(())
|
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.
|
/// Read bytes from SPI.
|
||||||
///
|
///
|
||||||
/// Sends out a stuffing byte for every byte to read. This function doesn't
|
/// Sends out a stuffing byte for every byte to read. This function doesn't
|
||||||
@ -2808,6 +2947,22 @@ impl Info {
|
|||||||
Ok(())
|
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.
|
/// Read received bytes from SPI FIFO.
|
||||||
///
|
///
|
||||||
/// Copies the contents of the SPI receive FIFO into `words`. This function
|
/// Copies the contents of the SPI receive FIFO into `words`. This function
|
||||||
@ -2833,6 +2988,59 @@ impl Info {
|
|||||||
Ok(())
|
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 {
|
fn busy(&self) -> bool {
|
||||||
let reg_block = self.register_block();
|
let reg_block = self.register_block();
|
||||||
reg_block.cmd().read().usr().bit_is_set()
|
reg_block.cmd().read().usr().bit_is_set()
|
||||||
@ -2857,12 +3065,30 @@ impl Info {
|
|||||||
Ok(words)
|
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) {
|
fn start_operation(&self) {
|
||||||
let reg_block = self.register_block();
|
let reg_block = self.register_block();
|
||||||
self.update();
|
self.update();
|
||||||
reg_block.cmd().modify(|_, w| w.usr().set_bit());
|
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)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn setup_half_duplex(
|
fn setup_half_duplex(
|
||||||
&self,
|
&self,
|
||||||
@ -3012,10 +3238,13 @@ impl PartialEq for Info {
|
|||||||
|
|
||||||
unsafe impl Sync 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 {
|
macro_rules! spi_instance {
|
||||||
($num:literal, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident $(, $sio2:ident, $sio3:ident)?) => {
|
($num:literal, $sclk:ident, $mosi:ident, $miso:ident, $cs:ident $(, $sio2:ident, $sio3:ident)?) => {
|
||||||
paste::paste! {
|
paste::paste! {
|
||||||
impl Instance for crate::peripherals::[<SPI $num>] {
|
impl PeripheralInstance for crate::peripherals::[<SPI $num>] {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn info(&self) -> &'static Info {
|
fn info(&self) -> &'static Info {
|
||||||
static INFO: Info = 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! {
|
delegate::delegate! {
|
||||||
to match &self.0 {
|
to match &self.0 {
|
||||||
super::AnySpiInner::Spi2(spi) => spi,
|
super::AnySpiInner::Spi2(spi) => spi,
|
||||||
@ -3082,3 +3311,104 @@ impl Instance for super::AnySpi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl QspiInstance 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -9,7 +9,6 @@
|
|||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use embedded_hal::spi::SpiBus;
|
use embedded_hal::spi::SpiBus;
|
||||||
#[cfg(pcnt)]
|
|
||||||
use embedded_hal_async::spi::SpiBus as SpiBusAsync;
|
use embedded_hal_async::spi::SpiBus as SpiBusAsync;
|
||||||
use esp_hal::{
|
use esp_hal::{
|
||||||
dma::{DmaDescriptor, DmaRxBuf, DmaTxBuf},
|
dma::{DmaDescriptor, DmaRxBuf, DmaTxBuf},
|
||||||
@ -107,6 +106,18 @@ mod tests {
|
|||||||
assert_eq!(write, read);
|
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]
|
#[test]
|
||||||
fn test_asymmetric_transfer(mut ctx: Context) {
|
fn test_asymmetric_transfer(mut ctx: Context) {
|
||||||
let write = [0xde, 0xad, 0xbe, 0xef];
|
let write = [0xde, 0xad, 0xbe, 0xef];
|
||||||
@ -118,6 +129,19 @@ mod tests {
|
|||||||
assert_eq!(read[2], 0x00u8);
|
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]
|
#[test]
|
||||||
#[cfg(pcnt)]
|
#[cfg(pcnt)]
|
||||||
fn test_asymmetric_write(mut ctx: Context) {
|
fn test_asymmetric_write(mut ctx: Context) {
|
||||||
@ -136,6 +160,50 @@ mod tests {
|
|||||||
assert_eq!(unit.value(), 9);
|
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]
|
#[test]
|
||||||
#[cfg(pcnt)]
|
#[cfg(pcnt)]
|
||||||
fn test_asymmetric_write_transfer(mut ctx: Context) {
|
fn test_asymmetric_write_transfer(mut ctx: Context) {
|
||||||
@ -154,21 +222,57 @@ mod tests {
|
|||||||
assert_eq!(unit.value(), 9);
|
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]
|
#[test]
|
||||||
fn test_symmetric_transfer_huge_buffer(mut ctx: Context) {
|
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() {
|
for byte in 0..write.len() {
|
||||||
write[byte] = byte as u8;
|
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");
|
SpiBus::transfer(&mut ctx.spi, &mut read[..], &write[..]).expect("Huge transfer failed");
|
||||||
assert_eq!(write, read);
|
assert_eq!(write, read);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_symmetric_transfer_huge_buffer_no_alloc(mut ctx: Context) {
|
async fn test_async_symmetric_transfer_huge_buffer(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 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() {
|
for byte in 0..write.len() {
|
||||||
write[byte] = byte as u8;
|
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]
|
#[test]
|
||||||
#[cfg(pcnt)]
|
#[cfg(pcnt)]
|
||||||
fn test_dma_read_dma_write_pcnt(ctx: Context) {
|
fn test_dma_read_dma_write_pcnt(ctx: Context) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user