From 305dfc9d1fbb69f4e9570b3a454cd3e3d8309498 Mon Sep 17 00:00:00 2001 From: Wilfried Chauveau Date: Thu, 6 Oct 2022 18:31:31 +0100 Subject: [PATCH] Add rtic-monotonic support for timer & alarms (feature gated) (#459) * Add rtic-monotonic support for timer & alarms * Force Alarm interrupt if timer is set too soon * timer: Remove non_exhaustive attribute from ScheduleAlarmError * timer: TooSoon is no longer emitted so it can be removed --- boards/rp-pico/Cargo.toml | 4 + .../rp-pico/examples/pico_rtic_monotonic.rs | 92 +++++++++++++++++++ rp2040-hal/Cargo.toml | 2 + rp2040-hal/src/timer.rs | 59 +++++++++++- 4 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 boards/rp-pico/examples/pico_rtic_monotonic.rs diff --git a/boards/rp-pico/Cargo.toml b/boards/rp-pico/Cargo.toml index 547e329..cbaf9b1 100644 --- a/boards/rp-pico/Cargo.toml +++ b/boards/rp-pico/Cargo.toml @@ -67,3 +67,7 @@ disable-intrinsics = ["rp2040-hal/disable-intrinsics"] # This enables ROM functions for f64 math that were not present in the earliest RP2040s rom-v2-intrinsics = ["rp2040-hal/rom-v2-intrinsics"] + +[[example]] +name = "pico_rtic_monotonic" +required-features = ["rp2040-hal/rtic-monotonic"] diff --git a/boards/rp-pico/examples/pico_rtic_monotonic.rs b/boards/rp-pico/examples/pico_rtic_monotonic.rs new file mode 100644 index 0000000..f5922f8 --- /dev/null +++ b/boards/rp-pico/examples/pico_rtic_monotonic.rs @@ -0,0 +1,92 @@ +#![no_std] +#![no_main] + +use panic_halt as _; + +#[rtic::app(device = rp_pico::hal::pac, peripherals = true, dispatchers = [I2C0_IRQ])] +mod app { + + use embedded_hal::digital::v2::OutputPin; + use fugit::ExtU64; + use fugit::MicrosDurationU32; + use rp_pico::{ + hal::{ + self, + clocks::init_clocks_and_plls, + timer::{monotonic::Monotonic, Alarm0}, + watchdog::Watchdog, + Sio, + }, + XOSC_CRYSTAL_FREQ, + }; + + const SCAN_TIME_US: MicrosDurationU32 = MicrosDurationU32::secs(1); + + #[shared] + struct Shared { + led: hal::gpio::Pin, + } + + #[monotonic(binds = TIMER_IRQ_0, default = true)] + type MyMono = Monotonic; + + #[local] + struct Local {} + + #[init] + fn init(c: init::Context) -> (Shared, Local, init::Monotonics) { + // Soft-reset does not release the hardware spinlocks + // Release them now to avoid a deadlock after debug or watchdog reset + unsafe { + hal::sio::spinlock_reset(); + } + let mut resets = c.device.RESETS; + let mut watchdog = Watchdog::new(c.device.WATCHDOG); + let _clocks = init_clocks_and_plls( + XOSC_CRYSTAL_FREQ, + c.device.XOSC, + c.device.CLOCKS, + c.device.PLL_SYS, + c.device.PLL_USB, + &mut resets, + &mut watchdog, + ) + .ok() + .unwrap(); + + let sio = Sio::new(c.device.SIO); + let pins = rp_pico::Pins::new( + c.device.IO_BANK0, + c.device.PADS_BANK0, + sio.gpio_bank0, + &mut resets, + ); + let mut led = pins.led.into_push_pull_output(); + led.set_low().unwrap(); + + let mut timer = hal::Timer::new(c.device.TIMER, &mut resets); + let alarm = timer.alarm_0().unwrap(); + blink_led::spawn_after(500.millis()).unwrap(); + + ( + Shared { led }, + Local {}, + init::Monotonics(Monotonic::new(timer, alarm)), + ) + } + + #[task( + shared = [led], + local = [tog: bool = true], + )] + fn blink_led(mut c: blink_led::Context) { + if *c.local.tog { + c.shared.led.lock(|l| l.set_high().unwrap()); + } else { + c.shared.led.lock(|l| l.set_low().unwrap()); + } + *c.local.tog = !*c.local.tog; + + blink_led::spawn_after(500.millis()).unwrap(); + } +} diff --git a/rp2040-hal/Cargo.toml b/rp2040-hal/Cargo.toml index 2781e7f..fd3d0c8 100644 --- a/rp2040-hal/Cargo.toml +++ b/rp2040-hal/Cargo.toml @@ -35,6 +35,8 @@ chrono = { version = "0.4", default-features = false, optional = true } defmt = { version = ">=0.2.0, <0.4", optional = true } +rtic-monotonic = { version = "1.0.0", optional = true } + [dev-dependencies] cortex-m-rt = "0.7" panic-halt = "0.2.0" diff --git a/rp2040-hal/src/timer.rs b/rp2040-hal/src/timer.rs index ab991b7..cb0b9e7 100644 --- a/rp2040-hal/src/timer.rs +++ b/rp2040-hal/src/timer.rs @@ -237,8 +237,11 @@ macro_rules! impl_alarm { // 1 instance of AlarmN so we can safely atomically clear this bit. unsafe { timer.armed.write_with_zero(|w| w.bits($armed_bit_mask)); + crate::atomic_register_access::write_bitmask_set( + timer.intf.as_ptr(), + $armed_bit_mask, + ); } - return Err(ScheduleAlarmError::AlarmTooSoon); } Ok(()) }) @@ -258,6 +261,10 @@ macro_rules! impl_alarm { // of the TIMER.inte register unsafe { let timer = &(*pac::TIMER::ptr()); + crate::atomic_register_access::write_bitmask_clear( + timer.intf.as_ptr(), + $armed_bit_mask, + ); timer.intr.write_with_zero(|w| w.$int_alarm().set_bit()); } } @@ -345,11 +352,8 @@ macro_rules! impl_alarm { } /// 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, /// Alarm time is too high. Should not be more than `u32::max_value()` in the future. AlarmTooLate, } @@ -381,3 +385,50 @@ impl_alarm!(Alarm3 { int_name: "TIMER_IRQ_3", armed_bit_mask: 0b1000 }); + +/// Support for RTIC monotonic trait. +#[cfg(feature = "rtic-monotonic")] +pub mod monotonic { + use super::{Alarm, Instant, Timer}; + use fugit::ExtU32; + + /// RTIC Monotonic Implementation + pub struct Monotonic(pub Timer, A); + impl Monotonic { + /// Creates a new monotonic. + pub const fn new(timer: Timer, alarm: A) -> Self { + Self(timer, alarm) + } + } + impl rtic_monotonic::Monotonic for Monotonic { + type Instant = Instant; + type Duration = fugit::MicrosDurationU64; + + const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = false; + + fn now(&mut self) -> Instant { + self.0.get_counter() + } + + fn set_compare(&mut self, instant: Instant) { + // The alarm can only trigger up to 2^32 - 1 ticks in the future. + // So, if `instant` is more than 2^32 - 2 in the future, we use `max_instant` instead. + let max_instant = self.0.get_counter() + 0xFFFF_FFFE.micros(); + let wake_at = core::cmp::min(instant, max_instant); + + // Cannot fail + let _ = self.1.schedule_at(wake_at); + self.1.enable_interrupt(); + } + + fn clear_compare_flag(&mut self) { + self.1.clear_interrupt(); + } + + fn zero() -> Self::Instant { + Instant::from_ticks(0) + } + + unsafe fn reset(&mut self) {} + } +}