diff --git a/esp-hal-common/src/spi.rs b/esp-hal-common/src/spi.rs index 07eaaea86..6a7cb8638 100644 --- a/esp-hal-common/src/spi.rs +++ b/esp-hal-common/src/spi.rs @@ -22,18 +22,23 @@ //! ); //! ``` -use core::convert::Infallible; - -use fugit::HertzU32; - use crate::{ clock::Clocks, pac::spi2::RegisterBlock, system::PeripheralClockControl, types::{InputSignal, OutputSignal}, - InputPin, - OutputPin, + InputPin, OutputPin, }; +use core::convert::Infallible; +use fugit::HertzU32; + +/// The size of the FIFO buffer for SPI +#[cfg(not(feature = "esp32s2"))] +const FIFO_SIZE: usize = 64; +#[cfg(feature = "esp32s2")] +const FIFO_SIZE: usize = 72; +/// Padding byte for empty write transfers +const EMPTY_WRITE_PAD: u8 = 0x00u8; #[derive(Debug, Clone, Copy)] pub enum SpiMode { @@ -200,41 +205,129 @@ where } #[cfg(feature = "eh1")] -impl embedded_hal_1::spi::ErrorType for Spi { - type Error = Infallible; -} +pub use ehal1::*; #[cfg(feature = "eh1")] -impl embedded_hal_1::spi::nb::FullDuplex for Spi -where - T: Instance, -{ - fn read(&mut self) -> nb::Result { - self.spi.read_byte() +mod ehal1 { + use super::*; + use embedded_hal_1::spi::blocking::{SpiBus, SpiBusFlush, SpiBusRead, SpiBusWrite}; + use embedded_hal_1::spi::nb::FullDuplex; + + + impl embedded_hal_1::spi::ErrorType for Spi { + type Error = Infallible; } - fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { - self.spi.write_byte(word) - } -} + impl FullDuplex for Spi + where + T: Instance, + { + fn read(&mut self) -> nb::Result { + self.spi.read_byte() + } -#[cfg(feature = "eh1")] -impl embedded_hal_1::spi::blocking::SpiBusWrite for Spi -where - T: Instance, -{ - fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { - self.spi.send_bytes(words) + fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { + self.spi.write_byte(word) + } } -} -#[cfg(feature = "eh1")] -impl embedded_hal_1::spi::blocking::SpiBusFlush for Spi -where - T: Instance, -{ - fn flush(&mut self) -> Result<(), Self::Error> { - self.spi.flush() + impl SpiBusWrite for Spi + where + T: Instance, + { + /// See also: [`write_bytes`]. + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.spi.write_bytes(words) + } + } + + impl SpiBusRead for Spi + where + T: Instance, + { + /// See also: [`read_bytes`]. + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.spi.read_bytes(words) + } + } + + impl SpiBus for Spi + where + T: Instance, + { + /// Write out data from `write`, read response into `read`. + /// + /// `read` and `write` are allowed to have different lengths. If `write` is longer, all + /// other bytes received are discarded. If `read` is longer, [`EMPTY_WRITE_PAD`] is written + /// out as necessary until enough bytes have been read. Reading and writing happens + /// simultaneously. + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + // Optimizations + if read.len() == 0 { + SpiBusWrite::write(self, write)?; + } else if write.len() == 0 { + SpiBusRead::read(self, read)?; + } + + let mut write_from = 0; + let mut read_from = 0; + + loop { + // How many bytes we write in this chunk + let write_inc = core::cmp::min(FIFO_SIZE, write.len() - write_from); + let write_to = write_from + write_inc; + // How many bytes we read in this chunk + let read_inc = core::cmp::min(FIFO_SIZE, read.len() - read_from); + let read_to = read_from + read_inc; + + if (write_inc == 0) && (read_inc == 0) { + break; + } + + if write_to < read_to { + // Read more than we write, must pad writing part with zeros + let mut empty = [EMPTY_WRITE_PAD; FIFO_SIZE]; + empty[0..write_inc].copy_from_slice(&write[write_from..write_to]); + SpiBusWrite::write(self, &empty)?; + } else { + SpiBusWrite::write(self, &write[write_from..write_to])?; + } + + SpiBusFlush::flush(self)?; + + if read_inc > 0 { + self.spi.read_bytes_from_fifo(&mut read[read_from..read_to])?; + } + + write_from = write_to; + read_from = read_to; + } + Ok(()) + } + + /// Transfer data in place. + /// + /// Writes data from `words` out on the bus and stores the reply into `words`. A convenient + /// wrapper around [`write`](SpiBusWrite::write), [`flush`](SpiBusFlush::flush) and + /// [`read`](SpiBusRead::read). + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + // Since we have the traits so neatly implemented above, use them! + for chunk in words.chunks_mut(FIFO_SIZE) { + SpiBusWrite::write(self, chunk)?; + SpiBusFlush::flush(self)?; + self.spi.read_bytes_from_fifo(chunk)?; + } + Ok(()) + } + } + + impl SpiBusFlush for Spi + where + T: Instance, + { + fn flush(&mut self) -> Result<(), Self::Error> { + self.spi.flush() + } } } @@ -308,12 +401,12 @@ pub trait Instance { reg_val = 1 << 31; } else { /* For best duty cycle resolution, we want n to be as close to 32 as - * possible, but we also need a pre/n combo that gets us as close as - * possible to the intended frequency. To do this, we bruteforce n and - * calculate the best pre to go along with that. If there's a choice - * between pre/n combos that give the same result, use the one with the - * higher n. - */ + * possible, but we also need a pre/n combo that gets us as close as + * possible to the intended frequency. To do this, we bruteforce n and + * calculate the best pre to go along with that. If there's a choice + * between pre/n combos that give the same result, use the one with the + * higher n. + */ let mut pre: i32; let mut bestn: i32 = -1; @@ -322,15 +415,16 @@ pub trait Instance { let mut errval: i32; /* Start at n = 2. We need to be able to set h/l so we have at least - * one high and one low pulse. - */ + * one high and one low pulse. + */ - for n in 2..64 { + for n in 2..64 { /* Effectively, this does: - * pre = round((APB_CLK_FREQ / n) / frequency) - */ + * pre = round((APB_CLK_FREQ / n) / frequency) + */ - pre = ((apb_clk_freq.raw() as i32/ n) + (frequency.raw() as i32 / 2)) / frequency.raw() as i32; + pre = ((apb_clk_freq.raw() as i32 / n) + (frequency.raw() as i32 / 2)) + / frequency.raw() as i32; if pre <= 0 { pre = 1; @@ -340,7 +434,9 @@ pub trait Instance { pre = 16; } - errval = (apb_clk_freq.raw() as i32 / (pre as i32 * n as i32) - frequency.raw() as i32).abs(); + errval = (apb_clk_freq.raw() as i32 / (pre as i32 * n as i32) + - frequency.raw() as i32) + .abs(); if bestn == -1 || errval <= besterr { besterr = errval; bestn = n as i32; @@ -353,18 +449,18 @@ pub trait Instance { let l: i32 = n; /* Effectively, this does: - * h = round((duty_cycle * n) / 256) - */ + * h = round((duty_cycle * n) / 256) + */ let mut h: i32 = (duty_cycle * n + 127) / 256; if h <= 0 { h = 1; } - reg_val = (l as u32 - 1) | - ((h as u32 - 1) << 6) | - ((n as u32 - 1) << 12) | - ((pre as u32 - 1) << 18); + reg_val = (l as u32 - 1) + | ((h as u32 - 1) << 6) + | ((n as u32 - 1) << 12) + | ((pre as u32 - 1) << 18); } self.register_block() @@ -450,37 +546,33 @@ pub trait Instance { Ok(()) } + /// Write bytes to SPI. + /// + /// Copies the content of `words` in chunks of 64 bytes into the SPI transmission FIFO. If + /// `words` is longer than 64 bytes, multiple sequential transfers are performed. This function + /// will return before all bytes of the last chunk to transmit have been sent to the wire. If + /// you must ensure that the whole messages was written correctly, use + /// [`flush`]. + // FIXME: See below. fn write_bytes(&mut self, words: &[u8]) -> Result<(), Infallible> { - let mut words_mut = [0u8; 256]; - words_mut[0..words.len()].clone_from_slice(&words[0..words.len()]); - - self.transfer(&mut words_mut[0..words.len()])?; - - Ok(()) - } - - fn send_bytes(&mut self, words: &[u8]) -> Result<(), Infallible> { let reg_block = self.register_block(); - let num_chuncks = words.len() / 64; + let num_chunks = words.len() / FIFO_SIZE; - // The fifo has a total of 16 32 bit registers (64 bytes) so the data - // must be chunked and then transmitted - for (i, chunk) in words.chunks(64).enumerate() { + // The fifo has a limited fixed size, so the data must be chunked and then transmitted + for (i, chunk) in words.chunks(FIFO_SIZE).enumerate() { self.configure_datalen(chunk.len() as u32 * 8); - let mut fifo_ptr = reg_block.w0.as_ptr(); - for chunk in chunk.chunks(4) { - let mut u32_as_bytes = [0u8; 4]; - unsafe { - let ptr = u32_as_bytes.as_mut_ptr(); - ptr.copy_from(chunk.as_ptr(), chunk.len()); - } - let reg_val: u32 = u32::from_le_bytes(u32_as_bytes); - - unsafe { - *fifo_ptr = reg_val; - fifo_ptr = fifo_ptr.offset(1); - }; + let fifo_ptr = reg_block.w0.as_ptr(); + unsafe { + // It seems that `copy_nonoverlapping` is significantly faster than regular `copy`, + // by about 20%... ? + core::ptr::copy_nonoverlapping::( + chunk.as_ptr() as *const u32, + fifo_ptr as *mut u32, + // FIXME: Using any other transfer length **does not work**. I don't understand + // why. + FIFO_SIZE / 4, + ); } self.update(); @@ -490,7 +582,7 @@ pub trait Instance { // Wait for all chunks to complete except the last one. // The function is allowed to return before the bus is idle. // see [embedded-hal flushing](https://docs.rs/embedded-hal/1.0.0-alpha.8/embedded_hal/spi/blocking/index.html#flushing) - if i < num_chuncks { + if i < num_chunks { while reg_block.cmd.read().usr().bit_is_set() { // wait } @@ -499,6 +591,54 @@ pub trait Instance { Ok(()) } + /// Read bytes from SPI. + /// + /// Sends out a stuffing byte for every byte to read. This function doesn't perform flushing. + /// If you want to read the response to something you have written before, consider using + /// [`transfer`] instead. + fn read_bytes(&mut self, words: &mut [u8]) -> Result<(), Infallible> { + let empty_array = [EMPTY_WRITE_PAD; FIFO_SIZE]; + + for chunk in words.chunks_mut(FIFO_SIZE) { + self.write_bytes(&empty_array[0..chunk.len()])?; + self.flush()?; + self.read_bytes_from_fifo(chunk)?; + } + Ok(()) + } + + + /// Read received bytes from SPI FIFO. + /// + /// Copies the contents of the SPI receive FIFO into `words`. This function doesn't perform + /// flushing. If you want to read the response to something you have written before, consider + /// using [`transfer`] instead. + // FIXME: Using something like `core::slice::from_raw_parts` and `copy_from_slice` on the + // receive registers works only for the esp32 and esp32c3 varaints. The reason for this is + // unknown. + fn read_bytes_from_fifo(&mut self, words: &mut [u8]) -> Result<(), Infallible> { + let reg_block = self.register_block(); + + for chunk in words.chunks_mut(FIFO_SIZE) { + self.configure_datalen(chunk.len() as u32 * 8); + + let mut fifo_ptr = reg_block.w0.as_ptr(); + for index in (0..chunk.len()).step_by(4) { + let reg_val = unsafe { *fifo_ptr }; + let bytes = reg_val.to_le_bytes(); + + let len = usize::min(chunk.len(), index + 4) - index; + chunk[index..(index + len)].clone_from_slice(&bytes[0..len]); + + unsafe { + fifo_ptr = fifo_ptr.offset(1); + }; + } + } + + Ok(()) + } + // Check if the bus is busy and if it is wait for it to be idle fn flush(&mut self) -> Result<(), Infallible> { let reg_block = self.register_block(); @@ -510,43 +650,10 @@ pub trait Instance { } fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Infallible> { - let reg_block = self.register_block(); - - for chunk in words.chunks_mut(64) { - self.configure_datalen(chunk.len() as u32 * 8); - - let mut fifo_ptr = reg_block.w0.as_ptr(); - for chunk in chunk.chunks(4) { - let mut u32_as_bytes = [0u8; 4]; - u32_as_bytes[0..(chunk.len())].clone_from_slice(chunk); - let reg_val: u32 = u32::from_le_bytes(u32_as_bytes); - - unsafe { - *fifo_ptr = reg_val; - fifo_ptr = fifo_ptr.offset(1); - }; - } - - self.update(); - - reg_block.cmd.modify(|_, w| w.usr().set_bit()); - - while reg_block.cmd.read().usr().bit_is_set() { - // wait - } - - let mut fifo_ptr = reg_block.w0.as_ptr(); - for index in (0..chunk.len()).step_by(4) { - let reg_val = unsafe { *fifo_ptr }; - let bytes = reg_val.to_le_bytes(); - - let len = usize::min(chunk.len(), index + 4) - index; - chunk[index..(index + len)].clone_from_slice(&bytes[0..len]); - - unsafe { - fifo_ptr = fifo_ptr.offset(1); - }; - } + for chunk in words.chunks_mut(FIFO_SIZE) { + self.write_bytes(chunk)?; + self.flush()?; + self.read_bytes_from_fifo(chunk)?; } Ok(words) diff --git a/esp32-hal/Cargo.toml b/esp32-hal/Cargo.toml index b49989f50..c7cfaf7d5 100644 --- a/esp32-hal/Cargo.toml +++ b/esp32-hal/Cargo.toml @@ -53,4 +53,8 @@ vectored = ["esp-hal-common/vectored"] [[example]] name = "hello_rgb" -required-features = ["smartled"] \ No newline at end of file +required-features = ["smartled"] + +[[example]] +name = "spi_eh1_loopback" +required-features = ["eh1"] diff --git a/esp32-hal/examples/spi_eh1_loopback.rs b/esp32-hal/examples/spi_eh1_loopback.rs new file mode 100644 index 000000000..5bdfa9b31 --- /dev/null +++ b/esp32-hal/examples/spi_eh1_loopback.rs @@ -0,0 +1,127 @@ +//! SPI loopback test +//! +//! Folowing pins are used: +//! SCLK GPIO19 +//! MISO GPIO25 +//! MOSI GPIO23 +//! CS GPIO22 +//! +//! Depending on your target and the board you are using you have to change the +//! pins. +//! +//! This example transfers data via SPI. +//! Connect MISO and MOSI pins to see the outgoing data is read as incoming +//! data. + +#![no_std] +#![no_main] + +use core::fmt::Write; + +use esp32_hal::{ + clock::ClockControl, + gpio::IO, + pac::Peripherals, + prelude::*, + spi::{Spi, SpiMode}, + timer::TimerGroup, + Delay, + Rtc, + Serial, +}; +use panic_halt as _; +use xtensa_lx_rt::entry; + +use embedded_hal_1::spi::blocking::SpiBus; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take().unwrap(); + let mut system = peripherals.DPORT.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 wdt = timer_group0.wdt; + let mut serial0 = Serial::new(peripherals.UART0); + + wdt.disable(); + rtc.rwdt.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let sclk = io.pins.gpio19; + let miso = io.pins.gpio25; + let mosi = io.pins.gpio23; + let cs = io.pins.gpio22; + + let mut spi = Spi::new( + peripherals.SPI2, + sclk, + mosi, + miso, + cs, + 1000u32.kHz(), + SpiMode::Mode0, + &mut system.peripheral_clock_control, + &clocks, + ); + + let mut delay = Delay::new(&clocks); + writeln!(serial0, "=== SPI example with embedded-hal-1 traits ===").unwrap(); + + loop { + // --- Symmetric transfer (Read as much as we write) --- + write!(serial0, "Starting symmetric transfer...").unwrap(); + let write = [0xde, 0xad, 0xbe, 0xef]; + let mut read: [u8; 4] = [0x00u8; 4]; + + SpiBus::transfer(&mut spi, &mut read[..], &write[..]).expect("Symmetric transfer failed"); + assert_eq!(write, read); + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + + + // --- Asymmetric transfer (Read more than we write) --- + write!(serial0, "Starting asymetric transfer (read > write)...").unwrap(); + let mut read: [u8; 4] = [0x00; 4]; + + SpiBus::transfer(&mut spi, &mut read[0..2], &write[..]).expect("Asymmetric transfer failed"); + assert_eq!(write[0], read[0]); + assert_eq!(read[2], 0x00u8); + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + + + // --- Symmetric transfer with huge buffer --- + // Only your RAM is the limit! + write!(serial0, "Starting huge transfer...").unwrap(); + let mut write = [0x55u8; 4096]; + for byte in 0..write.len() { + write[byte] = byte as u8; + } + let mut read = [0x00u8; 4096]; + + SpiBus::transfer(&mut spi, &mut read[..], &write[..]).expect("Huge transfer failed"); + assert_eq!(write, read); + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + + + // --- Symmetric transfer with huge buffer in-place (No additional allocation needed) --- + write!(serial0, "Starting huge transfer (in-place)...").unwrap(); + let mut write = [0x55u8; 4096]; + for byte in 0..write.len() { + write[byte] = byte as u8; + } + + SpiBus::transfer_in_place(&mut spi, &mut write[..]).expect("Huge transfer failed"); + for byte in 0..write.len() { + assert_eq!(write[byte], byte as u8); + } + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + } +} + diff --git a/esp32c3-hal/Cargo.toml b/esp32c3-hal/Cargo.toml index 6f4053bea..b46f176b3 100644 --- a/esp32c3-hal/Cargo.toml +++ b/esp32c3-hal/Cargo.toml @@ -54,3 +54,7 @@ vectored = ["esp-hal-common/vectored"] [[example]] name = "hello_rgb" required-features = ["smartled"] + +[[example]] +name = "spi_eh1_loopback" +required-features = ["eh1"] diff --git a/esp32c3-hal/examples/spi_eh1_loopback.rs b/esp32c3-hal/examples/spi_eh1_loopback.rs new file mode 100644 index 000000000..154f15f9d --- /dev/null +++ b/esp32c3-hal/examples/spi_eh1_loopback.rs @@ -0,0 +1,132 @@ +//! SPI loopback test +//! +//! Folowing pins are used: +//! SCLK GPIO6 +//! MISO GPIO2 +//! MOSI GPIO7 +//! CS GPIO10 +//! +//! Depending on your target and the board you are using you have to change the +//! pins. +//! +//! This example transfers data via SPI. +//! Connect MISO and MOSI pins to see the outgoing data is read as incoming +//! data. + +#![no_std] +#![no_main] + +use core::fmt::Write; + +use esp32c3_hal::{ + clock::ClockControl, + gpio::IO, + pac::Peripherals, + prelude::*, + spi::{Spi, SpiMode}, + timer::TimerGroup, + Delay, + Rtc, + Serial, +}; +use panic_halt as _; +use riscv_rt::entry; + +use embedded_hal_1::spi::blocking::SpiBus; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take().unwrap(); + let mut 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; + + let mut serial0 = Serial::new(peripherals.UART0); + + rtc.swd.disable(); + rtc.rwdt.disable(); + wdt0.disable(); + wdt1.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let sclk = io.pins.gpio6; + let miso = io.pins.gpio2; + let mosi = io.pins.gpio7; + let cs = io.pins.gpio10; + + let mut spi = Spi::new( + peripherals.SPI2, + sclk, + mosi, + miso, + cs, + 1000u32.kHz(), + SpiMode::Mode0, + &mut system.peripheral_clock_control, + &clocks, + ); + + let mut delay = Delay::new(&clocks); + writeln!(serial0, "=== SPI example with embedded-hal-1 traits ===").unwrap(); + + loop { + // --- Symmetric transfer (Read as much as we write) --- + write!(serial0, "Starting symmetric transfer...").unwrap(); + let write = [0xde, 0xad, 0xbe, 0xef]; + let mut read: [u8; 4] = [0x00u8; 4]; + + SpiBus::transfer(&mut spi, &mut read[..], &write[..]).expect("Symmetric transfer failed"); + assert_eq!(write, read); + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + + + // --- Asymmetric transfer (Read more than we write) --- + write!(serial0, "Starting asymetric transfer (read > write)...").unwrap(); + let mut read: [u8; 4] = [0x00; 4]; + + SpiBus::transfer(&mut spi, &mut read[0..2], &write[..]).expect("Asymmetric transfer failed"); + assert_eq!(write[0], read[0]); + assert_eq!(read[2], 0x00u8); + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + + + // --- Symmetric transfer with huge buffer --- + // Only your RAM is the limit! + write!(serial0, "Starting huge transfer...").unwrap(); + let mut write = [0x55u8; 4096]; + for byte in 0..write.len() { + write[byte] = byte as u8; + } + let mut read = [0x00u8; 4096]; + + SpiBus::transfer(&mut spi, &mut read[..], &write[..]).expect("Huge transfer failed"); + assert_eq!(write, read); + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + + + // --- Symmetric transfer with huge buffer in-place (No additional allocation needed) --- + write!(serial0, "Starting huge transfer (in-place)...").unwrap(); + let mut write = [0x55u8; 4096]; + for byte in 0..write.len() { + write[byte] = byte as u8; + } + + SpiBus::transfer_in_place(&mut spi, &mut write[..]).expect("Huge transfer failed"); + for byte in 0..write.len() { + assert_eq!(write[byte], byte as u8); + } + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + } +} + diff --git a/esp32s2-hal/Cargo.toml b/esp32s2-hal/Cargo.toml index dd8085454..03e7cc871 100644 --- a/esp32s2-hal/Cargo.toml +++ b/esp32s2-hal/Cargo.toml @@ -52,3 +52,7 @@ vectored = ["esp-hal-common/vectored"] [[example]] name = "hello_rgb" required-features = ["smartled"] + +[[example]] +name = "spi_eh1_loopback" +required-features = ["eh1"] diff --git a/esp32s2-hal/examples/spi_eh1_loopback.rs b/esp32s2-hal/examples/spi_eh1_loopback.rs new file mode 100644 index 000000000..98c055586 --- /dev/null +++ b/esp32s2-hal/examples/spi_eh1_loopback.rs @@ -0,0 +1,127 @@ +//! SPI loopback test +//! +//! Folowing pins are used: +//! SCLK GPIO36 +//! MISO GPIO37 +//! MOSI GPIO35 +//! CS GPIO34 +//! +//! Depending on your target and the board you are using you have to change the +//! pins. +//! +//! This example transfers data via SPI. +//! Connect MISO and MOSI pins to see the outgoing data is read as incoming +//! data. + +#![no_std] +#![no_main] + +use core::fmt::Write; + +use esp32s2_hal::{ + clock::ClockControl, + gpio::IO, + pac::Peripherals, + prelude::*, + spi::{Spi, SpiMode}, + timer::TimerGroup, + Delay, + Rtc, + Serial, +}; +use panic_halt as _; +use xtensa_lx_rt::entry; + +use embedded_hal_1::spi::blocking::SpiBus; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take().unwrap(); + let mut 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 wdt = timer_group0.wdt; + let mut serial0 = Serial::new(peripherals.UART0); + + wdt.disable(); + rtc.rwdt.disable(); + + let io = IO::new(peripherals.GPIO, peripherals.IO_MUX); + let sclk = io.pins.gpio36; + let miso = io.pins.gpio37; + let mosi = io.pins.gpio35; + let cs = io.pins.gpio34; + + let mut spi = Spi::new( + peripherals.SPI2, + sclk, + mosi, + miso, + cs, + 1000u32.kHz(), + SpiMode::Mode0, + &mut system.peripheral_clock_control, + &clocks, + ); + + let mut delay = Delay::new(&clocks); + writeln!(serial0, "=== SPI example with embedded-hal-1 traits ===").unwrap(); + + loop { + // --- Symmetric transfer (Read as much as we write) --- + write!(serial0, "Starting symmetric transfer...").unwrap(); + let write = [0xde, 0xad, 0xbe, 0xef]; + let mut read: [u8; 4] = [0x00u8; 4]; + + SpiBus::transfer(&mut spi, &mut read[..], &write[..]).expect("Symmetric transfer failed"); + assert_eq!(write, read); + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + + + // --- Asymmetric transfer (Read more than we write) --- + write!(serial0, "Starting asymetric transfer (read > write)...").unwrap(); + let mut read: [u8; 4] = [0x00; 4]; + + SpiBus::transfer(&mut spi, &mut read[0..2], &write[..]).expect("Asymmetric transfer failed"); + assert_eq!(write[0], read[0]); + assert_eq!(read[2], 0x00u8); + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + + + // --- Symmetric transfer with huge buffer --- + // Only your RAM is the limit! + write!(serial0, "Starting huge transfer...").unwrap(); + let mut write = [0x55u8; 4096]; + for byte in 0..write.len() { + write[byte] = byte as u8; + } + let mut read = [0x00u8; 4096]; + + SpiBus::transfer(&mut spi, &mut read[..], &write[..]).expect("Huge transfer failed"); + assert_eq!(write, read); + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + + + // --- Symmetric transfer with huge buffer in-place (No additional allocation needed) --- + write!(serial0, "Starting huge transfer (in-place)...").unwrap(); + let mut write = [0x55u8; 4096]; + for byte in 0..write.len() { + write[byte] = byte as u8; + } + + SpiBus::transfer_in_place(&mut spi, &mut write[..]).expect("Huge transfer failed"); + for byte in 0..write.len() { + assert_eq!(write[byte], byte as u8); + } + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + } +} + diff --git a/esp32s3-hal/Cargo.toml b/esp32s3-hal/Cargo.toml index 6b700051d..b04ef8332 100644 --- a/esp32s3-hal/Cargo.toml +++ b/esp32s3-hal/Cargo.toml @@ -54,3 +54,7 @@ vectored = ["esp-hal-common/vectored"] [[example]] name = "hello_rgb" required-features = ["smartled"] + +[[example]] +name = "spi_eh1_loopback" +required-features = ["eh1"] diff --git a/esp32s3-hal/examples/spi_eh1_loopback.rs b/esp32s3-hal/examples/spi_eh1_loopback.rs new file mode 100644 index 000000000..3e4643935 --- /dev/null +++ b/esp32s3-hal/examples/spi_eh1_loopback.rs @@ -0,0 +1,127 @@ +//! SPI loopback test +//! +//! Folowing pins are used: +//! SCLK GPIO12 +//! MISO GPIO11 +//! MOSI GPIO13 +//! CS GPIO10 +//! +//! Depending on your target and the board you are using you have to change the +//! pins. +//! +//! This example transfers data via SPI. +//! Connect MISO and MOSI pins to see the outgoing data is read as incoming +//! data. + +#![no_std] +#![no_main] + +use core::fmt::Write; + +use esp32s3_hal::{ + clock::ClockControl, + gpio::IO, + pac::Peripherals, + prelude::*, + spi::{Spi, SpiMode}, + timer::TimerGroup, + Delay, + Rtc, + Serial, +}; +use panic_halt as _; +use xtensa_lx_rt::entry; + +use embedded_hal_1::spi::blocking::SpiBus; + +#[entry] +fn main() -> ! { + let peripherals = Peripherals::take().unwrap(); + let mut 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 wdt = timer_group0.wdt; + let mut serial0 = Serial::new(peripherals.UART0); + + wdt.disable(); + rtc.rwdt.disable(); + + 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 = Spi::new( + peripherals.SPI2, + sclk, + mosi, + miso, + cs, + 1000u32.kHz(), + SpiMode::Mode0, + &mut system.peripheral_clock_control, + &clocks, + ); + + let mut delay = Delay::new(&clocks); + writeln!(serial0, "=== SPI example with embedded-hal-1 traits ===").unwrap(); + + loop { + // --- Symmetric transfer (Read as much as we write) --- + write!(serial0, "Starting symmetric transfer...").unwrap(); + let write = [0xde, 0xad, 0xbe, 0xef]; + let mut read: [u8; 4] = [0x00u8; 4]; + + SpiBus::transfer(&mut spi, &mut read[..], &write[..]).expect("Symmetric transfer failed"); + assert_eq!(write, read); + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + + + // --- Asymmetric transfer (Read more than we write) --- + write!(serial0, "Starting asymetric transfer (read > write)...").unwrap(); + let mut read: [u8; 4] = [0x00; 4]; + + SpiBus::transfer(&mut spi, &mut read[0..2], &write[..]).expect("Asymmetric transfer failed"); + assert_eq!(write[0], read[0]); + assert_eq!(read[2], 0x00u8); + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + + + // --- Symmetric transfer with huge buffer --- + // Only your RAM is the limit! + write!(serial0, "Starting huge transfer...").unwrap(); + let mut write = [0x55u8; 4096]; + for byte in 0..write.len() { + write[byte] = byte as u8; + } + let mut read = [0x00u8; 4096]; + + SpiBus::transfer(&mut spi, &mut read[..], &write[..]).expect("Huge transfer failed"); + assert_eq!(write, read); + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + + + // --- Symmetric transfer with huge buffer in-place (No additional allocation needed) --- + write!(serial0, "Starting huge transfer (in-place)...").unwrap(); + let mut write = [0x55u8; 4096]; + for byte in 0..write.len() { + write[byte] = byte as u8; + } + + SpiBus::transfer_in_place(&mut spi, &mut write[..]).expect("Huge transfer failed"); + for byte in 0..write.len() { + assert_eq!(write[byte], byte as u8); + } + writeln!(serial0, " SUCCESS").unwrap(); + delay.delay_ms(250u32); + } +} +