mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-29 21:30:39 +00:00

* RMT: Encapsulate STATE accesses Abstract away the unsafe AtomicU8 <-> RmtState conversion behind a safe API. (See the code comments next to the `mod state` fore more details.) This adds a few methods that are presently unused, but will be in future PRs. * RMT: remove unnecessary pub(crate) from *_SIGNAL arrays * add #[inline] to (un)listen_(rx|tx)_interrupt Ensure that these methods are always inlined (they probably would be anyway, but better be sure): - The `event` argument is usually known, which might allow for more optimizations. - Ideally, the compiler should be able to optimize away any complexity that the `EnumSet` might add. * RMT: inline Rmt::new_internal given that there's only a single caller since a previous refactoring * RMT: add test for channel RAM release on Drop * RMT: macro simplification - centralize all macros, getting close to a single source of truth for the channel specification - use paste! to avoid some repetitive macro arguments - replace the tt-muncher by simple repeat patterns: This makes the macro much easier to read. It would be possible to combine the declare_channels!, declare_rx_channels!, declare_tx_channels! by using one big complicated tt-muncher, but the cost in readability of the macro seems excessive - no need for absolute paths in these macros, we're the only user, and they're only used at the top level of the module * RMT: import crate::gpio to shorten paths we keep the gpio prefix, since a later commit will also use gpio::interconnect::{InputSignal, OutputSignal}
287 lines
8.6 KiB
Rust
287 lines
8.6 KiB
Rust
//! RMT Loopback Test
|
|
|
|
//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3
|
|
//% FEATURES: unstable
|
|
|
|
#![no_std]
|
|
#![no_main]
|
|
|
|
use esp_hal::{
|
|
DriverMode,
|
|
gpio::{InputPin, Level, NoPin, OutputPin},
|
|
rmt::{
|
|
AnyRxChannel,
|
|
AnyTxChannel,
|
|
Error,
|
|
PulseCode,
|
|
Rmt,
|
|
RxChannelConfig,
|
|
RxChannelCreator,
|
|
TxChannelConfig,
|
|
TxChannelCreator,
|
|
},
|
|
time::Rate,
|
|
};
|
|
use hil_test as _;
|
|
|
|
esp_bootloader_esp_idf::esp_app_desc!();
|
|
|
|
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,
|
|
) -> (AnyTxChannel<Dm>, AnyRxChannel<Dm>) {
|
|
let tx_channel = rmt
|
|
.channel0
|
|
.configure_tx(tx, tx_config.with_clk_divider(DIV))
|
|
.unwrap()
|
|
.degrade();
|
|
|
|
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()
|
|
.degrade();
|
|
|
|
(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
|
|
}
|
|
|
|
// 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) {
|
|
use esp_hal::rmt::{RxChannel, TxChannel};
|
|
|
|
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);
|
|
|
|
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.
|
|
async fn do_rmt_loopback_async<const TX_LEN: usize>(tx_memsize: u8, rx_memsize: u8) {
|
|
use esp_hal::rmt::{RxChannelAsync, TxChannelAsync};
|
|
|
|
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;
|
|
|
|
assert!(tx_res.is_ok());
|
|
assert!(rx_res.is_ok());
|
|
|
|
// 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> {
|
|
use esp_hal::rmt::TxChannel;
|
|
|
|
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();
|
|
}
|
|
}
|