From 15427ce521c95c091ab822f640404af7651dc7cb Mon Sep 17 00:00:00 2001 From: Juraj Sadel Date: Fri, 10 Oct 2025 16:48:41 +0200 Subject: [PATCH 1/3] Merge misc drivers HIL into single binary --- esp-hal/src/interrupt/riscv.rs | 4 +- hil-test/Cargo.toml | 20 +- hil-test/src/bin/misc_drivers.rs | 718 +++++++++++++++++++++++++++++++ hil-test/src/bin/pcnt.rs | 354 --------------- hil-test/src/bin/rng.rs | 63 --- hil-test/src/bin/systimer.rs | 163 ------- hil-test/src/bin/twai.rs | 160 ------- 7 files changed, 724 insertions(+), 758 deletions(-) create mode 100644 hil-test/src/bin/misc_drivers.rs delete mode 100644 hil-test/src/bin/pcnt.rs delete mode 100644 hil-test/src/bin/rng.rs delete mode 100644 hil-test/src/bin/systimer.rs delete mode 100644 hil-test/src/bin/twai.rs diff --git a/esp-hal/src/interrupt/riscv.rs b/esp-hal/src/interrupt/riscv.rs index 8e2b0bb4311..dc8bccfa71d 100644 --- a/esp-hal/src/interrupt/riscv.rs +++ b/esp-hal/src/interrupt/riscv.rs @@ -228,9 +228,9 @@ pub static RESERVED_INTERRUPTS: &[u32] = PRIORITY_TO_INTERRUPT; /// an error. /// /// ## Example -/// Visit the [interrupt] test to see a proper example of how to use direct vectoring. +/// Visit the [mod interrupt] test to see a proper example of how to use direct vectoring. /// -/// [interrupt]: https://github.com/esp-rs/esp-hal/blob/main/hil-test/src/bin/interrupt.rs +/// [mod interrupt]: https://github.com/esp-rs/esp-hal/blob/main/hil-test/src/bin/misc_drivers.rs pub fn enable_direct( interrupt: Interrupt, level: Priority, diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index 1a3ee09c22a..5041486828e 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -24,6 +24,10 @@ harness = false name = "gpio_custom_handler" harness = false +[[bin]] +name = "misc_drivers" +harness = false + [[bin]] name = "interrupt" harness = false @@ -44,10 +48,6 @@ harness = false name = "misc_non_drivers" harness = false -[[bin]] -name = "rng" -harness = false - [[bin]] name = "spi_full_duplex" harness = false @@ -68,10 +68,6 @@ harness = false name = "parl_io" harness = false -[[bin]] -name = "pcnt" -harness = false - [[bin]] name = "rmt" harness = false @@ -90,14 +86,6 @@ name = "embassy_timers_executors" harness = false required-features = ["embassy"] -[[bin]] -name = "systimer" -harness = false - -[[bin]] -name = "twai" -harness = false - [[bin]] name = "esp_radio" harness = false diff --git a/hil-test/src/bin/misc_drivers.rs b/hil-test/src/bin/misc_drivers.rs new file mode 100644 index 00000000000..578994e8285 --- /dev/null +++ b/hil-test/src/bin/misc_drivers.rs @@ -0,0 +1,718 @@ +//! Interrupt Tests +//! "Disabled" for now - see https://github.com/esp-rs/esp-hal/pull/1635#issuecomment-2137405251 +//! PCNT Tests +//! RNG Tests +//! System Timer Tests +//! TWAI Tests +//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 +//% FEATURES: unstable embassy + +#![no_std] +#![no_main] + +use hil_test as _; + +#[cfg(not(any(esp32c2, esp32c3)))] +#[embedded_test::tests(default_timeout = 3)] +mod pcnt { + use esp_hal::{ + delay::Delay, + gpio::{AnyPin, Input, InputConfig, Level, Output, OutputConfig, Pin, Pull}, + pcnt::{Pcnt, channel::EdgeMode}, + }; + + struct Context<'d> { + pcnt: Pcnt<'d>, + input: AnyPin<'d>, + output: AnyPin<'d>, + delay: Delay, + } + #[init] + fn init() -> Context<'static> { + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let (din, dout) = hil_test::common_test_pins!(peripherals); + + let din = din.degrade(); + let dout = dout.degrade(); + + Context { + pcnt: Pcnt::new(peripherals.PCNT), + input: din, + output: dout, + delay: Delay::new(), + } + } + + #[test] + fn test_increment_on_pos_edge(ctx: Context<'static>) { + let unit = ctx.pcnt.unit0; + + // Setup channel 0 to increment the count when gpio2 does LOW -> HIGH + unit.channel0.set_edge_signal(Input::new( + ctx.input, + InputConfig::default().with_pull(Pull::Down), + )); + unit.channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + let mut output = Output::new(ctx.output, Level::Low, OutputConfig::default()); + + unit.resume(); + + assert_eq!(0, unit.value()); + + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(1, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + + assert_eq!(1, unit.value()); + + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(2, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + + assert_eq!(2, unit.value()); + } + + #[test] + fn test_increment_on_neg_edge(ctx: Context<'static>) { + let unit = ctx.pcnt.unit1; + + // Setup channel 0 to increment the count when gpio2 does LOW -> HIGH + unit.channel0.set_edge_signal(Input::new( + ctx.input, + InputConfig::default().with_pull(Pull::Up), + )); + unit.channel0 + .set_input_mode(EdgeMode::Increment, EdgeMode::Hold); + + let mut output = Output::new(ctx.output, Level::High, OutputConfig::default()); + + unit.resume(); + + assert_eq!(0, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + + assert_eq!(1, unit.value()); + + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(1, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + + assert_eq!(2, unit.value()); + + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(2, unit.value()); + } + + #[test] + fn test_increment_past_high_limit(ctx: Context<'static>) { + let unit = ctx.pcnt.unit3; + + unit.set_high_limit(Some(3)).unwrap(); + + // Setup channel 0 to increment the count when gpio2 does LOW -> HIGH + unit.channel0.set_edge_signal(Input::new( + ctx.input, + InputConfig::default().with_pull(Pull::Up), + )); + unit.channel0 + .set_input_mode(EdgeMode::Increment, EdgeMode::Hold); + + let mut output = Output::new(ctx.output, Level::High, OutputConfig::default()); + + unit.resume(); + + assert_eq!(0, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(1, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(2, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(0, unit.value()); + assert!(unit.events().high_limit); + assert!(unit.interrupt_is_set()); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(1, unit.value()); + + // high limit event remains after next increment. + assert!(unit.events().high_limit); + + unit.reset_interrupt(); + assert!(!unit.interrupt_is_set()); + + // high limit event remains after interrupt is cleared. + assert!(unit.events().high_limit); + } + + #[test] + fn test_increment_past_thresholds(ctx: Context<'static>) { + let unit = ctx.pcnt.unit0; + + unit.set_threshold0(Some(2)); + unit.set_threshold1(Some(4)); + // For some reason this is needed for the above thresholds to apply. + unit.clear(); + + // Setup channel 0 to increment the count when gpio2 does LOW -> HIGH + unit.channel0.set_edge_signal(Input::new( + ctx.input, + InputConfig::default().with_pull(Pull::Up), + )); + unit.channel0 + .set_input_mode(EdgeMode::Increment, EdgeMode::Hold); + + let mut output = Output::new(ctx.output, Level::High, OutputConfig::default()); + + unit.resume(); + + assert_eq!(0, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(1, unit.value()); + assert!(!unit.interrupt_is_set()); + assert!(!unit.events().threshold0); + assert!(!unit.events().threshold1); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(2, unit.value()); + assert!(unit.interrupt_is_set()); + assert!(unit.events().threshold0); + assert!(!unit.events().threshold1); + + unit.reset_interrupt(); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(3, unit.value()); + assert!(!unit.interrupt_is_set()); + // threshold event remains after next increment and after interrupt is cleared. + assert!(unit.events().threshold0); + assert!(!unit.events().threshold1); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(4, unit.value()); + assert!(unit.interrupt_is_set()); + // threshold event cleared after new event occurs. + assert!(!unit.events().threshold0); + assert!(unit.events().threshold1); + } + + #[test] + fn test_decrement_past_low_limit(ctx: Context<'static>) { + let unit = ctx.pcnt.unit0; + + unit.set_low_limit(Some(-3)).unwrap(); + // For some reason this is needed for the above limit to apply. + unit.clear(); + + // Setup channel 0 to decrement the count when gpio2 does LOW -> HIGH + unit.channel0.set_edge_signal(Input::new( + ctx.input, + InputConfig::default().with_pull(Pull::Up), + )); + unit.channel0 + .set_input_mode(EdgeMode::Decrement, EdgeMode::Hold); + + let mut output = Output::new(ctx.output, Level::High, OutputConfig::default()); + + unit.resume(); + + assert_eq!(0, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(-1, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(-2, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(0, unit.value()); + assert!(unit.events().low_limit); + assert!(unit.interrupt_is_set()); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + + assert_eq!(-1, unit.value()); + + // low limit event remains after next increment. + assert!(unit.events().low_limit); + + unit.reset_interrupt(); + assert!(!unit.interrupt_is_set()); + + // low limit event remains after interrupt is cleared. + assert!(unit.events().low_limit); + } + + #[test] + fn test_unit_count_range(ctx: Context<'static>) { + let unit = ctx.pcnt.unit2; + + // Setup channel 1 to increment the count when gpio2 does LOW -> HIGH + unit.channel1.set_edge_signal(Input::new( + ctx.input, + InputConfig::default().with_pull(Pull::Up), + )); + unit.channel1 + .set_input_mode(EdgeMode::Increment, EdgeMode::Hold); + + let mut output = Output::new(ctx.output, Level::High, OutputConfig::default()); + + unit.resume(); + + assert_eq!(0, unit.value()); + + for i in (0..=i16::MAX).chain(i16::MIN..0) { + assert_eq!(i, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + } + + assert_eq!(0, unit.value()); + + // Channel 1 should now decrement. + unit.channel1 + .set_input_mode(EdgeMode::Decrement, EdgeMode::Hold); + + for i in (0..=i16::MAX).chain(i16::MIN..=0).rev() { + assert_eq!(i, unit.value()); + + output.set_low(); + ctx.delay.delay_micros(1); + output.set_high(); + ctx.delay.delay_micros(1); + } + } +} + +#[embedded_test::tests(default_timeout = 5)] +mod rng { + use esp_hal::rng::{Rng, Trng, TrngSource}; + + #[test] + fn test_trng_without_source_is_error() { + assert!(Trng::try_new().is_err()); + + // Rng can be created anyway: + let _rng = Rng::new(); + } + + #[test] + fn test_trng_source_cannot_be_disabled_while_in_use() { + let p = esp_hal::init(Default::default()); + let source = TrngSource::new(p.RNG, p.ADC1); + + let trng = Trng::try_new().unwrap(); + + let _source = source.try_disable().unwrap_err(); + + // Need to drop trng first + core::mem::drop(trng); + + // Rng will not prevent disabling the TrngSource + let _rng = Rng::new(); + } + + #[test] + #[should_panic] + fn test_trng_source_cannot_be_dropped_while_in_use() { + let p = esp_hal::init(Default::default()); + let source = TrngSource::new(p.RNG, p.ADC1); + + let _trng = Trng::try_new().unwrap(); + + core::mem::drop(source); + } + + #[test] + fn test_trng_source_can_be_dropped_if_unsafely_enabled() { + let p = esp_hal::init(Default::default()); + + let source = TrngSource::new(p.RNG, p.ADC1); + + // Unsafely increase the counter. Practically, this may be done in esp-radio. + unsafe { TrngSource::increase_entropy_source_counter() }; + + let _trng = Trng::try_new().unwrap(); + + core::mem::drop(source); + } +} + +#[cfg(not(esp32))] +#[embedded_test::tests(default_timeout = 3)] +mod systimer { + use core::cell::RefCell; + + use critical_section::Mutex; + use embedded_hal::delay::DelayNs; + use esp_hal::{ + Blocking, + delay::Delay, + handler, + time::Duration, + timer::{ + OneShotTimer, + PeriodicTimer, + systimer::{Alarm, SystemTimer}, + }, + }; + use portable_atomic::{AtomicUsize, Ordering}; + + static ALARM_TARGET: Mutex>>> = + Mutex::new(RefCell::new(None)); + static ALARM_PERIODIC: Mutex>>> = + Mutex::new(RefCell::new(None)); + + struct Context { + alarm0: Alarm<'static>, + alarm1: Alarm<'static>, + } + + #[handler(priority = esp_hal::interrupt::Priority::min())] + fn pass_test_if_called() { + critical_section::with(|cs| { + ALARM_TARGET + .borrow_ref_mut(cs) + .as_mut() + .unwrap() + .clear_interrupt() + }); + embedded_test::export::check_outcome(()); + } + + #[handler(priority = esp_hal::interrupt::Priority::min())] + fn handle_periodic_interrupt() { + critical_section::with(|cs| { + ALARM_PERIODIC + .borrow_ref_mut(cs) + .as_mut() + .unwrap() + .clear_interrupt() + }); + } + + static COUNTER: AtomicUsize = AtomicUsize::new(0); + + #[handler(priority = esp_hal::interrupt::Priority::min())] + fn pass_test_if_called_twice() { + critical_section::with(|cs| { + ALARM_PERIODIC + .borrow_ref_mut(cs) + .as_mut() + .unwrap() + .clear_interrupt() + }); + COUNTER.fetch_add(1, Ordering::Relaxed); + if COUNTER.load(Ordering::Relaxed) == 2 { + embedded_test::export::check_outcome(()); + } + } + + #[handler(priority = esp_hal::interrupt::Priority::min())] + fn target_fail_test_if_called_twice() { + critical_section::with(|cs| { + ALARM_TARGET + .borrow_ref_mut(cs) + .as_mut() + .unwrap() + .clear_interrupt() + }); + COUNTER.fetch_add(1, Ordering::Relaxed); + assert!(COUNTER.load(Ordering::Relaxed) != 2); + } + #[init] + fn init() -> Context { + let peripherals = esp_hal::init(esp_hal::Config::default()); + let systimer = SystemTimer::new(peripherals.SYSTIMER); + + Context { + alarm0: systimer.alarm0, + alarm1: systimer.alarm1, + } + } + + #[test] + fn target_interrupt_is_handled(ctx: Context) { + let mut alarm0 = OneShotTimer::new(ctx.alarm0); + + critical_section::with(|cs| { + alarm0.set_interrupt_handler(pass_test_if_called); + alarm0.listen(); + alarm0.schedule(Duration::from_millis(10)).unwrap(); + + ALARM_TARGET.borrow_ref_mut(cs).replace(alarm0); + }); + + // We'll end the test in the interrupt handler. + loop {} + } + + #[test] + fn target_interrupt_is_handled_once(ctx: Context) { + let mut alarm0 = OneShotTimer::new(ctx.alarm0); + let mut alarm1 = PeriodicTimer::new(ctx.alarm1); + + COUNTER.store(0, Ordering::Relaxed); + + critical_section::with(|cs| { + alarm0.set_interrupt_handler(target_fail_test_if_called_twice); + alarm0.listen(); + alarm0.schedule(Duration::from_millis(10)).unwrap(); + + alarm1.set_interrupt_handler(handle_periodic_interrupt); + alarm1.listen(); + alarm1.start(Duration::from_millis(100)).unwrap(); + + ALARM_TARGET.borrow_ref_mut(cs).replace(alarm0); + ALARM_PERIODIC.borrow_ref_mut(cs).replace(alarm1); + }); + + let mut delay = Delay::new(); + delay.delay_ms(300); + } + + #[test] + fn periodic_interrupt_is_handled(ctx: Context) { + let mut alarm1 = PeriodicTimer::new(ctx.alarm1); + + COUNTER.store(0, Ordering::Relaxed); + + critical_section::with(|cs| { + alarm1.set_interrupt_handler(pass_test_if_called_twice); + alarm1.listen(); + alarm1.start(Duration::from_millis(100)).unwrap(); + + ALARM_PERIODIC.borrow_ref_mut(cs).replace(alarm1); + }); + + // We'll end the test in the interrupt handler. + loop {} + } +} + +#[cfg(any(esp32, esp32c3, esp32c6, esp32h2, esp32s2, esp32s3))] +mod twai { + use embedded_can::Frame; + use esp_hal::{ + Async, + Blocking, + DriverMode, + interrupt, + interrupt::{Priority, software::SoftwareInterruptControl}, + peripherals::Interrupt::TWAI0, + system::Cpu, + timer::timg::TimerGroup, + twai::{self, ErrorKind, EspTwaiFrame, StandardId, TwaiMode, filter::SingleStandardFilter}, + }; + use nb::block; + + use crate::twai::Priority::Priority3; + + struct Context { + twai: twai::Twai<'static, D>, + } + + #[embedded_test::tests(default_timeout = 3)] + mod blocking_tests { + use super::*; + + #[init] + fn init() -> Context { + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let (loopback_pin, _) = hil_test::common_test_pins!(peripherals); + + let (rx, tx) = unsafe { loopback_pin.split() }; + + let mut config = twai::TwaiConfiguration::new( + peripherals.TWAI0, + rx, + tx, + twai::BaudRate::B1000K, + TwaiMode::SelfTest, + ); + + config.set_filter(SingleStandardFilter::new( + b"00000000000", + b"x", + [b"xxxxxxxx", b"xxxxxxxx"], + )); + + let twai = config.start(); + + Context { twai } + } + + #[test] + fn test_send_receive(mut ctx: Context) { + let frame = EspTwaiFrame::new_self_reception(StandardId::ZERO, &[1, 2, 3]).unwrap(); + block!(ctx.twai.transmit(&frame)).unwrap(); + + let frame = block!(ctx.twai.receive()).unwrap(); + + assert_eq!(frame.data(), &[1, 2, 3]) + } + } + + #[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())] + mod async_tests { + use super::*; + + #[init] + async fn init() -> Context { + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let (loopback_pin, _) = hil_test::common_test_pins!(peripherals); + + let (rx, tx) = unsafe { loopback_pin.split() }; + + let config = twai::TwaiConfiguration::new( + peripherals.TWAI0, + rx, + tx, + twai::BaudRate::B1000K, + TwaiMode::SelfTest, + ); + + let _sw_int = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); + + let timg0 = TimerGroup::new(peripherals.TIMG0); + + esp_rtos::start( + timg0.timer0, + #[cfg(riscv)] + _sw_int.software_interrupt0, + ); + + let twai = config.into_async().start(); + + Context { twai } + } + + async fn transmit_frames(ctx: &mut Context, frame: &EspTwaiFrame, count: usize) { + for _ in 0..count { + ctx.twai.transmit_async(frame).await.unwrap(); + } + } + + async fn receive_frames(ctx: &mut Context, expected_count: usize) { + let mut received_count = 0; + let mut iterations = 0; + + while received_count < expected_count { + match ctx.twai.receive_async().await { + Ok(_) => received_count += 1, + Err(esp_hal::twai::EspTwaiError::EmbeddedHAL(ErrorKind::Overrun)) => { + received_count += 1; + } + Err(err) => panic!("{:#?}", err), + } + iterations += 1; + } + + assert_eq!(expected_count, iterations, "receive_async loop iterations"); + assert_ne!(received_count, 0, "received_count"); + } + + #[test] + async fn test_async_transmit_and_receive(mut ctx: Context) { + let frame = + EspTwaiFrame::new_self_reception(StandardId::new(0).unwrap(), b"12345678").unwrap(); + transmit_frames(&mut ctx, &frame, 31).await; + receive_frames(&mut ctx, 31).await; + } + + #[test] + // regression test for https://github.com/esp-rs/esp-hal/issues/4235 + async fn test_buffer_overrun_on_empty_queue(mut ctx: Context) { + let frame = + EspTwaiFrame::new_self_reception(StandardId::new(0).unwrap(), b"12345678").unwrap(); + + interrupt::disable(Cpu::ProCpu, TWAI0); + + const NUM_SENT_FRAMES: usize = 10; + for _ in 0..NUM_SENT_FRAMES { + block!(ctx.twai.transmit(&frame)).unwrap(); + } + + let _ = interrupt::enable(TWAI0, Priority3); + + const NUM_ASYNC_SENT_FRAMES: usize = 20; + transmit_frames(&mut ctx, &frame, NUM_ASYNC_SENT_FRAMES).await; + + receive_frames(&mut ctx, NUM_SENT_FRAMES + NUM_ASYNC_SENT_FRAMES).await; + } + } +} diff --git a/hil-test/src/bin/pcnt.rs b/hil-test/src/bin/pcnt.rs deleted file mode 100644 index b32d7a5788d..00000000000 --- a/hil-test/src/bin/pcnt.rs +++ /dev/null @@ -1,354 +0,0 @@ -//! PCNT tests - -//% CHIPS: esp32 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: unstable - -#![no_std] -#![no_main] - -use esp_hal::{ - delay::Delay, - gpio::{AnyPin, Input, InputConfig, Level, Output, OutputConfig, Pin, Pull}, - pcnt::{Pcnt, channel::EdgeMode}, -}; -use hil_test as _; - -struct Context<'d> { - pcnt: Pcnt<'d>, - input: AnyPin<'d>, - output: AnyPin<'d>, - delay: Delay, -} - -#[embedded_test::tests(default_timeout = 3)] -mod tests { - use super::*; - - #[init] - fn init() -> Context<'static> { - let peripherals = esp_hal::init(esp_hal::Config::default()); - - let (din, dout) = hil_test::common_test_pins!(peripherals); - - let din = din.degrade(); - let dout = dout.degrade(); - - Context { - pcnt: Pcnt::new(peripherals.PCNT), - input: din, - output: dout, - delay: Delay::new(), - } - } - - #[test] - fn test_increment_on_pos_edge(ctx: Context<'static>) { - let unit = ctx.pcnt.unit0; - - // Setup channel 0 to increment the count when gpio2 does LOW -> HIGH - unit.channel0.set_edge_signal(Input::new( - ctx.input, - InputConfig::default().with_pull(Pull::Down), - )); - unit.channel0 - .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); - - let mut output = Output::new(ctx.output, Level::Low, OutputConfig::default()); - - unit.resume(); - - assert_eq!(0, unit.value()); - - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(1, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - - assert_eq!(1, unit.value()); - - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(2, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - - assert_eq!(2, unit.value()); - } - - #[test] - fn test_increment_on_neg_edge(ctx: Context<'static>) { - let unit = ctx.pcnt.unit1; - - // Setup channel 0 to increment the count when gpio2 does LOW -> HIGH - unit.channel0.set_edge_signal(Input::new( - ctx.input, - InputConfig::default().with_pull(Pull::Up), - )); - unit.channel0 - .set_input_mode(EdgeMode::Increment, EdgeMode::Hold); - - let mut output = Output::new(ctx.output, Level::High, OutputConfig::default()); - - unit.resume(); - - assert_eq!(0, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - - assert_eq!(1, unit.value()); - - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(1, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - - assert_eq!(2, unit.value()); - - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(2, unit.value()); - } - - #[test] - fn test_increment_past_high_limit(ctx: Context<'static>) { - let unit = ctx.pcnt.unit3; - - unit.set_high_limit(Some(3)).unwrap(); - - // Setup channel 0 to increment the count when gpio2 does LOW -> HIGH - unit.channel0.set_edge_signal(Input::new( - ctx.input, - InputConfig::default().with_pull(Pull::Up), - )); - unit.channel0 - .set_input_mode(EdgeMode::Increment, EdgeMode::Hold); - - let mut output = Output::new(ctx.output, Level::High, OutputConfig::default()); - - unit.resume(); - - assert_eq!(0, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(1, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(2, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(0, unit.value()); - assert!(unit.events().high_limit); - assert!(unit.interrupt_is_set()); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(1, unit.value()); - - // high limit event remains after next increment. - assert!(unit.events().high_limit); - - unit.reset_interrupt(); - assert!(!unit.interrupt_is_set()); - - // high limit event remains after interrupt is cleared. - assert!(unit.events().high_limit); - } - - #[test] - fn test_increment_past_thresholds(ctx: Context<'static>) { - let unit = ctx.pcnt.unit0; - - unit.set_threshold0(Some(2)); - unit.set_threshold1(Some(4)); - // For some reason this is needed for the above thresholds to apply. - unit.clear(); - - // Setup channel 0 to increment the count when gpio2 does LOW -> HIGH - unit.channel0.set_edge_signal(Input::new( - ctx.input, - InputConfig::default().with_pull(Pull::Up), - )); - unit.channel0 - .set_input_mode(EdgeMode::Increment, EdgeMode::Hold); - - let mut output = Output::new(ctx.output, Level::High, OutputConfig::default()); - - unit.resume(); - - assert_eq!(0, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(1, unit.value()); - assert!(!unit.interrupt_is_set()); - assert!(!unit.events().threshold0); - assert!(!unit.events().threshold1); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(2, unit.value()); - assert!(unit.interrupt_is_set()); - assert!(unit.events().threshold0); - assert!(!unit.events().threshold1); - - unit.reset_interrupt(); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(3, unit.value()); - assert!(!unit.interrupt_is_set()); - // threshold event remains after next increment and after interrupt is cleared. - assert!(unit.events().threshold0); - assert!(!unit.events().threshold1); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(4, unit.value()); - assert!(unit.interrupt_is_set()); - // threshold event cleared after new event occurs. - assert!(!unit.events().threshold0); - assert!(unit.events().threshold1); - } - - #[test] - fn test_decrement_past_low_limit(ctx: Context<'static>) { - let unit = ctx.pcnt.unit0; - - unit.set_low_limit(Some(-3)).unwrap(); - // For some reason this is needed for the above limit to apply. - unit.clear(); - - // Setup channel 0 to decrement the count when gpio2 does LOW -> HIGH - unit.channel0.set_edge_signal(Input::new( - ctx.input, - InputConfig::default().with_pull(Pull::Up), - )); - unit.channel0 - .set_input_mode(EdgeMode::Decrement, EdgeMode::Hold); - - let mut output = Output::new(ctx.output, Level::High, OutputConfig::default()); - - unit.resume(); - - assert_eq!(0, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(-1, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(-2, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(0, unit.value()); - assert!(unit.events().low_limit); - assert!(unit.interrupt_is_set()); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - - assert_eq!(-1, unit.value()); - - // low limit event remains after next increment. - assert!(unit.events().low_limit); - - unit.reset_interrupt(); - assert!(!unit.interrupt_is_set()); - - // low limit event remains after interrupt is cleared. - assert!(unit.events().low_limit); - } - - #[test] - fn test_unit_count_range(ctx: Context<'static>) { - let unit = ctx.pcnt.unit2; - - // Setup channel 1 to increment the count when gpio2 does LOW -> HIGH - unit.channel1.set_edge_signal(Input::new( - ctx.input, - InputConfig::default().with_pull(Pull::Up), - )); - unit.channel1 - .set_input_mode(EdgeMode::Increment, EdgeMode::Hold); - - let mut output = Output::new(ctx.output, Level::High, OutputConfig::default()); - - unit.resume(); - - assert_eq!(0, unit.value()); - - for i in (0..=i16::MAX).chain(i16::MIN..0) { - assert_eq!(i, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - } - - assert_eq!(0, unit.value()); - - // Channel 1 should now decrement. - unit.channel1 - .set_input_mode(EdgeMode::Decrement, EdgeMode::Hold); - - for i in (0..=i16::MAX).chain(i16::MIN..=0).rev() { - assert_eq!(i, unit.value()); - - output.set_low(); - ctx.delay.delay_micros(1); - output.set_high(); - ctx.delay.delay_micros(1); - } - } -} diff --git a/hil-test/src/bin/rng.rs b/hil-test/src/bin/rng.rs deleted file mode 100644 index 2153947a001..00000000000 --- a/hil-test/src/bin/rng.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! RNG Tests - -//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: unstable - -#![no_std] -#![no_main] - -use hil_test as _; - -#[embedded_test::tests(default_timeout = 5)] -mod tests { - use esp_hal::rng::{Rng, Trng, TrngSource}; - - #[test] - fn test_trng_without_source_is_error() { - assert!(Trng::try_new().is_err()); - - // Rng can be created anyway: - let _rng = Rng::new(); - } - - #[test] - fn test_trng_source_cannot_be_disabled_while_in_use() { - let p = esp_hal::init(Default::default()); - let source = TrngSource::new(p.RNG, p.ADC1); - - let trng = Trng::try_new().unwrap(); - - let _source = source.try_disable().unwrap_err(); - - // Need to drop trng first - core::mem::drop(trng); - - // Rng will not prevent disabling the TrngSource - let _rng = Rng::new(); - } - - #[test] - #[should_panic] - fn test_trng_source_cannot_be_dropped_while_in_use() { - let p = esp_hal::init(Default::default()); - let source = TrngSource::new(p.RNG, p.ADC1); - - let _trng = Trng::try_new().unwrap(); - - core::mem::drop(source); - } - - #[test] - fn test_trng_source_can_be_dropped_if_unsafely_enabled() { - let p = esp_hal::init(Default::default()); - - let source = TrngSource::new(p.RNG, p.ADC1); - - // Unsafely increase the counter. Practically, this may be done in esp-radio. - unsafe { TrngSource::increase_entropy_source_counter() }; - - let _trng = Trng::try_new().unwrap(); - - core::mem::drop(source); - } -} diff --git a/hil-test/src/bin/systimer.rs b/hil-test/src/bin/systimer.rs deleted file mode 100644 index 933539026fc..00000000000 --- a/hil-test/src/bin/systimer.rs +++ /dev/null @@ -1,163 +0,0 @@ -//! System Timer Test - -// esp32 disabled as it does not have a systimer -//% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: unstable - -#![no_std] -#![no_main] - -use core::cell::RefCell; - -use critical_section::Mutex; -use embedded_hal::delay::DelayNs; -use esp_hal::{ - Blocking, - delay::Delay, - handler, - time::Duration, - timer::{ - OneShotTimer, - PeriodicTimer, - systimer::{Alarm, SystemTimer}, - }, -}; -use hil_test as _; -use portable_atomic::{AtomicUsize, Ordering}; - -static ALARM_TARGET: Mutex>>> = - Mutex::new(RefCell::new(None)); -static ALARM_PERIODIC: Mutex>>> = - Mutex::new(RefCell::new(None)); - -struct Context { - alarm0: Alarm<'static>, - alarm1: Alarm<'static>, -} - -#[handler(priority = esp_hal::interrupt::Priority::min())] -fn pass_test_if_called() { - critical_section::with(|cs| { - ALARM_TARGET - .borrow_ref_mut(cs) - .as_mut() - .unwrap() - .clear_interrupt() - }); - embedded_test::export::check_outcome(()); -} - -#[handler(priority = esp_hal::interrupt::Priority::min())] -fn handle_periodic_interrupt() { - critical_section::with(|cs| { - ALARM_PERIODIC - .borrow_ref_mut(cs) - .as_mut() - .unwrap() - .clear_interrupt() - }); -} - -static COUNTER: AtomicUsize = AtomicUsize::new(0); - -#[handler(priority = esp_hal::interrupt::Priority::min())] -fn pass_test_if_called_twice() { - critical_section::with(|cs| { - ALARM_PERIODIC - .borrow_ref_mut(cs) - .as_mut() - .unwrap() - .clear_interrupt() - }); - COUNTER.fetch_add(1, Ordering::Relaxed); - if COUNTER.load(Ordering::Relaxed) == 2 { - embedded_test::export::check_outcome(()); - } -} - -#[handler(priority = esp_hal::interrupt::Priority::min())] -fn target_fail_test_if_called_twice() { - critical_section::with(|cs| { - ALARM_TARGET - .borrow_ref_mut(cs) - .as_mut() - .unwrap() - .clear_interrupt() - }); - COUNTER.fetch_add(1, Ordering::Relaxed); - assert!(COUNTER.load(Ordering::Relaxed) != 2); -} - -#[embedded_test::tests(default_timeout = 3)] -mod tests { - use super::*; - - #[init] - fn init() -> Context { - let peripherals = esp_hal::init(esp_hal::Config::default()); - let systimer = SystemTimer::new(peripherals.SYSTIMER); - - Context { - alarm0: systimer.alarm0, - alarm1: systimer.alarm1, - } - } - - #[test] - fn target_interrupt_is_handled(ctx: Context) { - let mut alarm0 = OneShotTimer::new(ctx.alarm0); - - critical_section::with(|cs| { - alarm0.set_interrupt_handler(pass_test_if_called); - alarm0.listen(); - alarm0.schedule(Duration::from_millis(10)).unwrap(); - - ALARM_TARGET.borrow_ref_mut(cs).replace(alarm0); - }); - - // We'll end the test in the interrupt handler. - loop {} - } - - #[test] - fn target_interrupt_is_handled_once(ctx: Context) { - let mut alarm0 = OneShotTimer::new(ctx.alarm0); - let mut alarm1 = PeriodicTimer::new(ctx.alarm1); - - COUNTER.store(0, Ordering::Relaxed); - - critical_section::with(|cs| { - alarm0.set_interrupt_handler(target_fail_test_if_called_twice); - alarm0.listen(); - alarm0.schedule(Duration::from_millis(10)).unwrap(); - - alarm1.set_interrupt_handler(handle_periodic_interrupt); - alarm1.listen(); - alarm1.start(Duration::from_millis(100)).unwrap(); - - ALARM_TARGET.borrow_ref_mut(cs).replace(alarm0); - ALARM_PERIODIC.borrow_ref_mut(cs).replace(alarm1); - }); - - let mut delay = Delay::new(); - delay.delay_ms(300); - } - - #[test] - fn periodic_interrupt_is_handled(ctx: Context) { - let mut alarm1 = PeriodicTimer::new(ctx.alarm1); - - COUNTER.store(0, Ordering::Relaxed); - - critical_section::with(|cs| { - alarm1.set_interrupt_handler(pass_test_if_called_twice); - alarm1.listen(); - alarm1.start(Duration::from_millis(100)).unwrap(); - - ALARM_PERIODIC.borrow_ref_mut(cs).replace(alarm1); - }); - - // We'll end the test in the interrupt handler. - loop {} - } -} diff --git a/hil-test/src/bin/twai.rs b/hil-test/src/bin/twai.rs deleted file mode 100644 index c693056ad68..00000000000 --- a/hil-test/src/bin/twai.rs +++ /dev/null @@ -1,160 +0,0 @@ -//! TWAI test - -//% CHIPS: esp32 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 -//% FEATURES: unstable embassy - -#![no_std] -#![no_main] - -use embedded_can::Frame; -use esp_hal::{ - Async, - Blocking, - DriverMode, - interrupt, - interrupt::{Priority, software::SoftwareInterruptControl}, - peripherals::Interrupt::TWAI0, - system::Cpu, - timer::timg::TimerGroup, - twai::{self, ErrorKind, EspTwaiFrame, StandardId, TwaiMode, filter::SingleStandardFilter}, -}; -use hil_test as _; -use nb::block; - -use crate::Priority::Priority3; - -struct Context { - twai: twai::Twai<'static, D>, -} - -#[embedded_test::tests(default_timeout = 3)] -mod blocking_tests { - use super::*; - - #[init] - fn init() -> Context { - let peripherals = esp_hal::init(esp_hal::Config::default()); - - let (loopback_pin, _) = hil_test::common_test_pins!(peripherals); - - let (rx, tx) = unsafe { loopback_pin.split() }; - - let mut config = twai::TwaiConfiguration::new( - peripherals.TWAI0, - rx, - tx, - twai::BaudRate::B1000K, - TwaiMode::SelfTest, - ); - - config.set_filter(SingleStandardFilter::new( - b"00000000000", - b"x", - [b"xxxxxxxx", b"xxxxxxxx"], - )); - - let twai = config.start(); - - Context { twai } - } - - #[test] - fn test_send_receive(mut ctx: Context) { - let frame = EspTwaiFrame::new_self_reception(StandardId::ZERO, &[1, 2, 3]).unwrap(); - block!(ctx.twai.transmit(&frame)).unwrap(); - - let frame = block!(ctx.twai.receive()).unwrap(); - - assert_eq!(frame.data(), &[1, 2, 3]) - } -} - -#[embedded_test::tests(default_timeout = 3, executor = hil_test::Executor::new())] -mod async_tests { - use super::*; - - #[init] - async fn init() -> Context { - let peripherals = esp_hal::init(esp_hal::Config::default()); - - let (loopback_pin, _) = hil_test::common_test_pins!(peripherals); - - let (rx, tx) = unsafe { loopback_pin.split() }; - - let config = twai::TwaiConfiguration::new( - peripherals.TWAI0, - rx, - tx, - twai::BaudRate::B1000K, - TwaiMode::SelfTest, - ); - - let _sw_int = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); - - let timg0 = TimerGroup::new(peripherals.TIMG0); - - esp_rtos::start( - timg0.timer0, - #[cfg(riscv)] - _sw_int.software_interrupt0, - ); - - let twai = config.into_async().start(); - - Context { twai } - } - - async fn transmit_frames(ctx: &mut Context, frame: &EspTwaiFrame, count: usize) { - for _ in 0..count { - ctx.twai.transmit_async(frame).await.unwrap(); - } - } - - async fn receive_frames(ctx: &mut Context, expected_count: usize) { - let mut received_count = 0; - let mut iterations = 0; - - while received_count < expected_count { - match ctx.twai.receive_async().await { - Ok(_) => received_count += 1, - Err(esp_hal::twai::EspTwaiError::EmbeddedHAL(ErrorKind::Overrun)) => { - received_count += 1; - } - Err(err) => panic!("{:#?}", err), - } - iterations += 1; - } - - assert_eq!(expected_count, iterations, "receive_async loop iterations"); - assert_ne!(received_count, 0, "received_count"); - } - - #[test] - async fn test_async_transmit_and_receive(mut ctx: Context) { - let frame = - EspTwaiFrame::new_self_reception(StandardId::new(0).unwrap(), b"12345678").unwrap(); - transmit_frames(&mut ctx, &frame, 31).await; - receive_frames(&mut ctx, 31).await; - } - - #[test] - // regression test for https://github.com/esp-rs/esp-hal/issues/4235 - async fn test_buffer_overrun_on_empty_queue(mut ctx: Context) { - let frame = - EspTwaiFrame::new_self_reception(StandardId::new(0).unwrap(), b"12345678").unwrap(); - - interrupt::disable(Cpu::ProCpu, TWAI0); - - const NUM_SENT_FRAMES: usize = 10; - for _ in 0..NUM_SENT_FRAMES { - block!(ctx.twai.transmit(&frame)).unwrap(); - } - - let _ = interrupt::enable(TWAI0, Priority3); - - const NUM_ASYNC_SENT_FRAMES: usize = 20; - transmit_frames(&mut ctx, &frame, NUM_ASYNC_SENT_FRAMES).await; - - receive_frames(&mut ctx, NUM_SENT_FRAMES + NUM_ASYNC_SENT_FRAMES).await; - } -} From 3e5b111b5024390ac4f25c5b4c8b50816fcbaf97 Mon Sep 17 00:00:00 2001 From: Juraj Sadel Date: Fri, 17 Oct 2025 09:13:26 +0200 Subject: [PATCH 2/3] Don't link test as example --- esp-hal/src/interrupt/riscv.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/esp-hal/src/interrupt/riscv.rs b/esp-hal/src/interrupt/riscv.rs index dc8bccfa71d..d90d1e55295 100644 --- a/esp-hal/src/interrupt/riscv.rs +++ b/esp-hal/src/interrupt/riscv.rs @@ -226,11 +226,6 @@ pub static RESERVED_INTERRUPTS: &[u32] = PRIORITY_TO_INTERRUPT; /// /// Trying using a reserved interrupt from [`RESERVED_INTERRUPTS`] will return /// an error. -/// -/// ## Example -/// Visit the [mod interrupt] test to see a proper example of how to use direct vectoring. -/// -/// [mod interrupt]: https://github.com/esp-rs/esp-hal/blob/main/hil-test/src/bin/misc_drivers.rs pub fn enable_direct( interrupt: Interrupt, level: Priority, From 3dd0475213e620e2e6acd604a7c3a02d5ba5a865 Mon Sep 17 00:00:00 2001 From: Juraj Sadel Date: Fri, 17 Oct 2025 09:19:12 +0200 Subject: [PATCH 3/3] Reformat and sort bin in Cargo.toml --- hil-test/Cargo.toml | 82 ++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index 5041486828e..5710bb514cc 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -8,88 +8,88 @@ publish = false name = "hil_test" [[bin]] -name = "aes" -harness = false +name = "aes" +harness = false [[bin]] -name = "alloc_psram" -harness = false +name = "alloc_psram" +harness = false required-features = ["psram"] [[bin]] -name = "gpio" -harness = false +name = "ecc_rsa_sha" +harness = false [[bin]] -name = "gpio_custom_handler" -harness = false +name = "embassy_timers_executors" +harness = false +required-features = ["embassy"] [[bin]] -name = "misc_drivers" -harness = false +name = "esp_radio" +harness = false +required-features = ["esp-radio", "esp-alloc"] [[bin]] -name = "interrupt" -harness = false +name = "gpio" +harness = false [[bin]] -name = "i2c" -harness = false +name = "gpio_custom_handler" +harness = false [[bin]] -name = "i2s" -harness = false +name = "i2c" +harness = false [[bin]] -name = "lcd_cam" -harness = false +name = "i2s" +harness = false [[bin]] -name = "misc_non_drivers" -harness = false +name = "interrupt" +harness = false [[bin]] -name = "spi_full_duplex" -harness = false +name = "lcd_cam" +harness = false [[bin]] -name = "spi_half_duplex_slave_qspi" -harness = false +name = "misc_drivers" +harness = false [[bin]] -name = "spi_half_duplex_write_psram" -harness = false +name = "misc_non_drivers" +harness = false [[bin]] -name = "storage_read_app_desc" -harness = false +name = "parl_io" +harness = false [[bin]] -name = "parl_io" -harness = false +name = "rmt" +harness = false +required-features = ["embassy"] [[bin]] -name = "rmt" -harness = false -required-features = ["embassy"] +name = "spi_full_duplex" +harness = false [[bin]] -name = "ecc_rsa_sha" -harness = false +name = "spi_half_duplex_slave_qspi" +harness = false [[bin]] -name = "uart" -harness = false +name = "spi_half_duplex_write_psram" +harness = false [[bin]] -name = "embassy_timers_executors" +name = "storage_read_app_desc" harness = false -required-features = ["embassy"] [[bin]] -name = "esp_radio" +name = "uart" harness = false -required-features = ["esp-radio", "esp-alloc"] [dependencies] allocator-api2 = { version = "0.3.0", default-features = false, features = ["alloc"] }