Skip to content

Commit

Permalink
SPI: Implement more SPI traits from embedded-hal 1.0.0-alpha.8 (#101)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
har7an authored Aug 17, 2022
1 parent 6e037b0 commit 2fe2753
Show file tree
Hide file tree
Showing 9 changed files with 743 additions and 107 deletions.
319 changes: 213 additions & 106 deletions esp-hal-common/src/spi.rs

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion esp32-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
127 changes: 127 additions & 0 deletions esp32-hal/examples/spi_eh1_loopback.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}

4 changes: 4 additions & 0 deletions esp32c3-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,7 @@ vectored = ["esp-hal-common/vectored"]
[[example]]
name = "hello_rgb"
required-features = ["smartled"]

[[example]]
name = "spi_eh1_loopback"
required-features = ["eh1"]
132 changes: 132 additions & 0 deletions esp32c3-hal/examples/spi_eh1_loopback.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}

4 changes: 4 additions & 0 deletions esp32s2-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ vectored = ["esp-hal-common/vectored"]
[[example]]
name = "hello_rgb"
required-features = ["smartled"]

[[example]]
name = "spi_eh1_loopback"
required-features = ["eh1"]
Loading

0 comments on commit 2fe2753

Please sign in to comment.