diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md
index 09abacb56c1..a67cd3e4bf6 100644
--- a/esp-hal/CHANGELOG.md
+++ b/esp-hal/CHANGELOG.md
@@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Expose `RtcClock::get_xtal_freq` and `RtcClock::get_slow_freq` publically for all chips (#2183)
- TWAI support for ESP32-H2 (#2199)
- Added a way to configure watchdogs in `esp_hal::init` (#2180)
+- Implement `embedded_hal_async::delay::DelayNs` for `TIMGx` timers (#2084)
### Changed
diff --git a/esp-hal/src/timer/systimer.rs b/esp-hal/src/timer/systimer.rs
index 6c274043438..42b5a8e9539 100644
--- a/esp-hal/src/timer/systimer.rs
+++ b/esp-hal/src/timer/systimer.rs
@@ -1029,11 +1029,11 @@ mod asynch {
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub(crate) struct AlarmFuture<'a, COMP: Comparator, UNIT: Unit> {
- alarm: &'a Alarm<'a, Periodic, crate::Async, COMP, UNIT>,
+ alarm: &'a Alarm<'a, Target, crate::Async, COMP, UNIT>,
}
impl<'a, COMP: Comparator, UNIT: Unit> AlarmFuture<'a, COMP, UNIT> {
- pub(crate) fn new(alarm: &'a Alarm<'a, Periodic, crate::Async, COMP, UNIT>) -> Self {
+ pub(crate) fn new(alarm: &'a Alarm<'a, Target, crate::Async, COMP, UNIT>) -> Self {
alarm.clear_interrupt();
let (interrupt, handler) = match alarm.comparator.channel() {
@@ -1047,6 +1047,8 @@ mod asynch {
interrupt::enable(interrupt, handler.priority()).unwrap();
}
+ alarm.set_interrupt_handler(handler);
+
alarm.enable_interrupt(true);
Self { alarm }
@@ -1076,11 +1078,13 @@ mod asynch {
}
impl<'d, COMP: Comparator, UNIT: Unit> embedded_hal_async::delay::DelayNs
- for Alarm<'d, Periodic, crate::Async, COMP, UNIT>
+ for Alarm<'d, Target, crate::Async, COMP, UNIT>
{
- async fn delay_ns(&mut self, ns: u32) {
- let period = MicrosDurationU32::from_ticks(ns / 1000);
- self.set_period(period);
+ async fn delay_ns(&mut self, nanos: u32) {
+ self.set_target(
+ self.unit.read_count()
+ + (nanos as u64 * SystemTimer::ticks_per_second()).div_ceil(1_000_000_000),
+ );
AlarmFuture::new(self).await;
}
diff --git a/esp-hal/src/timer/timg.rs b/esp-hal/src/timer/timg.rs
index eaec0a262f5..965d51b1984 100644
--- a/esp-hal/src/timer/timg.rs
+++ b/esp-hal/src/timer/timg.rs
@@ -304,6 +304,57 @@ where
{
/// Construct a new instance of [`TimerGroup`] in asynchronous mode
pub fn new_async(_timer_group: impl Peripheral
+ 'd) -> Self {
+ match T::id() {
+ 0 => {
+ use crate::timer::timg::asynch::timg0_timer0_handler;
+ unsafe {
+ interrupt::bind_interrupt(
+ Interrupt::TG0_T0_LEVEL,
+ timg0_timer0_handler.handler(),
+ );
+ interrupt::enable(Interrupt::TG0_T0_LEVEL, timg0_timer0_handler.priority())
+ .unwrap();
+
+ #[cfg(timg_timer1)]
+ {
+ use crate::timer::timg::asynch::timg0_timer1_handler;
+
+ interrupt::bind_interrupt(
+ Interrupt::TG0_T1_LEVEL,
+ timg0_timer1_handler.handler(),
+ );
+ interrupt::enable(Interrupt::TG0_T1_LEVEL, timg0_timer1_handler.priority())
+ .unwrap();
+ }
+ }
+ }
+ #[cfg(timg1)]
+ 1 => {
+ use crate::timer::timg::asynch::timg1_timer0_handler;
+ unsafe {
+ {
+ interrupt::bind_interrupt(
+ Interrupt::TG1_T0_LEVEL,
+ timg1_timer0_handler.handler(),
+ );
+ interrupt::enable(Interrupt::TG1_T0_LEVEL, timg1_timer0_handler.priority())
+ .unwrap();
+ }
+ #[cfg(timg_timer1)]
+ {
+ use crate::timer::timg::asynch::timg1_timer1_handler;
+ interrupt::bind_interrupt(
+ Interrupt::TG1_T1_LEVEL,
+ timg1_timer1_handler.handler(),
+ );
+ interrupt::enable(Interrupt::TG1_T1_LEVEL, timg1_timer1_handler.priority())
+ .unwrap();
+ }
+ }
+ }
+ _ => unreachable!(),
+ }
+
Self::new_inner(_timer_group)
}
}
@@ -1040,6 +1091,168 @@ where
}
}
+// Async functionality of the timer groups.
+mod asynch {
+ use core::{
+ pin::Pin,
+ task::{Context, Poll},
+ };
+
+ use embassy_sync::waitqueue::AtomicWaker;
+ use procmacros::handler;
+
+ use super::*;
+
+ cfg_if::cfg_if! {
+ if #[cfg(all(timg1, timg_timer1))] {
+ const NUM_WAKERS: usize = 4;
+ } else if #[cfg(timg1)] {
+ const NUM_WAKERS: usize = 2;
+ } else {
+ const NUM_WAKERS: usize = 1;
+ }
+ }
+
+ static WAKERS: [AtomicWaker; NUM_WAKERS] = [const { AtomicWaker::new() }; NUM_WAKERS];
+
+ pub(crate) struct TimerFuture<'a, T>
+ where
+ T: Instance,
+ {
+ timer: &'a Timer,
+ }
+
+ impl<'a, T> TimerFuture<'a, T>
+ where
+ T: Instance,
+ {
+ pub(crate) fn new(timer: &'a Timer) -> Self {
+ use crate::timer::Timer;
+
+ timer.enable_interrupt(true);
+
+ Self { timer }
+ }
+
+ fn event_bit_is_clear(&self) -> bool {
+ self.timer
+ .register_block()
+ .int_ena()
+ .read()
+ .t(self.timer.timer_number())
+ .bit_is_clear()
+ }
+ }
+
+ impl<'a, T> core::future::Future for TimerFuture<'a, T>
+ where
+ T: Instance,
+ {
+ type Output = ();
+
+ fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll {
+ let index = (self.timer.timer_number() << 1) | self.timer.timer_group();
+ WAKERS[index as usize].register(ctx.waker());
+
+ if self.event_bit_is_clear() {
+ Poll::Ready(())
+ } else {
+ Poll::Pending
+ }
+ }
+ }
+
+ impl<'a, T> Drop for TimerFuture<'a, T>
+ where
+ T: Instance,
+ {
+ fn drop(&mut self) {
+ self.timer.clear_interrupt();
+ }
+ }
+
+ impl embedded_hal_async::delay::DelayNs for Timer
+ where
+ T: Instance,
+ {
+ async fn delay_ns(&mut self, ns: u32) {
+ use crate::timer::Timer as _;
+
+ let period = MicrosDurationU64::from_ticks(ns.div_ceil(1000) as u64);
+ self.load_value(period).unwrap();
+ self.start();
+ self.listen();
+
+ TimerFuture::new(self).await;
+ }
+ }
+
+ // INT_ENA means that when the interrupt occurs, it will show up in the INT_ST.
+ // Clearing INT_ENA that it won't show up on INT_ST but if interrupt is
+ // already there, it won't clear it - that's why we need to clear the INT_CLR as
+ // well.
+ #[handler]
+ pub(crate) fn timg0_timer0_handler() {
+ lock(&INT_ENA_LOCK[0], || {
+ unsafe { &*crate::peripherals::TIMG0::PTR }
+ .int_ena()
+ .modify(|_, w| w.t(0).clear_bit())
+ });
+
+ unsafe { &*crate::peripherals::TIMG0::PTR }
+ .int_clr()
+ .write(|w| w.t(0).clear_bit_by_one());
+
+ WAKERS[0].wake();
+ }
+
+ #[cfg(timg1)]
+ #[handler]
+ pub(crate) fn timg1_timer0_handler() {
+ lock(&INT_ENA_LOCK[1], || {
+ unsafe { &*crate::peripherals::TIMG1::PTR }
+ .int_ena()
+ .modify(|_, w| w.t(0).clear_bit())
+ });
+ unsafe { &*crate::peripherals::TIMG1::PTR }
+ .int_clr()
+ .write(|w| w.t(0).clear_bit_by_one());
+
+ WAKERS[1].wake();
+ }
+
+ #[cfg(timg_timer1)]
+ #[handler]
+ pub(crate) fn timg0_timer1_handler() {
+ lock(&INT_ENA_LOCK[0], || {
+ unsafe { &*crate::peripherals::TIMG0::PTR }
+ .int_ena()
+ .modify(|_, w| w.t(1).clear_bit())
+ });
+ unsafe { &*crate::peripherals::TIMG0::PTR }
+ .int_clr()
+ .write(|w| w.t(1).clear_bit_by_one());
+
+ WAKERS[2].wake();
+ }
+
+ #[cfg(all(timg1, timg_timer1))]
+ #[handler]
+ pub(crate) fn timg1_timer1_handler() {
+ lock(&INT_ENA_LOCK[1], || {
+ unsafe { &*crate::peripherals::TIMG1::PTR }
+ .int_ena()
+ .modify(|_, w| w.t(1).clear_bit())
+ });
+
+ unsafe { &*crate::peripherals::TIMG1::PTR }
+ .int_clr()
+ .write(|w| w.t(1).clear_bit_by_one());
+
+ WAKERS[3].wake();
+ }
+}
+
/// Event Task Matrix
#[cfg(soc_etm)]
pub mod etm {
diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml
index f7f693d2294..209f2c098ce 100644
--- a/hil-test/Cargo.toml
+++ b/hil-test/Cargo.toml
@@ -27,6 +27,10 @@ harness = false
name = "delay"
harness = false
+[[test]]
+name = "delay_async"
+harness = false
+
[[test]]
name = "dma_macros"
harness = false
@@ -161,12 +165,12 @@ harness = false
[dependencies]
cfg-if = "1.0.0"
-critical-section = "1.1.2"
+critical-section = "1.1.3"
defmt = "0.3.8"
defmt-rtt = { version = "0.4.1", optional = true }
embassy-futures = "0.1.1"
embassy-sync = "0.6.0"
-embassy-time = { version = "0.3.1" }
+embassy-time = "0.3.2"
embedded-hal = "1.0.0"
embedded-hal-02 = { version = "0.2.7", package = "embedded-hal", features = ["unproven"] }
embedded-hal-async = "1.0.0"
@@ -174,7 +178,7 @@ embedded-hal-nb = { version = "1.0.0", optional = true }
esp-backtrace = { path = "../esp-backtrace", default-features = false, features = ["exception-handler", "panic-handler", "defmt", "semihosting"] }
esp-hal = { path = "../esp-hal", features = ["defmt", "digest"], optional = true }
esp-hal-embassy = { path = "../esp-hal-embassy", optional = true }
-portable-atomic = "1.6.0"
+portable-atomic = "1.7.0"
static_cell = { version = "2.1.0", features = ["nightly"] }
[dev-dependencies]
@@ -193,8 +197,8 @@ sha1 = { version = "0.10.6", default-features = false }
sha2 = { version = "0.10.8", default-features = false }
[build-dependencies]
-esp-build = { version = "0.1.0", path = "../esp-build" }
-esp-metadata = { version = "0.3.0", path = "../esp-metadata" }
+esp-build = { path = "../esp-build" }
+esp-metadata = { path = "../esp-metadata" }
[features]
default = ["embassy"]
diff --git a/hil-test/tests/delay_async.rs b/hil-test/tests/delay_async.rs
new file mode 100644
index 00000000000..3afe7b325bb
--- /dev/null
+++ b/hil-test/tests/delay_async.rs
@@ -0,0 +1,173 @@
+//! 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::{Alarm, FrozenUnit, SystemTimer};
+use esp_hal::{peripherals::Peripherals, timer::timg::TimerGroup};
+use hil_test as _;
+
+struct Context {
+ peripherals: Peripherals,
+}
+
+async fn test_async_delay_ns(mut timer: impl DelayNs, duration: u32) {
+ for _ 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: {:?}",
+ (t2 - t1).to_nanos()
+ );
+ }
+}
+
+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 mut alarms = SystemTimer::new(ctx.peripherals.SYSTIMER);
+ let unit = FrozenUnit::new(&mut alarms.unit0);
+ let alarm0 = Alarm::new_async(alarms.comparator0, &unit);
+
+ test_async_delay_ns(alarm0, 10_000_000).await;
+ }
+
+ #[test]
+ #[timeout(2)]
+ async fn test_timg0_async_delay_ns(ctx: Context) {
+ let timg0 = TimerGroup::new_async(ctx.peripherals.TIMG0);
+
+ test_async_delay_ns(timg0.timer0, 10_000_000).await;
+ #[cfg(timg_timer1)]
+ test_async_delay_ns(timg0.timer1, 10_000_000).await;
+ }
+
+ #[cfg(timg1)]
+ #[test]
+ #[timeout(2)]
+ async fn test_timg1_async_delay_ns(ctx: Context) {
+ let timg1 = TimerGroup::new_async(ctx.peripherals.TIMG1);
+
+ test_async_delay_ns(timg1.timer0, 10_000_000).await;
+ #[cfg(timg_timer1)]
+ test_async_delay_ns(timg1.timer1, 10_000_000).await;
+ }
+
+ #[cfg(systimer)]
+ #[test]
+ #[timeout(2)]
+ async fn test_systimer_async_delay_us(ctx: Context) {
+ let mut alarms = SystemTimer::new(ctx.peripherals.SYSTIMER);
+ let unit = FrozenUnit::new(&mut alarms.unit0);
+ let alarm0 = Alarm::new_async(alarms.comparator0, &unit);
+
+ test_async_delay_us(alarm0, 10_000).await;
+ }
+
+ #[test]
+ #[timeout(2)]
+ async fn test_timg0_async_delay_us(ctx: Context) {
+ let timg0 = TimerGroup::new_async(ctx.peripherals.TIMG0);
+
+ test_async_delay_us(timg0.timer0, 10_000).await;
+ #[cfg(timg_timer1)]
+ test_async_delay_us(timg0.timer1, 10_000).await;
+ }
+
+ #[cfg(timg1)]
+ #[test]
+ #[timeout(2)]
+ async fn test_timg1_async_delay_us(ctx: Context) {
+ let timg1 = TimerGroup::new_async(ctx.peripherals.TIMG1);
+
+ test_async_delay_us(timg1.timer0, 10_000).await;
+ #[cfg(timg_timer1)]
+ test_async_delay_us(timg1.timer1, 10_000).await;
+ }
+
+ #[cfg(systimer)]
+ #[test]
+ #[timeout(2)]
+ async fn test_systimer_async_delay_ms(ctx: Context) {
+ let mut alarms = SystemTimer::new(ctx.peripherals.SYSTIMER);
+ let unit = FrozenUnit::new(&mut alarms.unit0);
+ let alarm0 = Alarm::new_async(alarms.comparator0, &unit);
+
+ test_async_delay_ms(alarm0, 10).await;
+ }
+
+ #[test]
+ #[timeout(2)]
+ async fn test_timg0_async_delay_ms(ctx: Context) {
+ let timg0 = TimerGroup::new_async(ctx.peripherals.TIMG0);
+
+ test_async_delay_ms(timg0.timer0, 10).await;
+ #[cfg(timg_timer1)]
+ test_async_delay_ms(timg0.timer1, 10).await;
+ }
+
+ #[cfg(timg1)]
+ #[test]
+ #[timeout(2)]
+ async fn test_timg1_async_delay_ms(ctx: Context) {
+ let timg1 = TimerGroup::new_async(ctx.peripherals.TIMG1);
+
+ test_async_delay_ms(timg1.timer0, 10).await;
+ #[cfg(timg_timer1)]
+ test_async_delay_ms(timg1.timer1, 10).await;
+ }
+}