[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<AnyTimer> + '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
This commit is contained in:
Scott Mabin 2024-11-27 14:10:56 +00:00 committed by GitHub
parent b50a075449
commit 2512658653
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1066 additions and 175 deletions

View File

@ -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.

View File

@ -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<AnyTimer>`, `'static` and `InterruptConfigurable` (#2586)
- `systimer::etm::Event` no longer borrows the alarm indefinitely (#2586)
### Fixed

View File

@ -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`

View File

@ -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].

View File

@ -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<AnyTimer> + 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<M>,
}
impl<'d, T> OneShotTimer<'d, T>
impl<'d> OneShotTimer<'d, Blocking> {
/// Construct a new instance of [`OneShotTimer`].
pub fn new(inner: impl Peripheral<P = impl Timer> + '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<P = T> + 'd) -> Self {
/// Construct a typed instance of [`OneShotTimer`].
pub fn new_typed(inner: impl Peripheral<P = T> + '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<T> 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<T> crate::private::Sealed for OneShotTimer<'_, T> where T: Timer {}
impl<T> InterruptConfigurable for OneShotTimer<'_, T>
impl<M, T> crate::private::Sealed for OneShotTimer<'_, M, T>
where
T: Timer,
M: Mode,
{
}
impl<M, T> 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<T> embedded_hal::delay::DelayNs for OneShotTimer<'_, T>
impl<T> 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<T> embedded_hal_async::delay::DelayNs for OneShotTimer<'_, Async, T>
where
T: Timer,
{
/// Construct a new instance of [`PeriodicTimer`].
pub fn new(inner: impl Peripheral<P = T> + '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<M>,
}
impl<'d> PeriodicTimer<'d, Blocking> {
/// Construct a new instance of [`PeriodicTimer`].
pub fn new(inner: impl Peripheral<P = impl Timer> + '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<P = T> + '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<T> crate::private::Sealed for PeriodicTimer<'_, T> where T: Timer {}
impl<M, T> crate::private::Sealed for PeriodicTimer<'_, M, T> where T: Timer {}
impl<T> InterruptConfigurable for PeriodicTimer<'_, T>
impl<M, T> InterruptConfigurable for PeriodicTimer<'_, M, T>
where
M: Mode,
T: Timer,
{
fn set_interrupt_handler(&mut self, handler: crate::interrupt::InterruptHandler) {
@ -304,33 +402,13 @@ where
}
}
/// An enum of all timer types
enum AnyTimerInner {
/// Timer 0 of the TIMG0 peripheral in blocking mode.
crate::any_peripheral! {
/// Any Timer peripheral.
pub peripheral AnyTimer {
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<timg::Timer> for AnyTimer {
fn from(value: timg::Timer) -> Self {
Self(AnyTimerInner::TimgTimer(value))
}
}
#[cfg(systimer)]
impl From<systimer::Alarm> for AnyTimer {
fn from(value: systimer::Alarm) -> Self {
Self(AnyTimerInner::SystimerAlarm(value))
}
}
impl Timer for AnyTimer {
@ -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);
}
}
}

View File

@ -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 }
pub fn new(alarm: &Alarm) -> Self {
Self {
id: 50 + alarm.channel(),
}
/// Execute closure f with mutable access to the wrapped [Alarm].
pub fn with<R>(&self, f: impl FnOnce(&&'a mut Alarm) -> R) -> R {
let alarm = &self.alarm;
f(alarm)
}
}
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
}
}

View File

@ -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<u64, 1, 1_000_000> {
@ -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::<u64, 1, 1_000_000>::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},

View File

@ -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,

View File

@ -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};

View File

@ -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 {}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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<T: esp_hal::timer::Timer>(timer: impl Peripheral<P = T>) {
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<T: esp_hal::timer::Timer>(timer: impl Peripheral<P = T>) {
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");
}
}

165
hil-test/tests/systimer.rs Normal file
View File

@ -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<RefCell<Option<OneShotTimer<'static, Blocking>>>> =
Mutex::new(RefCell::new(None));
static ALARM_PERIODIC: Mutex<RefCell<Option<PeriodicTimer<'static, Blocking>>>> =
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 {}
}
}