mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-28 04:40:52 +00:00

* RMT: move configure_*x_channel to Channel impl These methods are essentially (unsafe) constructors for Channel, so it makes sense for them to be part of the Channel rather than free-standing functions. Importantly, this refactoring also reorders channel configuration and creation of GenericPeripheralGuard. Note that it's still not guranteed that the peripheral is clocked when configuring it, since code like the following compiles: ``` let ch0_creator = { let rmt = Rmt::new(peripherals.RMT, freq).unwrap(); rmt.channel0; // Other fields of `rmt` are dropped, including the // `Rmt.peripheral: RMT` field. // Since not GenericPeripheralGuard has been created at this // point, this will disable the peripheral's clock. }; // This re-enables RMT clocks. With this commit, it does so before // actually accessing RMT registers. However, the clock configuration that // happens in Rmt::new() will have been lost! // -> will be fixed in a later PR let ch0 = ch0_creator.configure_tx(pin, config).unwrap(); ``` There's no change to the API here. * RMT: merge transmit_continuously{,with_loopcount} ...by introducing an extra loopcount argument. Add the LoopCount enum such that the resulting code remains readable. This is in preparation for adding more variants of rx/tx methods in order to avoid combinatoric explosion. * RMT: use stop_tx if available, only for esp32 fill buffer with end markers note that stop_tx requires an update() call (according to TRMs and to IDF code) for consistency, this also removes all update() calls from the low-level methods in favor of explicit calls this de-duplicates some update calls for start_tx() * RMT: deduplicate ContinuousTxTransaction::{stop, stop_next} * RMT: remove spurious ch_tx_thr_event access on Rx channel for esp32 + esp32s2, which don't support the ch_rx_thr_event (this was partially cleaned up in #3701 already, but this instance was overseen) * RMT: assert!(...is_ok()) -> unwrap in HIL tests
374 lines
12 KiB
Rust
374 lines
12 KiB
Rust
//! RMT Loopback Test
|
|
|
|
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
|
|
//% FEATURES: unstable
|
|
|
|
#![no_std]
|
|
#![no_main]
|
|
|
|
use esp_hal::{
|
|
Blocking,
|
|
DriverMode,
|
|
gpio::{InputPin, Level, NoPin, OutputPin},
|
|
rmt::{
|
|
Channel,
|
|
Error,
|
|
PulseCode,
|
|
Rmt,
|
|
Rx,
|
|
RxChannelConfig,
|
|
RxChannelCreator,
|
|
Tx,
|
|
TxChannelConfig,
|
|
TxChannelCreator,
|
|
},
|
|
time::Rate,
|
|
};
|
|
use hil_test as _;
|
|
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(esp32h2)] {
|
|
const FREQ: Rate = Rate::from_mhz(32);
|
|
const DIV: u8 = 64;
|
|
} else {
|
|
const FREQ: Rate = Rate::from_mhz(80);
|
|
const DIV: u8 = 160;
|
|
}
|
|
}
|
|
|
|
fn setup<Dm: DriverMode>(
|
|
rmt: Rmt<'static, Dm>,
|
|
rx: impl InputPin,
|
|
tx: impl OutputPin,
|
|
tx_config: TxChannelConfig,
|
|
rx_config: RxChannelConfig,
|
|
) -> (Channel<Dm, Tx>, Channel<Dm, Rx>) {
|
|
let tx_channel = rmt
|
|
.channel0
|
|
.configure_tx(tx, tx_config.with_clk_divider(DIV))
|
|
.unwrap();
|
|
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(any(esp32, esp32s3))] {
|
|
let rx_channel_creator = rmt.channel4;
|
|
} else {
|
|
let rx_channel_creator = rmt.channel2;
|
|
}
|
|
};
|
|
let rx_channel = rx_channel_creator
|
|
.configure_rx(rx, rx_config.with_clk_divider(DIV))
|
|
.unwrap();
|
|
|
|
(tx_channel, rx_channel)
|
|
}
|
|
|
|
fn generate_tx_data<const TX_LEN: usize>(write_end_marker: bool) -> [PulseCode; TX_LEN] {
|
|
let mut tx_data: [_; TX_LEN] = core::array::from_fn(|i| {
|
|
PulseCode::new(Level::High, (100 + (i * 10) % 200) as u16, Level::Low, 50)
|
|
});
|
|
|
|
if write_end_marker {
|
|
tx_data[TX_LEN - 2] = PulseCode::new(Level::High, 3000, Level::Low, 500);
|
|
tx_data[TX_LEN - 1] = PulseCode::end_marker();
|
|
}
|
|
|
|
tx_data
|
|
}
|
|
|
|
fn do_rmt_loopback_inner<const TX_LEN: usize>(
|
|
tx_channel: Channel<Blocking, Tx>,
|
|
rx_channel: Channel<Blocking, Rx>,
|
|
) {
|
|
let tx_data: [_; TX_LEN] = generate_tx_data(true);
|
|
let mut rcv_data: [PulseCode; TX_LEN] = [PulseCode::default(); TX_LEN];
|
|
|
|
let mut rx_transaction = rx_channel.receive(&mut rcv_data).unwrap();
|
|
let mut tx_transaction = tx_channel.transmit(&tx_data).unwrap();
|
|
|
|
loop {
|
|
let tx_done = tx_transaction.poll();
|
|
let rx_done = rx_transaction.poll();
|
|
if tx_done && rx_done {
|
|
break;
|
|
}
|
|
}
|
|
|
|
tx_transaction.wait().unwrap();
|
|
rx_transaction.wait().unwrap();
|
|
|
|
// the last two pulse-codes are the ones which wait for the timeout so
|
|
// they can't be equal
|
|
assert_eq!(&tx_data[..TX_LEN - 2], &rcv_data[..TX_LEN - 2]);
|
|
}
|
|
|
|
// Run a test where some data is sent from one channel and looped back to
|
|
// another one for receive, and verify that the data matches.
|
|
fn do_rmt_loopback<const TX_LEN: usize>(tx_memsize: u8, rx_memsize: u8) {
|
|
let peripherals = esp_hal::init(esp_hal::Config::default());
|
|
let (rx, tx) = hil_test::common_test_pins!(peripherals);
|
|
let rmt = Rmt::new(peripherals.RMT, FREQ).unwrap();
|
|
|
|
let tx_config = TxChannelConfig::default().with_memsize(tx_memsize);
|
|
let rx_config = RxChannelConfig::default()
|
|
.with_idle_threshold(1000)
|
|
.with_memsize(rx_memsize);
|
|
|
|
let (tx_channel, rx_channel) = setup(rmt, rx, tx, tx_config, rx_config);
|
|
|
|
do_rmt_loopback_inner::<TX_LEN>(tx_channel, rx_channel);
|
|
}
|
|
|
|
// Run a test where some data is sent from one channel and looped back to
|
|
// another one for receive, and verify that the data matches.
|
|
async fn do_rmt_loopback_async<const TX_LEN: usize>(tx_memsize: u8, rx_memsize: u8) {
|
|
let peripherals = esp_hal::init(esp_hal::Config::default());
|
|
let (rx, tx) = hil_test::common_test_pins!(peripherals);
|
|
let rmt = Rmt::new(peripherals.RMT, FREQ).unwrap().into_async();
|
|
|
|
let tx_config = TxChannelConfig::default().with_memsize(tx_memsize);
|
|
let rx_config = RxChannelConfig::default()
|
|
.with_idle_threshold(1000)
|
|
.with_memsize(rx_memsize);
|
|
|
|
let (mut tx_channel, mut rx_channel) = setup(rmt, rx, tx, tx_config, rx_config);
|
|
|
|
let tx_data: [_; TX_LEN] = generate_tx_data(true);
|
|
let mut rcv_data: [PulseCode; TX_LEN] = [PulseCode::default(); TX_LEN];
|
|
|
|
let (rx_res, tx_res) = embassy_futures::join::join(
|
|
rx_channel.receive(&mut rcv_data),
|
|
tx_channel.transmit(&tx_data),
|
|
)
|
|
.await;
|
|
|
|
tx_res.unwrap();
|
|
rx_res.unwrap();
|
|
|
|
// the last two pulse-codes are the ones which wait for the timeout so
|
|
// they can't be equal
|
|
assert_eq!(&tx_data[..TX_LEN - 2], &rcv_data[..TX_LEN - 2]);
|
|
}
|
|
|
|
// Run a test that just sends some data, without trying to recive it.
|
|
#[must_use = "Tests should fail on errors"]
|
|
fn do_rmt_single_shot<const TX_LEN: usize>(
|
|
tx_memsize: u8,
|
|
write_end_marker: bool,
|
|
) -> Result<(), Error> {
|
|
let peripherals = esp_hal::init(esp_hal::Config::default());
|
|
let (rx, tx) = hil_test::common_test_pins!(peripherals);
|
|
let rmt = Rmt::new(peripherals.RMT, FREQ).unwrap();
|
|
|
|
let tx_config = TxChannelConfig::default()
|
|
.with_clk_divider(DIV)
|
|
.with_memsize(tx_memsize);
|
|
let (tx_channel, _) = setup(rmt, rx, tx, tx_config, Default::default());
|
|
|
|
let tx_data: [_; TX_LEN] = generate_tx_data(write_end_marker);
|
|
|
|
tx_channel.transmit(&tx_data)?.wait().map_err(|(e, _)| e)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[embedded_test::tests(default_timeout = 1, executor = hil_test::Executor::new())]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[init]
|
|
fn init() {}
|
|
|
|
#[test]
|
|
fn rmt_loopback_simple() {
|
|
// 20 codes fit a single RAM block
|
|
do_rmt_loopback::<20>(1, 1);
|
|
}
|
|
|
|
#[test]
|
|
async fn rmt_loopback_simple_async() {
|
|
// 20 codes fit a single RAM block
|
|
do_rmt_loopback_async::<20>(1, 1).await;
|
|
}
|
|
#[test]
|
|
fn rmt_loopback_extended_ram() {
|
|
// 80 codes require two RAM blocks
|
|
do_rmt_loopback::<80>(2, 2);
|
|
}
|
|
|
|
// FIXME: This test currently fails on esp32 with an rmt::Error::ReceiverError,
|
|
// which should imply a receiver overrun, which is unexpected (the buffer
|
|
// should hold 2 * 64 codes, which is sufficient).
|
|
// However, the problem already exists exists in the original code that added
|
|
// support for extended channel RAM, so we can't bisect to find a
|
|
// regression. Skip the test for now.
|
|
#[cfg(not(esp32))]
|
|
#[test]
|
|
fn rmt_loopback_tx_wrap() {
|
|
// 80 codes require two RAM blocks; thus a tx channel with only 1 block requires
|
|
// wrapping.
|
|
do_rmt_loopback::<80>(1, 2);
|
|
}
|
|
|
|
// FIXME: This test can't work right now, because wrapping rx is not
|
|
// implemented.
|
|
//
|
|
// #[test]
|
|
// fn rmt_loopback_rx_wrap() {
|
|
// // 80 codes require two RAM blocks; thus an rx channel with only 1 block
|
|
// // requires wrapping
|
|
// do_rmt_loopback<80>(2, 1);
|
|
// }
|
|
|
|
#[test]
|
|
fn rmt_single_shot_wrap() {
|
|
// Single RAM block (48 or 64 codes), requires wrapping
|
|
do_rmt_single_shot::<80>(1, true).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn rmt_single_shot_extended() {
|
|
// Two RAM blocks (96 or 128 codes), no wrapping
|
|
do_rmt_single_shot::<80>(2, true).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn rmt_single_shot_extended_wrap() {
|
|
// Two RAM blocks (96 or 128 codes), requires wrapping
|
|
do_rmt_single_shot::<150>(2, true).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn rmt_single_shot_fails_without_end_marker() {
|
|
let result = do_rmt_single_shot::<20>(1, false);
|
|
|
|
assert!(matches!(result, Err(Error::EndMarkerMissing)));
|
|
}
|
|
|
|
#[test]
|
|
fn rmt_overlapping_ram_fails() {
|
|
let peripherals = esp_hal::init(esp_hal::Config::default());
|
|
|
|
let rmt = Rmt::new(peripherals.RMT, FREQ).unwrap();
|
|
|
|
let _ch0 = rmt
|
|
.channel0
|
|
.configure_tx(NoPin, TxChannelConfig::default().with_memsize(2))
|
|
.unwrap();
|
|
|
|
// Configuring channel 1 should fail, since channel 0 already uses its memory.
|
|
let ch1 = rmt.channel1.configure_tx(NoPin, TxChannelConfig::default());
|
|
|
|
assert!(matches!(ch1, Err(Error::MemoryBlockNotAvailable)));
|
|
}
|
|
|
|
#[test]
|
|
fn rmt_overlapping_ram_release() {
|
|
use esp_hal::rmt::TxChannelCreator;
|
|
|
|
let peripherals = esp_hal::init(esp_hal::Config::default());
|
|
|
|
let rmt = Rmt::new(peripherals.RMT, FREQ).unwrap();
|
|
|
|
let ch0 = rmt
|
|
.channel0
|
|
.configure_tx(NoPin, TxChannelConfig::default().with_memsize(2))
|
|
.unwrap();
|
|
|
|
// After dropping channel 0, the memory that it reserved should become available
|
|
// again such that channel 1 configuration succeeds.
|
|
core::mem::drop(ch0);
|
|
rmt.channel1
|
|
.configure_tx(NoPin, TxChannelConfig::default())
|
|
.unwrap();
|
|
}
|
|
|
|
macro_rules! test_channel_pair {
|
|
(
|
|
$peripherals:ident,
|
|
$tx_pin:ident,
|
|
$rx_pin:ident,
|
|
$tx_channel:ident,
|
|
$rx_channel:ident
|
|
) => {
|
|
// It's currently not possible to reborrow ChannelCreators and reconfigure channels, so
|
|
// we reconfigure the whole peripheral for each sub-test.
|
|
let rmt = Rmt::new($peripherals.RMT.reborrow(), FREQ).unwrap();
|
|
|
|
let tx_config = TxChannelConfig::default();
|
|
let rx_config = RxChannelConfig::default().with_idle_threshold(1000);
|
|
|
|
let tx_channel = rmt
|
|
.$tx_channel
|
|
.configure_tx($tx_pin.reborrow(), tx_config)
|
|
.unwrap();
|
|
|
|
let rx_channel = rmt
|
|
.$rx_channel
|
|
.configure_rx($rx_pin.reborrow(), rx_config)
|
|
.unwrap();
|
|
|
|
do_rmt_loopback_inner::<20>(tx_channel, rx_channel);
|
|
};
|
|
}
|
|
|
|
// A simple test that uses all channels: This is useful to verify that all channel/register
|
|
// indexing in the driver is correct. The other tests are prone to hide related issues due to
|
|
// using low channel numbers only (in particular 0 for the tx channel).
|
|
#[test]
|
|
fn rmt_use_all_channels() {
|
|
let mut p = esp_hal::init(esp_hal::Config::default());
|
|
let (mut rx, mut tx) = hil_test::common_test_pins!(p);
|
|
|
|
// FIXME: Find a way to implement these with less boilerplate and without a macro.
|
|
// Maybe use ChannelCreator::steal() (doesn't help right now because it uses a const
|
|
// generic channel number)?
|
|
|
|
// Chips with combined RxTx channels
|
|
#[cfg(esp32)]
|
|
{
|
|
test_channel_pair!(p, tx, rx, channel0, channel1);
|
|
test_channel_pair!(p, tx, rx, channel0, channel2);
|
|
test_channel_pair!(p, tx, rx, channel0, channel3);
|
|
test_channel_pair!(p, tx, rx, channel0, channel4);
|
|
test_channel_pair!(p, tx, rx, channel0, channel5);
|
|
test_channel_pair!(p, tx, rx, channel0, channel6);
|
|
test_channel_pair!(p, tx, rx, channel0, channel7);
|
|
|
|
test_channel_pair!(p, tx, rx, channel1, channel0);
|
|
test_channel_pair!(p, tx, rx, channel2, channel0);
|
|
test_channel_pair!(p, tx, rx, channel3, channel0);
|
|
test_channel_pair!(p, tx, rx, channel4, channel0);
|
|
test_channel_pair!(p, tx, rx, channel5, channel0);
|
|
test_channel_pair!(p, tx, rx, channel6, channel0);
|
|
test_channel_pair!(p, tx, rx, channel7, channel0);
|
|
}
|
|
|
|
#[cfg(esp32s2)]
|
|
{
|
|
test_channel_pair!(p, tx, rx, channel0, channel1);
|
|
test_channel_pair!(p, tx, rx, channel0, channel2);
|
|
test_channel_pair!(p, tx, rx, channel0, channel3);
|
|
|
|
test_channel_pair!(p, tx, rx, channel1, channel0);
|
|
test_channel_pair!(p, tx, rx, channel2, channel0);
|
|
test_channel_pair!(p, tx, rx, channel3, channel0);
|
|
}
|
|
|
|
// Chips with separate Rx and Tx channels
|
|
#[cfg(esp32s3)]
|
|
{
|
|
test_channel_pair!(p, tx, rx, channel0, channel4);
|
|
test_channel_pair!(p, tx, rx, channel1, channel5);
|
|
test_channel_pair!(p, tx, rx, channel2, channel6);
|
|
test_channel_pair!(p, tx, rx, channel3, channel7);
|
|
}
|
|
|
|
#[cfg(not(any(esp32, esp32s2, esp32s3)))]
|
|
{
|
|
test_channel_pair!(p, tx, rx, channel0, channel2);
|
|
test_channel_pair!(p, tx, rx, channel1, channel3);
|
|
}
|
|
}
|
|
}
|