diff --git a/rp2040-hal/src/timer.rs b/rp2040-hal/src/timer.rs index 8f4808c..cf3f9dd 100644 --- a/rp2040-hal/src/timer.rs +++ b/rp2040-hal/src/timer.rs @@ -5,20 +5,22 @@ use embedded_time::duration::Microseconds; use crate::pac::{RESETS, TIMER}; use crate::resets::SubsystemReset; +use core::marker::PhantomData; /// Timer peripheral pub struct Timer { timer: TIMER, + alarms: [bool; 4], } -// Safety: All access is read-only. -unsafe impl Sync for Timer {} - impl Timer { /// Create a new [`Timer`] pub fn new(timer: TIMER, resets: &mut RESETS) -> Self { timer.reset_bring_up(resets); - Self { timer } + Self { + timer, + alarms: [true; 4], + } } /// Get the current counter value. @@ -47,6 +49,54 @@ impl Timer { next_end: None, } } + + /// Retrieve a reference to alarm 0. Will only return a value the first time this is called + pub fn alarm_0(&mut self) -> Option { + cortex_m::interrupt::free(|_| { + if self.alarms[0] { + self.alarms[0] = false; + Some(Alarm0(PhantomData)) + } else { + None + } + }) + } + + /// Retrieve a reference to alarm 1. Will only return a value the first time this is called + pub fn alarm_1(&mut self) -> Option { + cortex_m::interrupt::free(|_| { + if self.alarms[1] { + self.alarms[1] = false; + Some(Alarm1(PhantomData)) + } else { + None + } + }) + } + + /// Retrieve a reference to alarm 2. Will only return a value the first time this is called + pub fn alarm_2(&mut self) -> Option { + cortex_m::interrupt::free(|_| { + if self.alarms[2] { + self.alarms[2] = false; + Some(Alarm2(PhantomData)) + } else { + None + } + }) + } + + /// Retrieve a reference to alarm 3. Will only return a value the first time this is called + pub fn alarm_3(&mut self) -> Option { + cortex_m::interrupt::free(|_| { + if self.alarms[3] { + self.alarms[3] = false; + Some(Alarm3(PhantomData)) + } else { + None + } + }) + } } /// Delay implementation @@ -138,3 +188,124 @@ impl eh1_0_alpha::timer::nb::Cancel for CountDown<'_> { } } } + +macro_rules! impl_alarm { + ($name:ident { rb: $timer_alarm:ident, int: $int_alarm:ident, int_name: $int_name:tt, armed_bit_mask: $armed_bit_mask: expr }) => { + /// An alarm that can be used to schedule events in the future. Alarms can also be configured to trigger interrupts. + pub struct $name(PhantomData<()>); + + impl $name { + /// Clear the interrupt flag. This should be called after interrupt ` + #[doc = $int_name] + /// ` is called. + /// + /// The interrupt is unable to trigger a 2nd time until this interrupt is cleared. + pub fn clear_interrupt(&mut self, timer: &mut Timer) { + // safety: Because we have a mutable reference on `timer`, we have exclusive access to `TIMER::ptr()` + let _ = timer; + let timer = unsafe { &*TIMER::ptr() }; + + timer.intr.modify(|_, w| w.$int_alarm().set_bit()); + } + + /// Enable this alarm to trigger an interrupt. This alarm will trigger ` + #[doc = $int_name] + /// `. + /// + /// After this interrupt is triggered, make sure to clear the interrupt with [clear_interrupt]. + /// + /// [clear_interrupt]: #method.clear_interrupt + pub fn enable_interrupt(&mut self, timer: &mut Timer) { + // safety: Because we have a mutable reference on `timer`, we have exclusive access to `TIMER::ptr()` + let _ = timer; + let timer = unsafe { &*TIMER::ptr() }; + + timer.inte.modify(|_, w| w.$int_alarm().set_bit()); + } + + /// Disable this alarm, preventing it from triggering an interrupt. + pub fn disable_interrupt(&mut self, timer: &mut Timer) { + // safety: Because we have a mutable reference on `timer`, we have exclusive access to `TIMER::ptr()` + let _ = timer; + let timer = unsafe { &*TIMER::ptr() }; + + timer.inte.modify(|_, w| w.$int_alarm().clear_bit()); + } + + /// Schedule the alarm to be finished after `countdown`. If [enable_interrupt] is called, this will trigger interrupt ` + #[doc = $int_name] + /// ` whenever this time elapses. + /// + /// The RP2040 has been observed to take a little while to schedule an alarm. For this reason, the minimum time that this function accepts is `10.microseconds()` + /// + /// [enable_interrupt]: #method.enable_interrupt + pub fn schedule>( + &mut self, + countdown: TIME, + ) -> Result<(), ScheduleAlarmError> { + let duration = countdown.into().0; + + const MIN_MICROSECONDS: u32 = 10; + if duration < MIN_MICROSECONDS { + return Err(ScheduleAlarmError::AlarmTooSoon); + } else { + // safety: This is a read action and should not have any UB + let target_time = unsafe { &*TIMER::ptr() } + .timelr + .read() + .bits() + .wrapping_add(duration); + + // safety: This is the only code in the codebase that accesses memory address $timer_alarm + unsafe { &*TIMER::ptr() } + .$timer_alarm + .write(|w| unsafe { w.bits(target_time) }); + Ok(()) + } + } + + /// Return true if this alarm is finished. + pub fn finished(&self) -> bool { + // safety: This is a read action and should not have any UB + let bits: u32 = unsafe { &*TIMER::ptr() }.armed.read().bits(); + (bits & $armed_bit_mask) == 0 + } + } + }; +} + +/// Errors that can be returned from any of the `AlarmX::schedule` methods. +#[non_exhaustive] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum ScheduleAlarmError { + /// Alarm time is too low. Should be at least 10 microseconds. + AlarmTooSoon, +} + +impl_alarm!(Alarm0 { + rb: alarm0, + int: alarm_0, + int_name: "IRQ_TIMER_0", + armed_bit_mask: 0b0001 +}); + +impl_alarm!(Alarm1 { + rb: alarm1, + int: alarm_1, + int_name: "IRQ_TIMER_1", + armed_bit_mask: 0b0010 +}); + +impl_alarm!(Alarm2 { + rb: alarm2, + int: alarm_2, + int_name: "IRQ_TIMER_2", + armed_bit_mask: 0b0100 +}); + +impl_alarm!(Alarm3 { + rb: alarm3, + int: alarm_3, + int_name: "IRQ_TIMER_3", + armed_bit_mask: 0b1000 +});