Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement embedded_hal_async::delay::DelayNs for TIMGx timers #2084

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `Rtc::set_current_time` to allow setting RTC time, and `Rtc::current_time` to getting RTC time while taking into account boot time (#1883)
- Added APIs to allow connecting signals through the GPIO matrix. (#2128)
- Implement `TryFrom<u32>` for `ledc::timer::config::Duty` (#1984)
- Implement `embedded_hal_async::delay::DelayNs` for `TIMGx` timers (#2084)

### Changed

Expand Down
202 changes: 202 additions & 0 deletions esp-hal/src/timer/timg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,57 @@ where
{
/// Construct a new instance of [`TimerGroup`] in asynchronous mode
pub fn new_async(_timer_group: impl Peripheral<P = T> + '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)
}
}
Expand Down Expand Up @@ -1039,6 +1090,157 @@ 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;
}
}

#[allow(clippy::declare_interior_mutable_const)]
const INIT: AtomicWaker = AtomicWaker::new();
static WAKERS: [AtomicWaker; NUM_WAKERS] = [INIT; NUM_WAKERS];

pub(crate) struct TimerFuture<'a, T>
where
T: Instance,
{
timer: &'a Timer<T, crate::Async>,
}

impl<'a, T> TimerFuture<'a, T>
where
T: Instance,
{
pub(crate) fn new(timer: &'a Timer<T, crate::Async>) -> 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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When this future is dropped (early) it should disable the interrupt.

where
T: Instance,
{
type Output = ();

fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
let index = (self.timer.timer_number() << 1) | self.timer.timer_group();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind making a (const) function for this math? Then it can be used in the interrupt handlers as well, just so we know the indexing is consistent/correct.

WAKERS[index as usize].register(ctx.waker());

if self.event_bit_is_clear() {
Poll::Ready(())
} else {
Poll::Pending
}
}
}

impl<T> embedded_hal_async::delay::DelayNs for Timer<T, crate::Async>
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();
Comment on lines +1175 to +1176
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not super important for this PR but if the user cancels the future returned by this delay function, the call to start and listen should be undone. I'm guessing there's a stop and unlisten somewhere.


TimerFuture::new(self).await;
}
}

#[handler]
pub(crate) fn timg0_timer0_handler() {
lock(&INT_ENA_LOCK, || {
unsafe { &*crate::peripherals::TIMG0::PTR }
.int_ena()
.modify(|_, w| w.t(0).clear_bit())
});

unsafe { &*crate::peripherals::TIMG0::PTR }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind adding a comment here explaining why this INT_CLR is necessary even though the INT_ENA has been cleared above.

.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, || {
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, || {
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, || {
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 {
Expand Down
14 changes: 9 additions & 5 deletions hil-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ harness = false
name = "delay"
harness = false

[[test]]
name = "delay_async"
harness = false

[[test]]
name = "dma_macros"
harness = false
Expand Down Expand Up @@ -177,20 +181,20 @@ 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"
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]
Expand All @@ -209,8 +213,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"]
Expand Down
2 changes: 1 addition & 1 deletion hil-test/tests/delay.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Delay Test

//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32s2 esp32s3
//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3

#![no_std]
#![no_main]
Expand Down
Loading