From 25126586530e65fd90cdbafa34956e264cb255b1 Mon Sep 17 00:00:00 2001 From: Scott Mabin Date: Wed, 27 Nov 2024 14:10:56 +0000 Subject: [PATCH] [3/3] Timer refactor: Timer driver mode, ETM support and erasure (#2586) * Add mode param and type erasure to high level timer drivers * Add async impls for the timers plus eha impl * re add etm example, fix etm for systimer * re add tests * Add Into + 'static bounds to the Timer trait * remove set_alarm_active impl detail from the timer trait * clippy * doc fix ups * changelog and migration guide * review * fix h2, reuse schedule for delay --- esp-hal-embassy/src/time_driver.rs | 8 +- esp-hal/CHANGELOG.md | 5 + esp-hal/MIGRATING-0.22.md | 30 ++- esp-hal/src/gpio/mod.rs | 2 +- esp-hal/src/timer/mod.rs | 260 ++++++++++++------- esp-hal/src/timer/systimer.rs | 109 +++++--- esp-hal/src/timer/timg.rs | 124 ++++++--- esp-hal/src/touch.rs | 2 +- esp-wifi/src/lib.rs | 3 +- examples/src/bin/etm_timer.rs | 62 +++++ hil-test/Cargo.toml | 13 + hil-test/tests/delay_async.rs | 172 +++++++++++++ hil-test/tests/embassy_timers_executors.rs | 286 +++++++++++++++++++++ hil-test/tests/systimer.rs | 165 ++++++++++++ 14 files changed, 1066 insertions(+), 175 deletions(-) create mode 100644 examples/src/bin/etm_timer.rs create mode 100644 hil-test/tests/delay_async.rs create mode 100644 hil-test/tests/embassy_timers_executors.rs create mode 100644 hil-test/tests/systimer.rs diff --git a/esp-hal-embassy/src/time_driver.rs b/esp-hal-embassy/src/time_driver.rs index e072e75fd..fa785cdee 100644 --- a/esp-hal-embassy/src/time_driver.rs +++ b/esp-hal-embassy/src/time_driver.rs @@ -7,9 +7,10 @@ use esp_hal::{ sync::Locked, time::now, timer::{AnyTimer, OneShotTimer}, + Blocking, }; -pub type Timer = OneShotTimer<'static, AnyTimer>; +pub type Timer = OneShotTimer<'static, Blocking, AnyTimer>; enum AlarmState { Created(extern "C" fn()), @@ -17,10 +18,7 @@ enum AlarmState { Initialized(&'static mut Timer), } impl AlarmState { - fn initialize( - timer: &'static mut OneShotTimer<'_, AnyTimer>, - interrupt_handler: extern "C" fn(), - ) -> AlarmState { + fn initialize(timer: &'static mut Timer, interrupt_handler: extern "C" fn()) -> AlarmState { // If the driver is initialized, bind the interrupt handler to the // timer. This ensures that alarms allocated after init are correctly // bound to the core that created the executor. diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 09aae247a..360c1427d 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ESP32-C6, H2, S3: Added `split` function to the `DmaChannel` trait. (#2526) - Added PSRAM configuration to `esp_hal::Config` if `quad-psram` or `octal-psram` is enabled (#2546) - Added `esp_hal::psram::psram_raw_parts` (#2546) +- The timer drivers `OneShotTimer` & `PeriodicTimer` have `into_async` and `new_typed` methods (#2586) +- `timer::Timer` trait has three new methods, `wait`, `async_interrupt_handler` and `peripheral_interrupt` (#2586) ### Changed @@ -37,6 +39,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - PSRAM is now initialized automatically if `quad-psram` or `octal-psram` is enabled (#2546) - DMA channels are now available via the `Peripherals` struct, and have been renamed accordingly. (#2545) - Moved interrupt related items from lib.rs, moved to the `interrupt` module (#2613) +- The timer drivers `OneShotTimer` & `PeriodicTimer` now have a `Mode` parameter and type erase the underlying driver by default (#2586) +- `timer::Timer` has new trait requirements of `Into`, `'static` and `InterruptConfigurable` (#2586) +- `systimer::etm::Event` no longer borrows the alarm indefinitely (#2586) ### Fixed diff --git a/esp-hal/MIGRATING-0.22.md b/esp-hal/MIGRATING-0.22.md index 35ce135b2..64ca5ea99 100644 --- a/esp-hal/MIGRATING-0.22.md +++ b/esp-hal/MIGRATING-0.22.md @@ -117,6 +117,17 @@ If you are writing a driver and need to store a channel in a structure, you can The low level timers, `SystemTimer` and `TimerGroup` are now "dumb". They contain no logic for operating modes or trait implementations (except the low level `Timer` trait). +### Timer drivers - `OneShotTimer` & `PeriodicTimer` + +Both drivers now have a `Mode` parameter. Both also type erase the underlying driver by default, call `new_typed` to retain the type. + +```diff +- OneShotTimer<'static, systimer::Alarm>; ++ OneShotTimer<'static, Blocking>; +- PeriodicTimer<'static, systimer::Alarm>; ++ PeriodicTimer<'static, Blocking>; +``` + ### SystemTimer ```diff @@ -140,6 +151,23 @@ Timer group timers have been type erased. + timg::Timer ``` +### ETM usage has changed + +Timer dependant ETM events should be created _prior_ to initializing the timer with the chosen driver. + +```diff +let task = ...; // ETM task +let syst = SystemTimer::new(peripherals.SYSTIMER); +let alarm0 = syst.alarm0; +- alarm0.load_value(1u64.millis()).unwrap(); +- alarm0.start(); +- let event = Event::new(&mut alarm0); ++ let event = Event::new(&alarm0); ++ let timer = OneShotTimer::new(alarm0); ++ timer.schedule(1u64.millis()).unwrap(); +let _configured_channel = channel0.setup(&event, &task); +``` + ## PSRAM is now initialized automatically Calling `esp_hal::initialize` will now configure PSRAM if either the `quad-psram` or `octal-psram` @@ -192,4 +220,4 @@ https://github.com/rust-embedded/embedded-hal/blob/master/docs/migrating-from-0. - use esp_hal::DEFAULT_INTERRUPT_HANDLER; + use esp_hal::interrupt::InterruptConfigurable; + use esp_hal::interrupt::DEFAULT_INTERRUPT_HANDLER; -``` \ No newline at end of file +``` diff --git a/esp-hal/src/gpio/mod.rs b/esp-hal/src/gpio/mod.rs index ec4cccbf5..25c43ade5 100644 --- a/esp-hal/src/gpio/mod.rs +++ b/esp-hal/src/gpio/mod.rs @@ -29,7 +29,7 @@ //! //! The [`Io`] struct can also be used to configure the interrupt handler for //! GPIO interrupts. For more information, see the -//! [`Io::set_interrupt_handler`]. +//! [`InterruptConfigurable::set_interrupt_handler`](crate::interrupt::InterruptConfigurable::set_interrupt_handler). //! //! This driver also implements pin-related traits from [embedded-hal] and //! [Wait](embedded_hal_async::digital::Wait) trait from [embedded-hal-async]. diff --git a/esp-hal/src/timer/mod.rs b/esp-hal/src/timer/mod.rs index 4657d8706..562f510bc 100644 --- a/esp-hal/src/timer/mod.rs +++ b/esp-hal/src/timer/mod.rs @@ -19,7 +19,7 @@ //! # use esp_hal::timer::{OneShotTimer, PeriodicTimer, timg::TimerGroup}; //! # //! let timg0 = TimerGroup::new(peripherals.TIMG0); -//! let one_shot = OneShotTimer::new(timg0.timer0); +//! let mut one_shot = OneShotTimer::new(timg0.timer0); //! //! one_shot.delay_millis(500); //! # } @@ -40,11 +40,18 @@ //! # } //! ``` +use core::marker::PhantomData; + use fugit::{ExtU64, Instant, MicrosDurationU64}; use crate::{ interrupt::{InterruptConfigurable, InterruptHandler}, peripheral::{Peripheral, PeripheralRef}, + peripherals::Interrupt, + Async, + Blocking, + Cpu, + Mode, }; #[cfg(systimer)] @@ -67,7 +74,7 @@ pub enum Error { } /// Functionality provided by any timer peripheral. -pub trait Timer: crate::private::Sealed { +pub trait Timer: Into + InterruptConfigurable + 'static + crate::private::Sealed { /// Start the timer. fn start(&self); @@ -95,68 +102,130 @@ pub trait Timer: crate::private::Sealed { /// Clear the timer's interrupt. fn clear_interrupt(&self); - /// Set the interrupt handler - /// - /// Note that this will replace any previously set interrupt handler - fn set_interrupt_handler(&self, handler: InterruptHandler); - /// Has the timer triggered? fn is_interrupt_set(&self) -> bool; - // NOTE: This is an unfortunate implementation detail of `TIMGx` - #[doc(hidden)] - fn set_alarm_active(&self, state: bool); + /// Asynchronously wait for the timer interrupt to fire. + /// + /// Requires the correct `InterruptHandler` to be installed to function + /// correctly. + async fn wait(&self); + + /// Returns the HAL provided async interrupt handler + fn async_interrupt_handler(&self) -> InterruptHandler; + + /// Returns the interrupt source for the underlying timer + fn peripheral_interrupt(&self) -> Interrupt; } /// A one-shot timer. -pub struct OneShotTimer<'d, T> { +pub struct OneShotTimer<'d, M, T = AnyTimer> { inner: PeripheralRef<'d, T>, + _ph: PhantomData, } -impl<'d, T> OneShotTimer<'d, T> +impl<'d> OneShotTimer<'d, Blocking> { + /// Construct a new instance of [`OneShotTimer`]. + pub fn new(inner: impl Peripheral

+ 'd) -> OneShotTimer<'d, Blocking> { + Self::new_typed(inner.map_into()) + } +} + +impl<'d, T> OneShotTimer<'d, Blocking, T> where T: Timer, { - /// Construct a new instance of [`OneShotTimer`]. - pub fn new(inner: impl Peripheral

+ 'd) -> Self { + /// Construct a typed instance of [`OneShotTimer`]. + pub fn new_typed(inner: impl Peripheral

+ 'd) -> Self { crate::into_ref!(inner); - - Self { inner } + Self { + inner, + _ph: PhantomData, + } } - /// Pauses execution for *at least* `ms` milliseconds. - pub fn delay_millis(&self, ms: u32) { + /// Converts the driver to [`Async`] mode. + pub fn into_async(mut self) -> OneShotTimer<'d, Async, T> { + let handler = self.inner.async_interrupt_handler(); + self.inner.set_interrupt_handler(handler); + OneShotTimer { + inner: self.inner, + _ph: PhantomData, + } + } +} + +impl<'d, T> OneShotTimer<'d, Async, T> +where + T: Timer, +{ + /// Converts the driver to [`Blocking`] mode. + pub fn into_blocking(self) -> Self { + crate::interrupt::disable(Cpu::current(), self.inner.peripheral_interrupt()); + Self { + inner: self.inner, + _ph: PhantomData, + } + } +} + +impl OneShotTimer<'_, Async, T> +where + T: Timer, +{ + /// Delay for *at least* `ns` nanoseconds. + pub async fn delay_nanos_async(&mut self, ns: u32) { + self.delay_async(MicrosDurationU64::from_ticks(ns.div_ceil(1000) as u64)) + .await + } + + /// Delay for *at least* `ms` milliseconds. + pub async fn delay_millis_async(&mut self, ms: u32) { + self.delay_async((ms as u64).millis()).await; + } + + /// Delay for *at least* `us` microseconds. + pub async fn delay_micros_async(&mut self, us: u32) { + self.delay_async((us as u64).micros()).await; + } + + async fn delay_async(&mut self, us: MicrosDurationU64) { + unwrap!(self.schedule(us)); + self.inner.wait().await; + self.stop(); + self.clear_interrupt(); + } +} + +impl<'d, M, T> OneShotTimer<'d, M, T> +where + M: Mode, + T: Timer, +{ + /// Delay for *at least* `ms` milliseconds. + pub fn delay_millis(&mut self, ms: u32) { self.delay((ms as u64).millis()); } - /// Pauses execution for *at least* `us` microseconds. - pub fn delay_micros(&self, us: u32) { + /// Delay for *at least* `us` microseconds. + pub fn delay_micros(&mut self, us: u32) { self.delay((us as u64).micros()); } - /// Pauses execution for *at least* `ns` nanoseconds. - pub fn delay_nanos(&self, ns: u32) { + /// Delay for *at least* `ns` nanoseconds. + pub fn delay_nanos(&mut self, ns: u32) { self.delay((ns.div_ceil(1000) as u64).micros()) } - fn delay(&self, us: MicrosDurationU64) { - if self.inner.is_running() { - self.inner.stop(); - } - - self.inner.clear_interrupt(); - self.inner.reset(); - - self.inner.enable_auto_reload(false); - self.inner.load_value(us).unwrap(); - self.inner.start(); + fn delay(&mut self, us: MicrosDurationU64) { + self.schedule(us).unwrap(); while !self.inner.is_interrupt_set() { // Wait } - self.inner.stop(); - self.inner.clear_interrupt(); + self.stop(); + self.clear_interrupt(); } /// Start counting until the given timeout and raise an interrupt @@ -195,22 +264,27 @@ where /// Clear the interrupt flag pub fn clear_interrupt(&mut self) { self.inner.clear_interrupt(); - self.inner.set_alarm_active(false); } } -impl crate::private::Sealed for OneShotTimer<'_, T> where T: Timer {} - -impl InterruptConfigurable for OneShotTimer<'_, T> +impl crate::private::Sealed for OneShotTimer<'_, M, T> where T: Timer, + M: Mode, +{ +} + +impl InterruptConfigurable for OneShotTimer<'_, M, T> +where + M: Mode, + T: Timer, { fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { OneShotTimer::set_interrupt_handler(self, handler); } } -impl embedded_hal::delay::DelayNs for OneShotTimer<'_, T> +impl embedded_hal::delay::DelayNs for OneShotTimer<'_, Blocking, T> where T: Timer, { @@ -219,22 +293,47 @@ where } } -/// A periodic timer. -pub struct PeriodicTimer<'d, T> { - inner: PeripheralRef<'d, T>, -} - -impl<'d, T> PeriodicTimer<'d, T> +impl embedded_hal_async::delay::DelayNs for OneShotTimer<'_, Async, T> where T: Timer, { - /// Construct a new instance of [`PeriodicTimer`]. - pub fn new(inner: impl Peripheral

+ 'd) -> Self { - crate::into_ref!(inner); - - Self { inner } + async fn delay_ns(&mut self, ns: u32) { + self.delay_nanos_async(ns).await } +} +/// A periodic timer. +pub struct PeriodicTimer<'d, M, T = AnyTimer> { + inner: PeripheralRef<'d, T>, + _ph: PhantomData, +} + +impl<'d> PeriodicTimer<'d, Blocking> { + /// Construct a new instance of [`PeriodicTimer`]. + pub fn new(inner: impl Peripheral

+ 'd) -> PeriodicTimer<'d, Blocking> { + Self::new_typed(inner.map_into()) + } +} + +impl<'d, T> PeriodicTimer<'d, Blocking, T> +where + T: Timer, +{ + /// Construct a typed instance of [`PeriodicTimer`]. + pub fn new_typed(inner: impl Peripheral

+ 'd) -> Self { + crate::into_ref!(inner); + Self { + inner, + _ph: PhantomData, + } + } +} + +impl<'d, M, T> PeriodicTimer<'d, M, T> +where + M: Mode, + T: Timer, +{ /// Start a new count down. pub fn start(&mut self, timeout: MicrosDurationU64) -> Result<(), Error> { if self.inner.is_running() { @@ -255,7 +354,6 @@ where pub fn wait(&mut self) -> nb::Result<(), void::Void> { if self.inner.is_interrupt_set() { self.inner.clear_interrupt(); - self.inner.set_alarm_active(true); Ok(()) } else { @@ -289,14 +387,14 @@ where /// Clear the interrupt flag pub fn clear_interrupt(&mut self) { self.inner.clear_interrupt(); - self.inner.set_alarm_active(true); } } -impl crate::private::Sealed for PeriodicTimer<'_, T> where T: Timer {} +impl crate::private::Sealed for PeriodicTimer<'_, M, T> where T: Timer {} -impl InterruptConfigurable for PeriodicTimer<'_, T> +impl InterruptConfigurable for PeriodicTimer<'_, M, T> where + M: Mode, T: Timer, { fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) { @@ -304,32 +402,12 @@ where } } -/// An enum of all timer types -enum AnyTimerInner { - /// Timer 0 of the TIMG0 peripheral in blocking mode. - TimgTimer(timg::Timer), - /// Systimer Alarm - #[cfg(systimer)] - SystimerAlarm(systimer::Alarm), -} - -/// A type-erased timer -/// -/// You can create an instance of this by just calling `.into()` on a timer. -pub struct AnyTimer(AnyTimerInner); - -impl crate::private::Sealed for AnyTimer {} - -impl From for AnyTimer { - fn from(value: timg::Timer) -> Self { - Self(AnyTimerInner::TimgTimer(value)) - } -} - -#[cfg(systimer)] -impl From for AnyTimer { - fn from(value: systimer::Alarm) -> Self { - Self(AnyTimerInner::SystimerAlarm(value)) +crate::any_peripheral! { + /// Any Timer peripheral. + pub peripheral AnyTimer { + TimgTimer(timg::Timer), + #[cfg(systimer)] + SystimerAlarm(systimer::Alarm), } } @@ -349,18 +427,22 @@ impl Timer for AnyTimer { fn enable_auto_reload(&self, auto_reload: bool); fn enable_interrupt(&self, state: bool); fn clear_interrupt(&self); - fn set_interrupt_handler(&self, handler: InterruptHandler); fn is_interrupt_set(&self) -> bool; - fn set_alarm_active(&self, state: bool); + async fn wait(&self); + fn async_interrupt_handler(&self) -> InterruptHandler; + fn peripheral_interrupt(&self) -> Interrupt; } } } -impl Peripheral for AnyTimer { - type P = Self; - - #[inline] - unsafe fn clone_unchecked(&self) -> Self::P { - core::ptr::read(self as *const _) +impl InterruptConfigurable for AnyTimer { + delegate::delegate! { + to match &mut self.0 { + AnyTimerInner::TimgTimer(inner) => inner, + #[cfg(systimer)] + AnyTimerInner::SystimerAlarm(inner) => inner, + } { + fn set_interrupt_handler(&mut self, handler: InterruptHandler); + } } } diff --git a/esp-hal/src/timer/systimer.rs b/esp-hal/src/timer/systimer.rs index 6efa78e58..786eeba03 100644 --- a/esp-hal/src/timer/systimer.rs +++ b/esp-hal/src/timer/systimer.rs @@ -23,7 +23,7 @@ use fugit::{Instant, MicrosDurationU64}; use super::{Error, Timer as _}; use crate::{ - interrupt::{self, InterruptHandler}, + interrupt::{self, InterruptConfigurable, InterruptHandler}, peripheral::Peripheral, peripherals::{Interrupt, SYSTIMER}, sync::{lock, Lock}, @@ -400,7 +400,7 @@ impl Alarm { } /// Set the interrupt handler for this comparator. - fn set_interrupt_handler(&self, handler: InterruptHandler) { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { let interrupt = match self.channel() { 0 => Interrupt::SYSTIMER_TARGET0, 1 => Interrupt::SYSTIMER_TARGET1, @@ -457,6 +457,12 @@ impl Alarm { } } +impl InterruptConfigurable for Alarm { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler) + } +} + /// The modes of a comparator. #[derive(Copy, Clone)] enum ComparatorMode { @@ -583,12 +589,26 @@ impl super::Timer for Alarm { .bit_is_set() } - fn set_alarm_active(&self, _active: bool) { - // Nothing to do + async fn wait(&self) { + asynch::AlarmFuture::new(self).await } - fn set_interrupt_handler(&self, handler: InterruptHandler) { - self.set_interrupt_handler(handler); + fn async_interrupt_handler(&self) -> InterruptHandler { + match self.channel() { + 0 => asynch::target0_handler, + 1 => asynch::target1_handler, + 2 => asynch::target2_handler, + _ => unreachable!(), + } + } + + fn peripheral_interrupt(&self) -> Interrupt { + match self.channel() { + 0 => Interrupt::SYSTIMER_TARGET0, + 1 => Interrupt::SYSTIMER_TARGET1, + 2 => Interrupt::SYSTIMER_TARGET2, + _ => unreachable!(), + } } } @@ -611,7 +631,6 @@ static INT_ENA_LOCK: Lock = Lock::new(); // Async functionality of the system timer. mod asynch { - #![allow(unused)] // FIXME (mabez) use core::{ pin::Pin, task::{Context, Poll}, @@ -633,21 +652,6 @@ mod asynch { impl<'a> AlarmFuture<'a> { pub(crate) fn new(alarm: &'a Alarm) -> Self { - alarm.clear_interrupt(); - - let (interrupt, handler) = match alarm.channel() { - 0 => (Interrupt::SYSTIMER_TARGET0, target0_handler), - 1 => (Interrupt::SYSTIMER_TARGET1, target1_handler), - _ => (Interrupt::SYSTIMER_TARGET2, target2_handler), - }; - - unsafe { - interrupt::bind_interrupt(interrupt, handler.handler()); - interrupt::enable(interrupt, handler.priority()).unwrap(); - } - - alarm.set_interrupt_handler(handler); - alarm.enable_interrupt(true); Self { alarm } @@ -723,33 +727,64 @@ pub mod etm { //! The system timer can generate the following ETM events: //! - SYSTIMER_EVT_CNT_CMPx: Indicates the alarm pulses generated by //! COMPx - // FIXME(mabez) + //! ## Example + //! ```rust, no_run + #![doc = crate::before_snippet!()] + //! # use esp_hal::timer::systimer::{etm::Event, SystemTimer}; + //! # use esp_hal::timer::PeriodicTimer; + //! # use esp_hal::etm::Etm; + //! # use esp_hal::gpio::{ + //! # etm::{Channels, OutputConfig}, + //! # Level, + //! # Pull, + //! # }; + //! # use fugit::ExtU32; + //! let syst = SystemTimer::new(peripherals.SYSTIMER); + //! let etm = Etm::new(peripherals.SOC_ETM); + //! let gpio_ext = Channels::new(peripherals.GPIO_SD); + //! let alarm0 = syst.alarm0; + //! let mut led = peripherals.GPIO1; + //! + //! let timer_event = Event::new(&alarm0); + //! let led_task = gpio_ext.channel0_task.toggle( + //! &mut led, + //! OutputConfig { + //! open_drain: false, + //! pull: Pull::None, + //! initial_state: Level::High, + //! }, + //! ); + //! + //! let _configured_etm_channel = etm.channel0.setup(&timer_event, + //! &led_task); + //! + //! let timer = PeriodicTimer::new(alarm0); + //! // configure the timer as usual + //! // when it fires it will toggle the GPIO + //! # } + //! ``` use super::*; /// An ETM controlled SYSTIMER event - pub struct Event<'a> { - alarm: &'a mut Alarm, + pub struct Event { + id: u8, } - impl<'a> Event<'a> { + impl Event { /// Creates an ETM event from the given [Alarm] - pub fn new(alarm: &'a mut Alarm) -> Self { - Self { alarm } - } - - /// Execute closure f with mutable access to the wrapped [Alarm]. - pub fn with(&self, f: impl FnOnce(&&'a mut Alarm) -> R) -> R { - let alarm = &self.alarm; - f(alarm) + pub fn new(alarm: &Alarm) -> Self { + Self { + id: 50 + alarm.channel(), + } } } - impl crate::private::Sealed for Event<'_> {} + impl crate::private::Sealed for Event {} - impl crate::etm::EtmEvent for Event<'_> { + impl crate::etm::EtmEvent for Event { fn id(&self) -> u8 { - 50 + self.alarm.channel() + self.id } } diff --git a/esp-hal/src/timer/timg.rs b/esp-hal/src/timer/timg.rs index 7d7bbafcc..af005b852 100644 --- a/esp-hal/src/timer/timg.rs +++ b/esp-hal/src/timer/timg.rs @@ -221,7 +221,7 @@ impl TimerGroupInstance for crate::peripherals::TIMG1 { } else if #[cfg(any(esp32c6, esp32h2))] { unsafe { &*crate::peripherals::PCR::PTR } .timergroup1_wdt_clk_conf() - .modify(|_, w| unsafe { w.tg1_wdt_clk_sel().bits(1) }); + .modify(|_, w| unsafe { w.tg1_wdt_clk_sel().bits(TIMG_DEFAULT_CLK_SRC) }); } } } @@ -315,7 +315,69 @@ impl super::Timer for Timer { self.clear_interrupt() } - fn set_interrupt_handler(&self, handler: InterruptHandler) { + fn is_interrupt_set(&self) -> bool { + self.is_interrupt_set() + } + + async fn wait(&self) { + asynch::TimerFuture::new(self).await + } + + fn async_interrupt_handler(&self) -> InterruptHandler { + match (self.timer_group(), self.timer_number()) { + (0, 0) => asynch::timg0_timer0_handler, + #[cfg(timg_timer1)] + (0, 1) => asynch::timg0_timer1_handler, + #[cfg(timg1)] + (1, 0) => asynch::timg1_timer0_handler, + #[cfg(all(timg_timer1, timg1))] + (1, 1) => asynch::timg1_timer1_handler, + _ => unreachable!(), + } + } + + fn peripheral_interrupt(&self) -> Interrupt { + match (self.timer_group(), self.timer_number()) { + (0, 0) => Interrupt::TG0_T0_LEVEL, + #[cfg(timg_timer1)] + (0, 1) => Interrupt::TG0_T1_LEVEL, + #[cfg(timg1)] + (1, 0) => Interrupt::TG1_T0_LEVEL, + #[cfg(all(timg_timer1, timg1))] + (1, 1) => Interrupt::TG1_T1_LEVEL, + _ => unreachable!(), + } + } +} + +impl InterruptConfigurable for Timer { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { + self.set_interrupt_handler(handler) + } +} + +impl Peripheral for Timer { + type P = Self; + + #[inline] + unsafe fn clone_unchecked(&self) -> Self::P { + core::ptr::read(self as *const _) + } +} + +/// A timer within a Timer Group. +pub struct Timer { + register_block: *const RegisterBlock, + timer: u8, + tg: u8, +} + +impl Sealed for Timer {} +unsafe impl Send for Timer {} + +/// Timer peripheral instance +impl Timer { + fn set_interrupt_handler(&mut self, handler: InterruptHandler) { let interrupt = match (self.timer_group(), self.timer_number()) { (0, 0) => Interrupt::TG0_T0_LEVEL, #[cfg(timg_timer1)] @@ -334,41 +396,6 @@ impl super::Timer for Timer { unwrap!(interrupt::enable(interrupt, handler.priority())); } - fn is_interrupt_set(&self) -> bool { - self.is_interrupt_set() - } - - fn set_alarm_active(&self, state: bool) { - self.set_alarm_active(state) - } -} - -impl Peripheral for Timer { - type P = Self; - - #[inline] - unsafe fn clone_unchecked(&self) -> Self::P { - core::ptr::read(self as *const _) - } -} - -/// A timer within a Timer Group. -pub struct Timer { - /// Pointer to the register block for this TimerGroup instance. - pub register_block: *const RegisterBlock, - - /// The timer number inside the TimerGroup - pub timer: u8, - - /// The TimerGroup number - pub tg: u8, -} - -impl Sealed for Timer {} -unsafe impl Send for Timer {} - -/// Timer peripheral instance -impl Timer { fn register_block(&self) -> &RegisterBlock { unsafe { &*self.register_block } } @@ -419,7 +446,15 @@ impl Timer { } fn load_value(&self, value: MicrosDurationU64) -> Result<(), Error> { - let ticks = timeout_to_ticks(value, Clocks::get().apb_clock, self.divider()); + cfg_if::cfg_if! { + if #[cfg(esp32h2)] { + // ESP32-H2 is using PLL_48M_CLK source instead of APB_CLK + let clk_src = Clocks::get().pll_48m_clock; + } else { + let clk_src = Clocks::get().apb_clock; + } + } + let ticks = timeout_to_ticks(value, clk_src, self.divider()); // The counter is 54-bits wide, so we must ensure that the provided // value is not too wide: @@ -442,6 +477,8 @@ impl Timer { self.register_block() .int_clr() .write(|w| w.t(self.timer).clear_bit_by_one()); + let periodic = self.t().config().read().autoreload().bit_is_set(); + self.set_alarm_active(periodic); } fn now(&self) -> Instant { @@ -456,7 +493,15 @@ impl Timer { let value_hi = t.hi().read().bits() as u64; let ticks = (value_hi << 32) | value_lo; - let micros = ticks_to_timeout(ticks, Clocks::get().apb_clock, self.divider()); + cfg_if::cfg_if! { + if #[cfg(esp32h2)] { + // ESP32-H2 is using PLL_48M_CLK source instead of APB_CLK + let clk_src = Clocks::get().pll_48m_clock; + } else { + let clk_src = Clocks::get().apb_clock; + } + } + let micros = ticks_to_timeout(ticks, clk_src, self.divider()); Instant::::from_ticks(micros) } @@ -747,7 +792,6 @@ where // Async functionality of the timer groups. mod asynch { - #![allow(unused)] // FIXME(mabez) use core::{ pin::Pin, task::{Context, Poll}, diff --git a/esp-hal/src/touch.rs b/esp-hal/src/touch.rs index 3ea39ebb1..be26e24c5 100644 --- a/esp-hal/src/touch.rs +++ b/esp-hal/src/touch.rs @@ -29,9 +29,9 @@ use core::marker::PhantomData; use crate::{ gpio::TouchPin, - interrupt::InterruptConfigurable, peripheral::{Peripheral, PeripheralRef}, peripherals::{RTC_CNTL, SENS, TOUCH}, + prelude::*, private::{Internal, Sealed}, rtc_cntl::Rtc, Async, diff --git a/esp-wifi/src/lib.rs b/esp-wifi/src/lib.rs index a7c890210..924799eac 100644 --- a/esp-wifi/src/lib.rs +++ b/esp-wifi/src/lib.rs @@ -99,6 +99,7 @@ use hal::{ rng::{Rng, Trng}, system::RadioClockController, timer::{timg::Timer as TimgTimer, AnyTimer, PeriodicTimer}, + Blocking, }; use portable_atomic::Ordering; @@ -216,7 +217,7 @@ const _: () = { core::assert!(CONFIG.rx_ba_win < (CONFIG.static_rx_buf_num * 2), "WiFi configuration check: rx_ba_win should not be larger than double of the static_rx_buf_num!"); }; -type TimeBase = PeriodicTimer<'static, AnyTimer>; +type TimeBase = PeriodicTimer<'static, Blocking, AnyTimer>; pub(crate) mod flags { use portable_atomic::{AtomicBool, AtomicUsize}; diff --git a/examples/src/bin/etm_timer.rs b/examples/src/bin/etm_timer.rs new file mode 100644 index 000000000..a5dedabd5 --- /dev/null +++ b/examples/src/bin/etm_timer.rs @@ -0,0 +1,62 @@ +//! Control LED by the boot button via ETM without involving the CPU. + +//! The following wiring is assumed: +//! - LED => GPIO2 + +//% CHIPS: esp32c6 esp32h2 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + etm::Etm, + gpio::{ + etm::{Channels, OutputConfig}, + Level, + Output, + Pull, + }, + prelude::*, + timer::{ + systimer::{etm::Event, SystemTimer}, + PeriodicTimer, + }, +}; + +#[entry] +fn main() -> ! { + let peripherals = esp_hal::init(esp_hal::Config::default()); + + let mut led = Output::new(peripherals.GPIO2, Level::Low); + led.set_high(); + + let syst = SystemTimer::new(peripherals.SYSTIMER); + let alarm = syst.alarm0; + + let timer_event = Event::new(&alarm); + + // setup ETM + let gpio_ext = Channels::new(peripherals.GPIO_SD); + let led_task = gpio_ext.channel0_task.toggle( + led, + OutputConfig { + open_drain: false, + pull: Pull::None, + initial_state: Level::Low, + }, + ); + + let etm = Etm::new(peripherals.SOC_ETM); + let channel0 = etm.channel0; + + // make sure the configured channel doesn't get dropped - dropping it will + // disable the channel + let _configured_channel = channel0.setup(&timer_event, &led_task); + + let mut timer = PeriodicTimer::new(alarm); + timer.start(1u64.secs()).unwrap(); + + // the LED is controlled by the button without involving the CPU + loop {} +} diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index a51aafa23..e033ac3f3 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -31,6 +31,10 @@ harness = false name = "delay" harness = false +[[test]] +name = "delay_async" +harness = false + [[test]] name = "dma_macros" harness = false @@ -160,6 +164,11 @@ harness = false name = "uart_tx_rx_async" harness = false +[[test]] +name = "embassy_timers_executors" +harness = false +required-features = ["embassy"] + [[test]] name = "embassy_interrupt_executor" harness = false @@ -170,6 +179,10 @@ name = "embassy_interrupt_spi_dma" harness = false required-features = ["embassy"] +[[test]] +name = "systimer" +harness = false + [[test]] name = "twai" harness = false diff --git a/hil-test/tests/delay_async.rs b/hil-test/tests/delay_async.rs new file mode 100644 index 000000000..130a67971 --- /dev/null +++ b/hil-test/tests/delay_async.rs @@ -0,0 +1,172 @@ +//! Async Delay Test +//! +//! Specifically tests the various implementations of the +//! `embedded_hal_async::delay::DelayNs` trait. + +//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 + +#![no_std] +#![no_main] + +use embedded_hal_async::delay::DelayNs; +#[cfg(systimer)] +use esp_hal::timer::systimer::SystemTimer; +use esp_hal::{ + peripherals::Peripherals, + timer::{timg::TimerGroup, OneShotTimer}, +}; +use hil_test as _; + +struct Context { + peripherals: Peripherals, +} + +async fn test_async_delay_ns(mut timer: impl DelayNs, duration: u32) { + for i in 1..5 { + let t1 = esp_hal::time::now(); + timer.delay_ns(duration).await; + let t2 = esp_hal::time::now(); + + assert!(t2 > t1); + assert!( + (t2 - t1).to_nanos() >= duration as u64, + "diff[{}]: {:?} >= {}", + i, + (t2 - t1).to_nanos(), + duration + ); + } +} + +async fn test_async_delay_us(mut timer: impl DelayNs, duration: u32) { + for _ in 1..5 { + let t1 = esp_hal::time::now(); + timer.delay_us(duration).await; + let t2 = esp_hal::time::now(); + + assert!(t2 > t1); + assert!( + (t2 - t1).to_nanos() >= duration as u64, + "diff: {:?}", + (t2 - t1).to_nanos() + ); + } +} + +async fn test_async_delay_ms(mut timer: impl DelayNs, duration: u32) { + for _ in 1..5 { + let t1 = esp_hal::time::now(); + timer.delay_ms(duration).await; + let t2 = esp_hal::time::now(); + + assert!(t2 > t1); + assert!( + (t2 - t1).to_nanos() >= duration as u64, + "diff: {:?}", + (t2 - t1).to_nanos() + ); + } +} + +#[cfg(test)] +#[embedded_test::tests(executor = esp_hal_embassy::Executor::new())] +mod tests { + use super::*; + + #[init] + fn init() -> Context { + Context { + peripherals: esp_hal::init(esp_hal::Config::default()), + } + } + + #[cfg(systimer)] + #[test] + #[timeout(2)] + async fn test_systimer_async_delay_ns(ctx: Context) { + let alarms = SystemTimer::new(ctx.peripherals.SYSTIMER); + + test_async_delay_ns(OneShotTimer::new(alarms.alarm0).into_async(), 10_000_000).await; + } + + #[test] + #[timeout(2)] + async fn test_timg0_async_delay_ns(ctx: Context) { + let timg0 = TimerGroup::new(ctx.peripherals.TIMG0); + + test_async_delay_ns(OneShotTimer::new(timg0.timer0).into_async(), 10_000_000).await; + #[cfg(timg_timer1)] + test_async_delay_ns(OneShotTimer::new(timg0.timer1).into_async(), 10_000_000).await; + } + + #[cfg(timg1)] + #[test] + #[timeout(2)] + async fn test_timg1_async_delay_ns(ctx: Context) { + let timg1 = TimerGroup::new(ctx.peripherals.TIMG1); + + test_async_delay_ns(OneShotTimer::new(timg1.timer0).into_async(), 10_000_000).await; + #[cfg(timg_timer1)] + test_async_delay_ns(OneShotTimer::new(timg1.timer1).into_async(), 10_000_000).await; + } + + #[cfg(systimer)] + #[test] + #[timeout(2)] + async fn test_systimer_async_delay_us(ctx: Context) { + let alarms = SystemTimer::new(ctx.peripherals.SYSTIMER); + + test_async_delay_us(OneShotTimer::new(alarms.alarm0).into_async(), 10_000).await; + } + + #[test] + #[timeout(2)] + async fn test_timg0_async_delay_us(ctx: Context) { + let timg0 = TimerGroup::new(ctx.peripherals.TIMG0); + + test_async_delay_us(OneShotTimer::new(timg0.timer0).into_async(), 10_000).await; + #[cfg(timg_timer1)] + test_async_delay_us(OneShotTimer::new(timg0.timer1).into_async(), 10_000).await; + } + + #[cfg(timg1)] + #[test] + #[timeout(2)] + async fn test_timg1_async_delay_us(ctx: Context) { + let timg1 = TimerGroup::new(ctx.peripherals.TIMG1); + + test_async_delay_us(OneShotTimer::new(timg1.timer0).into_async(), 10_000).await; + #[cfg(timg_timer1)] + test_async_delay_us(OneShotTimer::new(timg1.timer1).into_async(), 10_000).await; + } + + #[cfg(systimer)] + #[test] + #[timeout(2)] + async fn test_systimer_async_delay_ms(ctx: Context) { + let alarms = SystemTimer::new(ctx.peripherals.SYSTIMER); + + test_async_delay_ms(OneShotTimer::new(alarms.alarm0).into_async(), 10).await; + } + + #[test] + #[timeout(2)] + async fn test_timg0_async_delay_ms(ctx: Context) { + let timg0 = TimerGroup::new(ctx.peripherals.TIMG0); + + test_async_delay_ms(OneShotTimer::new(timg0.timer0).into_async(), 10).await; + #[cfg(timg_timer1)] + test_async_delay_ms(OneShotTimer::new(timg0.timer1).into_async(), 10).await; + } + + #[cfg(timg1)] + #[test] + #[timeout(2)] + async fn test_timg1_async_delay_ms(ctx: Context) { + let timg1 = TimerGroup::new(ctx.peripherals.TIMG1); + + test_async_delay_ms(OneShotTimer::new(timg1.timer0).into_async(), 10).await; + #[cfg(timg_timer1)] + test_async_delay_ms(OneShotTimer::new(timg1.timer1).into_async(), 10).await; + } +} diff --git a/hil-test/tests/embassy_timers_executors.rs b/hil-test/tests/embassy_timers_executors.rs new file mode 100644 index 000000000..a8327acbb --- /dev/null +++ b/hil-test/tests/embassy_timers_executors.rs @@ -0,0 +1,286 @@ +//! Embassy timer and executor Test + +//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 +//% FEATURES: integrated-timers +//% FEATURES: generic-queue + +#![no_std] +#![no_main] + +use embassy_time::{Duration, Ticker, Timer}; +#[cfg(not(feature = "esp32"))] +use esp_hal::{ + interrupt::software::SoftwareInterruptControl, + interrupt::Priority, + timer::systimer::SystemTimer, + timer::AnyTimer, +}; +use esp_hal::{ + peripherals::Peripherals, + prelude::*, + timer::{timg::TimerGroup, OneShotTimer, PeriodicTimer}, +}; +#[cfg(not(feature = "esp32"))] +use esp_hal_embassy::InterruptExecutor; +use hil_test as _; + +#[cfg(not(feature = "esp32"))] +macro_rules! mk_static { + ($t:ty,$val:expr) => {{ + static STATIC_CELL: static_cell::StaticCell<$t> = static_cell::StaticCell::new(); + #[deny(unused_attributes)] + let x = STATIC_CELL.uninit().write(($val)); + x + }}; +} + +// List of the functions that are ACTUALLY TESTS but are called in the invokers +mod test_helpers { + use super::*; + + #[embassy_executor::task] + pub async fn e_task30ms() { + Timer::after_millis(30).await; + } +} + +mod test_cases { + use esp_hal::peripheral::Peripheral; + + use super::*; + + pub async fn run_test_one_shot_async() { + let t1 = esp_hal::time::now(); + Timer::after_millis(50).await; + Timer::after_millis(30).await; + let t2 = esp_hal::time::now(); + + assert!(t2 > t1, "t2: {:?}, t1: {:?}", t2, t1); + assert!( + (t2 - t1).to_millis() >= 80u64, + "diff: {:?}", + (t2 - t1).to_millis() + ); + } + + pub fn run_test_periodic_timer(timer: impl Peripheral

) { + let mut periodic = PeriodicTimer::new_typed(timer); + + let t1 = esp_hal::time::now(); + periodic.start(100.millis()).unwrap(); + + nb::block!(periodic.wait()).unwrap(); + let t2 = esp_hal::time::now(); + + assert!(t2 > t1, "t2: {:?}, t1: {:?}", t2, t1); + assert!( + (t2 - t1).to_millis() >= 100u64, + "diff: {:?}", + (t2 - t1).to_millis() + ); + } + + pub fn run_test_oneshot_timer(timer: impl Peripheral

) { + let mut timer = OneShotTimer::new_typed(timer); + + let t1 = esp_hal::time::now(); + timer.delay_millis(50); + let t2 = esp_hal::time::now(); + + assert!(t2 > t1, "t2: {:?}, t1: {:?}", t2, t1); + assert!( + (t2 - t1).to_millis() >= 50u64, + "diff: {:?}", + (t2 - t1).to_millis() + ); + } + + pub async fn run_join_test() { + let t1 = esp_hal::time::now(); + embassy_futures::join::join(Timer::after_millis(50), Timer::after_millis(30)).await; + Timer::after_millis(50).await; + let t2 = esp_hal::time::now(); + + assert!(t2 > t1, "t2: {:?}, t1: {:?}", t2, t1); + assert!( + (t2 - t1).to_millis() >= 100u64, + "diff: {:?}", + (t2 - t1).to_millis() + ); + } +} + +fn set_up_embassy_with_timg0(peripherals: Peripherals) { + let timg0 = TimerGroup::new(peripherals.TIMG0); + esp_hal_embassy::init(timg0.timer0); +} + +#[cfg(not(feature = "esp32"))] +fn set_up_embassy_with_systimer(peripherals: Peripherals) { + let systimer = SystemTimer::new(peripherals.SYSTIMER); + esp_hal_embassy::init(systimer.alarm0); +} + +#[cfg(test)] +#[embedded_test::tests(executor = esp_hal_embassy::Executor::new())] +mod test { + use super::*; + use crate::test_cases::*; + #[cfg(not(feature = "esp32"))] + use crate::test_helpers::*; + + #[init] + fn init() -> Peripherals { + esp_hal::init(esp_hal::Config::default()) + } + + #[test] + #[timeout(3)] + async fn test_one_shot_timg(peripherals: Peripherals) { + set_up_embassy_with_timg0(peripherals); + + run_test_one_shot_async().await; + } + + #[test] + #[timeout(3)] + #[cfg(not(feature = "esp32"))] + async fn test_one_shot_systimer(peripherals: Peripherals) { + set_up_embassy_with_systimer(peripherals); + + run_test_one_shot_async().await; + } + + #[test] + #[timeout(3)] + fn test_periodic_timg(peripherals: Peripherals) { + let timg0 = TimerGroup::new(peripherals.TIMG0); + + run_test_periodic_timer(timg0.timer0); + } + + #[test] + #[timeout(3)] + #[cfg(not(feature = "esp32"))] + fn test_periodic_systimer(peripherals: Peripherals) { + let systimer = SystemTimer::new(peripherals.SYSTIMER); + + run_test_periodic_timer(systimer.alarm0); + } + + #[test] + #[timeout(3)] + fn test_periodic_oneshot_timg(peripherals: Peripherals) { + let mut timg0 = TimerGroup::new(peripherals.TIMG0); + run_test_periodic_timer(&mut timg0.timer0); + run_test_oneshot_timer(&mut timg0.timer0); + } + + #[test] + #[timeout(3)] + #[cfg(not(feature = "esp32"))] + fn test_periodic_oneshot_systimer(peripherals: Peripherals) { + let mut systimer = SystemTimer::new(peripherals.SYSTIMER); + run_test_periodic_timer(&mut systimer.alarm0); + run_test_oneshot_timer(&mut systimer.alarm0); + } + + #[test] + #[timeout(3)] + async fn test_join_timg(peripherals: Peripherals) { + set_up_embassy_with_timg0(peripherals); + + run_join_test().await; + } + + #[test] + #[timeout(3)] + #[cfg(not(feature = "esp32"))] + async fn test_join_systimer(peripherals: Peripherals) { + set_up_embassy_with_systimer(peripherals); + + run_join_test().await; + } + + /// Test that the ticker works in tasks ran by the interrupt executors. + #[test] + #[timeout(3)] + #[cfg(not(feature = "esp32"))] + async fn test_interrupt_executor(peripherals: Peripherals) { + let timg0 = TimerGroup::new(peripherals.TIMG0); + let timer0: AnyTimer = timg0.timer0.into(); + + let systimer = SystemTimer::new(peripherals.SYSTIMER); + let alarm0: AnyTimer = systimer.alarm0.into(); + + esp_hal_embassy::init([timer0, alarm0]); + + let sw_ints = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); + + let executor = mk_static!( + InterruptExecutor<2>, + InterruptExecutor::new(sw_ints.software_interrupt2) + ); + + #[embassy_executor::task] + #[cfg(not(feature = "esp32"))] + async fn test_interrupt_executor_invoker() { + let outcome = async { + let mut ticker = Ticker::every(Duration::from_millis(30)); + + let t1 = esp_hal::time::now(); + ticker.next().await; + ticker.next().await; + ticker.next().await; + let t2 = esp_hal::time::now(); + + assert!(t2 > t1, "t2: {:?}, t1: {:?}", t2, t1); + assert!( + (t2 - t1).to_micros() >= 85000u64, + "diff: {:?}", + (t2 - t1).to_micros() + ); + }; + + embedded_test::export::check_outcome(outcome.await); + } + + let spawner_int = executor.start(Priority::Priority3); + spawner_int.must_spawn(test_interrupt_executor_invoker()); + + let spawner = embassy_executor::Spawner::for_current_executor().await; + spawner.must_spawn(e_task30ms()); + + // The test ends once the interrupt executor's task has finished + loop {} + } + + /// Test that timg0 and systimer don't have vastly different tick rates. + #[test] + #[timeout(3)] + async fn tick_test_timer_tick_rates(peripherals: Peripherals) { + set_up_embassy_with_timg0(peripherals); + + // We are retrying 5 times because probe-rs polling RTT may introduce some + // jitter. + for _ in 0..5 { + let t1 = esp_hal::time::now(); + + let mut ticker = Ticker::every(Duration::from_hz(100_000)); + for _ in 0..2000 { + ticker.next().await; + } + let t2 = esp_hal::time::now(); + + assert!(t2 > t1, "t2: {:?}, t1: {:?}", t2, t1); + let duration = (t2 - t1).to_micros(); + + assert!(duration >= 19000, "diff: {:?}", (t2 - t1).to_micros()); + if duration <= 21000 { + return; + } + } + + assert!(false, "Test failed after 5 retries"); + } +} diff --git a/hil-test/tests/systimer.rs b/hil-test/tests/systimer.rs new file mode 100644 index 000000000..112e4393c --- /dev/null +++ b/hil-test/tests/systimer.rs @@ -0,0 +1,165 @@ +//! System Timer Test + +// esp32 disabled as it does not have a systimer +//% CHIPS: esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 + +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use critical_section::Mutex; +use embedded_hal::delay::DelayNs; +use esp_hal::{ + delay::Delay, + prelude::*, + timer::{ + systimer::{Alarm, SystemTimer}, + OneShotTimer, + PeriodicTimer, + }, + Blocking, +}; +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, + alarm1: Alarm, +} + +#[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); +} + +#[cfg(test)] +#[embedded_test::tests] +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] + #[timeout(3)] + 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.enable_interrupt(true); + alarm0.schedule(10_u64.millis()).unwrap(); + + ALARM_TARGET.borrow_ref_mut(cs).replace(alarm0); + }); + + // We'll end the test in the interrupt handler. + loop {} + } + + #[test] + #[timeout(3)] + 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.enable_interrupt(true); + alarm0.schedule(10_u64.millis()).unwrap(); + + alarm1.set_interrupt_handler(handle_periodic_interrupt); + alarm1.enable_interrupt(true); + alarm1.start(100u64.millis()).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] + #[timeout(3)] + 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.enable_interrupt(true); + alarm1.start(100u64.millis()).unwrap(); + + ALARM_PERIODIC.borrow_ref_mut(cs).replace(alarm1); + }); + + // We'll end the test in the interrupt handler. + loop {} + } +}