diff --git a/rtic-monotonics/Cargo.toml b/rtic-monotonics/Cargo.toml index 24b739cdff..3f157d4de7 100644 --- a/rtic-monotonics/Cargo.toml +++ b/rtic-monotonics/Cargo.toml @@ -32,6 +32,7 @@ features = [ "stm32_tim5", "stm32_tim15", "esp32c3-systimer", + "atsamd21g", ] rustdoc-flags = ["--cfg", "docsrs"] @@ -70,6 +71,9 @@ imxrt-ral = { version = "0.5.3", optional = true } esp32c3 = {version = "0.25.0", optional = true } riscv = {version = "0.12.1", optional = true } +# ATSAMD21 +atsamd21g = {version = "0.14.1", optional = true } + [build-dependencies] proc-macro2 = { version = "1.0.36", optional = true } @@ -113,6 +117,10 @@ imxrt_gpt2 = ["imxrt"] # ESP32-C3 Timer esp32c3-systimer = ["dep:esp32c3", "dep:riscv"] +# ATSAMD21 Timer +atsamd21g = ["dep:atsamd21g"] + + # STM32 timers # Use as `features = ["stm32g081kb", "stm32_tim15"]` stm32_tim2 = [] diff --git a/rtic-monotonics/src/atsamd21.rs b/rtic-monotonics/src/atsamd21.rs new file mode 100644 index 0000000000..2ac4272864 --- /dev/null +++ b/rtic-monotonics/src/atsamd21.rs @@ -0,0 +1,224 @@ +//! [`Monotonic`](rtic_time::Monotonic) implementation for the TC4/5 timers. +//! +//! # Example +//! +//! ``` +//! use rtic_monotonics::atsamd21::prelude::*; +//! atsamd21_tc4_tc5_monotonic!(Mono); +//! +//! fn init(mut device: pac::Peripherals) { +//! let mut clocks = GenericClockController::with_internal_32kosc( +//! device.gclk, +//! &mut device.pm, +//! &mut device.sysctrl, +//! &mut device.nvmctrl, +//! ); +//! let gclk0 = clocks.gclk0(); +//! let _tc4_tc5_clk = clocks.tc4_tc5(&gclk0).unwrap(); +//! Mono::start(device.tc4, device.tc5, &mut device.pm); +//! } +//! +//! async fn usage() { +//! loop { +//! // Use the monotonic +//! let timestamp = Mono::now(); +//! Mono::delay(100.millis()).await; +//! } +//! } +//! +//! // FIXME: the interrupt handler is not working, but re-implementing it in a RTIC task does +//! // Comment the interrupt handler `unsafe extern "C" fn TC4()` and add the following RTIC task +//! #[task(binds = TC4)] +//! fn tc4(_cx: tc4::Context) { +//! use rtic_time::timer_queue::TimerQueueBackend; +//! unsafe { Tc4Tc5Backend::timer_queue().on_monotonic_interrupt() }; +//! } +//! ``` + +/// Common definitions and traits for using the ATSAMD21 TC4/5 monotonic +pub mod prelude { + pub use crate::atsamd21_tc4_tc5_monotonic; + pub use crate::Monotonic; + pub use fugit::{self, ExtU64, ExtU64Ceil}; +} + +use atsamd21g::Pm; + +#[cfg(feature = "atsamd21g")] +use atsamd21g as pac; + +use portable_atomic::{AtomicU32, Ordering}; +use rtic_time::{ + half_period_counter::calculate_now, + timer_queue::{TimerQueue, TimerQueueBackend}, +}; + +static HALF_PERIOD_COUNT: AtomicU32 = AtomicU32::new(0); +static TIMER_QUEUE: TimerQueue = TimerQueue::new(); + +/// TC4/5 based [`TimerQueueBackend`]. +pub struct Tc4Tc5Backend; + +impl Tc4Tc5Backend { + #[inline] + fn register() -> &'static pac::tc4::Count32 { + unsafe { pac::Tc4::ptr().as_ref().unwrap().count32() } + } + + #[inline] + fn sync() { + while Self::register().status().read().syncbusy().bit_is_set() {} + } + + /// Starts the clock. + /// + /// **Do not use this function directly.** + /// + /// Use the [`atsamd21_tc4_tc5_monotonic`] macro instead. + pub fn _start(tc4: pac::Tc4, _tc5: pac::Tc5, pm: &mut Pm) { + let tc4 = &mut tc4.count32(); + + // Enable the TC4 clock + pm.apbcmask().modify(|_, w| w.tc4_().set_bit()); + + // Disable the peripheral while we reconfigure it + tc4.ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); + + // Reset the peripheral + tc4.ctrla().write(|w| w.swrst().set_bit()); + Self::sync(); + + // Set the counter to 32 bits + tc4.ctrla().write(|w| w.mode().count32()); + Self::sync(); + + // Reset the counter to 0 + tc4.count().reset(); + + // Prepare the half-period counter and timer queue + HALF_PERIOD_COUNT.store(0, Ordering::SeqCst); + TIMER_QUEUE.initialize(Self); + + // Continuously update the counter register withotu having to sync + tc4.readreq() + .write(|w| unsafe { w.rcont().set_bit().addr().bits(0x10) }); + + // We extend the 32 bit counter to 63 bits using half-period counting. + // On overflow and half period, we increment the half-period counter. + // We use comparator 0 for user timing, comparator 1 for half-period counting. + tc4.intenset().write(|w| w.ovf().set_bit().mc1().set_bit()); + tc4.cc(0).write(|w| unsafe { w.cc().bits(0x0) }); // Used for timer queue, interrupt disabled by default + tc4.cc(1).write(|w| unsafe { w.cc().bits(0x8000_0000) }); // Half-period value + + // Enable the timer + tc4.ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); + } +} + +impl TimerQueueBackend for Tc4Tc5Backend { + type Ticks = u64; + + fn now() -> Self::Ticks { + calculate_now( + || HALF_PERIOD_COUNT.load(Ordering::Relaxed), + || Self::register().count().read().bits(), + ) + } + + fn clear_compare_flag() { + let reg = Self::register(); + let intflag = reg.intflag().read(); + + if intflag.mc0().bit_is_set() { + reg.intflag().write(|w| w.mc0().set_bit()); + } + } + + fn on_interrupt() { + let reg = Self::register(); + let intflag = reg.intflag().read(); + + if intflag.ovf().bit_is_set() { + reg.intflag().write(|w| w.ovf().set_bit()); + let prev = HALF_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); + assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!"); + } + if intflag.mc1().bit_is_set() { + reg.intflag().write(|w| w.mc1().set_bit()); + let prev = HALF_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); + assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!"); + } + } + + fn set_compare(instant: Self::Ticks) { + let now = Self::now(); + + // Since the timer may overflow based on the requested compare val, we check how many ticks are left. + // `wrapping_sub` takes care of the u64 integer overflow special case. + let val = if instant.wrapping_sub(now) <= (u32::MAX as u64) { + instant + } else { + // In the past or will overflow + 0 + }; + + // Set the compare value and enable the interrupt + Self::register() + .cc(0) + .write(|w| unsafe { w.bits(val as u32) }); + Self::register().intenset().write(|w| w.mc0().set_bit()); + } + + fn pend_interrupt() { + pac::NVIC::pend(pac::Interrupt::TC4); + } + + fn timer_queue() -> &'static TimerQueue { + &TIMER_QUEUE + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! atsamd21_tc4_tc5_monotonic { + ($name:ident) => { + /// A `Monotonic` based on the TC4/5 peripherals. + pub struct $name; + + impl $name { + /// Starts the `Monotonic`. + /// + /// This method must be called only once. + pub fn start(tc4: pac::Tc4, tc5: pac::Tc5, pm: &mut pac::Pm) { + #[no_mangle] + #[allow(non_snake_case)] + unsafe extern "C" fn TC4() { + defmt::info!("test"); + use $crate::TimerQueueBackend; + $crate::atsamd21::Tc4Tc5Backend::timer_queue().on_monotonic_interrupt(); + } + + $crate::atsamd21::Tc4Tc5Backend::_start(tc4, tc5, pm); + } + } + + impl $crate::TimerQueueBasedMonotonic for $name { + type Backend = $crate::atsamd21::Tc4Tc5Backend; + type Instant = $crate::fugit::Instant< + ::Ticks, + 1, + 48_000_000, + >; + type Duration = $crate::fugit::Duration< + ::Ticks, + 1, + 48_000_000, + >; + } + + $crate::rtic_time::impl_embedded_hal_delay_fugit!($name); + $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name); + }; +} diff --git a/rtic-monotonics/src/lib.rs b/rtic-monotonics/src/lib.rs index 3db10f8a57..925a2b06b2 100644 --- a/rtic-monotonics/src/lib.rs +++ b/rtic-monotonics/src/lib.rs @@ -78,6 +78,9 @@ pub mod nrf; #[cfg(stm32)] pub mod stm32; +#[cfg(feature = "atsamd21g")] +pub mod atsamd21; + #[allow(dead_code)] pub(crate) const fn cortex_logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits)