diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index c3e38928585..61c68eed5f6 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Introduce DMA buffer objects (#1856) +- Introduce DMA buffer objects (#1856, #1985) - Added new `Io::new_no_bind_interrupt` constructor (#1861) - Added touch pad support for esp32 (#1873, #1956) - Allow configuration of period updating method for MCPWM timers (#1898) @@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Peripheral driver constructors don't take `InterruptHandler`s anymore. Use `set_interrupt_handler` to explicitly set the interrupt handler now. (#1819) -- Migrate SPI driver to use DMA buffer objects (#1856) +- Migrate SPI driver to use DMA buffer objects (#1856, #1985) - Use the peripheral ref pattern for `OneShotTimer` and `PeriodicTimer` (#1855) - Improve SYSTIMER API (#1871) - DMA buffers now don't require a static lifetime. Make sure to never `mem::forget` an in-progress DMA transfer (consider using `#[deny(clippy::mem_forget)]`) (#1837) @@ -51,6 +51,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `AesFlavour` trait no longer has the `ENCRYPT_MODE`/`DECRYPT_MODE` associated constants (#1849) - Removed `FlashSafeDma` (#1856) - Remove redundant WithDmaSpi traits (#1975) +- `IsFullDuplex` and `IsHalfDuplex` traits (#1985) ## [0.19.0] - 2024-07-15 diff --git a/esp-hal/src/dma/mod.rs b/esp-hal/src/dma/mod.rs index 270f8236e45..c0cc6ca1181 100644 --- a/esp-hal/src/dma/mod.rs +++ b/esp-hal/src/dma/mod.rs @@ -16,7 +16,7 @@ #![doc = crate::before_snippet!()] //! # use esp_hal::dma_buffers; //! # use esp_hal::gpio::Io; -//! # use esp_hal::spi::{master::{Spi, prelude::*}, SpiMode}; +//! # use esp_hal::spi::{master::Spi, SpiMode}; //! # use esp_hal::dma::{Dma, DmaPriority}; //! # use crate::esp_hal::prelude::_fugit_RateExtU32; //! let dma = Dma::new(peripherals.DMA); diff --git a/esp-hal/src/spi/master.rs b/esp-hal/src/spi/master.rs index 1c479a532d2..ddbfa3250cf 100644 --- a/esp-hal/src/spi/master.rs +++ b/esp-hal/src/spi/master.rs @@ -58,6 +58,7 @@ use core::marker::PhantomData; +pub use dma::*; #[cfg(not(any(esp32, esp32s2)))] use enumset::EnumSet; #[cfg(not(any(esp32, esp32s2)))] @@ -67,12 +68,11 @@ use fugit::HertzU32; use procmacros::ram; use super::{ + DmaError, DuplexMode, Error, FullDuplexMode, HalfDuplexMode, - IsFullDuplex, - IsHalfDuplex, SpiBitOrder, SpiDataMode, SpiMode, @@ -88,17 +88,10 @@ use crate::{ system::PeripheralClockControl, }; -/// Prelude for the SPI (Master) driver -pub mod prelude { - pub use super::{ - Instance as _esp_hal_spi_master_Instance, - InstanceDma as _esp_hal_spi_master_InstanceDma, - }; -} - /// Enumeration of possible SPI interrupt events. #[cfg(not(any(esp32, esp32s2)))] #[derive(EnumSetType)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SpiInterrupt { /// Indicates that the SPI transaction has completed successfully. /// @@ -123,6 +116,8 @@ const MAX_DMA_SIZE: usize = 32736; /// /// Used to define specific commands sent over the SPI bus. /// Can be [Command::None] if command phase should be suppressed. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Command { /// No command is sent. None, @@ -236,6 +231,8 @@ impl Command { /// /// This can be used to specify the address phase of SPI transactions. /// Can be [Address::None] if address phase should be suppressed. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Address { /// No address phase. None, @@ -458,10 +455,9 @@ pub struct Spi<'d, T, M> { _mode: PhantomData, } -impl<'d, T, M> Spi<'d, T, M> +impl<'d, T> Spi<'d, T, FullDuplexMode> where T: Instance, - M: IsFullDuplex, { /// Read bytes from SPI. /// @@ -854,10 +850,9 @@ where } } -impl HalfDuplexReadWrite for Spi<'_, T, M> +impl HalfDuplexReadWrite for Spi<'_, T, HalfDuplexMode> where T: Instance, - M: IsHalfDuplex, { type Error = Error; @@ -902,10 +897,9 @@ where } #[cfg(feature = "embedded-hal-02")] -impl embedded_hal_02::spi::FullDuplex for Spi<'_, T, M> +impl embedded_hal_02::spi::FullDuplex for Spi<'_, T, FullDuplexMode> where T: Instance, - M: IsFullDuplex, { type Error = Error; @@ -919,10 +913,9 @@ where } #[cfg(feature = "embedded-hal-02")] -impl embedded_hal_02::blocking::spi::Transfer for Spi<'_, T, M> +impl embedded_hal_02::blocking::spi::Transfer for Spi<'_, T, FullDuplexMode> where T: Instance, - M: IsFullDuplex, { type Error = Error; @@ -932,10 +925,9 @@ where } #[cfg(feature = "embedded-hal-02")] -impl embedded_hal_02::blocking::spi::Write for Spi<'_, T, M> +impl embedded_hal_02::blocking::spi::Write for Spi<'_, T, FullDuplexMode> where T: Instance, - M: IsFullDuplex, { type Error = Error; @@ -945,8 +937,7 @@ where } } -/// DMA (Direct Memory Access) funtionality (Master). -pub mod dma { +mod dma { use core::{ cmp::min, sync::atomic::{fence, Ordering}, @@ -968,7 +959,6 @@ pub mod dma { SpiPeripheral, TxPrivate, }, - Blocking, InterruptConfigurable, Mode, }; @@ -1031,24 +1021,30 @@ pub mod dma { } /// A DMA capable SPI instance. - pub struct SpiDma<'d, T, C, M, DmaMode> + /// + /// Using `SpiDma` is not recommended unless you wish + /// to manage buffers yourself. It's recommended to use + /// [`SpiDmaBus`] via `with_buffers` to get access + /// to a DMA capable SPI bus that implements the + /// embedded-hal traits. + pub struct SpiDma<'d, T, C, D, M> where C: DmaChannel, C::P: SpiPeripheral, - M: DuplexMode, - DmaMode: Mode, + D: DuplexMode, + M: Mode, { pub(crate) spi: PeripheralRef<'d, T>, - pub(crate) channel: Channel<'d, C, DmaMode>, - _mode: PhantomData, + pub(crate) channel: Channel<'d, C, M>, + _mode: PhantomData, } - impl<'d, T, C, M, DmaMode> core::fmt::Debug for SpiDma<'d, T, C, M, DmaMode> + impl<'d, T, C, D, M> core::fmt::Debug for SpiDma<'d, T, C, D, M> where C: DmaChannel, C::P: SpiPeripheral, - M: DuplexMode, - DmaMode: Mode, + D: DuplexMode, + M: Mode, { /// Formats the `SpiDma` instance for debugging purposes. /// @@ -1059,13 +1055,13 @@ pub mod dma { } } - impl<'d, T, C, M, DmaMode> SpiDma<'d, T, C, M, DmaMode> + impl<'d, T, C, D, M> SpiDma<'d, T, C, D, M> where T: InstanceDma, C: DmaChannel, C::P: SpiPeripheral, - M: DuplexMode, - DmaMode: Mode, + D: DuplexMode, + M: Mode, { /// Sets the interrupt handler /// @@ -1099,23 +1095,23 @@ pub mod dma { } } - impl<'d, T, C, M, DmaMode> crate::private::Sealed for SpiDma<'d, T, C, M, DmaMode> + impl<'d, T, C, D, M> crate::private::Sealed for SpiDma<'d, T, C, D, M> where T: InstanceDma, C: DmaChannel, C::P: SpiPeripheral, - M: DuplexMode, - DmaMode: Mode, + D: DuplexMode, + M: Mode, { } - impl<'d, T, C, M, DmaMode> InterruptConfigurable for SpiDma<'d, T, C, M, DmaMode> + impl<'d, T, C, D, M> InterruptConfigurable for SpiDma<'d, T, C, D, M> where T: InstanceDma, C: DmaChannel, C::P: SpiPeripheral, - M: DuplexMode, - DmaMode: Mode, + D: DuplexMode, + M: Mode, { /// Configures the interrupt handler for the DMA-enabled SPI instance. fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { @@ -1123,13 +1119,13 @@ pub mod dma { } } - impl<'d, T, C, M, DmaMode> SpiDma<'d, T, C, M, DmaMode> + impl<'d, T, C, D, M> SpiDma<'d, T, C, D, M> where T: InstanceDma, C: DmaChannel, C::P: SpiPeripheral, - M: DuplexMode, - DmaMode: Mode, + D: DuplexMode, + M: Mode, { /// Changes the SPI bus frequency for the DMA-enabled SPI instance. pub fn change_bus_frequency(&mut self, frequency: HertzU32, clocks: &Clocks<'d>) { @@ -1137,11 +1133,13 @@ pub mod dma { } } - impl<'d, T, C> SpiDma<'d, T, C, FullDuplexMode, Blocking> + impl<'d, T, C, D, M> SpiDma<'d, T, C, D, M> where T: InstanceDma, C: DmaChannel, C::P: SpiPeripheral, + D: DuplexMode, + M: Mode, { /// Configures the DMA buffers for the SPI instance. /// @@ -1152,44 +1150,23 @@ pub mod dma { self, dma_tx_buf: DmaTxBuf, dma_rx_buf: DmaRxBuf, - ) -> SpiDmaBus<'d, T, C> { + ) -> SpiDmaBus<'d, T, C, D, M> { SpiDmaBus::new(self, dma_tx_buf, dma_rx_buf) } } - #[cfg(feature = "async")] - impl<'d, T, C> SpiDma<'d, T, C, FullDuplexMode, crate::Async> - where - T: InstanceDma, - C: DmaChannel, - C::P: SpiPeripheral, - { - /// Configures the DMA buffers for asynchronous SPI communication. - /// - /// This method sets up both TX and RX buffers for DMA transfers. - /// It eturns an instance of `SpiDmaAsyncBus` to be used for - /// asynchronous SPI operations. - pub fn with_buffers( - self, - dma_tx_buf: DmaTxBuf, - dma_rx_buf: DmaRxBuf, - ) -> asynch::SpiDmaAsyncBus<'d, T, C> { - asynch::SpiDmaAsyncBus::new(self, dma_tx_buf, dma_rx_buf) - } - } - /// A structure representing a DMA transfer for SPI. /// /// This structure holds references to the SPI instance, DMA buffers, and /// transfer status. - pub struct SpiDmaTransfer<'d, T, C, M, DmaMode, Buf> + pub struct SpiDmaTransfer<'d, T, C, D, M, Buf> where C: DmaChannel, C::P: SpiPeripheral, - M: DuplexMode, - DmaMode: Mode, + D: DuplexMode, + M: Mode, { - spi_dma: SpiDma<'d, T, C, M, DmaMode>, + spi_dma: SpiDma<'d, T, C, D, M>, dma_buf: Buf, is_rx: bool, is_tx: bool, @@ -1198,20 +1175,15 @@ pub mod dma { tx_future_awaited: bool, } - impl<'d, T, C, M, DmaMode, Buf> SpiDmaTransfer<'d, T, C, M, DmaMode, Buf> + impl<'d, T, C, D, M, Buf> SpiDmaTransfer<'d, T, C, D, M, Buf> where T: Instance, C: DmaChannel, C::P: SpiPeripheral, - M: DuplexMode, - DmaMode: Mode, + D: DuplexMode, + M: Mode, { - fn new( - spi_dma: SpiDma<'d, T, C, M, DmaMode>, - dma_buf: Buf, - is_rx: bool, - is_tx: bool, - ) -> Self { + fn new(spi_dma: SpiDma<'d, T, C, D, M>, dma_buf: Buf, is_rx: bool, is_tx: bool) -> Self { Self { spi_dma, dma_buf, @@ -1254,7 +1226,7 @@ pub mod dma { /// /// This method blocks until the transfer is finished and returns the /// `SpiDma` instance and the associated buffer. - pub fn wait(mut self) -> (SpiDma<'d, T, C, M, DmaMode>, Buf) { + pub fn wait(mut self) -> (SpiDma<'d, T, C, D, M>, Buf) { self.spi_dma.spi.flush().ok(); fence(Ordering::Acquire); (self.spi_dma, self.dma_buf) @@ -1262,12 +1234,12 @@ pub mod dma { } #[cfg(feature = "async")] - impl<'d, T, C, M, Buf> SpiDmaTransfer<'d, T, C, M, crate::Async, Buf> + impl<'d, T, C, D, Buf> SpiDmaTransfer<'d, T, C, D, crate::Async, Buf> where T: Instance, C: DmaChannel, C::P: SpiPeripheral, - M: DuplexMode, + D: DuplexMode, { /// Waits for the DMA transfer to complete asynchronously. /// @@ -1287,13 +1259,12 @@ pub mod dma { } } - impl<'d, T, C, M, DmaMode> SpiDma<'d, T, C, M, DmaMode> + impl<'d, T, C, M> SpiDma<'d, T, C, FullDuplexMode, M> where T: InstanceDma, C: DmaChannel, C::P: SpiPeripheral, - M: IsFullDuplex, - DmaMode: Mode, + M: Mode, { /// Perform a DMA write. /// @@ -1305,7 +1276,7 @@ pub mod dma { pub fn dma_write( mut self, buffer: DmaTxBuf, - ) -> Result, (Error, Self, DmaTxBuf)> + ) -> Result, (Error, Self, DmaTxBuf)> { let bytes_to_write = buffer.len(); if bytes_to_write > MAX_DMA_SIZE { @@ -1333,7 +1304,7 @@ pub mod dma { pub fn dma_read( mut self, buffer: DmaRxBuf, - ) -> Result, (Error, Self, DmaRxBuf)> + ) -> Result, (Error, Self, DmaRxBuf)> { let bytes_to_read = buffer.len(); if bytes_to_read > MAX_DMA_SIZE { @@ -1362,7 +1333,7 @@ pub mod dma { tx_buffer: DmaTxBuf, rx_buffer: DmaRxBuf, ) -> Result< - SpiDmaTransfer<'d, T, C, M, DmaMode, (DmaTxBuf, DmaRxBuf)>, + SpiDmaTransfer<'d, T, C, FullDuplexMode, M, (DmaTxBuf, DmaRxBuf)>, (Error, Self, DmaTxBuf, DmaRxBuf), > { let bytes_to_write = tx_buffer.len(); @@ -1400,13 +1371,12 @@ pub mod dma { } } - impl<'d, T, C, M, DmaMode> SpiDma<'d, T, C, M, DmaMode> + impl<'d, T, C, M> SpiDma<'d, T, C, HalfDuplexMode, M> where T: InstanceDma, C: DmaChannel, C::P: SpiPeripheral, - M: IsHalfDuplex, - DmaMode: Mode, + M: Mode, { /// Perform a half-duplex read operation using DMA. #[allow(clippy::type_complexity)] @@ -1418,7 +1388,7 @@ pub mod dma { address: Address, dummy: u8, buffer: DmaRxBuf, - ) -> Result, (Error, Self, DmaRxBuf)> + ) -> Result, (Error, Self, DmaRxBuf)> { let bytes_to_read = buffer.len(); if bytes_to_read > MAX_DMA_SIZE { @@ -1496,7 +1466,7 @@ pub mod dma { address: Address, dummy: u8, buffer: DmaTxBuf, - ) -> Result, (Error, Self, DmaTxBuf)> + ) -> Result, (Error, Self, DmaTxBuf)> { let bytes_to_write = buffer.len(); if bytes_to_write > MAX_DMA_SIZE { @@ -1565,96 +1535,135 @@ pub mod dma { } } - /// A DMA-capable SPI bus that handles full-duplex transfers. + #[derive(Default)] + enum State<'d, T, C, D, M> + where + T: InstanceDma, + C: DmaChannel, + C::P: SpiPeripheral, + D: DuplexMode, + M: Mode, + { + Idle(SpiDma<'d, T, C, D, M>, DmaTxBuf, DmaRxBuf), + Reading(SpiDmaTransfer<'d, T, C, D, M, DmaRxBuf>, DmaTxBuf), + Writing(SpiDmaTransfer<'d, T, C, D, M, DmaTxBuf>, DmaRxBuf), + Transferring(SpiDmaTransfer<'d, T, C, D, M, (DmaTxBuf, DmaRxBuf)>), + #[default] + TemporarilyRemoved, + } + + /// A DMA-capable SPI bus. /// /// This structure is responsible for managing SPI transfers using DMA /// buffers. - pub struct SpiDmaBus<'d, T, C> + pub struct SpiDmaBus<'d, T, C, D, M> where T: InstanceDma, C: DmaChannel, C::P: SpiPeripheral, + D: DuplexMode, + M: Mode, { - spi_dma: Option>, - buffers: Option<(DmaTxBuf, DmaRxBuf)>, + state: State<'d, T, C, D, M>, } - impl<'d, T, C> SpiDmaBus<'d, T, C> + impl<'d, T, C, D, M> SpiDmaBus<'d, T, C, D, M> where T: InstanceDma, C: DmaChannel, C::P: SpiPeripheral, + D: DuplexMode, + M: Mode, { /// Creates a new `SpiDmaBus` with the specified SPI instance and DMA /// buffers. pub fn new( - spi_dma: SpiDma<'d, T, C, FullDuplexMode, crate::Blocking>, - tx_buffer: DmaTxBuf, - rx_buffer: DmaRxBuf, + spi: SpiDma<'d, T, C, D, M>, + dma_tx_buf: DmaTxBuf, + dma_rx_buf: DmaRxBuf, ) -> Self { Self { - spi_dma: Some(spi_dma), - buffers: Some((tx_buffer, rx_buffer)), + state: State::Idle(spi, dma_tx_buf, dma_rx_buf), + } + } + + fn wait_for_idle(&mut self) -> (SpiDma<'d, T, C, D, M>, DmaTxBuf, DmaRxBuf) { + match core::mem::take(&mut self.state) { + State::Idle(spi, tx_buf, rx_buf) => (spi, tx_buf, rx_buf), + State::Reading(transfer, tx_buf) => { + let (spi, rx_buf) = transfer.wait(); + (spi, tx_buf, rx_buf) + } + State::Writing(transfer, rx_buf) => { + let (spi, tx_buf) = transfer.wait(); + (spi, tx_buf, rx_buf) + } + State::Transferring(transfer) => { + let (spi, (tx_buf, rx_buf)) = transfer.wait(); + (spi, tx_buf, rx_buf) + } + State::TemporarilyRemoved => unreachable!(), } } + } + impl<'d, T, C, M> SpiDmaBus<'d, T, C, FullDuplexMode, M> + where + T: InstanceDma, + C: DmaChannel, + C::P: SpiPeripheral, + M: Mode, + { /// Reads data from the SPI bus using DMA. pub fn read(&mut self, words: &mut [u8]) -> Result<(), Error> { - let mut spi_dma = self.spi_dma.take().unwrap(); - let (tx_buf, mut rx_buf) = self.buffers.take().unwrap(); + let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle(); for chunk in words.chunks_mut(rx_buf.capacity()) { rx_buf.set_length(chunk.len()); - let transfer = match spi_dma.dma_read(rx_buf) { - Ok(transfer) => transfer, + match spi_dma.dma_read(rx_buf) { + Ok(transfer) => self.state = State::Reading(transfer, tx_buf), Err((e, spi, rx)) => { - self.spi_dma = Some(spi); - self.buffers = Some((tx_buf, rx)); + self.state = State::Idle(spi, tx_buf, rx); return Err(e); } - }; - (spi_dma, rx_buf) = transfer.wait(); + } + (spi_dma, tx_buf, rx_buf) = self.wait_for_idle(); let bytes_read = rx_buf.read_received_data(chunk); debug_assert_eq!(bytes_read, chunk.len()); } - self.spi_dma = Some(spi_dma); - self.buffers = Some((tx_buf, rx_buf)); + self.state = State::Idle(spi_dma, tx_buf, rx_buf); Ok(()) } /// Writes data to the SPI bus using DMA. pub fn write(&mut self, words: &[u8]) -> Result<(), Error> { - let mut spi_dma = self.spi_dma.take().unwrap(); - let (mut tx_buf, rx_buf) = self.buffers.take().unwrap(); + let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle(); for chunk in words.chunks(tx_buf.capacity()) { tx_buf.fill(chunk); - let transfer = match spi_dma.dma_write(tx_buf) { - Ok(transfer) => transfer, + match spi_dma.dma_write(tx_buf) { + Ok(transfer) => self.state = State::Writing(transfer, rx_buf), Err((e, spi, tx)) => { - self.spi_dma = Some(spi); - self.buffers = Some((tx, rx_buf)); + self.state = State::Idle(spi, tx, rx_buf); return Err(e); } - }; - (spi_dma, tx_buf) = transfer.wait(); + } + (spi_dma, tx_buf, rx_buf) = self.wait_for_idle(); } - self.spi_dma = Some(spi_dma); - self.buffers = Some((tx_buf, rx_buf)); + self.state = State::Idle(spi_dma, tx_buf, rx_buf); Ok(()) } /// Transfers data to and from the SPI bus simultaneously using DMA. pub fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { - let mut spi_dma = self.spi_dma.take().unwrap(); - let (mut tx_buf, mut rx_buf) = self.buffers.take().unwrap(); + let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle(); let chunk_size = min(tx_buf.capacity(), rx_buf.capacity()); @@ -1669,22 +1678,20 @@ pub mod dma { tx_buf.fill(write_chunk); rx_buf.set_length(read_chunk.len()); - let transfer = match spi_dma.dma_transfer(tx_buf, rx_buf) { - Ok(transfer) => transfer, + match spi_dma.dma_transfer(tx_buf, rx_buf) { + Ok(transfer) => self.state = State::Transferring(transfer), Err((e, spi, tx, rx)) => { - self.spi_dma = Some(spi); - self.buffers = Some((tx, rx)); + self.state = State::Idle(spi, tx, rx); return Err(e); } - }; - (spi_dma, (tx_buf, rx_buf)) = transfer.wait(); + } + (spi_dma, tx_buf, rx_buf) = self.wait_for_idle(); let bytes_read = rx_buf.read_received_data(read_chunk); debug_assert_eq!(bytes_read, read_chunk.len()); } - self.spi_dma = Some(spi_dma); - self.buffers = Some((tx_buf, rx_buf)); + self.state = State::Idle(spi_dma, tx_buf, rx_buf); if !read_remainder.is_empty() { self.read(read_remainder) @@ -1697,8 +1704,7 @@ pub mod dma { /// Transfers data in place on the SPI bus using DMA. pub fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { - let mut spi_dma = self.spi_dma.take().unwrap(); - let (mut tx_buf, mut rx_buf) = self.buffers.take().unwrap(); + let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle(); let chunk_size = min(tx_buf.capacity(), rx_buf.capacity()); @@ -1706,29 +1712,100 @@ pub mod dma { tx_buf.fill(chunk); rx_buf.set_length(chunk.len()); - let transfer = match spi_dma.dma_transfer(tx_buf, rx_buf) { - Ok(transfer) => transfer, + match spi_dma.dma_transfer(tx_buf, rx_buf) { + Ok(transfer) => self.state = State::Transferring(transfer), Err((e, spi, tx, rx)) => { - self.spi_dma = Some(spi); - self.buffers = Some((tx, rx)); + self.state = State::Idle(spi, tx, rx); return Err(e); } - }; - (spi_dma, (tx_buf, rx_buf)) = transfer.wait(); + } + (spi_dma, tx_buf, rx_buf) = self.wait_for_idle(); let bytes_read = rx_buf.read_received_data(chunk); debug_assert_eq!(bytes_read, chunk.len()); } - self.spi_dma = Some(spi_dma); - self.buffers = Some((tx_buf, rx_buf)); + self.state = State::Idle(spi_dma, tx_buf, rx_buf); + Ok(()) + } + } + + impl<'d, T, C, M> HalfDuplexReadWrite for SpiDmaBus<'d, T, C, HalfDuplexMode, M> + where + T: InstanceDma, + C: DmaChannel, + C::P: SpiPeripheral, + M: Mode, + { + type Error = super::Error; + + /// Half-duplex read. + fn read( + &mut self, + data_mode: SpiDataMode, + cmd: Command, + address: Address, + dummy: u8, + buffer: &mut [u8], + ) -> Result<(), Self::Error> { + let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle(); + if buffer.len() > rx_buf.capacity() { + return Err(super::Error::DmaError(DmaError::Overflow)); + } + + rx_buf.set_length(buffer.len()); + + match spi_dma.read(data_mode, cmd, address, dummy, rx_buf) { + Ok(transfer) => self.state = State::Reading(transfer, tx_buf), + Err((e, spi, rx)) => { + self.state = State::Idle(spi, tx_buf, rx); + return Err(e); + } + } + (spi_dma, tx_buf, rx_buf) = self.wait_for_idle(); + + let bytes_read = rx_buf.read_received_data(buffer); + debug_assert_eq!(bytes_read, buffer.len()); + + self.state = State::Idle(spi_dma, tx_buf, rx_buf); + + Ok(()) + } + + /// Half-duplex write. + fn write( + &mut self, + data_mode: SpiDataMode, + cmd: Command, + address: Address, + dummy: u8, + buffer: &[u8], + ) -> Result<(), Self::Error> { + let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle(); + if buffer.len() > tx_buf.capacity() { + return Err(super::Error::DmaError(DmaError::Overflow)); + } + + tx_buf.fill(buffer); + + match spi_dma.write(data_mode, cmd, address, dummy, tx_buf) { + Ok(transfer) => self.state = State::Writing(transfer, rx_buf), + Err((e, spi, tx)) => { + self.state = State::Idle(spi, tx, rx_buf); + return Err(e); + } + } + (spi_dma, tx_buf, rx_buf) = self.wait_for_idle(); + + self.state = State::Idle(spi_dma, tx_buf, rx_buf); Ok(()) } } #[cfg(feature = "embedded-hal-02")] - impl<'d, T, C> embedded_hal_02::blocking::spi::Transfer for SpiDmaBus<'d, T, C> + impl<'d, T, C> embedded_hal_02::blocking::spi::Transfer + for SpiDmaBus<'d, T, C, FullDuplexMode, crate::Blocking> where T: InstanceDma, C: DmaChannel, @@ -1743,7 +1820,8 @@ pub mod dma { } #[cfg(feature = "embedded-hal-02")] - impl<'d, T, C> embedded_hal_02::blocking::spi::Write for SpiDmaBus<'d, T, C> + impl<'d, T, C> embedded_hal_02::blocking::spi::Write + for SpiDmaBus<'d, T, C, FullDuplexMode, crate::Blocking> where T: InstanceDma, C: DmaChannel, @@ -1759,76 +1837,18 @@ pub mod dma { /// Async functionality #[cfg(feature = "async")] - pub mod asynch { + mod asynch { use core::{cmp::min, mem::take}; - use embedded_hal::spi::ErrorType; - use super::*; - #[derive(Default)] - enum State<'d, T, C> + impl<'d, T, C> SpiDmaBus<'d, T, C, FullDuplexMode, crate::Async> where T: InstanceDma, C: DmaChannel, C::P: SpiPeripheral, { - Idle( - SpiDma<'d, T, C, FullDuplexMode, crate::Async>, - DmaTxBuf, - DmaRxBuf, - ), - Reading( - SpiDmaTransfer<'d, T, C, FullDuplexMode, crate::Async, DmaRxBuf>, - DmaTxBuf, - ), - Writing( - SpiDmaTransfer<'d, T, C, FullDuplexMode, crate::Async, DmaTxBuf>, - DmaRxBuf, - ), - Transferring( - SpiDmaTransfer<'d, T, C, FullDuplexMode, crate::Async, (DmaTxBuf, DmaRxBuf)>, - ), - #[default] - InUse, - } - - /// An asynchronous DMA-capable SPI bus for full-duplex operations. - /// - /// This struct provides an interface for SPI operations using DMA in an - /// asynchronous way. - pub struct SpiDmaAsyncBus<'d, T, C> - where - T: InstanceDma, - C: DmaChannel, - C::P: SpiPeripheral, - { - state: State<'d, T, C>, - } - - impl<'d, T, C> SpiDmaAsyncBus<'d, T, C> - where - T: InstanceDma, - C: DmaChannel, - C::P: SpiPeripheral, - { - /// Creates a new asynchronous DMA SPI bus instance. - /// - /// Initializes the bus with the provided SPI instance and DMA - /// buffers for transmit and receive operations. - pub fn new( - spi: SpiDma<'d, T, C, FullDuplexMode, crate::Async>, - dma_tx_buf: DmaTxBuf, - dma_rx_buf: DmaRxBuf, - ) -> Self { - Self { - state: State::Idle(spi, dma_tx_buf, dma_rx_buf), - } - } - - /// Waits for the current SPI DMA transfer to complete, ensuring the - /// bus is idle. - async fn wait_for_idle( + async fn wait_for_idle_async( &mut self, ) -> ( SpiDma<'d, T, C, FullDuplexMode, crate::Async>, @@ -1840,7 +1860,7 @@ pub mod dma { State::Reading(transfer, _) => transfer.wait_for_done().await, State::Writing(transfer, _) => transfer.wait_for_done().await, State::Transferring(transfer) => transfer.wait_for_done().await, - State::InUse => unreachable!(), + State::TemporarilyRemoved => unreachable!(), } match take(&mut self.state) { State::Idle(spi, tx_buf, rx_buf) => (spi, tx_buf, rx_buf), @@ -1856,31 +1876,14 @@ pub mod dma { let (spi, (tx_buf, rx_buf)) = transfer.wait(); (spi, tx_buf, rx_buf) } - State::InUse => unreachable!(), + State::TemporarilyRemoved => unreachable!(), } } - } - impl<'d, T, C> ErrorType for SpiDmaAsyncBus<'d, T, C> - where - T: InstanceDma, - C: DmaChannel, - C::P: SpiPeripheral, - { - type Error = Error; - } - - impl<'d, T, C> embedded_hal_async::spi::SpiBus for SpiDmaAsyncBus<'d, T, C> - where - T: InstanceDma, - C: DmaChannel, - C::P: SpiPeripheral, - { - /// Asynchronously reads data from the SPI bus into the provided - /// buffer. - async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + /// Fill the given buffer with data from the bus. + pub async fn read_async(&mut self, words: &mut [u8]) -> Result<(), super::Error> { // Get previous transfer. - let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle().await; + let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle_async().await; for chunk in words.chunks_mut(rx_buf.capacity()) { rx_buf.set_length(chunk.len()); @@ -1895,17 +1898,7 @@ pub mod dma { } }; - match &mut self.state { - State::Reading(transfer, _) => transfer.wait_for_done().await, - _ => unreachable!(), - }; - (spi_dma, tx_buf, rx_buf) = match take(&mut self.state) { - State::Reading(transfer, tx_buf) => { - let (spi, rx_buf) = transfer.wait(); - (spi, tx_buf, rx_buf) - } - _ => unreachable!(), - }; + (spi_dma, tx_buf, rx_buf) = self.wait_for_idle_async().await; let bytes_read = rx_buf.read_received_data(chunk); debug_assert_eq!(bytes_read, chunk.len()); @@ -1916,11 +1909,10 @@ pub mod dma { Ok(()) } - /// Asynchronously writes data to the SPI bus from the provided - /// buffer. - async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + /// Transmit the given buffer to the bus. + pub async fn write_async(&mut self, words: &[u8]) -> Result<(), super::Error> { // Get previous transfer. - let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle().await; + let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle_async().await; for chunk in words.chunks(tx_buf.capacity()) { tx_buf.fill(chunk); @@ -1935,18 +1927,7 @@ pub mod dma { } }; - match &mut self.state { - State::Writing(transfer, _) => transfer.wait_for_done().await, - _ => unreachable!(), - }; - - (spi_dma, tx_buf, rx_buf) = match take(&mut self.state) { - State::Writing(transfer, rx_buf) => { - let (spi, tx_buf) = transfer.wait(); - (spi, tx_buf, rx_buf) - } - _ => unreachable!(), - }; + (spi_dma, tx_buf, rx_buf) = self.wait_for_idle_async().await; } self.state = State::Idle(spi_dma, tx_buf, rx_buf); @@ -1954,15 +1935,15 @@ pub mod dma { Ok(()) } - /// Asynchronously performs a full-duplex transfer over the SPI bus. - /// - /// This method splits the transfer operation into chunks and - /// processes it asynchronously. It simultaneously - /// writes data from the `write` buffer and reads data into the - /// `read` buffer. - async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + /// Transfer by writing out a buffer and reading the response from + /// the bus into another buffer. + pub async fn transfer_async( + &mut self, + read: &mut [u8], + write: &[u8], + ) -> Result<(), super::Error> { // Get previous transfer. - let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle().await; + let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle_async().await; let chunk_size = min(tx_buf.capacity(), rx_buf.capacity()); @@ -1987,18 +1968,7 @@ pub mod dma { } }; - match &mut self.state { - State::Transferring(transfer) => transfer.wait_for_done().await, - _ => unreachable!(), - }; - - (spi_dma, tx_buf, rx_buf) = match take(&mut self.state) { - State::Transferring(transfer) => { - let (spi, (tx_buf, rx_buf)) = transfer.wait(); - (spi, tx_buf, rx_buf) - } - _ => unreachable!(), - }; + (spi_dma, tx_buf, rx_buf) = self.wait_for_idle_async().await; let bytes_read = rx_buf.read_received_data(read_chunk); assert_eq!(bytes_read, read_chunk.len()); @@ -2007,19 +1977,22 @@ pub mod dma { self.state = State::Idle(spi_dma, tx_buf, rx_buf); if !read_remainder.is_empty() { - self.read(read_remainder).await + self.read_async(read_remainder).await } else if !write_remainder.is_empty() { - self.write(write_remainder).await + self.write_async(write_remainder).await } else { Ok(()) } } - /// Asynchronously performs an in-place full-duplex transfer over - /// the SPI bus. - async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + /// Transfer by writing out a buffer and reading the response from + /// the bus into the same buffer. + pub async fn transfer_in_place_async( + &mut self, + words: &mut [u8], + ) -> Result<(), super::Error> { // Get previous transfer. - let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle().await; + let (mut spi_dma, mut tx_buf, mut rx_buf) = self.wait_for_idle_async().await; for chunk in words.chunks_mut(tx_buf.capacity()) { tx_buf.fill(chunk); @@ -2057,13 +2030,41 @@ pub mod dma { Ok(()) } - async fn flush(&mut self) -> Result<(), Self::Error> { + /// Flush any pending data in the SPI peripheral. + pub async fn flush_async(&mut self) -> Result<(), super::Error> { // Get previous transfer. - let (spi_dma, tx_buf, rx_buf) = self.wait_for_idle().await; + let (spi_dma, tx_buf, rx_buf) = self.wait_for_idle_async().await; self.state = State::Idle(spi_dma, tx_buf, rx_buf); Ok(()) } } + + impl<'d, T, C> embedded_hal_async::spi::SpiBus for SpiDmaBus<'d, T, C, FullDuplexMode, crate::Async> + where + T: InstanceDma, + C: DmaChannel, + C::P: SpiPeripheral, + { + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read_async(words).await + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write_async(words).await + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.transfer_async(read, write).await + } + + 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 + } + } } #[cfg(feature = "embedded-hal")] @@ -2072,20 +2073,22 @@ pub mod dma { use super::*; - impl<'d, T, C> ErrorType for SpiDmaBus<'d, T, C> + impl<'d, T, C, M> ErrorType for SpiDmaBus<'d, T, C, FullDuplexMode, M> where T: InstanceDma, C: DmaChannel, C::P: SpiPeripheral, + M: Mode, { type Error = Error; } - impl<'d, T, C> SpiBus for SpiDmaBus<'d, T, C> + impl<'d, T, C, M> SpiBus for SpiDmaBus<'d, T, C, FullDuplexMode, M> where T: InstanceDma, C: DmaChannel, C::P: SpiPeripheral, + M: Mode, { fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { self.read(words) @@ -2122,10 +2125,9 @@ mod ehal1 { type Error = super::Error; } - impl FullDuplex for Spi<'_, T, M> + impl FullDuplex for Spi<'_, T, FullDuplexMode> where T: Instance, - M: IsFullDuplex, { fn read(&mut self) -> nb::Result { self.spi.read_byte() @@ -2136,10 +2138,9 @@ mod ehal1 { } } - impl SpiBus for Spi<'_, T, M> + impl SpiBus for Spi<'_, T, FullDuplexMode> where T: Instance, - M: IsFullDuplex, { fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { self.spi.read_bytes(words) diff --git a/esp-hal/src/spi/mod.rs b/esp-hal/src/spi/mod.rs index 9f30272438a..3c73581c159 100644 --- a/esp-hal/src/spi/mod.rs +++ b/esp-hal/src/spi/mod.rs @@ -79,14 +79,10 @@ pub enum SpiBitOrder { } /// Trait marker for defining SPI duplex modes. -pub trait DuplexMode {} -/// Trait marker for SPI full-duplex mode. -pub trait IsFullDuplex: DuplexMode {} -/// Trait marker for SPI half-duplex mode. -pub trait IsHalfDuplex: DuplexMode {} +pub trait DuplexMode: crate::private::Sealed {} /// SPI data mode -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum SpiDataMode { /// `Single` Data Mode - 1 bit, 2 wires. @@ -100,9 +96,9 @@ pub enum SpiDataMode { /// Full-duplex operation pub struct FullDuplexMode {} impl DuplexMode for FullDuplexMode {} -impl IsFullDuplex for FullDuplexMode {} +impl crate::private::Sealed for FullDuplexMode {} /// Half-duplex operation pub struct HalfDuplexMode {} impl DuplexMode for HalfDuplexMode {} -impl IsHalfDuplex for HalfDuplexMode {} +impl crate::private::Sealed for HalfDuplexMode {} diff --git a/hil-test/tests/spi_full_duplex_dma.rs b/hil-test/tests/spi_full_duplex_dma.rs index 3988d59f249..7810c4de7b2 100644 --- a/hil-test/tests/spi_full_duplex_dma.rs +++ b/hil-test/tests/spi_full_duplex_dma.rs @@ -21,7 +21,7 @@ use esp_hal::{ peripherals::{Peripherals, SPI2}, prelude::*, spi::{ - master::{dma::SpiDma, Spi}, + master::{Spi, SpiDma}, FullDuplexMode, SpiMode, }, @@ -49,6 +49,7 @@ struct Context { #[embedded_test::tests] mod tests { use defmt::assert_eq; + use esp_hal::dma::{DmaRxBuf, DmaTxBuf}; use super::*; diff --git a/hil-test/tests/spi_full_duplex_dma_async.rs b/hil-test/tests/spi_full_duplex_dma_async.rs index 75fee9b2d00..3dd9bc45998 100644 --- a/hil-test/tests/spi_full_duplex_dma_async.rs +++ b/hil-test/tests/spi_full_duplex_dma_async.rs @@ -34,10 +34,12 @@ use esp_hal::{ peripherals::{Peripherals, SPI2}, prelude::*, spi::{ - master::{dma::asynch::SpiDmaAsyncBus, Spi}, + master::{Spi, SpiDmaBus}, + FullDuplexMode, SpiMode, }, system::SystemControl, + Async, }; use hil_test as _; @@ -55,7 +57,7 @@ cfg_if::cfg_if! { const DMA_BUFFER_SIZE: usize = 5; struct Context { - spi: SpiDmaAsyncBus<'static, SPI2, DmaChannel0>, + spi: SpiDmaBus<'static, SPI2, DmaChannel0, FullDuplexMode, Async>, pcnt_unit: Unit<'static, 0>, out_pin: Output<'static, GpioPin<5>>, mosi_mirror: GpioPin<2>, diff --git a/hil-test/tests/spi_full_duplex_dma_pcnt.rs b/hil-test/tests/spi_full_duplex_dma_pcnt.rs index e304784240d..50e78fea918 100644 --- a/hil-test/tests/spi_full_duplex_dma_pcnt.rs +++ b/hil-test/tests/spi_full_duplex_dma_pcnt.rs @@ -30,7 +30,7 @@ use esp_hal::{ peripherals::{Peripherals, SPI2}, prelude::*, spi::{ - master::{dma::SpiDma, Spi}, + master::{Spi, SpiDma}, FullDuplexMode, SpiMode, }, diff --git a/hil-test/tests/spi_half_duplex_read.rs b/hil-test/tests/spi_half_duplex_read.rs index ab392a520b3..eda40ba5e55 100644 --- a/hil-test/tests/spi_half_duplex_read.rs +++ b/hil-test/tests/spi_half_duplex_read.rs @@ -15,13 +15,13 @@ use esp_hal::{ clock::ClockControl, - dma::{Dma, DmaPriority, DmaRxBuf}, + dma::{Dma, DmaPriority, DmaRxBuf, DmaTxBuf}, dma_buffers, gpio::{GpioPin, Io, Level, Output}, peripherals::{Peripherals, SPI2}, prelude::*, spi::{ - master::{dma::SpiDma, Address, Command, Spi}, + master::{Address, Command, HalfDuplexReadWrite, Spi, SpiDma}, HalfDuplexMode, SpiDataMode, SpiMode, @@ -129,4 +129,45 @@ mod tests { assert_eq!(dma_rx_buf.as_slice(), &[0xFF; DMA_BUFFER_SIZE]); } + + #[test] + #[timeout(3)] + fn test_spidmabus_reads_correctly_from_gpio_pin(mut ctx: Context) { + const DMA_BUFFER_SIZE: usize = 4; + + let (buffer, descriptors, tx, txd) = dma_buffers!(DMA_BUFFER_SIZE, 1); + let dma_rx_buf = DmaRxBuf::new(descriptors, buffer).unwrap(); + let dma_tx_buf = DmaTxBuf::new(txd, tx).unwrap(); + + let mut spi = ctx.spi.with_buffers(dma_tx_buf, dma_rx_buf); + + // SPI should read '0's from the MISO pin + ctx.miso_mirror.set_low(); + + let mut buffer = [0xAA; DMA_BUFFER_SIZE]; + spi.read( + SpiDataMode::Single, + Command::None, + Address::None, + 0, + &mut buffer, + ) + .unwrap(); + + assert_eq!(buffer.as_slice(), &[0x00; DMA_BUFFER_SIZE]); + + // SPI should read '1's from the MISO pin + ctx.miso_mirror.set_high(); + + spi.read( + SpiDataMode::Single, + Command::None, + Address::None, + 0, + &mut buffer, + ) + .unwrap(); + + assert_eq!(buffer.as_slice(), &[0xFF; DMA_BUFFER_SIZE]); + } } diff --git a/hil-test/tests/spi_half_duplex_write.rs b/hil-test/tests/spi_half_duplex_write.rs index 1acd34b17ce..e62ecd94d3d 100644 --- a/hil-test/tests/spi_half_duplex_write.rs +++ b/hil-test/tests/spi_half_duplex_write.rs @@ -15,7 +15,7 @@ use esp_hal::{ clock::ClockControl, - dma::{Dma, DmaPriority, DmaTxBuf}, + dma::{Dma, DmaPriority, DmaRxBuf, DmaTxBuf}, dma_buffers, gpio::{GpioPin, Io, Pull}, pcnt::{ @@ -26,7 +26,7 @@ use esp_hal::{ peripherals::{Peripherals, SPI2}, prelude::*, spi::{ - master::{dma::SpiDma, Address, Command, Spi}, + master::{Address, Command, HalfDuplexReadWrite, Spi, SpiDma}, HalfDuplexMode, SpiDataMode, SpiMode, @@ -143,4 +143,48 @@ mod tests { assert_eq!(unit.get_value(), (6 * DMA_BUFFER_SIZE) as _); } + + #[test] + #[timeout(3)] + fn test_spidmabus_writes_are_correctly_by_pcnt(ctx: Context) { + const DMA_BUFFER_SIZE: usize = 4; + + let (buffer, descriptors, rx, rxd) = dma_buffers!(DMA_BUFFER_SIZE, 1); + let dma_tx_buf = DmaTxBuf::new(descriptors, buffer).unwrap(); + let dma_rx_buf = DmaRxBuf::new(rxd, rx).unwrap(); + + let unit = ctx.pcnt_unit; + let mut spi = ctx.spi.with_buffers(dma_tx_buf, dma_rx_buf); + + unit.channel0.set_edge_signal(PcntSource::from_pin( + ctx.mosi_mirror, + PcntInputConfig { pull: Pull::Down }, + )); + unit.channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + let buffer = [0b0110_1010; DMA_BUFFER_SIZE]; + // Write the buffer where each byte has 3 pos edges. + spi.write( + SpiDataMode::Single, + Command::None, + Address::None, + 0, + &buffer, + ) + .unwrap(); + + assert_eq!(unit.get_value(), (3 * DMA_BUFFER_SIZE) as _); + + spi.write( + SpiDataMode::Single, + Command::None, + Address::None, + 0, + &buffer, + ) + .unwrap(); + + assert_eq!(unit.get_value(), (6 * DMA_BUFFER_SIZE) as _); + } }