Skip to content

Commit

Permalink
Bugfixing; async timer example
Browse files Browse the repository at this point in the history
  • Loading branch information
ivmarkov committed Oct 9, 2023
1 parent 071d7c6 commit 092055b
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 33 deletions.
19 changes: 19 additions & 0 deletions examples/timer_async.rs
Original file line number Diff line number Diff line change
@@ -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");
}
})
}
28 changes: 13 additions & 15 deletions examples/timer_notify.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -9,40 +9,38 @@ 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
// default APB clk is available with the APB_CLK_FREQ constant
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");
Expand Down
10 changes: 9 additions & 1 deletion src/gpio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"))]
Expand Down
54 changes: 37 additions & 17 deletions src/timer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub trait Timer: Send {
pub struct TimerDriver<'d> {
timer: u8,
divider: u32,
isr_registered: bool,
_p: PhantomData<&'d mut ()>,
}

Expand Down Expand Up @@ -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")]
Expand All @@ -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> {
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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)
Expand All @@ -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(())
}

Expand Down Expand Up @@ -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();
}
}
Expand Down

0 comments on commit 092055b

Please sign in to comment.