Bugfixing; async timer example

This commit is contained in:
ivmarkov 2023-10-09 18:55:44 +00:00
parent 071d7c6620
commit 092055b967
4 changed files with 78 additions and 33 deletions

19
examples/timer_async.rs Normal file
View File

@ -0,0 +1,19 @@
use esp_idf_hal::sys::EspError;
fn main() -> Result<(), EspError> {
// It is necessary to call this function once. Otherwise some patches to the runtime
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_hal::sys::link_patches();
let per = esp_idf_hal::peripherals::Peripherals::take().unwrap();
let timer_conf = esp_idf_hal::timer::config::Config::new().auto_reload(true);
let mut timer = esp_idf_hal::timer::TimerDriver::new(per.timer00, &timer_conf)?;
esp_idf_hal::task::block_on(async move {
loop {
timer.delay(timer.tick_hz()).await?; // Every second
println!("Tick");
}
})
}

View File

@ -1,6 +1,6 @@
use std::num::NonZeroU32;
use esp_idf_hal::sys::{EspError, TaskHandle_t}; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
use esp_idf_hal::{sys::EspError, task::notification::Notification}; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
fn main() -> Result<(), EspError> {
// It is necessary to call this function once. Otherwise some patches to the runtime
@ -9,9 +9,8 @@ fn main() -> Result<(), EspError> {
let per = esp_idf_hal::peripherals::Peripherals::take().unwrap();
// This handle will be used as the address for the event in the callback.
// Make sure that the handle / thread lives always longer as the callback it is used in
let main_task_handle: TaskHandle_t = esp_idf_hal::task::current().unwrap();
// A safer abstraction over FreeRTOS/ESP-IDF task notifications.
let notification = Notification::new();
// BaseClock for the Timer is the APB_CLK that is running on 80MHz at default
// The default clock-divider is -> 80
@ -19,30 +18,29 @@ fn main() -> Result<(), EspError> {
let timer_conf = esp_idf_hal::timer::config::Config::new().auto_reload(true);
let mut timer = esp_idf_hal::timer::TimerDriver::new(per.timer00, &timer_conf)?;
// Calculate value needed for alarm in seconds
// (APB_CLK_FREQ / DEVIDER ) * seconds = count
// example every 200 us
// ( 80*10^6 / 80 ) * 200 *10^(-6) = 200
timer.set_alarm(200)?;
// Every half a second
timer.set_alarm(timer.tick_hz() / 2)?;
// Saftey: make sure the task handle stays valid for longer than the subscribtion
// is active
let notifier = notification.notifier();
// Saftey: make sure the `Notification` object is not dropped while the subscription is active
unsafe {
timer.subscribe(move || {
let bitset = 0b10001010101;
esp_idf_hal::task::notify_and_yield(main_task_handle, NonZeroU32::new(bitset).unwrap());
notifier.notify_and_yield(NonZeroU32::new(bitset).unwrap());
})?;
}
timer.enable_interrupt()?;
timer.enable_alarm(true)?;
timer.enable(true)?;
loop {
// Notify approach
// The benefit with this approach over checking a global static variable is
// that the scheduler can hold the task, and resume when signaled
// so no spinlock is needed
let bitset = esp_idf_hal::task::wait_notification(esp_idf_hal::delay::BLOCK);
// that the scheduler can block the task, and quickly resume it when notified
// so no spinlock is needed / the CPU does not waste cycles.
let bitset = notification.wait(esp_idf_hal::delay::BLOCK);
if let Some(bitset) = bitset {
println!("got event with bits {bitset:#b} from ISR");

View File

@ -1134,6 +1134,14 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> {
Ok(())
}
/// Subscribes the provided callback for ISR notifications.
/// As a side effect, interrupts will be disabled, so to receive a notification, one has
/// to also call `PinDriver::enable_interrupt` after calling this method.
///
/// Note that `PinDriver::enable_interrupt` should also be called after
/// each received notification **from non-ISR context**, because the driver will automatically
/// disable ISR interrupts on each received ISR notification (so as to avoid IWDT triggers).
///
/// # Safety
///
/// Care should be taken not to call STD, libc or FreeRTOS APIs (except for a few allowed ones)
@ -1151,7 +1159,7 @@ impl<'d, T: Pin, MODE> PinDriver<'d, T, MODE> {
chip::PIN_ISR_HANDLER[self.pin.pin() as usize] =
Some(unsafe { core::mem::transmute(callback) });
self.enable_interrupt()
Ok(())
}
#[cfg(not(feature = "riscv-ulp-hal"))]

View File

@ -74,6 +74,7 @@ pub trait Timer: Send {
pub struct TimerDriver<'d> {
timer: u8,
divider: u32,
isr_registered: bool,
_p: PhantomData<&'d mut ()>,
}
@ -112,11 +113,12 @@ impl<'d> TimerDriver<'d> {
Ok(Self {
timer: ((TIMER::group() as u8) << 4) | (TIMER::index() as u8),
divider: config.divider,
isr_registered: false,
_p: PhantomData,
})
}
pub fn tick_hz(&self) -> u32 {
pub fn tick_hz(&self) -> u64 {
let hz;
#[cfg(esp_idf_version_major = "4")]
@ -129,7 +131,7 @@ impl<'d> TimerDriver<'d> {
hz = APB_CLK_FREQ / self.divider;
}
hz
hz as _
}
pub fn enable(&mut self, enable: bool) -> Result<(), EspError> {
@ -217,22 +219,36 @@ impl<'d> TimerDriver<'d> {
pub fn enable_interrupt(&mut self) -> Result<(), EspError> {
self.check();
esp!(unsafe {
timer_isr_callback_add(
self.group(),
self.index(),
Some(Self::handle_isr),
(self.group() * timer_group_t_TIMER_GROUP_MAX + self.index())
as *mut core::ffi::c_void,
0,
)
})
if !self.isr_registered {
// Driver will complain if we try to register when ISR CB is already registered
esp!(unsafe {
timer_isr_callback_add(
self.group(),
self.index(),
Some(Self::handle_isr),
(self.group() * timer_group_t_TIMER_GROUP_MAX + self.index())
as *mut core::ffi::c_void,
0,
)
})?;
self.isr_registered = true;
}
Ok(())
}
pub fn disable_interrupt(&mut self) -> Result<(), EspError> {
self.check();
esp!(unsafe { timer_isr_callback_remove(self.group(), self.index()) })
if self.isr_registered {
// Driver will complain if we try to deregister when ISR callback is not registered
esp!(unsafe { timer_isr_callback_remove(self.group(), self.index()) })?;
self.isr_registered = false;
}
Ok(())
}
pub async fn delay(&mut self, counter: u64) -> Result<(), EspError> {
@ -265,6 +281,10 @@ impl<'d> TimerDriver<'d> {
Ok(())
}
/// Subscribes the provided callback for ISR notifications.
/// As a side effect, interrupts will be disabled, so to receive a notification, one has
/// to also call `TimerDriver::enable_interrupt` after calling this method.
///
/// # Safety
///
/// Care should be taken not to call STD, libc or FreeRTOS APIs (except for a few allowed ones)
@ -280,8 +300,6 @@ impl<'d> TimerDriver<'d> {
ISR_HANDLERS[(self.group() * timer_group_t_TIMER_GROUP_MAX + self.index()) as usize] =
Some(unsafe { core::mem::transmute(callback) });
self.enable_interrupt()?;
Ok(())
}
@ -352,12 +370,14 @@ unsafe impl<'d> Send for TimerDriver<'d> {}
#[cfg(feature = "nightly")]
impl<'d> embedded_hal_async::delay::DelayUs for TimerDriver<'d> {
async fn delay_us(&mut self, us: u32) {
let counter = (self.tick_hz() as u64 * us as u64) / 1000000;
let counter = core::cmp::max((self.tick_hz() * us as u64) / 1000000, 1);
self.delay(counter).await.unwrap();
}
async fn delay_ms(&mut self, ms: u32) {
let counter = (self.tick_hz() as u64 * ms as u64) / 1000;
let counter = core::cmp::max((self.tick_hz() * ms as u64) / 1000, 1);
self.delay(counter).await.unwrap();
}
}