Migrate PARL_IO driver to newer DMA API (#3033)

* Migrate PARL_IO driver to newer DMA API

* Register waker when necessary

* Basic HIL test

* fmt

* oops

* return resources on error

* Invert sampling edge

* Attempt manual formatting

---------

Co-authored-by: Dominic Fischer <git@dominicfischer.me>
This commit is contained in:
Dominic Fischer 2025-02-03 11:58:20 +00:00 committed by GitHub
parent 8ba212c717
commit fe53061931
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 514 additions and 287 deletions

View File

@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Rng` and `Trng` now implement `Peripheral<P = Self>` (#2992)
- SPI, UART, I2C: `with_<pin>` functions of peripheral drivers now disconnect the previously assigned pins from the peripheral. (#3012)
- SPI, UART, I2C: Dropping a driver now disconnects pins from their peripherals. (#3012)
- Migrate PARL_IO driver to DMA move API (#3033)
- `Async` drivers are no longer `Send` (#2980)
- GPIO drivers now take configuration structs (#2990, #3029)
- `flip-link` feature is now a config option (`ESP_HAL_CONFIG_FLIP_LINK`) (#3001)

View File

@ -174,6 +174,35 @@ config/config.toml
+ ESP_HAL_CONFIG_PSRAM_MODE = "octal"
```
## PARL_IO changes
Parallel IO now uses the newer DMA Move API.
Changes on the TX side
```diff
let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, 32000);
+ let mut dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
- let transfer = parl_io_tx.write_dma(&tx_buffer).unwrap();
- transfer.wait().unwrap();
+ let transfer = parl_io_tx.write(dma_tx_buf.len(), dma_tx_buf).unwrap();
+ (result, parl_io_tx, dma_tx_buf) = transfer.wait();
+ result.unwrap();
```
Changes on the RX side
```diff
let (rx_buffer, rx_descriptors, _, _) = dma_buffers!(32000, 0);
+ let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
- let transfer = parl_io_rx.read_dma(&mut rx_buffer).unwrap();
- transfer.wait().unwrap();
+ let transfer = parl_io_rx.read(Some(dma_rx_buf.len()), dma_rx_buf).unwrap();
+ (_, parl_io_rx, dma_rx_buf) = transfer.wait();
```
On the RX side, the `EofMode` is now decided at transfer time, rather than config time.
- `EofMode::ByteLen` -> `Some(<number of bytes to receive>)`
- `EofMode::EnableSignal` -> `None`
## GPIO changes
GPIO drivers now take configuration structs.

File diff suppressed because it is too large Load Diff

View File

@ -115,6 +115,10 @@ harness = false
name = "spi_slave"
harness = false
[[test]]
name = "parl_io"
harness = false
[[test]]
name = "parl_io_tx"
harness = false

121
hil-test/tests/parl_io.rs Normal file
View File

@ -0,0 +1,121 @@
//! PARL_IO test
//% CHIPS: esp32c6 esp32h2
//% FEATURES: unstable
#![no_std]
#![no_main]
use esp_hal::{
dma::{DmaChannel0, DmaRxBuf, DmaTxBuf},
dma_buffers,
gpio::{AnyPin, Pin},
parl_io::{
BitPackOrder,
ClkOutPin,
EnableMode,
ParlIoFullDuplex,
RxClkInPin,
RxFourBits,
RxPinConfigWithValidPin,
SampleEdge,
TxFourBits,
TxPinConfigWithValidPin,
},
peripherals::PARL_IO,
time::RateExtU32,
};
use hil_test as _;
struct Context {
parl_io: PARL_IO,
dma_channel: DmaChannel0,
clock_pin: AnyPin,
valid_pin: AnyPin,
data_pins: [AnyPin; 4],
}
#[cfg(test)]
#[embedded_test::tests(default_timeout = 3)]
mod tests {
use super::*;
#[init]
fn init() -> Context {
let peripherals = esp_hal::init(esp_hal::Config::default());
let dma_channel = peripherals.DMA_CH0;
let parl_io = peripherals.PARL_IO;
Context {
parl_io,
dma_channel,
clock_pin: peripherals.GPIO11.degrade(),
valid_pin: peripherals.GPIO10.degrade(),
data_pins: [
peripherals.GPIO1.degrade(),
peripherals.GPIO0.degrade(),
peripherals.GPIO14.degrade(),
peripherals.GPIO23.degrade(),
],
}
}
#[test]
fn test_parl_io_rx_can_read_tx(ctx: Context) {
const BUFFER_SIZE: usize = 64;
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(BUFFER_SIZE);
let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
let mut dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
let (clock_rx, clock_tx) = ctx.clock_pin.split();
let (valid_rx, valid_tx) = ctx.valid_pin.split();
let [(d0_rx, d0_tx), (d1_rx, d1_tx), (d2_rx, d2_tx), (d3_rx, d3_tx)] =
ctx.data_pins.map(|pin| pin.split());
let tx_pins = TxFourBits::new(d0_tx, d1_tx, d2_tx, d3_tx);
let rx_pins = RxFourBits::new(d0_rx, d1_rx, d2_rx, d3_rx);
let tx_pins = TxPinConfigWithValidPin::new(tx_pins, valid_tx);
let mut rx_pins = RxPinConfigWithValidPin::new(rx_pins, valid_rx, EnableMode::HighLevel);
let clock_out_pin = ClkOutPin::new(clock_tx);
let mut clock_in_pin = RxClkInPin::new(clock_rx, SampleEdge::Normal);
let pio = ParlIoFullDuplex::new(ctx.parl_io, ctx.dma_channel, 40.MHz()).unwrap();
let pio_tx = pio
.tx
.with_config(
tx_pins,
clock_out_pin,
0,
SampleEdge::Invert,
BitPackOrder::Lsb,
)
.unwrap();
let pio_rx = pio
.rx
.with_config(&mut rx_pins, &mut clock_in_pin, BitPackOrder::Lsb, None)
.unwrap();
for (i, b) in dma_tx_buf.as_mut_slice().iter_mut().enumerate() {
*b = i as u8;
}
let rx_transfer = pio_rx
.read(Some(dma_rx_buf.len()), dma_rx_buf)
.map_err(|e| e.0)
.unwrap();
let tx_transfer = pio_tx
.write(dma_tx_buf.len(), dma_tx_buf)
.map_err(|e| e.0)
.unwrap();
(_, _, dma_tx_buf) = tx_transfer.wait();
(_, _, dma_rx_buf) = rx_transfer.wait();
assert_eq!(dma_rx_buf.as_slice(), dma_tx_buf.as_slice());
}
}

View File

@ -9,7 +9,8 @@
#[cfg(esp32c6)]
use esp_hal::parl_io::{TxPinConfigWithValidPin, TxSixteenBits};
use esp_hal::{
dma::DmaChannel0,
dma::{DmaChannel0, DmaTxBuf},
dma_tx_buffer,
gpio::{
interconnect::{InputSignal, OutputSignal},
NoPin,
@ -78,8 +79,8 @@ mod tests {
#[test]
fn test_parl_io_tx_16bit_valid_clock_count(ctx: Context) {
const BUFFER_SIZE: usize = 64;
let tx_buffer = [0u16; BUFFER_SIZE];
let (_, tx_descriptors) = esp_hal::dma_descriptors!(0, 2 * BUFFER_SIZE);
let mut dma_tx_buf: DmaTxBuf = dma_tx_buffer!(2 * BUFFER_SIZE).unwrap();
let pins = TxSixteenBits::new(
NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin,
@ -88,8 +89,7 @@ mod tests {
let mut pins = TxPinConfigIncludingValidPin::new(pins);
let mut clock_pin = ClkOutPin::new(ctx.clock);
let pio =
ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, tx_descriptors, 10.MHz()).unwrap();
let pio = ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, 10.MHz()).unwrap();
let mut pio = pio
.tx
@ -102,7 +102,7 @@ mod tests {
)
.unwrap(); // TODO: handle error
// use a PCNT unit to count the negitive clock edges only when valid is high
// use a PCNT unit to count the negative clock edges only when valid is high
let clock_unit = ctx.pcnt_unit;
clock_unit.channel0.set_edge_signal(ctx.clock_loopback);
clock_unit.channel0.set_ctrl_signal(ctx.valid_loopback);
@ -115,8 +115,11 @@ mod tests {
for _ in 0..100 {
clock_unit.clear();
let xfer = pio.write_dma(&tx_buffer).unwrap();
xfer.wait().unwrap();
let xfer = pio
.write(dma_tx_buf.len(), dma_tx_buf)
.map_err(|e| e.0)
.unwrap();
(_, pio, dma_tx_buf) = xfer.wait();
info!("clock count: {}", clock_unit.value());
assert_eq!(clock_unit.value(), BUFFER_SIZE as _);
}
@ -125,8 +128,7 @@ mod tests {
#[test]
fn test_parl_io_tx_8bit_valid_clock_count(ctx: Context) {
const BUFFER_SIZE: usize = 64;
let tx_buffer = [0u8; BUFFER_SIZE];
let (_, tx_descriptors) = esp_hal::dma_descriptors!(0, 2 * BUFFER_SIZE);
let mut dma_tx_buf: DmaTxBuf = dma_tx_buffer!(BUFFER_SIZE).unwrap();
let pins = TxEightBits::new(
NoPin,
@ -149,8 +151,7 @@ mod tests {
let mut clock_pin = ClkOutPin::new(ctx.clock);
let pio =
ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, tx_descriptors, 10.MHz()).unwrap();
let pio = ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, 10.MHz()).unwrap();
let mut pio = pio
.tx
@ -176,8 +177,11 @@ mod tests {
for _ in 0..100 {
clock_unit.clear();
let xfer = pio.write_dma(&tx_buffer).unwrap();
xfer.wait().unwrap();
let xfer = pio
.write(dma_tx_buf.len(), dma_tx_buf)
.map_err(|e| e.0)
.unwrap();
(_, pio, dma_tx_buf) = xfer.wait();
info!("clock count: {}", clock_unit.value());
assert_eq!(clock_unit.value(), BUFFER_SIZE as _);
}

View File

@ -9,7 +9,8 @@
#[cfg(esp32c6)]
use esp_hal::parl_io::{TxPinConfigWithValidPin, TxSixteenBits};
use esp_hal::{
dma::DmaChannel0,
dma::{DmaChannel0, DmaTxBuf},
dma_tx_buffer,
gpio::{
interconnect::{InputSignal, OutputSignal},
NoPin,
@ -78,8 +79,7 @@ mod tests {
#[test]
async fn test_parl_io_tx_async_16bit_valid_clock_count(ctx: Context) {
const BUFFER_SIZE: usize = 64;
let tx_buffer = [0u16; BUFFER_SIZE];
let (_, tx_descriptors) = esp_hal::dma_descriptors!(0, 2 * BUFFER_SIZE);
let mut dma_tx_buf: DmaTxBuf = dma_tx_buffer!(2 * BUFFER_SIZE).unwrap();
let pins = TxSixteenBits::new(
NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin,
@ -88,7 +88,7 @@ mod tests {
let mut pins = TxPinConfigIncludingValidPin::new(pins);
let mut clock_pin = ClkOutPin::new(ctx.clock);
let pio = ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, tx_descriptors, 10.MHz())
let pio = ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, 10.MHz())
.unwrap()
.into_async();
@ -116,7 +116,12 @@ mod tests {
for _ in 0..100 {
clock_unit.clear();
pio.write_dma_async(&tx_buffer).await.unwrap();
let mut xfer = pio
.write(dma_tx_buf.len(), dma_tx_buf)
.map_err(|e| e.0)
.unwrap();
xfer.wait_for_done().await;
(_, pio, dma_tx_buf) = xfer.wait();
info!("clock count: {}", clock_unit.value());
assert_eq!(clock_unit.value(), BUFFER_SIZE as _);
}
@ -125,8 +130,8 @@ mod tests {
#[test]
async fn test_parl_io_tx_async_8bit_valid_clock_count(ctx: Context) {
const BUFFER_SIZE: usize = 64;
let tx_buffer = [0u8; BUFFER_SIZE];
let (_, tx_descriptors) = esp_hal::dma_descriptors!(0, 2 * BUFFER_SIZE);
let mut dma_tx_buf: DmaTxBuf = dma_tx_buffer!(BUFFER_SIZE).unwrap();
let pins = TxEightBits::new(
NoPin,
@ -149,7 +154,7 @@ mod tests {
let mut clock_pin = ClkOutPin::new(ctx.clock);
let pio = ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, tx_descriptors, 10.MHz())
let pio = ParlIoTxOnly::new(ctx.parl_io, ctx.dma_channel, 10.MHz())
.unwrap()
.into_async();
@ -178,7 +183,12 @@ mod tests {
for _ in 0..100 {
clock_unit.clear();
pio.write_dma_async(&tx_buffer).await.unwrap();
let mut xfer = pio
.write(dma_tx_buf.len(), dma_tx_buf)
.map_err(|e| e.0)
.unwrap();
xfer.wait_for_done().await;
(_, pio, dma_tx_buf) = xfer.wait();
info!("clock count: {}", clock_unit.value());
assert_eq!(clock_unit.value(), BUFFER_SIZE as _);
}