From 0aa0232f1b5935c80ee5d638fda83868b31efede Mon Sep 17 00:00:00 2001 From: Bryan Kadzban Date: Tue, 10 Oct 2023 09:00:18 -0700 Subject: [PATCH] SPI slave support (#580) * Duplicate spi to spi_slave * Restore spi * Add barebones SPI slave mode, DMA only. This setup allows registering buffers for future transactions the master does (lowering cs, toggling sclk, and raising cs again). The transfer struct returned from the registration API will complete its wait() or return true from is_done() after cs has been raised. Copied from spi.rs, so most of the changes are deleting code that handles e.g. segmented transfers or synchronous operations. Fix non-c3 devices' builds * Limit spi_slave to non-pdma devices * SPI slave DMA example Ensure the API "feels" right. Since there's no way to route GPIOs to other peripherals, we choose four other wires and bit-bang SPI for the master side, relying on the person running the example to connect the bus. This way we ensure the slave code works, since we created the master ourselves. Also, it's not really possible to use a second ESP device as the master anyway: all the digital lines have glitches on startup, and those glitches cause the slave's DMA engine to skip descriptors (it thinks they're intended CS indicators); this causes it to lose data. Then, fix the bitbang master (recording the progression here) - When bitbanging, iterate the bits by "for _ in 0..8", instead of the broken "for _ in [0..8]". The latter only runs the iteration once, since there's only one list given ... and because the code uses _ instead of a real loop variable, type checking didn't save us. - When bitbanging, send the bits out (and read them in) MSB first, since that's actually how we have the slave configured. * Add changelog entry * Split DMA prepare_transfer into two fns. The first does everything but write to the start bit and check for an error. The second does those. We need 2 fns because the SPI slave needs to start the transfer only after resetting the various afifo hardware components (if it starts the transfer before, the first 8 bytes will be lost when that reset happens). Use the split fns everywhere. Also split flush(). It needs to be pollable, so split it into one fn that polls and one that waits until the poll returns clear. Also call the poll fn from the is_done() fn, so we don't trample in-progress transfers. * Make example code fill rx buffer before transfer This way we can tell if it's ever touching certain bytes - 0xff is never added to the master transmit buffer. While I'm changing this, make the slave tx buffer never contain 0xff either (go from 254 to 0). --------- Co-authored-by: Jesse Braham --- CHANGELOG.md | 1 + esp-hal-common/src/aes/mod.rs | 30 +- esp-hal-common/src/dma/mod.rs | 48 +- esp-hal-common/src/i2s.rs | 84 +- esp-hal-common/src/lib.rs | 2 + esp-hal-common/src/parl_io.rs | 6 +- esp-hal-common/src/prelude.rs | 6 + esp-hal-common/src/spi.rs | 16 +- esp-hal-common/src/spi_slave.rs | 1089 ++++++++++++++++++++++ esp32-hal/examples/spi_loopback_dma.rs | 6 +- esp32c2-hal/examples/spi_loopback_dma.rs | 6 +- esp32c2-hal/examples/spi_slave_dma.rs | 170 ++++ esp32c3-hal/examples/spi_loopback_dma.rs | 6 +- esp32c3-hal/examples/spi_slave_dma.rs | 172 ++++ esp32c6-hal/examples/spi_loopback_dma.rs | 6 +- esp32c6-hal/examples/spi_slave_dma.rs | 172 ++++ esp32h2-hal/examples/spi_slave_dma.rs | 172 ++++ esp32s2-hal/examples/spi_loopback_dma.rs | 6 +- esp32s3-hal/examples/spi_loopback_dma.rs | 6 +- esp32s3-hal/examples/spi_slave_dma.rs | 172 ++++ 20 files changed, 2086 insertions(+), 90 deletions(-) create mode 100644 esp-hal-common/src/spi_slave.rs create mode 100644 esp32c2-hal/examples/spi_slave_dma.rs create mode 100644 esp32c3-hal/examples/spi_slave_dma.rs create mode 100644 esp32c6-hal/examples/spi_slave_dma.rs create mode 100644 esp32h2-hal/examples/spi_slave_dma.rs create mode 100644 esp32s3-hal/examples/spi_slave_dma.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3df2c6d5d..6cf43880f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Adding async support for RSA peripheral(doesn't work properly for `esp32` chip - issue will be created)(#790) - Added sleep support for ESP32-C3 with timer and GPIO wakeups (#795) - Support for ULP-RISCV including Delay and GPIO (#840) +- Add bare-bones SPI slave support, DMA only (#580) ### Changed diff --git a/esp-hal-common/src/aes/mod.rs b/esp-hal-common/src/aes/mod.rs index ffcb855b8..681fb91bd 100644 --- a/esp-hal-common/src/aes/mod.rs +++ b/esp-hal-common/src/aes/mod.rs @@ -442,18 +442,24 @@ pub mod dma { self.channel.tx.is_done(); self.channel.rx.is_done(); - self.channel.tx.prepare_transfer( - self.dma_peripheral(), - false, - write_buffer_ptr, - write_buffer_len, - )?; - self.channel.rx.prepare_transfer( - false, - self.dma_peripheral(), - read_buffer_ptr, - read_buffer_len, - )?; + self.channel + .tx + .prepare_transfer_without_start( + self.dma_peripheral(), + false, + write_buffer_ptr, + write_buffer_len, + ) + .and_then(|_| self.channel.tx.start_transfer())?; + self.channel + .rx + .prepare_transfer_without_start( + false, + self.dma_peripheral(), + read_buffer_ptr, + read_buffer_len, + ) + .and_then(|_| self.channel.rx.start_transfer())?; self.enable_dma(true); self.enable_interrupt(); self.set_mode(mode); diff --git a/esp-hal-common/src/dma/mod.rs b/esp-hal-common/src/dma/mod.rs index 5648299e3..3a5ab9ed7 100644 --- a/esp-hal-common/src/dma/mod.rs +++ b/esp-hal-common/src/dma/mod.rs @@ -277,7 +277,7 @@ pub trait RxPrivate { fn init_channel(&mut self); - fn prepare_transfer( + fn prepare_transfer_without_start( &mut self, circular: bool, peri: DmaPeripheral, @@ -285,6 +285,8 @@ pub trait RxPrivate { len: usize, ) -> Result<(), DmaError>; + fn start_transfer(&mut self) -> Result<(), DmaError>; + fn listen_ch_in_done(&self); fn clear_ch_in_done(&self); @@ -331,7 +333,7 @@ where R::set_in_priority(priority); } - fn prepare_transfer( + fn prepare_transfer_without_start( &mut self, descriptors: &mut [u32], circular: bool, @@ -382,13 +384,16 @@ where R::reset_in(); R::set_in_descriptors(descriptors.as_ptr() as u32); R::set_in_peripheral(peri as u8); + Ok(()) + } + fn start_transfer(&mut self) -> Result<(), DmaError> { R::start_in(); if R::has_in_descriptor_error() { - return Err(DmaError::DescriptorError); + Err(DmaError::DescriptorError) + } else { + Ok(()) } - - Ok(()) } fn is_done(&self) -> bool { @@ -435,7 +440,7 @@ where self.rx_impl.init(burst_mode, priority); } - fn prepare_transfer( + fn prepare_transfer_without_start( &mut self, circular: bool, peri: DmaPeripheral, @@ -464,8 +469,11 @@ where self.read_buffer_start = data; self.rx_impl - .prepare_transfer(self.descriptors, circular, peri, data, len)?; - Ok(()) + .prepare_transfer_without_start(self.descriptors, circular, peri, data, len) + } + + fn start_transfer(&mut self) -> Result<(), DmaError> { + self.rx_impl.start_transfer() } fn listen_ch_in_done(&self) { @@ -616,7 +624,7 @@ pub trait TxPrivate { fn init_channel(&mut self); - fn prepare_transfer( + fn prepare_transfer_without_start( &mut self, peri: DmaPeripheral, circular: bool, @@ -624,6 +632,8 @@ pub trait TxPrivate { len: usize, ) -> Result<(), DmaError>; + fn start_transfer(&mut self) -> Result<(), DmaError>; + fn clear_ch_out_done(&self); fn is_ch_out_done_set(&self) -> bool; @@ -661,7 +671,7 @@ where R::set_out_priority(priority); } - fn prepare_transfer( + fn prepare_transfer_without_start( &mut self, descriptors: &mut [u32], circular: bool, @@ -712,13 +722,17 @@ where R::reset_out(); R::set_out_descriptors(descriptors.as_ptr() as u32); R::set_out_peripheral(peri as u8); + Ok(()) + } + + fn start_transfer(&mut self) -> Result<(), DmaError> { R::start_out(); if R::has_out_descriptor_error() { - return Err(DmaError::DescriptorError); + Err(DmaError::DescriptorError) + } else { + Ok(()) } - - Ok(()) } fn clear_ch_out_done(&self) { @@ -800,7 +814,7 @@ where R::init_channel(); } - fn prepare_transfer( + fn prepare_transfer_without_start( &mut self, peri: DmaPeripheral, circular: bool, @@ -827,9 +841,11 @@ where self.buffer_len = len; self.tx_impl - .prepare_transfer(self.descriptors, circular, peri, data, len)?; + .prepare_transfer_without_start(self.descriptors, circular, peri, data, len) + } - Ok(()) + fn start_transfer(&mut self) -> Result<(), DmaError> { + self.tx_impl.start_transfer() } fn clear_ch_out_done(&self) { diff --git a/esp-hal-common/src/i2s.rs b/esp-hal-common/src/i2s.rs index 31d5a11e0..e512c6169 100644 --- a/esp-hal-common/src/i2s.rs +++ b/esp-hal-common/src/i2s.rs @@ -798,12 +798,14 @@ where // Enable corresponding interrupts if needed // configure DMA outlink - self.tx_channel.prepare_transfer( - self.register_access.get_dma_peripheral(), - false, - ptr, - data.len(), - )?; + self.tx_channel + .prepare_transfer_without_start( + self.register_access.get_dma_peripheral(), + false, + ptr, + data.len(), + ) + .and_then(|_| self.tx_channel.start_transfer())?; // set I2S_TX_STOP_EN if needed @@ -832,12 +834,14 @@ where // Enable corresponding interrupts if needed // configure DMA outlink - self.tx_channel.prepare_transfer( - self.register_access.get_dma_peripheral(), - circular, - ptr, - len, - )?; + self.tx_channel + .prepare_transfer_without_start( + self.register_access.get_dma_peripheral(), + circular, + ptr, + len, + ) + .and_then(|_| self.tx_channel.start_transfer())?; // set I2S_TX_STOP_EN if needed @@ -870,12 +874,14 @@ where // Enable corresponding interrupts if needed // configure DMA outlink - self.tx_channel.prepare_transfer( - self.register_access.get_dma_peripheral(), - circular, - ptr, - len, - )?; + self.tx_channel + .prepare_transfer_without_start( + self.register_access.get_dma_peripheral(), + circular, + ptr, + len, + ) + .and_then(|_| self.tx_channel.start_transfer())?; // set I2S_TX_STOP_EN if needed @@ -964,12 +970,14 @@ where // Enable corresponding interrupts if needed // configure DMA outlink - self.rx_channel.prepare_transfer( - false, - self.register_access.get_dma_peripheral(), - ptr, - data.len(), - )?; + self.rx_channel + .prepare_transfer_without_start( + false, + self.register_access.get_dma_peripheral(), + ptr, + data.len(), + ) + .and_then(|_| self.rx_channel.start_transfer())?; // set I2S_TX_STOP_EN if needed @@ -1002,12 +1010,14 @@ where // Enable corresponding interrupts if needed // configure DMA outlink - self.rx_channel.prepare_transfer( - circular, - self.register_access.get_dma_peripheral(), - ptr, - len, - )?; + self.rx_channel + .prepare_transfer_without_start( + circular, + self.register_access.get_dma_peripheral(), + ptr, + len, + ) + .and_then(|_| self.rx_channel.start_transfer())?; // set I2S_TX_STOP_EN if needed @@ -1047,12 +1057,14 @@ where // Enable corresponding interrupts if needed // configure DMA outlink - self.rx_channel.prepare_transfer( - circular, - self.register_access.get_dma_peripheral(), - ptr, - len, - )?; + self.rx_channel + .prepare_transfer_without_start( + circular, + self.register_access.get_dma_peripheral(), + ptr, + len, + ) + .and_then(|_| self.rx_channel.start_transfer())?; // set I2S_TX_STOP_EN if needed diff --git a/esp-hal-common/src/lib.rs b/esp-hal-common/src/lib.rs index ed3cf315f..21e416254 100644 --- a/esp-hal-common/src/lib.rs +++ b/esp-hal-common/src/lib.rs @@ -138,6 +138,8 @@ pub mod rtc_cntl; pub mod sha; #[cfg(any(spi0, spi1, spi2, spi3))] pub mod spi; +#[cfg(all(any(spi0, spi1, spi2, spi3), not(pdma)))] +pub mod spi_slave; #[cfg(any(dport, pcr, system))] pub mod system; #[cfg(systimer)] diff --git a/esp-hal-common/src/parl_io.rs b/esp-hal-common/src/parl_io.rs index 731136e70..c8da196ac 100644 --- a/esp-hal-common/src/parl_io.rs +++ b/esp-hal-common/src/parl_io.rs @@ -1209,7 +1209,8 @@ where self.tx_channel.is_done(); self.tx_channel - .prepare_transfer(DmaPeripheral::ParlIo, false, ptr, len)?; + .prepare_transfer_without_start(DmaPeripheral::ParlIo, false, ptr, len) + .and_then(|_| self.tx_channel.start_transfer())?; loop { if Instance::is_tx_ready() { @@ -1331,7 +1332,8 @@ where Instance::set_rx_bytes(len as u16); self.rx_channel - .prepare_transfer(false, DmaPeripheral::ParlIo, ptr, len)?; + .prepare_transfer_without_start(false, DmaPeripheral::ParlIo, ptr, len) + .and_then(|_| self.rx_channel.start_transfer())?; Instance::set_rx_reg_update(); diff --git a/esp-hal-common/src/prelude.rs b/esp-hal-common/src/prelude.rs index 8c529f903..889bcc96e 100644 --- a/esp-hal-common/src/prelude.rs +++ b/esp-hal-common/src/prelude.rs @@ -70,6 +70,12 @@ pub use crate::spi::{ Instance as _esp_hal_spi_Instance, InstanceDma as _esp_hal_spi_InstanceDma, }; +#[cfg(all(any(spi0, spi1, spi2, spi3), not(pdma)))] +pub use crate::spi_slave::{ + dma::WithDmaSpi2 as _esp_hal_spi_slave_dma_WithDmaSpi2, + Instance as _esp_hal_spi_slave_Instance, + InstanceDma as _esp_hal_spi_slave_InstanceDma, +}; #[cfg(any(dport, pcr, system))] pub use crate::system::SystemExt as _esp_hal_system_SystemExt; #[cfg(any(timg0, timg1))] diff --git a/esp-hal-common/src/spi.rs b/esp-hal-common/src/spi.rs index cca0eb0c5..b0c166daf 100644 --- a/esp-hal-common/src/spi.rs +++ b/esp-hal-common/src/spi.rs @@ -1901,18 +1901,20 @@ where self.update(); reset_dma_before_load_dma_dscr(reg_block); - tx.prepare_transfer( + tx.prepare_transfer_without_start( self.dma_peripheral(), false, write_buffer_ptr, write_buffer_len, - )?; - rx.prepare_transfer( + ) + .and_then(|_| tx.start_transfer())?; + rx.prepare_transfer_without_start( false, self.dma_peripheral(), read_buffer_ptr, read_buffer_len, - )?; + ) + .and_then(|_| rx.start_transfer())?; self.clear_dma_interrupts(); reset_dma_before_usr_cmd(reg_block); @@ -1948,7 +1950,8 @@ where self.update(); reset_dma_before_load_dma_dscr(reg_block); - tx.prepare_transfer(self.dma_peripheral(), false, ptr, len)?; + tx.prepare_transfer_without_start(self.dma_peripheral(), false, ptr, len) + .and_then(|_| tx.start_transfer())?; self.clear_dma_interrupts(); reset_dma_before_usr_cmd(reg_block); @@ -1973,7 +1976,8 @@ where self.update(); reset_dma_before_load_dma_dscr(reg_block); - rx.prepare_transfer(false, self.dma_peripheral(), ptr, len)?; + rx.prepare_transfer_without_start(false, self.dma_peripheral(), ptr, len) + .and_then(|_| rx.start_transfer())?; self.clear_dma_interrupts(); reset_dma_before_usr_cmd(reg_block); diff --git a/esp-hal-common/src/spi_slave.rs b/esp-hal-common/src/spi_slave.rs new file mode 100644 index 000000000..039af9b89 --- /dev/null +++ b/esp-hal-common/src/spi_slave.rs @@ -0,0 +1,1089 @@ +//! # Serial Peripheral Interface, slave mode +//! +//! ## Overview +//! There are multiple ways to use SPI, depending on your needs. Regardless of +//! which way you choose, you must first create an SPI instance with +//! [`Spi::new`]. +//! +//! ## Example +//! ```rust +//! let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); +//! let sclk = io.pins.gpio12; +//! let miso = io.pins.gpio11; +//! let mosi = io.pins.gpio13; +//! let cs = io.pins.gpio10; +//! +//! let mut spi = hal::spi_slave::Spi::new(peripherals.SPI2, sclk, mosi, miso, cs, SpiMode::Mode0); +//! ``` +//! +//! There are several options for working with the SPI peripheral in slave mode, +//! but the code currently only supports single transfers (not segmented +//! transfers), full duplex, single bit (not dual or quad SPI), and DMA mode +//! (not CPU mode). It also does not support blocking operations, as the actual +//! transfer is controlled by the SPI master; if these are necessary, +//! then the DmaTransfer trait instance can be wait()ed on or polled for +//! is_done(). +//! +//! ```rust +//! let dma = Gdma::new(peripherals.DMA); +//! const N: usize = (buffer_size + 4091) / 4092; +//! let mut tx_descriptors = [0u32; N * 3]; +//! let mut rx_descriptors = [0u32; N * 3]; +//! let mut spi = spi.with_dma(dma.channel0.configure( +//! /* circular = */ false, +//! tx_descriptors, +//! rx_descriptors, +//! DmaPriority::Priority0, +//! )); +//! // This is not legal rust, but any method of getting a &mut 'static is good. +//! let tx_buf = &'static [0u8; N * 4092]; +//! let rx_buf = &mut 'static [0u8; N * 4092]; +//! let transfer = spi.dma_transfer(tx_buf, rx_buf).unwrap(); +//! // Do other operations, checking transfer.is_done() +//! // When the master sends enough clock pulses, is_done() will be true. +//! (tx_buf, rx_buf, spi) = transfer.wait(); +//! ``` +//! +//! TODO: +//! - Notify the Spi user when the master wants to send or receive data, if +//! possible +//! - Blocking transfers +//! - Half duplex +//! - Segmented transfers +//! - Interrupt support +//! - Custom interrupts from segmented transfer commands +//! - Dual and quad SPI +//! - CPU mode + +use core::marker::PhantomData; + +use crate::{ + dma::{DmaPeripheral, Rx, Tx}, + gpio::{InputPin, InputSignal, OutputPin, OutputSignal}, + peripheral::{Peripheral, PeripheralRef}, + peripherals::spi2::RegisterBlock, + system::PeripheralClockControl, +}; + +const MAX_DMA_SIZE: usize = 32768 - 32; +pub use crate::spi::{Error, FullDuplexMode, SpiMode}; + +/// SPI peripheral driver +pub struct Spi<'d, T, M> { + spi: PeripheralRef<'d, T>, + #[allow(dead_code)] + data_mode: SpiMode, + _mode: PhantomData, +} + +impl<'d, T> Spi<'d, T, FullDuplexMode> +where + T: Instance, +{ + /// Constructs an SPI instance in 8bit dataframe mode. + pub fn new( + spi: impl Peripheral

+ 'd, + sck: impl Peripheral

+ 'd, + mosi: impl Peripheral

+ 'd, + miso: impl Peripheral

+ 'd, + cs: impl Peripheral

+ 'd, + mode: SpiMode, + ) -> Spi<'d, T, FullDuplexMode> { + crate::into_ref!(spi, sck, mosi, miso, cs); + sck.set_to_input() + .connect_input_to_peripheral(spi.sclk_signal()); + + mosi.set_to_input() + .connect_input_to_peripheral(spi.mosi_signal()); + + miso.set_to_push_pull_output() + .connect_peripheral_to_output(spi.miso_signal()); + + cs.set_to_input() + .connect_input_to_peripheral(spi.cs_signal()); + + Self::new_internal(spi, mode) + } + + pub(crate) fn new_internal( + spi: PeripheralRef<'d, T>, + mode: SpiMode, + ) -> Spi<'d, T, FullDuplexMode> { + spi.enable_peripheral(); + + let mut spi = Spi { + spi, + data_mode: mode, + _mode: PhantomData::default(), + }; + spi.spi.init(); + spi.spi.set_data_mode(mode); + + spi + } +} + +pub mod dma { + use core::mem; + + use embedded_dma::{ReadBuffer, WriteBuffer}; + + #[cfg(any(esp32, esp32s2, esp32s3))] + use super::Spi3Instance; + #[allow(unused_imports)] + use super::SpiMode; + use super::{FullDuplexMode, Instance, InstanceDma, Spi, Spi2Instance, MAX_DMA_SIZE}; + #[cfg(any(esp32, esp32s2, esp32s3))] + use crate::dma::Spi3Peripheral; + use crate::{ + dma::{ + Channel, + ChannelTypes, + DmaError, + DmaTransfer, + DmaTransferRxTx, + RxPrivate, + Spi2Peripheral, + SpiPeripheral, + TxPrivate, + }, + peripheral::PeripheralRef, + }; + + pub trait WithDmaSpi2<'d, T, C> + where + T: Instance + Spi2Instance, + C: ChannelTypes, + C::P: SpiPeripheral, + { + fn with_dma(self, channel: Channel<'d, C>) -> SpiDma<'d, T, C>; + } + + #[cfg(any(esp32, esp32s2, esp32s3))] + pub trait WithDmaSpi3<'d, T, C> + where + T: Instance + Spi3Instance, + C: ChannelTypes, + C::P: SpiPeripheral, + { + fn with_dma(self, channel: Channel<'d, C>) -> SpiDma<'d, T, C>; + } + + impl<'d, T, C> WithDmaSpi2<'d, T, C> for Spi<'d, T, FullDuplexMode> + where + T: Instance + Spi2Instance, + C: ChannelTypes, + C::P: SpiPeripheral + Spi2Peripheral, + { + fn with_dma(self, mut channel: Channel<'d, C>) -> SpiDma<'d, T, C> { + channel.tx.init_channel(); // no need to call this for both, TX and RX + + #[cfg(esp32)] + match self.data_mode { + SpiMode::Mode0 | SpiMode::Mode2 => { + self.spi.invert_i_edge(); + } + _ => {} + } + + SpiDma { + spi: self.spi, + channel, + } + } + } + + #[cfg(any(esp32, esp32s2, esp32s3))] + impl<'d, T, C> WithDmaSpi3<'d, T, C> for Spi<'d, T, FullDuplexMode> + where + T: Instance + Spi3Instance, + C: ChannelTypes, + C::P: SpiPeripheral + Spi3Peripheral, + { + fn with_dma(self, mut channel: Channel<'d, C>) -> SpiDma<'d, T, C> { + channel.tx.init_channel(); // no need to call this for both, TX and RX + + #[cfg(esp32)] + match self.data_mode { + SpiMode::Mode0 | SpiMode::Mode2 => { + self.spi.invert_i_edge(); + } + _ => {} + } + + SpiDma { + spi: self.spi, + channel, + } + } + } + /// An in-progress DMA transfer + pub struct SpiDmaTransferRxTx<'d, T, C, RBUFFER, TBUFFER> + where + T: InstanceDma, C::Rx<'d>>, + C: ChannelTypes, + C::P: SpiPeripheral, + { + spi_dma: SpiDma<'d, T, C>, + rbuffer: RBUFFER, + tbuffer: TBUFFER, + } + + impl<'d, T, C, RXBUF, TXBUF> DmaTransferRxTx> + for SpiDmaTransferRxTx<'d, T, C, RXBUF, TXBUF> + where + T: InstanceDma, C::Rx<'d>>, + C: ChannelTypes, + C::P: SpiPeripheral, + { + /// Wait for the DMA transfer to complete and return the buffers and the + /// SPI instance. + fn wait( + mut self, + ) -> Result<(RXBUF, TXBUF, SpiDma<'d, T, C>), (DmaError, RXBUF, TXBUF, SpiDma<'d, T, C>)> + { + while !self.is_done() {} + self.spi_dma.spi.flush().ok(); // waiting for the DMA transfer is not enough + + // `DmaTransfer` needs to have a `Drop` implementation, because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `DmaTransfer`'s fields, so we use `ptr::read` + // and `mem::forget`. + // + // NOTE(unsafe) There is no panic branch between getting the resources + // and forgetting `self`. + unsafe { + let rbuffer = core::ptr::read(&self.rbuffer); + let tbuffer = core::ptr::read(&self.tbuffer); + let payload = core::ptr::read(&self.spi_dma); + let err = (&self).spi_dma.channel.rx.has_error() + || (&self).spi_dma.channel.tx.has_error(); + mem::forget(self); + if err { + Err((DmaError::DescriptorError, rbuffer, tbuffer, payload)) + } else { + Ok((rbuffer, tbuffer, payload)) + } + } + } + + /// Check if the DMA transfer is complete + fn is_done(&self) -> bool { + let ch = &self.spi_dma.channel; + ch.tx.is_done() && ch.rx.is_done() && !self.spi_dma.spi.is_bus_busy() + } + } + + impl<'d, T, C, RXBUF, TXBUF> Drop for SpiDmaTransferRxTx<'d, T, C, RXBUF, TXBUF> + where + T: InstanceDma, C::Rx<'d>>, + C: ChannelTypes, + C::P: SpiPeripheral, + { + fn drop(&mut self) { + while !self.is_done() {} + self.spi_dma.spi.flush().ok(); + } + } + + /// An in-progress DMA transfer. + pub struct SpiDmaTransfer<'d, T, C, BUFFER> + where + T: InstanceDma, C::Rx<'d>>, + C: ChannelTypes, + C::P: SpiPeripheral, + { + spi_dma: SpiDma<'d, T, C>, + buffer: BUFFER, + } + + impl<'d, T, C, BUFFER> DmaTransfer> for SpiDmaTransfer<'d, T, C, BUFFER> + where + T: InstanceDma, C::Rx<'d>>, + C: ChannelTypes, + C::P: SpiPeripheral, + { + /// Wait for the DMA transfer to complete and return the buffers and the + /// SPI instance. + fn wait( + mut self, + ) -> Result<(BUFFER, SpiDma<'d, T, C>), (DmaError, BUFFER, SpiDma<'d, T, C>)> { + while !self.is_done() {} + self.spi_dma.spi.flush().ok(); // waiting for the DMA transfer is not enough + + // `DmaTransfer` needs to have a `Drop` implementation, because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `DmaTransfer`'s fields, so we use `ptr::read` + // and `mem::forget`. + // + // NOTE(unsafe) There is no panic branch between getting the resources + // and forgetting `self`. + unsafe { + let buffer = core::ptr::read(&self.buffer); + let payload = core::ptr::read(&self.spi_dma); + let err = (&self).spi_dma.channel.rx.has_error() + || (&self).spi_dma.channel.tx.has_error(); + mem::forget(self); + if err { + Err((DmaError::DescriptorError, buffer, payload)) + } else { + Ok((buffer, payload)) + } + } + } + + /// Check if the DMA transfer is complete + fn is_done(&self) -> bool { + let ch = &self.spi_dma.channel; + ch.tx.is_done() && ch.rx.is_done() + } + } + + impl<'d, T, C, BUFFER> Drop for SpiDmaTransfer<'d, T, C, BUFFER> + where + T: InstanceDma, C::Rx<'d>>, + C: ChannelTypes, + C::P: SpiPeripheral, + { + fn drop(&mut self) { + while !self.is_done() {} + self.spi_dma.spi.flush().ok(); // waiting for the DMA transfer is + // not enough + } + } + + /// A DMA capable SPI instance. + pub struct SpiDma<'d, T, C> + where + C: ChannelTypes, + C::P: SpiPeripheral, + { + pub(crate) spi: PeripheralRef<'d, T>, + pub(crate) channel: Channel<'d, C>, + } + + impl<'d, T, C> core::fmt::Debug for SpiDma<'d, T, C> + where + C: ChannelTypes, + C::P: SpiPeripheral, + { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SpiDma").finish() + } + } + + impl<'d, T, C> SpiDma<'d, T, C> + where + T: InstanceDma, C::Rx<'d>>, + C: ChannelTypes, + C::P: SpiPeripheral, + { + /// Register a buffer for a DMA write. + /// + /// This will return a [SpiDmaTransfer] owning the buffer(s) and the SPI + /// instance. The maximum amount of data to be sent is 32736 + /// bytes. + /// + /// The write is driven by the SPI master's sclk signal and cs line. + pub fn dma_write( + mut self, + words: TXBUF, + ) -> Result, super::Error> + where + TXBUF: ReadBuffer, + { + let (ptr, len) = unsafe { words.read_buffer() }; + + if len > MAX_DMA_SIZE { + return Err(super::Error::MaxDmaTransferSizeExceeded); + } + + self.spi + .start_write_bytes_dma(ptr, len, &mut self.channel.tx) + .map(move |_| SpiDmaTransfer { + spi_dma: self, + buffer: words, + }) + } + + /// Register a buffer for a DMA read. + /// + /// This will return a [SpiDmaTransfer] owning the buffer(s) and the SPI + /// instance. The maximum amount of data to be received is 32736 + /// bytes. + /// + /// The read is driven by the SPI master's sclk signal and cs line. + pub fn dma_read( + mut self, + mut words: RXBUF, + ) -> Result, super::Error> + where + RXBUF: WriteBuffer, + { + let (ptr, len) = unsafe { words.write_buffer() }; + + if len > MAX_DMA_SIZE { + return Err(super::Error::MaxDmaTransferSizeExceeded); + } + + self.spi + .start_read_bytes_dma(ptr, len, &mut self.channel.rx) + .map(move |_| SpiDmaTransfer { + spi_dma: self, + buffer: words, + }) + } + + /// Register buffers for a DMA transfer. + /// + /// This will return a [SpiDmaTransfer] owning the buffer(s) and the SPI + /// instance. The maximum amount of data to be sent/received is + /// 32736 bytes. + /// + /// The data transfer is driven by the SPI master's sclk signal and cs + /// line. + pub fn dma_transfer( + mut self, + words: TXBUF, + mut read_buffer: RXBUF, + ) -> Result, super::Error> + where + TXBUF: ReadBuffer, + RXBUF: WriteBuffer, + { + let (write_ptr, write_len) = unsafe { words.read_buffer() }; + let (read_ptr, read_len) = unsafe { read_buffer.write_buffer() }; + + if write_len > MAX_DMA_SIZE || read_len > MAX_DMA_SIZE { + return Err(super::Error::MaxDmaTransferSizeExceeded); + } + + self.spi + .start_transfer_dma( + write_ptr, + write_len, + read_ptr, + read_len, + &mut self.channel.tx, + &mut self.channel.rx, + ) + .map(move |_| SpiDmaTransferRxTx { + spi_dma: self, + rbuffer: read_buffer, + tbuffer: words, + }) + } + } +} + +pub trait InstanceDma: Instance +where + TX: Tx, + RX: Rx, +{ + fn start_transfer_dma( + &mut self, + write_buffer_ptr: *const u8, + write_buffer_len: usize, + read_buffer_ptr: *mut u8, + read_buffer_len: usize, + tx: &mut TX, + rx: &mut RX, + ) -> Result<(), Error> { + let reg_block = self.register_block(); + + tx.is_done(); + rx.is_done(); + + self.enable_dma(); + + reset_dma_before_load_dma_dscr(reg_block); + tx.prepare_transfer_without_start( + self.dma_peripheral(), + false, + write_buffer_ptr, + write_buffer_len, + )?; + rx.prepare_transfer_without_start( + false, + self.dma_peripheral(), + read_buffer_ptr, + read_buffer_len, + )?; + + self.clear_dma_interrupts(); + reset_dma_before_usr_cmd(reg_block); + + // On the esp32, all full-duplex transfers are single, and all half-duplex + // transfers use the cmd/addr/dummy/data sequence (but are still + // single). + #[cfg(not(esp32))] + reg_block + .dma_conf + .modify(|_, w| w.dma_slv_seg_trans_en().clear_bit()); + + tx.start_transfer()?; + Ok(rx.start_transfer()?) + } + + fn start_write_bytes_dma( + &mut self, + ptr: *const u8, + len: usize, + tx: &mut TX, + ) -> Result<(), Error> { + let reg_block = self.register_block(); + + tx.is_done(); + + self.enable_dma(); + + reset_dma_before_load_dma_dscr(reg_block); + tx.prepare_transfer_without_start(self.dma_peripheral(), false, ptr, len)?; + + self.clear_dma_interrupts(); + reset_dma_before_usr_cmd(reg_block); + + // On the esp32, all full-duplex transfers are single, and all half-duplex + // transfers use the cmd/addr/dummy/data sequence (but are still + // single). + #[cfg(not(esp32))] + reg_block + .dma_conf + .modify(|_, w| w.dma_slv_seg_trans_en().clear_bit()); + + Ok(tx.start_transfer()?) + } + + fn start_read_bytes_dma(&mut self, ptr: *mut u8, len: usize, rx: &mut RX) -> Result<(), Error> { + let reg_block = self.register_block(); + + rx.is_done(); + + self.enable_dma(); + + reset_dma_before_load_dma_dscr(reg_block); + rx.prepare_transfer_without_start(false, self.dma_peripheral(), ptr, len)?; + + self.clear_dma_interrupts(); + reset_dma_before_usr_cmd(reg_block); + + // On the esp32, all full-duplex transfers are single, and all half-duplex + // transfers use the cmd/addr/dummy/data sequence (but are still + // single). + #[cfg(not(esp32))] + reg_block + .dma_conf + .modify(|_, w| w.dma_slv_seg_trans_en().clear_bit()); + + Ok(rx.start_transfer()?) + } + + fn dma_peripheral(&self) -> DmaPeripheral { + match self.spi_num() { + 2 => DmaPeripheral::Spi2, + #[cfg(any(esp32, esp32s2, esp32s3))] + 3 => DmaPeripheral::Spi3, + _ => panic!("Illegal SPI instance"), + } + } + + #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] + fn enable_dma(&self) { + let reg_block = self.register_block(); + reg_block.dma_conf.modify(|_, w| { + w.dma_tx_ena() + .set_bit() + .dma_rx_ena() + .set_bit() + .rx_eof_en() + .clear_bit() + }); + } + + #[cfg(any(esp32, esp32s2))] + fn enable_dma(&self) { + // for non GDMA this is done in `assign_tx_device` / `assign_rx_device` + } + + #[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2, esp32s3))] + fn clear_dma_interrupts(&self) { + let reg_block = self.register_block(); + reg_block.dma_int_clr.write(|w| { + w.dma_infifo_full_err_int_clr() + .set_bit() + .dma_outfifo_empty_err_int_clr() + .set_bit() + .trans_done_int_clr() + .set_bit() + .mst_rx_afifo_wfull_err_int_clr() + .set_bit() + .mst_tx_afifo_rempty_err_int_clr() + .set_bit() + }); + } + + #[cfg(any(esp32, esp32s2))] + fn clear_dma_interrupts(&self) { + let reg_block = self.register_block(); + reg_block.dma_int_clr.write(|w| { + w.inlink_dscr_empty_int_clr() + .set_bit() + .outlink_dscr_error_int_clr() + .set_bit() + .inlink_dscr_error_int_clr() + .set_bit() + .in_done_int_clr() + .set_bit() + .in_err_eof_int_clr() + .set_bit() + .in_suc_eof_int_clr() + .set_bit() + .out_done_int_clr() + .set_bit() + .out_eof_int_clr() + .set_bit() + .out_total_eof_int_clr() + .set_bit() + }); + } +} + +#[cfg(not(any(esp32, esp32s2)))] +fn reset_dma_before_usr_cmd(reg_block: &RegisterBlock) { + reg_block.dma_conf.modify(|_, w| { + w.rx_afifo_rst() + .set_bit() + .buf_afifo_rst() + .set_bit() + .dma_afifo_rst() + .set_bit() + }); +} + +#[cfg(any(esp32, esp32s2))] +fn reset_dma_before_usr_cmd(_reg_block: &RegisterBlock) {} + +#[cfg(not(any(esp32, esp32s2)))] +fn reset_dma_before_load_dma_dscr(_reg_block: &RegisterBlock) {} + +#[cfg(any(esp32, esp32s2))] +fn reset_dma_before_load_dma_dscr(reg_block: &RegisterBlock) { + reg_block.dma_conf.modify(|_, w| { + w.out_rst() + .set_bit() + .in_rst() + .set_bit() + .ahbm_fifo_rst() + .set_bit() + .ahbm_rst() + .set_bit() + }); +} + +impl InstanceDma for crate::peripherals::SPI2 +where + TX: Tx, + RX: Rx, +{ +} + +#[cfg(any(esp32, esp32s2, esp32s3))] +impl InstanceDma for crate::peripherals::SPI3 +where + TX: Tx, + RX: Rx, +{ +} + +pub trait Instance { + fn register_block(&self) -> &RegisterBlock; + + fn sclk_signal(&self) -> InputSignal; + + fn mosi_signal(&self) -> InputSignal; + + fn miso_signal(&self) -> OutputSignal; + + fn cs_signal(&self) -> InputSignal; + + fn enable_peripheral(&self); + + fn spi_num(&self) -> u8; + + /// Initialize for full-duplex 1 bit mode + fn init(&mut self) { + let reg_block = self.register_block(); + reg_block.slave.write(|w| w.mode().set_bit()); + + reg_block.user.modify(|_, w| { + w.usr_miso_highpart() + .clear_bit() + .doutdin() + .set_bit() + .usr_miso() + .set_bit() + .usr_mosi() + .set_bit() + .usr_dummy_idle() + .clear_bit() + .usr_addr() + .clear_bit() + .usr_command() + .clear_bit() + }); + + #[cfg(not(any(esp32, esp32s2)))] + reg_block.clk_gate.modify(|_, w| { + w.clk_en() + .clear_bit() + .mst_clk_active() + .clear_bit() + .mst_clk_sel() + .clear_bit() + }); + + #[cfg(not(any(esp32, esp32s2)))] + reg_block.ctrl.modify(|_, w| { + w.q_pol() + .clear_bit() + .d_pol() + .clear_bit() + .hold_pol() + .clear_bit() + }); + + #[cfg(esp32s2)] + reg_block + .ctrl + .modify(|_, w| w.q_pol().clear_bit().d_pol().clear_bit().wp().clear_bit()); + + #[cfg(esp32)] + reg_block.ctrl.modify(|_, w| w.wp().clear_bit()); + + #[cfg(not(esp32))] + reg_block.misc.write(|w| unsafe { w.bits(0) }); + } + + #[cfg(not(esp32))] + fn set_data_mode(&mut self, data_mode: SpiMode) -> &mut Self { + let reg_block = self.register_block(); + + match data_mode { + SpiMode::Mode0 => { + reg_block + .user + .modify(|_, w| w.tsck_i_edge().clear_bit().rsck_i_edge().clear_bit()); + #[cfg(esp32s2)] + reg_block.ctrl1.modify(|_, w| w.clk_mode_13().clear_bit()); + #[cfg(not(esp32s2))] + reg_block.slave.modify(|_, w| w.clk_mode_13().clear_bit()); + } + SpiMode::Mode1 => { + reg_block + .user + .modify(|_, w| w.tsck_i_edge().set_bit().rsck_i_edge().set_bit()); + #[cfg(esp32s2)] + reg_block.ctrl1.modify(|_, w| w.clk_mode_13().set_bit()); + #[cfg(not(esp32s2))] + reg_block.slave.modify(|_, w| w.clk_mode_13().set_bit()); + } + SpiMode::Mode2 => { + reg_block + .user + .modify(|_, w| w.tsck_i_edge().set_bit().rsck_i_edge().set_bit()); + #[cfg(esp32s2)] + reg_block.ctrl1.modify(|_, w| w.clk_mode_13().clear_bit()); + #[cfg(not(esp32s2))] + reg_block.slave.modify(|_, w| w.clk_mode_13().clear_bit()); + } + SpiMode::Mode3 => { + reg_block + .user + .modify(|_, w| w.tsck_i_edge().clear_bit().rsck_i_edge().clear_bit()); + #[cfg(esp32s2)] + reg_block.ctrl1.modify(|_, w| w.clk_mode_13().set_bit()); + #[cfg(not(esp32s2))] + reg_block.slave.modify(|_, w| w.clk_mode_13().set_bit()); + } + } + self + } + + #[cfg(esp32)] + fn set_data_mode(&mut self, data_mode: SpiMode) -> &mut Self { + let reg_block = self.register_block(); + + match data_mode { + SpiMode::Mode0 => { + reg_block.pin.modify(|_, w| w.ck_idle_edge().set_bit()); + reg_block.user.modify(|_, w| w.ck_i_edge().clear_bit()); + } + SpiMode::Mode1 => { + reg_block.pin.modify(|_, w| w.ck_idle_edge().set_bit()); + reg_block.user.modify(|_, w| w.ck_i_edge().set_bit()); + } + SpiMode::Mode2 => { + reg_block.pin.modify(|_, w| w.ck_idle_edge().clear_bit()); + reg_block.user.modify(|_, w| w.ck_i_edge().set_bit()); + } + SpiMode::Mode3 => { + reg_block.pin.modify(|_, w| w.ck_idle_edge().clear_bit()); + reg_block.user.modify(|_, w| w.ck_i_edge().clear_bit()); + } + } + self + } + + // The ESP32 needs its _edge bits inverted in DMA slave mode, when in mode 0 or + // 2. set_data_mode above sets the registers up for non-DMA mode. + #[cfg(esp32)] + fn invert_i_edge(&self) { + let reg_block = self.register_block(); + + reg_block + .pin + .modify(|r, w| w.ck_idle_edge().variant(r.ck_idle_edge().bit_is_clear())); + reg_block + .user + .modify(|r, w| w.ck_i_edge().variant(r.ck_i_edge().bit_is_clear())); + } + + fn is_bus_busy(&self) -> bool { + let reg_block = self.register_block(); + + #[cfg(any(esp32, esp32s2))] + { + reg_block.slave.read().trans_done().bit_is_clear() + } + #[cfg(not(any(esp32, esp32s2)))] + { + reg_block + .dma_int_raw + .read() + .trans_done_int_raw() + .bit_is_clear() + } + } + + // Check if the bus is busy and if it is wait for it to be idle + fn flush(&mut self) -> Result<(), Error> { + while self.is_bus_busy() { + // Wait for bus to be clear + } + Ok(()) + } + + // Clear the transaction-done interrupt flag so flush() can work properly. Not + // used in DMA mode. + fn setup_for_flush(&self) { + #[cfg(any(esp32, esp32s2))] + self.register_block() + .slave + .modify(|_, w| w.trans_done().clear_bit()); + #[cfg(not(any(esp32, esp32s2)))] + self.register_block() + .dma_int_clr + .write(|w| w.trans_done_int_clr().set_bit()); + } +} + +#[cfg(any(esp32c2, esp32c3, esp32c6, esp32h2))] +impl Instance for crate::peripherals::SPI2 { + #[inline(always)] + fn register_block(&self) -> &RegisterBlock { + self + } + + #[inline(always)] + fn sclk_signal(&self) -> InputSignal { + InputSignal::FSPICLK + } + + #[inline(always)] + fn mosi_signal(&self) -> InputSignal { + InputSignal::FSPID + } + + #[inline(always)] + fn miso_signal(&self) -> OutputSignal { + OutputSignal::FSPIQ + } + + #[inline(always)] + fn cs_signal(&self) -> InputSignal { + InputSignal::FSPICS0 + } + + #[inline(always)] + fn enable_peripheral(&self) { + PeripheralClockControl::enable(crate::system::Peripheral::Spi2); + } + + #[inline(always)] + fn spi_num(&self) -> u8 { + 2 + } +} + +#[cfg(any(esp32))] +impl Instance for crate::peripherals::SPI2 { + #[inline(always)] + fn register_block(&self) -> &RegisterBlock { + self + } + + #[inline(always)] + fn sclk_signal(&self) -> InputSignal { + InputSignal::HSPICLK + } + + #[inline(always)] + fn mosi_signal(&self) -> InputSignal { + InputSignal::HSPID + } + + #[inline(always)] + fn miso_signal(&self) -> OutputSignal { + OutputSignal::HSPIQ + } + + #[inline(always)] + fn cs_signal(&self) -> InputSignal { + InputSignal::HSPICS0 + } + + #[inline(always)] + fn enable_peripheral(&self) { + PeripheralClockControl::enable(crate::system::Peripheral::Spi2); + } + + #[inline(always)] + fn spi_num(&self) -> u8 { + 2 + } +} + +#[cfg(any(esp32))] +impl Instance for crate::peripherals::SPI3 { + #[inline(always)] + fn register_block(&self) -> &RegisterBlock { + self + } + + #[inline(always)] + fn sclk_signal(&self) -> InputSignal { + InputSignal::VSPICLK + } + + #[inline(always)] + fn mosi_signal(&self) -> InputSignal { + InputSignal::VSPID + } + + #[inline(always)] + fn miso_signal(&self) -> OutputSignal { + OutputSignal::VSPIQ + } + + #[inline(always)] + fn cs_signal(&self) -> InputSignal { + InputSignal::VSPICS0 + } + + #[inline(always)] + fn enable_peripheral(&self) { + PeripheralClockControl::enable(crate::system::Peripheral::Spi3) + } + + #[inline(always)] + fn spi_num(&self) -> u8 { + 3 + } +} + +#[cfg(any(esp32s2, esp32s3))] +impl Instance for crate::peripherals::SPI2 { + #[inline(always)] + fn register_block(&self) -> &RegisterBlock { + self + } + + #[inline(always)] + fn sclk_signal(&self) -> InputSignal { + InputSignal::FSPICLK + } + + #[inline(always)] + fn mosi_signal(&self) -> InputSignal { + InputSignal::FSPID + } + + #[inline(always)] + fn miso_signal(&self) -> OutputSignal { + OutputSignal::FSPIQ + } + + #[inline(always)] + fn cs_signal(&self) -> InputSignal { + InputSignal::FSPICS0 + } + + #[inline(always)] + fn enable_peripheral(&self) { + PeripheralClockControl::enable(crate::system::Peripheral::Spi2) + } + + #[inline(always)] + fn spi_num(&self) -> u8 { + 2 + } +} + +#[cfg(any(esp32s2, esp32s3))] +impl Instance for crate::peripherals::SPI3 { + #[inline(always)] + fn register_block(&self) -> &RegisterBlock { + self + } + + #[inline(always)] + fn sclk_signal(&self) -> InputSignal { + InputSignal::SPI3_CLK + } + + #[inline(always)] + fn mosi_signal(&self) -> InputSignal { + InputSignal::SPI3_D + } + + #[inline(always)] + fn miso_signal(&self) -> OutputSignal { + OutputSignal::SPI3_Q + } + + #[inline(always)] + fn cs_signal(&self) -> InputSignal { + InputSignal::SPI3_CS0 + } + + #[inline(always)] + fn enable_peripheral(&self) { + PeripheralClockControl::enable(crate::system::Peripheral::Spi3) + } + + #[inline(always)] + fn spi_num(&self) -> u8 { + 3 + } +} + +pub trait Spi2Instance {} + +#[cfg(any(esp32, esp32s2, esp32s3))] +pub trait Spi3Instance {} + +impl Spi2Instance for crate::peripherals::SPI2 {} + +#[cfg(any(esp32, esp32s2, esp32s3))] +impl Spi3Instance for crate::peripherals::SPI3 {} diff --git a/esp32-hal/examples/spi_loopback_dma.rs b/esp32-hal/examples/spi_loopback_dma.rs index 12665c44f..224e13a7f 100644 --- a/esp32-hal/examples/spi_loopback_dma.rs +++ b/esp32-hal/examples/spi_loopback_dma.rs @@ -82,12 +82,12 @@ fn main() -> ! { let transfer = spi.dma_transfer(send, receive).unwrap(); // here we could do something else while DMA transfer is in progress - let mut i = 0; + let mut n = 0; // Check is_done until the transfer is almost done (32000 bytes at 100kHz is // 2.56 seconds), then move to wait(). - while !transfer.is_done() && i < 10 { + while !transfer.is_done() && n < 10 { delay.delay_ms(250u32); - i += 1; + n += 1; } // the buffers and spi is moved into the transfer and we can get it back via // `wait` diff --git a/esp32c2-hal/examples/spi_loopback_dma.rs b/esp32c2-hal/examples/spi_loopback_dma.rs index be4adc832..a0466e407 100644 --- a/esp32c2-hal/examples/spi_loopback_dma.rs +++ b/esp32c2-hal/examples/spi_loopback_dma.rs @@ -82,12 +82,12 @@ fn main() -> ! { let transfer = spi.dma_transfer(send, receive).unwrap(); // here we could do something else while DMA transfer is in progress - let mut i = 0; + let mut n = 0; // Check is_done until the transfer is almost done (32000 bytes at 100kHz is // 2.56 seconds), then move to wait(). - while !transfer.is_done() && i < 10 { + while !transfer.is_done() && n < 10 { delay.delay_ms(250u32); - i += 1; + n += 1; } // the buffers and spi is moved into the transfer and we can get it back via // `wait` diff --git a/esp32c2-hal/examples/spi_slave_dma.rs b/esp32c2-hal/examples/spi_slave_dma.rs new file mode 100644 index 000000000..79bc9c4fa --- /dev/null +++ b/esp32c2-hal/examples/spi_slave_dma.rs @@ -0,0 +1,170 @@ +//! SPI slave loopback test using DMA +//! +//! Following pins are used for the slave: +//! SCLK GPIO6 +//! MISO GPIO2 +//! MOSI GPIO7 +//! CS GPIO10 +//! +//! Following pins are used for the (bitbang) master: +//! SCLK GPIO5 +//! MISO GPIO1 +//! MOSI GPIO8 +//! CS GPIO9 +//! +//! Depending on your target and the board you are using you have to change the +//! pins. +//! +//! This example transfers data via SPI. +//! Connect corresponding master and slave pins to see the outgoing data is read +//! as incoming data. The master-side pins are chosen to make these connections +//! easy for the barebones ESP32C3 chip; all are immediate neighbors of the +//! slave-side pins except SCLK. SCLK is between MOSI and VDD3P3_RTC on the +//! barebones ESP32C3, so no immediate neighbor is available. + +#![no_std] +#![no_main] + +use esp32c2_hal::{ + clock::ClockControl, + dma::DmaPriority, + gdma::Gdma, + gpio::IO, + peripherals::Peripherals, + prelude::*, + spi_slave::{Spi, SpiMode}, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. For the ESP32-C2, this includes the Super WDT, + // the RTC WDT, and the TIMG WDT. + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt0 = timer_group0.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let slave_sclk = io.pins.gpio6; + let mut master_sclk = io.pins.gpio5.into_push_pull_output(); + let slave_miso = io.pins.gpio2; + let master_miso = io.pins.gpio1.into_floating_input(); + let slave_mosi = io.pins.gpio7; + let mut master_mosi = io.pins.gpio8.into_push_pull_output(); + let slave_cs = io.pins.gpio10; + let mut master_cs = io.pins.gpio9.into_push_pull_output(); + master_cs.set_high().unwrap(); + master_sclk.set_low().unwrap(); + master_mosi.set_low().unwrap(); + + let dma = Gdma::new(peripherals.DMA); + let dma_channel = dma.channel0; + + let mut descriptors = [0u32; 8 * 3]; + let mut rx_descriptors = [0u32; 8 * 3]; + + let mut spi = Spi::new( + peripherals.SPI2, + slave_sclk, + slave_mosi, + slave_miso, + slave_cs, + SpiMode::Mode0, + ) + .with_dma(dma_channel.configure( + false, + &mut descriptors, + &mut rx_descriptors, + DmaPriority::Priority0, + )); + + let mut delay = Delay::new(&clocks); + + // DMA buffer require a static life-time + let master_send = &mut [0u8; 3200]; + let master_receive = &mut [0u8; 3200]; + let mut slave_send = buffer1(); + let mut slave_receive = buffer2(); + let mut i = 0; + + for (i, v) in master_send.iter_mut().enumerate() { + *v = (i % 255) as u8; + } + for (i, v) in slave_send.iter_mut().enumerate() { + *v = (254 - (i % 255)) as u8; + } + + loop { + master_send[0] = i; + master_send[master_send.len() - 1] = i; + slave_send[0] = i; + slave_send[slave_send.len() - 1] = i; + slave_receive.fill(0xff); + i = i.wrapping_add(1); + + let transfer = spi.dma_transfer(slave_send, slave_receive).unwrap(); + // Bit-bang out the contents of master_send and read into master_receive + // as quickly as manageable. MSB first. Mode 0, so sampled on the rising + // edge and set on the falling edge. + master_cs.set_low().unwrap(); + for (j, v) in master_send.iter().enumerate() { + let mut b = *v; + let mut rb = 0u8; + for _ in 0..8 { + if b & 128 != 0 { + master_mosi.set_high().unwrap(); + } else { + master_mosi.set_low().unwrap(); + } + master_sclk.set_low().unwrap(); + b <<= 1; + rb <<= 1; + // Delay to ensure the SPI peripheral notices the low clock. + // One microsecond is about twice as long as we need to pause, + // but it'll still work. + delay.delay_us(1u32); + master_sclk.set_high().unwrap(); + if master_miso.is_high().unwrap() { + rb |= 1; + } + } + master_receive[j] = rb; + } + master_cs.set_high().unwrap(); + master_sclk.set_low().unwrap(); + // the buffers and spi is moved into the transfer and we can get it back via + // `wait` + (slave_receive, slave_send, spi) = transfer.wait().unwrap(); + println!( + "slave got {:x?} .. {:x?}, master got {:x?} .. {:x?}", + &slave_receive[..10], + &slave_receive[slave_receive.len() - 10..], + &master_receive[..10], + &master_receive[master_receive.len() - 10..] + ); + + delay.delay_ms(250u32); + } +} + +fn buffer1() -> &'static mut [u8; 3200] { + static mut BUFFER: [u8; 3200] = [0u8; 3200]; + unsafe { &mut BUFFER } +} + +fn buffer2() -> &'static mut [u8; 3200] { + static mut BUFFER: [u8; 3200] = [0u8; 3200]; + unsafe { &mut BUFFER } +} diff --git a/esp32c3-hal/examples/spi_loopback_dma.rs b/esp32c3-hal/examples/spi_loopback_dma.rs index bf478929c..44d1c29e2 100644 --- a/esp32c3-hal/examples/spi_loopback_dma.rs +++ b/esp32c3-hal/examples/spi_loopback_dma.rs @@ -82,12 +82,12 @@ fn main() -> ! { let transfer = spi.dma_transfer(send, receive).unwrap(); // here we could do something else while DMA transfer is in progress - let mut i = 0; + let mut n = 0; // Check is_done until the transfer is almost done (32000 bytes at 100kHz is // 2.56 seconds), then move to wait(). - while !transfer.is_done() && i < 10 { + while !transfer.is_done() && n < 10 { delay.delay_ms(250u32); - i += 1; + n += 1; } // the buffers and spi is moved into the transfer and we can get it back via // `wait` diff --git a/esp32c3-hal/examples/spi_slave_dma.rs b/esp32c3-hal/examples/spi_slave_dma.rs new file mode 100644 index 000000000..52e19e55b --- /dev/null +++ b/esp32c3-hal/examples/spi_slave_dma.rs @@ -0,0 +1,172 @@ +//! SPI slave loopback test using DMA +//! +//! Following pins are used for the slave: +//! SCLK GPIO6 +//! MISO GPIO2 +//! MOSI GPIO7 +//! CS GPIO10 +//! +//! Following pins are used for the (bitbang) master: +//! SCLK GPIO5 +//! MISO GPIO1 +//! MOSI GPIO8 +//! CS GPIO9 +//! +//! Depending on your target and the board you are using you have to change the +//! pins. +//! +//! This example transfers data via SPI. +//! Connect corresponding master and slave pins to see the outgoing data is read +//! as incoming data. The master-side pins are chosen to make these connections +//! easy for the barebones ESP32C3 chip; all are immediate neighbors of the +//! slave-side pins except SCLK. SCLK is between MOSI and VDD3P3_RTC on the +//! barebones ESP32C3, so no immediate neighbor is available. + +#![no_std] +#![no_main] + +use esp32c3_hal::{ + clock::ClockControl, + dma::DmaPriority, + gdma::Gdma, + gpio::IO, + peripherals::Peripherals, + prelude::*, + spi_slave::{Spi, SpiMode}, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. For the ESP32-C3, this includes the Super WDT, + // the RTC WDT, and the TIMG WDTs. + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt0 = timer_group0.wdt; + let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks); + let mut wdt1 = timer_group1.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + wdt1.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let slave_sclk = io.pins.gpio6; + let mut master_sclk = io.pins.gpio5.into_push_pull_output(); + let slave_miso = io.pins.gpio2; + let master_miso = io.pins.gpio1.into_floating_input(); + let slave_mosi = io.pins.gpio7; + let mut master_mosi = io.pins.gpio8.into_push_pull_output(); + let slave_cs = io.pins.gpio10; + let mut master_cs = io.pins.gpio9.into_push_pull_output(); + master_cs.set_high().unwrap(); + master_sclk.set_low().unwrap(); + master_mosi.set_low().unwrap(); + + let dma = Gdma::new(peripherals.DMA); + let dma_channel = dma.channel0; + + let mut descriptors = [0u32; 8 * 3]; + let mut rx_descriptors = [0u32; 8 * 3]; + + let mut spi = Spi::new( + peripherals.SPI2, + slave_sclk, + slave_mosi, + slave_miso, + slave_cs, + SpiMode::Mode0, + ) + .with_dma(dma_channel.configure( + false, + &mut descriptors, + &mut rx_descriptors, + DmaPriority::Priority0, + )); + + let mut delay = Delay::new(&clocks); + + // DMA buffer require a static life-time + let master_send = &mut [0u8; 32000]; + let master_receive = &mut [0u8; 32000]; + let mut slave_send = buffer1(); + let mut slave_receive = buffer2(); + let mut i = 0; + + for (i, v) in master_send.iter_mut().enumerate() { + *v = (i % 255) as u8; + } + for (i, v) in slave_send.iter_mut().enumerate() { + *v = (254 - (i % 255)) as u8; + } + + loop { + master_send[0] = i; + master_send[master_send.len() - 1] = i; + slave_send[0] = i; + slave_send[slave_send.len() - 1] = i; + slave_receive.fill(0xff); + i = i.wrapping_add(1); + + let transfer = spi.dma_transfer(slave_send, slave_receive).unwrap(); + // Bit-bang out the contents of master_send and read into master_receive + // as quickly as manageable. MSB first. Mode 0, so sampled on the rising + // edge and set on the falling edge. + master_cs.set_low().unwrap(); + for (j, v) in master_send.iter().enumerate() { + let mut b = *v; + let mut rb = 0u8; + for _ in 0..8 { + if b & 128 != 0 { + master_mosi.set_high().unwrap(); + } else { + master_mosi.set_low().unwrap(); + } + master_sclk.set_low().unwrap(); + b <<= 1; + rb <<= 1; + // NB: adding about 24 NOPs here makes the clock's duty cycle + // run at about 50% ... but we don't strictly need the delay, + // either. + master_sclk.set_high().unwrap(); + if master_miso.is_high().unwrap() { + rb |= 1; + } + } + master_receive[j] = rb; + } + master_cs.set_high().unwrap(); + master_sclk.set_low().unwrap(); + // the buffers and spi is moved into the transfer and we can get it back via + // `wait` + (slave_receive, slave_send, spi) = transfer.wait().unwrap(); + println!( + "slave got {:x?} .. {:x?}, master got {:x?} .. {:x?}", + &slave_receive[..10], + &slave_receive[slave_receive.len() - 10..], + &master_receive[..10], + &master_receive[master_receive.len() - 10..] + ); + + delay.delay_ms(250u32); + } +} + +fn buffer1() -> &'static mut [u8; 32000] { + static mut BUFFER: [u8; 32000] = [0u8; 32000]; + unsafe { &mut BUFFER } +} + +fn buffer2() -> &'static mut [u8; 32000] { + static mut BUFFER: [u8; 32000] = [0u8; 32000]; + unsafe { &mut BUFFER } +} diff --git a/esp32c6-hal/examples/spi_loopback_dma.rs b/esp32c6-hal/examples/spi_loopback_dma.rs index 336a451d5..89427ef84 100644 --- a/esp32c6-hal/examples/spi_loopback_dma.rs +++ b/esp32c6-hal/examples/spi_loopback_dma.rs @@ -82,12 +82,12 @@ fn main() -> ! { let transfer = spi.dma_transfer(send, receive).unwrap(); // here we could do something else while DMA transfer is in progress - let mut i = 0; + let mut n = 0; // Check is_done until the transfer is almost done (32000 bytes at 100kHz is // 2.56 seconds), then move to wait(). - while !transfer.is_done() && i < 10 { + while !transfer.is_done() && n < 10 { delay.delay_ms(250u32); - i += 1; + n += 1; } // the buffers and spi is moved into the transfer and we can get it back via // `wait` diff --git a/esp32c6-hal/examples/spi_slave_dma.rs b/esp32c6-hal/examples/spi_slave_dma.rs new file mode 100644 index 000000000..e150aa001 --- /dev/null +++ b/esp32c6-hal/examples/spi_slave_dma.rs @@ -0,0 +1,172 @@ +//! SPI slave loopback test using DMA +//! +//! Following pins are used for the slave: +//! SCLK GPIO6 +//! MISO GPIO2 +//! MOSI GPIO7 +//! CS GPIO10 +//! +//! Following pins are used for the (bitbang) master: +//! SCLK GPIO5 +//! MISO GPIO1 +//! MOSI GPIO8 +//! CS GPIO9 +//! +//! Depending on your target and the board you are using you have to change the +//! pins. +//! +//! This example transfers data via SPI. +//! Connect corresponding master and slave pins to see the outgoing data is read +//! as incoming data. The master-side pins are chosen to make these connections +//! easy for the barebones ESP32C3 chip; all are immediate neighbors of the +//! slave-side pins except SCLK. SCLK is between MOSI and VDD3P3_RTC on the +//! barebones ESP32C3, so no immediate neighbor is available. + +#![no_std] +#![no_main] + +use esp32c6_hal::{ + clock::ClockControl, + dma::DmaPriority, + gdma::Gdma, + gpio::IO, + peripherals::Peripherals, + prelude::*, + spi_slave::{Spi, SpiMode}, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. For the ESP32-C3, this includes the Super WDT, + // the RTC WDT, and the TIMG WDTs. + let mut rtc = Rtc::new(peripherals.LP_CLKRST); + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt0 = timer_group0.wdt; + let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks); + let mut wdt1 = timer_group1.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + wdt1.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let slave_sclk = io.pins.gpio6; + let mut master_sclk = io.pins.gpio5.into_push_pull_output(); + let slave_miso = io.pins.gpio2; + let master_miso = io.pins.gpio1.into_floating_input(); + let slave_mosi = io.pins.gpio7; + let mut master_mosi = io.pins.gpio8.into_push_pull_output(); + let slave_cs = io.pins.gpio10; + let mut master_cs = io.pins.gpio9.into_push_pull_output(); + master_cs.set_high().unwrap(); + master_sclk.set_low().unwrap(); + master_mosi.set_low().unwrap(); + + let dma = Gdma::new(peripherals.DMA); + let dma_channel = dma.channel0; + + let mut descriptors = [0u32; 8 * 3]; + let mut rx_descriptors = [0u32; 8 * 3]; + + let mut spi = Spi::new( + peripherals.SPI2, + slave_sclk, + slave_mosi, + slave_miso, + slave_cs, + SpiMode::Mode0, + ) + .with_dma(dma_channel.configure( + false, + &mut descriptors, + &mut rx_descriptors, + DmaPriority::Priority0, + )); + + let mut delay = Delay::new(&clocks); + + // DMA buffer require a static life-time + let master_send = &mut [0u8; 32000]; + let master_receive = &mut [0u8; 32000]; + let mut slave_send = buffer1(); + let mut slave_receive = buffer2(); + let mut i = 0; + + for (i, v) in master_send.iter_mut().enumerate() { + *v = (i % 255) as u8; + } + for (i, v) in slave_send.iter_mut().enumerate() { + *v = (254 - (i % 255)) as u8; + } + + loop { + master_send[0] = i; + master_send[master_send.len() - 1] = i; + slave_send[0] = i; + slave_send[slave_send.len() - 1] = i; + slave_receive.fill(0xff); + i = i.wrapping_add(1); + + let transfer = spi.dma_transfer(slave_send, slave_receive).unwrap(); + // Bit-bang out the contents of master_send and read into master_receive + // as quickly as manageable. MSB first. Mode 0, so sampled on the rising + // edge and set on the falling edge. + master_cs.set_low().unwrap(); + for (j, v) in master_send.iter().enumerate() { + let mut b = *v; + let mut rb = 0u8; + for _ in 0..8 { + if b & 128 != 0 { + master_mosi.set_high().unwrap(); + } else { + master_mosi.set_low().unwrap(); + } + master_sclk.set_low().unwrap(); + b <<= 1; + rb <<= 1; + // NB: adding about 24 NOPs here makes the clock's duty cycle + // run at about 50% ... but we don't strictly need the delay, + // either. + master_sclk.set_high().unwrap(); + if master_miso.is_high().unwrap() { + rb |= 1; + } + } + master_receive[j] = rb; + } + master_cs.set_high().unwrap(); + master_sclk.set_low().unwrap(); + // the buffers and spi is moved into the transfer and we can get it back via + // `wait` + (slave_receive, slave_send, spi) = transfer.wait().unwrap(); + println!( + "slave got {:x?} .. {:x?}, master got {:x?} .. {:x?}", + &slave_receive[..10], + &slave_receive[slave_receive.len() - 10..], + &master_receive[..10], + &master_receive[master_receive.len() - 10..] + ); + + delay.delay_ms(250u32); + } +} + +fn buffer1() -> &'static mut [u8; 32000] { + static mut BUFFER: [u8; 32000] = [0u8; 32000]; + unsafe { &mut BUFFER } +} + +fn buffer2() -> &'static mut [u8; 32000] { + static mut BUFFER: [u8; 32000] = [0u8; 32000]; + unsafe { &mut BUFFER } +} diff --git a/esp32h2-hal/examples/spi_slave_dma.rs b/esp32h2-hal/examples/spi_slave_dma.rs new file mode 100644 index 000000000..b6b61633c --- /dev/null +++ b/esp32h2-hal/examples/spi_slave_dma.rs @@ -0,0 +1,172 @@ +//! SPI slave loopback test using DMA +//! +//! Following pins are used for the slave: +//! SCLK GPIO6 +//! MISO GPIO2 +//! MOSI GPIO7 +//! CS GPIO10 +//! +//! Following pins are used for the (bitbang) master: +//! SCLK GPIO5 +//! MISO GPIO1 +//! MOSI GPIO8 +//! CS GPIO9 +//! +//! Depending on your target and the board you are using you have to change the +//! pins. +//! +//! This example transfers data via SPI. +//! Connect corresponding master and slave pins to see the outgoing data is read +//! as incoming data. The master-side pins are chosen to make these connections +//! easy for the barebones ESP32C3 chip; all are immediate neighbors of the +//! slave-side pins except SCLK. SCLK is between MOSI and VDD3P3_RTC on the +//! barebones ESP32C3, so no immediate neighbor is available. + +#![no_std] +#![no_main] + +use esp32h2_hal::{ + clock::ClockControl, + dma::DmaPriority, + gdma::Gdma, + gpio::IO, + peripherals::Peripherals, + prelude::*, + spi_slave::{Spi, SpiMode}, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. For the ESP32-C6, this includes the Super WDT, + // the RTC WDT, and the TIMG WDTs. + let mut rtc = Rtc::new(peripherals.LP_CLKRST); + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt0 = timer_group0.wdt; + let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks); + let mut wdt1 = timer_group1.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + wdt1.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let slave_sclk = io.pins.gpio4; + let mut master_sclk = io.pins.gpio5.into_push_pull_output(); + let slave_miso = io.pins.gpio2; + let master_miso = io.pins.gpio1.into_floating_input(); + let slave_mosi = io.pins.gpio12; + let mut master_mosi = io.pins.gpio11.into_push_pull_output(); + let slave_cs = io.pins.gpio10; + let mut master_cs = io.pins.gpio9.into_push_pull_output(); + master_cs.set_high().unwrap(); + master_sclk.set_low().unwrap(); + master_mosi.set_low().unwrap(); + + let dma = Gdma::new(peripherals.DMA); + let dma_channel = dma.channel0; + + let mut descriptors = [0u32; 8 * 3]; + let mut rx_descriptors = [0u32; 8 * 3]; + + let mut spi = Spi::new( + peripherals.SPI2, + slave_sclk, + slave_mosi, + slave_miso, + slave_cs, + SpiMode::Mode0, + ) + .with_dma(dma_channel.configure( + false, + &mut descriptors, + &mut rx_descriptors, + DmaPriority::Priority0, + )); + + let mut delay = Delay::new(&clocks); + + // DMA buffer require a static life-time + let master_send = &mut [0u8; 32000]; + let master_receive = &mut [0u8; 32000]; + let mut slave_send = buffer1(); + let mut slave_receive = buffer2(); + let mut i = 0; + + for (i, v) in master_send.iter_mut().enumerate() { + *v = (i % 255) as u8; + } + for (i, v) in slave_send.iter_mut().enumerate() { + *v = (254 - (i % 255)) as u8; + } + + loop { + master_send[0] = i; + master_send[master_send.len() - 1] = i; + slave_send[0] = i; + slave_send[slave_send.len() - 1] = i; + slave_receive.fill(0xff); + i = i.wrapping_add(1); + + let transfer = spi.dma_transfer(slave_send, slave_receive).unwrap(); + // Bit-bang out the contents of master_send and read into master_receive + // as quickly as manageable. MSB first. Mode 0, so sampled on the rising + // edge and set on the falling edge. + master_cs.set_low().unwrap(); + for (j, v) in master_send.iter().enumerate() { + let mut b = *v; + let mut rb = 0u8; + for _ in 0..8 { + if b & 128 != 0 { + master_mosi.set_high().unwrap(); + } else { + master_mosi.set_low().unwrap(); + } + master_sclk.set_low().unwrap(); + b <<= 1; + rb <<= 1; + // NB: adding about 24 NOPs here makes the clock's duty cycle + // run at about 50% ... but we don't strictly need the delay, + // either. + master_sclk.set_high().unwrap(); + if master_miso.is_high().unwrap() { + rb |= 1; + } + } + master_receive[j] = rb; + } + master_cs.set_high().unwrap(); + master_sclk.set_low().unwrap(); + // the buffers and spi is moved into the transfer and we can get it back via + // `wait` + (slave_receive, slave_send, spi) = transfer.wait().unwrap(); + println!( + "slave got {:x?} .. {:x?}, master got {:x?} .. {:x?}", + &slave_receive[..10], + &slave_receive[slave_receive.len() - 10..], + &master_receive[..10], + &master_receive[master_receive.len() - 10..] + ); + + delay.delay_ms(250u32); + } +} + +fn buffer1() -> &'static mut [u8; 32000] { + static mut BUFFER: [u8; 32000] = [0u8; 32000]; + unsafe { &mut BUFFER } +} + +fn buffer2() -> &'static mut [u8; 32000] { + static mut BUFFER: [u8; 32000] = [0u8; 32000]; + unsafe { &mut BUFFER } +} diff --git a/esp32s2-hal/examples/spi_loopback_dma.rs b/esp32s2-hal/examples/spi_loopback_dma.rs index 8e52f6c27..c5f6826c9 100644 --- a/esp32s2-hal/examples/spi_loopback_dma.rs +++ b/esp32s2-hal/examples/spi_loopback_dma.rs @@ -82,12 +82,12 @@ fn main() -> ! { let transfer = spi.dma_transfer(send, receive).unwrap(); // here we could do something else while DMA transfer is in progress - let mut i = 0; + let mut n = 0; // Check is_done until the transfer is almost done (32000 bytes at 100kHz is // 2.56 seconds), then move to wait(). - while !transfer.is_done() && i < 10 { + while !transfer.is_done() && n < 10 { delay.delay_ms(250u32); - i += 1; + n += 1; } // the buffers and spi is moved into the transfer and we can get it back via // `wait` diff --git a/esp32s3-hal/examples/spi_loopback_dma.rs b/esp32s3-hal/examples/spi_loopback_dma.rs index 95aee5d6a..b5d801f3d 100644 --- a/esp32s3-hal/examples/spi_loopback_dma.rs +++ b/esp32s3-hal/examples/spi_loopback_dma.rs @@ -82,12 +82,12 @@ fn main() -> ! { let transfer = spi.dma_transfer(send, receive).unwrap(); // here we could do something else while DMA transfer is in progress - let mut i = 0; + let mut n = 0; // Check is_done until the transfer is almost done (32000 bytes at 100kHz is // 2.56 seconds), then move to wait(). - while !transfer.is_done() && i < 10 { + while !transfer.is_done() && n < 10 { delay.delay_ms(250u32); - i += 1; + n += 1; } // the buffers and spi is moved into the transfer and we can get it back via // `wait` diff --git a/esp32s3-hal/examples/spi_slave_dma.rs b/esp32s3-hal/examples/spi_slave_dma.rs new file mode 100644 index 000000000..18ecc4d4e --- /dev/null +++ b/esp32s3-hal/examples/spi_slave_dma.rs @@ -0,0 +1,172 @@ +//! SPI slave loopback test using DMA +//! +//! Following pins are used for the slave: +//! SCLK GPIO6 +//! MISO GPIO2 +//! MOSI GPIO7 +//! CS GPIO10 +//! +//! Following pins are used for the (bitbang) master: +//! SCLK GPIO5 +//! MISO GPIO1 +//! MOSI GPIO8 +//! CS GPIO9 +//! +//! Depending on your target and the board you are using you have to change the +//! pins. +//! +//! This example transfers data via SPI. +//! Connect corresponding master and slave pins to see the outgoing data is read +//! as incoming data. The master-side pins are chosen to make these connections +//! easy for the barebones ESP32C3 chip; all are immediate neighbors of the +//! slave-side pins except SCLK. SCLK is between MOSI and VDD3P3_RTC on the +//! barebones ESP32C3, so no immediate neighbor is available. + +#![no_std] +#![no_main] + +use esp32s3_hal::{ + clock::ClockControl, + dma::DmaPriority, + gdma::Gdma, + gpio::IO, + peripherals::Peripherals, + prelude::*, + spi_slave::{Spi, SpiMode}, + timer::TimerGroup, + Delay, + Rtc, +}; +use esp_backtrace as _; +use esp_println::println; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take(); + let system = peripherals.SYSTEM.split(); + let clocks = ClockControl::boot_defaults(system.clock_control).freeze(); + + // Disable the watchdog timers. For the ESP32-C3, this includes the Super WDT, + // the RTC WDT, and the TIMG WDTs. + let mut rtc = Rtc::new(peripherals.RTC_CNTL); + let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks); + let mut wdt0 = timer_group0.wdt; + let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks); + let mut wdt1 = timer_group1.wdt; + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + wdt1.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let slave_sclk = io.pins.gpio6; + let mut master_sclk = io.pins.gpio5.into_push_pull_output(); + let slave_miso = io.pins.gpio2; + let master_miso = io.pins.gpio1.into_floating_input(); + let slave_mosi = io.pins.gpio7; + let mut master_mosi = io.pins.gpio8.into_push_pull_output(); + let slave_cs = io.pins.gpio10; + let mut master_cs = io.pins.gpio9.into_push_pull_output(); + master_cs.set_high().unwrap(); + master_sclk.set_low().unwrap(); + master_mosi.set_low().unwrap(); + + let dma = Gdma::new(peripherals.DMA); + let dma_channel = dma.channel0; + + let mut descriptors = [0u32; 8 * 3]; + let mut rx_descriptors = [0u32; 8 * 3]; + + let mut spi = Spi::new( + peripherals.SPI2, + slave_sclk, + slave_mosi, + slave_miso, + slave_cs, + SpiMode::Mode0, + ) + .with_dma(dma_channel.configure( + false, + &mut descriptors, + &mut rx_descriptors, + DmaPriority::Priority0, + )); + + let mut delay = Delay::new(&clocks); + + // DMA buffer require a static life-time + let master_send = &mut [0u8; 32000]; + let master_receive = &mut [0u8; 32000]; + let mut slave_send = buffer1(); + let mut slave_receive = buffer2(); + let mut i = 0; + + for (i, v) in master_send.iter_mut().enumerate() { + *v = (i % 255) as u8; + } + for (i, v) in slave_send.iter_mut().enumerate() { + *v = (254 - (i % 255)) as u8; + } + + loop { + master_send[0] = i; + master_send[master_send.len() - 1] = i; + slave_send[0] = i; + slave_send[slave_send.len() - 1] = i; + slave_receive.fill(0xff); + i = i.wrapping_add(1); + + let transfer = spi.dma_transfer(slave_send, slave_receive).unwrap(); + // Bit-bang out the contents of master_send and read into master_receive + // as quickly as manageable. MSB first. Mode 0, so sampled on the rising + // edge and set on the falling edge. + master_cs.set_low().unwrap(); + for (j, v) in master_send.iter().enumerate() { + let mut b = *v; + let mut rb = 0u8; + for _ in 0..8 { + if b & 128 != 0 { + master_mosi.set_high().unwrap(); + } else { + master_mosi.set_low().unwrap(); + } + master_sclk.set_low().unwrap(); + b <<= 1; + rb <<= 1; + // NB: adding about 24 NOPs here makes the clock's duty cycle + // run at about 50% ... but we don't strictly need the delay, + // either. + master_sclk.set_high().unwrap(); + if master_miso.is_high().unwrap() { + rb |= 1; + } + } + master_receive[j] = rb; + } + master_cs.set_high().unwrap(); + master_sclk.set_low().unwrap(); + // the buffers and spi is moved into the transfer and we can get it back via + // `wait` + (slave_receive, slave_send, spi) = transfer.wait().unwrap(); + println!( + "slave got {:x?} .. {:x?}, master got {:x?} .. {:x?}", + &slave_receive[..10], + &slave_receive[slave_receive.len() - 10..], + &master_receive[..10], + &master_receive[master_receive.len() - 10..] + ); + + delay.delay_ms(250u32); + } +} + +fn buffer1() -> &'static mut [u8; 32000] { + static mut BUFFER: [u8; 32000] = [0u8; 32000]; + unsafe { &mut BUFFER } +} + +fn buffer2() -> &'static mut [u8; 32000] { + static mut BUFFER: [u8; 32000] = [0u8; 32000]; + unsafe { &mut BUFFER } +}