[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:
parent
b50a075449
commit
2512658653
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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].
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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},
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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};
|
||||
|
||||
62
examples/src/bin/etm_timer.rs
Normal file
62
examples/src/bin/etm_timer.rs
Normal 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 {}
|
||||
}
|
||||
@ -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
|
||||
|
||||
172
hil-test/tests/delay_async.rs
Normal file
172
hil-test/tests/delay_async.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
286
hil-test/tests/embassy_timers_executors.rs
Normal file
286
hil-test/tests/embassy_timers_executors.rs
Normal 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
165
hil-test/tests/systimer.rs
Normal 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 {}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user