SPI: Implement more SPI traits from embedded-hal 1.0.0-alpha.8 (#101)

* common/spi: Turn fifo size into const

instead of hard-coding it into the code in various places.

* common/spi: Alias `write_bytes` to `send_bytes`

since they share the same interface and the same code anyway.

* common/spi: Implement `read_bytes`

as counterpart to `send_bytes` that is responsible only for reading
bytes received via SPI.

* common/spi: Rewrite `transfer`

to use `send_bytes` and `read_bytes` under the hood and remove duplicate
code.

* common/spi: Create submodule for embedded_hal_1

that is re-exported when the `eh1` feature flag is active. This removes
lots of duplicate `#[cfg(...)]` macros previously part of the code.

* common/spi: Implement `SpiBus` and `SpiBusWrite`

traits from the `embedded-hal 1.0.0-alpha.8`.

* common/spi: Make `mosi` pin optional

* esp32-hal: Add new SPI example with `eh1` traits

* esp32-hal/examples/spi_eh1: Add huge transfer

and bump the SPI speed to 1 MHz.

* common/spi: Apply rustfmt

* common/spi: Use `memcpy` to read from registers

This cuts down the time between consecutive transfers from about 2 ms
to less than 1 ms.

* WIP: common/spi: Use `ptr::copy` to fill write FIFO

cutting down the time between transfers from just below 1 ms to ~370 us.

The implementation is currently broken in that it will always fill the
entire FIFO from the input it is given, even if that isn't FIFO-sized...

* common/spi: Add more documentation

* esp32/examples/spi_eh1: Fix `transfer_in_place`

* esp32/examples/spi_eh1: Add conditional compile

and compile a dummy instead when the "eh1" feature isn't present.

* esp32-hal: Ignore spi_eh1 example

in normal builds, where the feature flag "eh1" isn't given. Building the
example directly via `cargo build --example spi_eh1_loopback` will now
print an error that this requires a feature flag to be active.

* common/spi: Use `write_bytes`

and drop `send_bytes` instead. Previoulsy, both served the same purpose,
but `send_bytes` was introduced more recently and is hence less likely
to cause breaking changes in existing code.

* common/spi: Fix mosi pin setup

* Add SPI examples with ehal 1.0.0-alpha8 traits

to all targets.

* common/spi: Fix `read` behavior

The previous `read` implementation would only read the contents of the
SPI receive FIFO and return that as data. However, the `SpiBusRead`
trait defines that while reading, bytes should be written out to the bus
(Because SPI is transactional, without writing nothing can be read).

Reimplements the `embedded-hal` traits to correctly implement this
behavior.

* common/spi: Use full FIFO size on all variants

All esp variants except for the esp32s2 have a 64 byte FIFO, whereas the
esp32s2 has a 72 byte FIFO.

* common/spi: Use common pad byte for empty writes

* common/spi: Fix reading bytes from FIFO

by reverting to the old method of reading 32 bytes at a time and
assembling the return buffer from that. It turns out that the previous
`core::slice::from_raw_parts()` doesn't work for the esp32s2 and esp32s3
variants, returning bogus data even though the correct data is present
in the registers.

* common/spi: Fix typos

* examples: Fix spi_eh1_loopback examples
This commit is contained in:
har7an 2022-08-17 10:57:55 +00:00 committed by GitHub
parent 6e037b08ca
commit 2fe27536aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 754 additions and 118 deletions

View File

@ -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<T> embedded_hal_1::spi::ErrorType for Spi<T> {
type Error = Infallible;
}
pub use ehal1::*;
#[cfg(feature = "eh1")]
impl<T> embedded_hal_1::spi::nb::FullDuplex for Spi<T>
where
T: Instance,
{
fn read(&mut self) -> nb::Result<u8, Self::Error> {
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<T> embedded_hal_1::spi::ErrorType for Spi<T> {
type Error = Infallible;
}
fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> {
self.spi.write_byte(word)
}
}
impl<T> FullDuplex for Spi<T>
where
T: Instance,
{
fn read(&mut self) -> nb::Result<u8, Self::Error> {
self.spi.read_byte()
}
#[cfg(feature = "eh1")]
impl<T> embedded_hal_1::spi::blocking::SpiBusWrite for Spi<T>
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<T> embedded_hal_1::spi::blocking::SpiBusFlush for Spi<T>
where
T: Instance,
{
fn flush(&mut self) -> Result<(), Self::Error> {
self.spi.flush()
impl<T> SpiBusWrite for Spi<T>
where
T: Instance,
{
/// See also: [`write_bytes`].
fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
self.spi.write_bytes(words)
}
}
impl<T> SpiBusRead for Spi<T>
where
T: Instance,
{
/// See also: [`read_bytes`].
fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> {
self.spi.read_bytes(words)
}
}
impl<T> SpiBus for Spi<T>
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<T> SpiBusFlush for Spi<T>
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::<u32>(
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)

View File

@ -53,4 +53,8 @@ vectored = ["esp-hal-common/vectored"]
[[example]]
name = "hello_rgb"
required-features = ["smartled"]
required-features = ["smartled"]
[[example]]
name = "spi_eh1_loopback"
required-features = ["eh1"]

View File

@ -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);
}
}

View File

@ -54,3 +54,7 @@ vectored = ["esp-hal-common/vectored"]
[[example]]
name = "hello_rgb"
required-features = ["smartled"]
[[example]]
name = "spi_eh1_loopback"
required-features = ["eh1"]

View File

@ -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);
}
}

View File

@ -52,3 +52,7 @@ vectored = ["esp-hal-common/vectored"]
[[example]]
name = "hello_rgb"
required-features = ["smartled"]
[[example]]
name = "spi_eh1_loopback"
required-features = ["eh1"]

View File

@ -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);
}
}

View File

@ -54,3 +54,7 @@ vectored = ["esp-hal-common/vectored"]
[[example]]
name = "hello_rgb"
required-features = ["smartled"]
[[example]]
name = "spi_eh1_loopback"
required-features = ["eh1"]

View File

@ -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);
}
}