diff --git a/CHANGELOG.md b/CHANGELOG.md index 3df2c6d5dae..6cf43880f89 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 ffcb855b895..681fb91bd61 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 5648299e378..3a5ab9ed7a0 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 31d5a11e035..e512c616942 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 ed3cf315f61..21e4162543d 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 731136e70d0..c8da196acd3 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 8c529f903d6..889bcc96e1e 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 cca0eb0c5bd..b0c166daf47 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 00000000000..039af9b8920 --- /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 12665c44fbe..224e13a7fb3 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 be4adc83292..a0466e40786 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 00000000000..79bc9c4fa84 --- /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 bf478929c56..44d1c29e225 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 00000000000..52e19e55b80 --- /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 336a451d59d..89427ef8493 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 00000000000..e150aa001db --- /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 00000000000..b6b61633c00 --- /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 8e52f6c275a..c5f6826c9f7 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 95aee5d6a50..b5d801f3dfa 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 00000000000..18ecc4d4ef1 --- /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 } +}