From bd298e925892569ecbea6b601873e8852a240ec7 Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Fri, 15 Nov 2024 16:10:29 -0500 Subject: [PATCH 01/13] feat(rtic-v2-rtc-monotonics): Initial import of both the RTC mode0 and mode1 monotonics. --- hal/Cargo.toml | 41 +- hal/src/lib.rs | 3 + hal/src/prelude.rs | 10 + hal/src/{rtc.rs => rtc/mod.rs} | 41 +- hal/src/rtc/rtic.rs | 859 +++++++++++++++++++++++++++++++++ 5 files changed, 900 insertions(+), 54 deletions(-) rename hal/src/{rtc.rs => rtc/mod.rs} (93%) create mode 100644 hal/src/rtc/rtic.rs diff --git a/hal/Cargo.toml b/hal/Cargo.toml index b771ed9c7f7..6cd220649f0 100644 --- a/hal/Cargo.toml +++ b/hal/Cargo.toml @@ -63,12 +63,15 @@ defmt = { version = "0.3.8", optional = true} embassy-sync = {version = "0.6.0", optional = true} embedded-hal-async = {version = "1.0.0", optional = true} embedded-io-async = {version = "0.6.1", optional = true} +critical-section = {version = "1.2.0", optional = true} embedded-sdmmc = {version = "0.3", optional = true} futures = {version = "0.3.31", default-features = false, features = ["async-await"], optional = true} jlink_rtt = {version = "0.2", optional = true} mcan-core = {version = "0.2", optional = true} +portable-atomic = {version = "1.9.0", optional = true} rtic-monotonic = {version = "1.0", optional = true} usb-device = {version = "0.3.2", optional = true} +rtic-time = {version = "2.0", optional = true} #=============================================================================== # PACs @@ -80,27 +83,27 @@ usb-device = {version = "0.3.2", optional = true} # users should specify a corresponding variant (see below). The variant features # will select the correct PAC, as well as other configuration features. -atsamd11c = { version = "0.14.1", path = "../pac/atsamd11c", optional = true} -atsamd11d = { version = "0.14.1", path = "../pac/atsamd11d", optional = true} +atsamd11c = {version = "0.14.1", path = "../pac/atsamd11c", optional = true} +atsamd11d = {version = "0.14.1", path = "../pac/atsamd11d", optional = true} -atsamd21e = { version = "0.14.1", path = "../pac/atsamd21e", optional = true} -atsamd21g = { version = "0.14.1", path = "../pac/atsamd21g", optional = true} -atsamd21j = { version = "0.14.1", path = "../pac/atsamd21j", optional = true} +atsamd21e = {version = "0.14.1", path = "../pac/atsamd21e", optional = true} +atsamd21g = {version = "0.14.1", path = "../pac/atsamd21g", optional = true} +atsamd21j = {version = "0.14.1", path = "../pac/atsamd21j", optional = true} -atsamd51g = { version = "0.14.1", path = "../pac/atsamd51g", optional = true} -atsamd51j = { version = "0.14.1", path = "../pac/atsamd51j", optional = true} -atsamd51n = { version = "0.14.1", path = "../pac/atsamd51n", optional = true} -atsamd51p = { version = "0.14.1", path = "../pac/atsamd51p", optional = true} +atsamd51g = {version = "0.14.1", path = "../pac/atsamd51g", optional = true} +atsamd51j = {version = "0.14.1", path = "../pac/atsamd51j", optional = true} +atsamd51n = {version = "0.14.1", path = "../pac/atsamd51n", optional = true} +atsamd51p = {version = "0.14.1", path = "../pac/atsamd51p", optional = true} -atsame51g = { version = "0.14.1", path = "../pac/atsame51g", optional = true} -atsame51j = { version = "0.14.1", path = "../pac/atsame51j", optional = true} -atsame51n = { version = "0.14.1", path = "../pac/atsame51n", optional = true} +atsame51g = {version = "0.14.1", path = "../pac/atsame51g", optional = true} +atsame51j = {version = "0.14.1", path = "../pac/atsame51j", optional = true} +atsame51n = {version = "0.14.1", path = "../pac/atsame51n", optional = true} -atsame53j = { version = "0.14.1", path = "../pac/atsame53j", optional = true} -atsame53n = { version = "0.14.1", path = "../pac/atsame53n", optional = true} +atsame53j = {version = "0.14.1", path = "../pac/atsame53j", optional = true} +atsame53n = {version = "0.14.1", path = "../pac/atsame53n", optional = true} -atsame54n = { version = "0.14.1", path = "../pac/atsame54n", optional = true} -atsame54p = { version = "0.14.1", path = "../pac/atsame54p", optional = true} +atsame54n = {version = "0.14.1", path = "../pac/atsame54n", optional = true} +atsame54p = {version = "0.14.1", path = "../pac/atsame54p", optional = true} #=============================================================================== # Features @@ -182,11 +185,11 @@ same54p-rt = ["same54p", "atsame54p/rt"] # These features are user-selectable and enable additional features within the # HAL, like USB or DMA support. can = ["mcan-core"] -dma = [] defmt = ["dep:defmt"] +dma = [] enable_unsafe_aes_newblock_cipher = [] max-channels = ["dma"] -rtic = ["rtic-monotonic"] +rtic = ["rtic-monotonic", "rtic-time", "critical-section", "portable-atomic"] sdmmc = ["embedded-sdmmc"] usb = ["usb-device"] use_rtt = ["jlink_rtt"] @@ -208,4 +211,6 @@ async = [ # The `device` feature tells the HAL that a device has been selected from the # feature list. It exists mostly to provide better error messages. +critical-section = ["dep:critical-section"] device = [] +rtic-time = ["dep:rtic-time"] diff --git a/hal/src/lib.rs b/hal/src/lib.rs index 5fde9260fce..db675493bdd 100644 --- a/hal/src/lib.rs +++ b/hal/src/lib.rs @@ -91,6 +91,9 @@ pub mod timer_traits; #[cfg(feature = "dma")] pub mod dmac; +#[cfg(feature = "rtic")] +pub use rtic_time; + #[doc(hidden)] mod peripherals; #[doc(inline)] diff --git a/hal/src/prelude.rs b/hal/src/prelude.rs index 1a78e5211cb..8b8cbc32e94 100644 --- a/hal/src/prelude.rs +++ b/hal/src/prelude.rs @@ -11,3 +11,13 @@ pub use crate::ehal_02::digital::v2::OutputPin as _atsamd_hal_embedded_hal_digit pub use crate::ehal_02::digital::v2::ToggleableOutputPin as _atsamd_hal_embedded_hal_digital_v2_ToggleableOutputPin; pub use crate::ehal_02::prelude::*; + +#[cfg(feature = "rtic")] +pub use crate::{ + rtc_mode0_monotonic_1k_ext, rtc_mode0_monotonic_1k_int, rtc_mode0_monotonic_32k_ext, + rtc_mode0_monotonic_32k_int, rtc_mode1_monotonic_1k_ext, rtc_mode1_monotonic_1k_int, + rtc_mode1_monotonic_32k_ext, rtc_mode1_monotonic_32k_int, +}; + +#[cfg(feature = "rtic")] +pub use rtic_time::Monotonic; diff --git a/hal/src/rtc.rs b/hal/src/rtc/mod.rs similarity index 93% rename from hal/src/rtc.rs rename to hal/src/rtc/mod.rs index 0bfb27a6d96..a3a559a7e4c 100644 --- a/hal/src/rtc.rs +++ b/hal/src/rtc/mod.rs @@ -15,11 +15,7 @@ use core::marker::PhantomData; use embedded_sdmmc::{TimeSource, Timestamp}; #[cfg(feature = "rtic")] -pub type Instant = fugit::Instant; -#[cfg(feature = "rtic")] -pub type Duration = fugit::Duration; -#[cfg(feature = "rtic")] -use rtic_monotonic::Monotonic; +pub mod rtic; // SAMx5x imports #[hal_cfg("rtc-d5x")] @@ -238,6 +234,10 @@ impl Rtc { pub fn count32_mode(rtc: pac::Rtc, rtc_clock_freq: Hertz, pm: &mut Pm) -> Self { pm.apbamask().modify(|_, w| w.rtc_().set_bit()); + // TODO: This may not work properly because here the count sync bit is not set + // as it is in Self::into_count32_mode Maybe we can just call that to + // avoid code duplication + let mut new_rtc = Self { rtc, rtc_clock_freq, @@ -481,34 +481,3 @@ impl TimerParams { TimerParams { divider, cycles } } } - -#[cfg(feature = "rtic")] -impl Monotonic for Rtc { - type Instant = Instant; - type Duration = Duration; - unsafe fn reset(&mut self) { - // Since reset is only called once, we use it to enable the interrupt generation - // bit. - self.mode0().intenset().write(|w| w.cmp0().set_bit()); - } - - fn now(&mut self) -> Self::Instant { - Self::Instant::from_ticks(self.count32()) - } - - fn zero() -> Self::Instant { - Self::Instant::from_ticks(0) - } - - fn set_compare(&mut self, instant: Self::Instant) { - unsafe { - self.mode0() - .comp(0) - .write(|w| w.comp().bits(instant.ticks())) - } - } - - fn clear_compare_flag(&mut self) { - self.mode0().intflag().write(|w| w.cmp0().set_bit()); - } -} diff --git a/hal/src/rtc/rtic.rs b/hal/src/rtc/rtic.rs new file mode 100644 index 00000000000..c65b5aba563 --- /dev/null +++ b/hal/src/rtc/rtic.rs @@ -0,0 +1,859 @@ +//! [`Monotonic`](rtic_time::Monotonic) implementations for the Real Time +//! Clock (RTC). +//! +//! TODO: Somewhere make a note about u32 implementation limited to 36 hours and +//! 24 minutes +//! +//! TODO: Mention v1 and v2. +//! TODO: Have prelude for monotonic that includes extension traits like in +//! `rtic-monotonics`? +//! +//! # Example +//! +//! ``` +//! // TODO: Update this +//! use atsamd_hal::rtc::rtic::prelude::*; +//! rtc_monotonic!(Mono); +//! +//! fn init() { +//! # // This is normally provided by the selected PAC +//! # let rtc = unsafe { core::mem::transmute(()) }; +//! # let mut mclk = unsafe { core::mem::transmute(()) }; +//! // Start the monotonic +//! Mono::start(rtc, &mut mclk); +//! } +//! +//! async fn usage() { +//! loop { +//! // Use the monotonic +//! let timestamp = Mono::now(); +//! Mono::delay(100.millis()).await; +//! } +//! } +//! ``` + +use super::{Count32Mode, Rtc}; + +// TODO: how to handle this? +pub use v2::*; + +mod v1 { + use super::*; + use rtic_monotonic::Monotonic; + + /// The RTC clock frequency in Hz. + pub const CLOCK_FREQ: u32 = 32_768; + + type Instant = fugit::Instant; + type Duration = fugit::Duration; + + impl Monotonic for Rtc { + type Instant = Instant; + type Duration = Duration; + unsafe fn reset(&mut self) { + // Since reset is only called once, we use it to enable the interrupt generation + // bit. + self.mode0().intenset().write(|w| w.cmp0().set_bit()); + } + + fn now(&mut self) -> Self::Instant { + Self::Instant::from_ticks(self.count32()) + } + + fn zero() -> Self::Instant { + Self::Instant::from_ticks(0) + } + + fn set_compare(&mut self, instant: Self::Instant) { + unsafe { + self.mode0() + .comp(0) + .write(|w| w.comp().bits(instant.ticks())) + } + } + + fn clear_compare_flag(&mut self) { + self.mode0().intflag().write(|w| w.cmp0().set_bit()); + } + } +} + +pub mod v2 { + use crate::pac; + use atsamd_hal_macros::hal_macro_helper; + use rtic_time::timer_queue::{TimerQueue, TimerQueueBackend}; + + const MIN_COMPARE_TICKS: u32 = 5; + + // TODO: docs or suppress? Can we move to enum type programming? + /// The exact 1 kHz clock frequency in Hz + pub const CLOCK_FREQ_1K: u32 = 1_024; + /// The exact 32 kHz clock frequency in Hz + pub const CLOCK_FREQ_32K: u32 = 32_768; + + #[doc(hidden)] + #[macro_export] + macro_rules! __internal_create_rtc_interrupt { + ($mode:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + unsafe extern "C" fn RTC() { + use $crate::rtic_time::timer_queue::TimerQueueBackend; + $crate::rtc::rtic::$mode::RtcBackend::timer_queue().on_monotonic_interrupt(); + } + }; + } + + #[doc(hidden)] + #[macro_export] + macro_rules! __internal_create_rtc_struct { + ($name:ident, $mode:ident, $clock_rate:expr, $clock_source:ident) => { + /// A `Monotonic` based on the RTC peripheral. + pub struct $name; + + impl $name { + /// Starts the `Monotonic`. + /// + /// This method must be called only once. + pub fn start( + rtc: $crate::pac::Rtc, + mclk: &mut $crate::pac::Mclk, + osc32kctrl: &mut $crate::pac::Osc32kctrl, + ) { + $crate::__internal_create_rtc_interrupt!($mode); + + $crate::rtc::rtic::$mode::RtcBackend::_start( + rtc, + mclk, + osc32kctrl, + $crate::rtc::rtic::ClockSource::$clock_source, + ); + } + } + + impl $crate::rtic_time::monotonic::TimerQueueBasedMonotonic for $name { + type Backend = $crate::rtc::rtic::$mode::RtcBackend; + type Instant = $crate::fugit::Instant< + ::Ticks, + 1, + { $clock_rate }, + >; + type Duration = $crate::fugit::Duration< + ::Ticks, + 1, + { $clock_rate }, + >; + } + + $crate::rtic_time::impl_embedded_hal_delay_fugit!($name); + $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name); + }; + } + + // TODO: Have separate macros, or have additional type arguments? + /* #[doc = r#"This is + + a multiline string"#] */ + + /* /// Create an RTC based monotonic for RTIC v2 and register the RTC + * interrupt for it with a 1.024 kHz internal clock. + * + * - **Total monotonic period:** ~48 days + * - **Time precision:** ~977 μs + * + * See the [`rtc`](crate::rtc) module for more details. */ + #[macro_export] + macro_rules! rtc_mode0_monotonic_1k_int { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode0, + $crate::rtc::rtic::v2::CLOCK_FREQ_1K, + Int1k + ); + }; + } + + /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt + /// for it with a 1.024 kHz internal clock. + /// + /// - **Total monotonic period:** ~48 days + /// - **Time precision:** ~977 μs + /// + /// See the [`rtc`](crate::rtc) module for more details. + #[macro_export] + macro_rules! rtc_mode0_monotonic_1k_ext { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode0, + $crate::rtc::rtic::v2::CLOCK_FREQ_1K, + Ext1k + ); + }; + } + + /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt + /// for it with a 32.768 kHz internal clock. + /// + /// - **Total monotonic period:** ~36 hours + /// - **Time precision:** ~31 μs + /// + /// See the [`rtc`](crate::rtc) module for more details. + #[macro_export] + macro_rules! rtc_mode0_monotonic_32k_int { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode0, + $crate::rtc::rtic::v2::CLOCK_FREQ_32K, + Int32k + ); + }; + } + + /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt + /// for it with a 32.768 kHz internal clock. + /// + /// - **Total monotonic period:** ~36 hours + /// - **Time precision:** ~31 μs + /// + /// See the [`rtc`](crate::rtc) module for more details. + #[macro_export] + macro_rules! rtc_mode0_monotonic_32k_ext { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode0, + $crate::rtc::rtic::v2::CLOCK_FREQ_32K, + Ext32k + ); + }; + } + + /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt + /// for it with a 1.024 kHz internal clock. + /// + /// - **Total monotonic period:** ~571 million years + /// - **Time precision:** ~977 μs + /// + /// See the [`rtc`](crate::rtc) module for more details. + #[macro_export] + macro_rules! rtc_mode1_monotonic_1k_int { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode1, + $crate::rtc::rtic::v2::CLOCK_FREQ_1K, + Int1k + ); + }; + } + + /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt + /// for it with a 1.024 kHz external clock. + /// + /// - **Total monotonic period:** ~571 million years + /// - **Time precision:** ~977 μs + /// + /// See the [`rtc`](crate::rtc) module for more details. + #[macro_export] + macro_rules! rtc_mode1_monotonic_1k_ext { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode1, + $crate::rtc::rtic::v2::CLOCK_FREQ_1K, + Ext1k + ); + }; + } + + /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt + /// for it with a 32.768 kHz internal clock. + /// + /// - **Total monotonic period:** ~17.8 million years + /// - **Time precision:** ~31 μs + /// + /// See the [`rtc`](crate::rtc) module for more details. + #[macro_export] + macro_rules! rtc_mode1_monotonic_32k_int { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode1, + $crate::rtc::rtic::v2::CLOCK_FREQ_32K, + Int32k + ); + }; + } + + /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt + /// for it with a 32.768 kHz external clock. + /// + /// - **Total monotonic period:** ~17.8 million years + /// - **Time precision:** ~31 μs + /// + /// See the [`rtc`](crate::rtc) module for more details. + #[macro_export] + macro_rules! rtc_mode1_monotonic_32k_ext { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode1, + $crate::rtc::rtic::v2::CLOCK_FREQ_32K, + Ext32k + ); + }; + } + + // TODO: Suppress doc probably + // TODO: Move register write here + // TODO: Can we use type programming enum instead? + pub enum ClockSource { + Int1k, + Ext1k, + Int32k, + Ext32k, + } + + pub mod mode0 { + use super::*; + + /// RTC based [`TimerQueueBackend`]. + pub struct RtcBackend; + + static RTC_TQ: TimerQueue = TimerQueue::new(); + + #[hal_macro_helper] + impl RtcBackend { + #[inline] + fn sync() { + let rtc = unsafe { &pac::Rtc::steal() }; + + #[hal_cfg("rtc-d5x")] + while rtc.mode0().syncbusy().read().bits() != 0 {} + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + while rtc.mode0().status().read().syncbusy().bit_is_set() {} + } + + pub fn raw_count() -> u32 { + let rtc = unsafe { &pac::Rtc::steal() }; + + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + { + rtc.mode0().readreq().modify(|_, w| w.rcont().set_bit()); + Self::sync(); + } + // NOTE: Sync is automatic on SAMD5x chips. + rtc.mode0().count().read().bits() + } + + /// Starts the clock. + /// + /// **Do not use this function directly.** + /// + /// TODO: Update + /// Use the [`rtc_monotonic`] macro instead. + pub fn _start( + rtc: pac::Rtc, + mclk: &mut pac::Mclk, + osc32kctrl: &mut pac::Osc32kctrl, + clock_source: ClockSource, + ) { + // Enables the APBA clock for the RTC. + mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); + + // It is necessary to disable the RTC before resetting it. + // NOTE: This register and field are the same in all modes. + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); + + // Initialize the timer queue + RTC_TQ.initialize(Self {}); + + // Reset RTC back to initial settings, which disables it and enters mode 0. + rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); + // Wait for the reset to complete + while rtc.mode0().ctrla().read().swrst().bit_is_set() {} + + // Set the RTC clock source. + match clock_source { + ClockSource::Int1k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().ulp1k()), + ClockSource::Ext1k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().xosc1k()), + ClockSource::Int32k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().ulp32k()), + ClockSource::Ext32k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().xosc32k()), + } + + // Set the the initial compare + unsafe { + rtc.mode0().comp(0).write(|w| w.comp().bits(0)); + } + Self::sync(); + + // Timing critical, make sure we don't get interrupted. + critical_section::with(|_| { + // Start the RTC counter. + rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); + + // Enable counter sync on SAMx5x, the counter cannot be read otherwise. + #[hal_cfg("rtc-d5x")] + { + rtc.mode0().ctrla().modify(|_, w| w.countsync().set_bit()); + + // Errata: The first read of the count is incorrect so we need to read it + // then wait for it to change. + let count = Self::now(); + while Self::now() == count {} + + // Clear the triggered compare flag + Self::clear_compare_flag(); + + // Clear the compare flag and enable the interrupt + rtc.mode0().intenset().write(|w| w.cmp0().set_bit()); + } + + // Enable the RTC interrupt at the highest priority in the NVIC. + unsafe { + set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); + pac::NVIC::unmask(pac::Interrupt::RTC); + } + }); + } + } + + impl TimerQueueBackend for RtcBackend { + type Ticks = u32; + + #[hal_macro_helper] + fn now() -> Self::Ticks { + Self::raw_count() + } + + fn enable_timer() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); + } + + fn disable_timer() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); + } + + fn on_interrupt() { + let rtc = unsafe { pac::Rtc::steal() }; + + // For some strange reason that was unable to be determined, often the compare + // interrupt will trigger, but the count will be less than the compare value, + // even after syncing, which causes the TimerQueue to not register that the + // timeout is up. There is nothing about this in the chip errata or + // documentation. + // + // Testing showed that usually the count is only one less than + // the compare. We correct for this here by waiting until the counter reaches + // the compare value. + let compare = rtc.mode0().comp(0).read().bits(); + loop { + let counter = Self::raw_count(); + + if counter >= compare { + break; + } + } + } + + fn set_compare(mut instant: Self::Ticks) { + let rtc = unsafe { pac::Rtc::steal() }; + + // Evidently the compare interrupt will not trigger if the instant is within a + // couple of ticks, so delay it a bit if it is too close. + // This is not mentioned in the documentation or errata, but is known to be an + // issue for other microcontrollers as well (e.g. nRF family). + if instant.saturating_sub(Self::now()) < MIN_COMPARE_TICKS { + instant = instant.wrapping_add(MIN_COMPARE_TICKS) + } + + unsafe { rtc.mode0().comp(0).write(|w| w.comp().bits(instant as u32)) }; + Self::sync(); + } + + fn clear_compare_flag() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode0().intflag().modify(|_, w| w.cmp0().set_bit()); + // NOTE: Should not need to sync here + } + + fn pend_interrupt() { + pac::NVIC::pend(pac::Interrupt::RTC); + } + + fn timer_queue() -> &'static TimerQueue { + &RTC_TQ + } + } + } + + pub mod mode1 { + use core::sync::atomic::Ordering; + + use super::*; + use portable_atomic::AtomicU64; + use rtic_time::half_period_counter::calculate_now; + + struct TimerValueU16(u16); + impl rtic_time::half_period_counter::TimerValue for TimerValueU16 { + const BITS: u32 = 16; + } + impl From for u64 { + fn from(value: TimerValueU16) -> Self { + Self::from(value.0) + } + } + + /// RTC based [`TimerQueueBackend`]. + pub struct RtcBackend; + + static RTC_PERIOD_COUNT: AtomicU64 = AtomicU64::new(0); + static RTC_TQ: TimerQueue = TimerQueue::new(); + + #[hal_macro_helper] + impl RtcBackend { + #[inline] + fn sync() { + let rtc = unsafe { &pac::Rtc::steal() }; + + #[hal_cfg("rtc-d5x")] + while rtc.mode1().syncbusy().read().bits() != 0 {} + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + while rtc.mode1().status().read().syncbusy().bit_is_set() {} + } + + pub fn raw_count() -> u16 { + let rtc = unsafe { &pac::Rtc::steal() }; + + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + { + rtc.mode1().readreq().modify(|_, w| w.rcont().set_bit()); + Self::sync(); + } + // NOTE: Sync is automatic on SAMD5x chips. + + rtc.mode1().count().read().bits() + } + + /// Starts the clock. + /// + /// **Do not use this function directly.** + /// + /// TODO: Update + /// Use the [`rtc_monotonic`] macro instead. + pub fn _start( + rtc: pac::Rtc, + mclk: &mut pac::Mclk, + osc32kctrl: &mut pac::Osc32kctrl, + clock_source: ClockSource, + ) { + // Enables the APBA clock for the RTC. + mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); + + // It is necessary to disable the RTC before resetting it. + // NOTE: This register and field are the same in all modes. + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); + + // Reset RTC back to initial settings, which disables it and enters mode 0. + rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); + // Wait for the reset to complete + while rtc.mode0().ctrla().read().swrst().bit_is_set() {} + + // Set the RTC clock source. + match clock_source { + ClockSource::Int1k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().ulp1k()), + ClockSource::Ext1k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().xosc1k()), + ClockSource::Int32k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().ulp32k()), + ClockSource::Ext32k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().xosc32k()), + } + + // Set mode 1 (16 bit counter) + rtc.mode0().ctrla().modify(|_, w| w.mode().count16()); + + // Set the mode 1 period + unsafe { rtc.mode1().per().write(|w| w.bits(0xFFFF)) }; + + // Configure the compare registers + unsafe { + rtc.mode1().comp(0).write(|w| w.bits(0)); // Dynamic wakeup + rtc.mode1().comp(1).write(|w| w.bits(0x8000)); // Half-period + } + Self::sync(); + + // Timing critical, make sure we don't get interrupted. + critical_section::with(|_| { + // Start the RTC counter. + rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); + + // Enable counter sync on SAMx5x, the counter cannot be read otherwise. + #[hal_cfg("rtc-d5x")] + { + rtc.mode1().ctrla().modify(|_, w| w.countsync().set_bit()); + Self::sync(); + + // Errata: The first read of the count is incorrect so we need to read it + // then wait for it to change. + let count = Self::raw_count(); + while Self::raw_count() == count {} + } + + // Make sure period counter is synced with the timer value + RTC_PERIOD_COUNT.store(0, Ordering::SeqCst); + + // Initialize the timer queue + RTC_TQ.initialize(Self {}); + + // Clear the triggered compare flag + Self::clear_compare_flag(); + + // Enable the compare and overflow interrupts. + rtc.mode1() + .intenset() + .write(|w| w.ovf().set_bit().cmp0().set_bit().cmp1().set_bit()); + + // Enable the RTC interrupt in the NVIC and set its priority. + // SAFETY: We take full ownership of the peripheral and interrupt vector, + // plus we are not using any external shared resources so we won't impact + // basepri/source masking based critical sections. + unsafe { + set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); + pac::NVIC::unmask(pac::Interrupt::RTC); + } + }); + } + } + + impl TimerQueueBackend for RtcBackend { + type Ticks = u64; + + #[hal_macro_helper] + fn now() -> Self::Ticks { + calculate_now( + || RTC_PERIOD_COUNT.load(Ordering::Relaxed), + || TimerValueU16(Self::raw_count()), + ) + } + + fn enable_timer() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); + } + + fn disable_timer() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode1().ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); + } + + fn on_interrupt() { + let rtc: pac::Rtc = unsafe { pac::Rtc::steal() }; + let intflag = rtc.mode1().intflag().read(); + + if intflag.ovf().bit_is_set() { + // This was an overflow interrupt + rtc.mode1().intflag().modify(|_, w| w.ovf().set_bit()); + let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); + assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!"); + } + if intflag.cmp1().bit_is_set() { + // This was half-period interrupt + rtc.mode1().intflag().modify(|_, w| w.cmp1().set_bit()); + let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); + assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!"); + } + + // For some strange reason that was unable to be determined, often the + // interrupt will trigger, but the count will be less the value that is + // supposed to trigger the interrupt, even after syncing. This causes + // now() to return an incorrect time, which causes the TimerQueue lots + // of problems. + // + // Testing showed that usually the count is only one less than the + // expected value. We correct for this here by waiting until the counter + // reaches the compare value. + let expected_compare = if intflag.ovf().bit_is_set() { + 0 + } else if intflag.cmp1().bit_is_set() { + rtc.mode1().comp(1).read().bits() + } else { + // The cmp0 flag has already been cleared by this point, but if it + // is neither of the others, than the interrupt must have been triggered + // by a cmp0 + rtc.mode1().comp(0).read().bits() + }; + + loop { + let counter = Self::raw_count(); + + if expected_compare < 0x1000 { + // Wait for the counter to roll over + if counter < 0x8000 && counter >= expected_compare { + break; + } + } else { + // Wait for the counter to catch up to the compare value + if counter >= expected_compare { + break; + } + } + } + } + + fn set_compare(mut instant: Self::Ticks) { + let rtc = unsafe { pac::Rtc::steal() }; + + const MAX: u64 = 0xFFFF; + + // Disable interrupts because this section is timing critical. + // We rely on the fact that this entire section runs within one + // RTC clock tick. (which it will do easily if it doesn't get + // interrupted) + critical_section::with(|_| { + let now = Self::now(); + // wrapping_sub deals with the u64 overflow corner case + let diff = instant.wrapping_sub(now); + let val = if diff <= MAX { + // Now we know `instant` will happen within one `MAX` time duration. + + // Evidently the compare interrupt will not trigger if the instant is within + // a couple of ticks, so delay it a bit if it is too + // close. This is not mentioned in the documentation + // or errata, but is known to be an issue for other + // microcontrollers as well (e.g. nRF family). + if diff < MIN_COMPARE_TICKS.into() { + instant = instant.wrapping_add(MIN_COMPARE_TICKS.into()) + } + + (instant & MAX) as u16 + } else { + 0 + }; + + unsafe { rtc.mode1().comp(0).write(|w| w.comp().bits(val)) }; + Self::sync(); + }); + } + + fn clear_compare_flag() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode1().intflag().write(|w| w.cmp0().set_bit()); + // NOTE: Should not need to sync here + } + + fn pend_interrupt() { + pac::NVIC::pend(pac::Interrupt::RTC); + } + + fn timer_queue() -> &'static TimerQueue { + &RTC_TQ + } + } + } + + const fn cortex_logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { + ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) + } + + unsafe fn set_monotonic_prio( + prio_bits: u8, + interrupt: impl cortex_m::interrupt::InterruptNumber, + ) { + extern "C" { + static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8; + } + + let max_prio = RTIC_ASYNC_MAX_LOGICAL_PRIO.max(1).min(1 << prio_bits); + let hw_prio = cortex_logical2hw(max_prio, prio_bits); + + // We take ownership of the entire IRQ and all settings to it, we only change + // settings for the IRQ we control. + // This will also compile-error in case the NVIC changes in size. + let mut nvic: cortex_m::peripheral::NVIC = core::mem::transmute(()); + + nvic.set_priority(interrupt, hw_prio); + } +} + +// TODO: Delete? +/* mod v2 { + use super::*; + use crate::pac; + use portable_atomic::{AtomicU64, Ordering}; + use rtic_time::{ + half_period_counter::calculate_now, + timer_queue::{TimerQueue, TimerQueueBackend}, + }; + + #[doc(hidden)] + #[macro_export] + macro_rules! __internal_create_rtc_interrupt { + () => { + #[no_mangle] + #[allow(non_snake_case)] + unsafe extern "C" fn RTC() { + use $crate::rtic_time::timer_queue::TimerQueueBackend; + $crate::rtc::rtic::RtcBackend::timer_queue().on_monotonic_interrupt(); + } + }; + } + + #[doc(hidden)] + #[macro_export] + macro_rules! __internal_create_rtc_struct { + ($name:ident, $clock_rate:literal, $clock_source:ident) => { + /// A `Monotonic` based on the RTC peripheral. + pub struct $name; + + impl $name { + /// Starts the `Monotonic`. + /// + /// This method must be called only once. + pub fn start( + rtc: $crate::pac::Rtc, + mclk: &mut $crate::pac::Mclk, + osc32kctrl: &mut $crate::pac::Osc32kctrl, + ) { + $crate::__internal_create_rtc_interrupt!(); + + $crate::rtc::rtic::RtcBackend::_start( + rtc, + mclk, + osc32kctrl, + $crate::rtc::rtic::ClockSource::$clock_source, + ); + } + } + + impl $crate::rtic_time::monotonic::TimerQueueBasedMonotonic for $name { + type Backend = $crate::rtc::rtic::RtcBackend; + type Instant = $crate::fugit::Instant< + ::Ticks, + 1, + $clock_rate, + >; + type Duration = $crate::fugit::Duration< + ::Ticks, + 1, + $clock_rate, + >; + } + + $crate::rtic_time::impl_embedded_hal_delay_fugit!($name); + $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name); + }; + } + + +} */ From 6102b9529906176e2a82ea45fc6633ab9229fb3c Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Sat, 16 Nov 2024 12:45:06 -0500 Subject: [PATCH 02/13] feat(rtic-v2-rtc-monotonics): Refactors and simplifies the monotonics and macros, now uses type-level programming to specify the RTC clock source at compile time. --- hal/src/prelude.rs | 9 +- hal/src/rtc/rtic.rs | 1354 +++++++++++++++++++++---------------------- 2 files changed, 667 insertions(+), 696 deletions(-) diff --git a/hal/src/prelude.rs b/hal/src/prelude.rs index 8b8cbc32e94..1360b965940 100644 --- a/hal/src/prelude.rs +++ b/hal/src/prelude.rs @@ -13,11 +13,4 @@ pub use crate::ehal_02::digital::v2::ToggleableOutputPin as _atsamd_hal_embedded pub use crate::ehal_02::prelude::*; #[cfg(feature = "rtic")] -pub use crate::{ - rtc_mode0_monotonic_1k_ext, rtc_mode0_monotonic_1k_int, rtc_mode0_monotonic_32k_ext, - rtc_mode0_monotonic_32k_int, rtc_mode1_monotonic_1k_ext, rtc_mode1_monotonic_1k_int, - rtc_mode1_monotonic_32k_ext, rtc_mode1_monotonic_32k_int, -}; - -#[cfg(feature = "rtic")] -pub use rtic_time::Monotonic; +pub use crate::rtc::rtic::prelude::*; diff --git a/hal/src/rtc/rtic.rs b/hal/src/rtc/rtic.rs index c65b5aba563..5174f5fd91a 100644 --- a/hal/src/rtc/rtic.rs +++ b/hal/src/rtc/rtic.rs @@ -2,10 +2,11 @@ //! Clock (RTC). //! //! TODO: Somewhere make a note about u32 implementation limited to 36 hours and -//! 24 minutes +//! 24 minutes. //! //! TODO: Mention v1 and v2. -//! TODO: Have prelude for monotonic that includes extension traits like in +//! TODO: Have prelude for monotonic that includes extension traits like in. +//! TODO: Written with the intention of no HAL dependence for move. //! `rtic-monotonics`? //! //! # Example @@ -32,13 +33,12 @@ //! } //! ``` -use super::{Count32Mode, Rtc}; - -// TODO: how to handle this? -pub use v2::*; +// TODO: Before HAL PR +// - Make sure every chip compiles +// - Complete documentation mod v1 { - use super::*; + use super::super::{Count32Mode, Rtc}; use rtic_monotonic::Monotonic; /// The RTC clock frequency in Hz. @@ -78,782 +78,760 @@ mod v1 { } } -pub mod v2 { - use crate::pac; - use atsamd_hal_macros::hal_macro_helper; - use rtic_time::timer_queue::{TimerQueue, TimerQueueBackend}; - - const MIN_COMPARE_TICKS: u32 = 5; - - // TODO: docs or suppress? Can we move to enum type programming? - /// The exact 1 kHz clock frequency in Hz - pub const CLOCK_FREQ_1K: u32 = 1_024; - /// The exact 32 kHz clock frequency in Hz - pub const CLOCK_FREQ_32K: u32 = 32_768; - - #[doc(hidden)] - #[macro_export] - macro_rules! __internal_create_rtc_interrupt { - ($mode:ident) => { - #[no_mangle] - #[allow(non_snake_case)] - unsafe extern "C" fn RTC() { - use $crate::rtic_time::timer_queue::TimerQueueBackend; - $crate::rtc::rtic::$mode::RtcBackend::timer_queue().on_monotonic_interrupt(); - } - }; - } +use crate::pac; +use atsamd_hal_macros::hal_macro_helper; +use core::sync::atomic::Ordering; +use portable_atomic::AtomicU64; +use rtic_time::{ + half_period_counter::calculate_now, + timer_queue::{TimerQueue, TimerQueueBackend}, +}; - #[doc(hidden)] - #[macro_export] - macro_rules! __internal_create_rtc_struct { - ($name:ident, $mode:ident, $clock_rate:expr, $clock_source:ident) => { - /// A `Monotonic` based on the RTC peripheral. - pub struct $name; - - impl $name { - /// Starts the `Monotonic`. - /// - /// This method must be called only once. - pub fn start( - rtc: $crate::pac::Rtc, - mclk: &mut $crate::pac::Mclk, - osc32kctrl: &mut $crate::pac::Osc32kctrl, - ) { - $crate::__internal_create_rtc_interrupt!($mode); - - $crate::rtc::rtic::$mode::RtcBackend::_start( - rtc, - mclk, - osc32kctrl, - $crate::rtc::rtic::ClockSource::$clock_source, - ); - } - } +const MIN_COMPARE_TICKS: u32 = 5; - impl $crate::rtic_time::monotonic::TimerQueueBasedMonotonic for $name { - type Backend = $crate::rtc::rtic::$mode::RtcBackend; - type Instant = $crate::fugit::Instant< - ::Ticks, - 1, - { $clock_rate }, - >; - type Duration = $crate::fugit::Duration< - ::Ticks, - 1, - { $clock_rate }, - >; - } +pub mod prelude { + pub use super::rtc_clock; + pub use crate::{rtc_mode0_monotonic, rtc_mode1_monotonic}; + pub use rtic_time::Monotonic; - $crate::rtic_time::impl_embedded_hal_delay_fugit!($name); - $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name); - }; - } + pub use fugit::{self, ExtU32, ExtU32Ceil, ExtU64, ExtU64Ceil}; +} - // TODO: Have separate macros, or have additional type arguments? - /* #[doc = r#"This is - - a multiline string"#] */ - - /* /// Create an RTC based monotonic for RTIC v2 and register the RTC - * interrupt for it with a 1.024 kHz internal clock. - * - * - **Total monotonic period:** ~48 days - * - **Time precision:** ~977 μs - * - * See the [`rtc`](crate::rtc) module for more details. */ - #[macro_export] - macro_rules! rtc_mode0_monotonic_1k_int { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode0, - $crate::rtc::rtic::v2::CLOCK_FREQ_1K, - Int1k - ); - }; - } +// Note about +pub mod rtc_clock { + use crate::pac::{generic::W, osc32kctrl::rtcctrl::RtcctrlSpec}; + use core::marker::PhantomData; - /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt - /// for it with a 1.024 kHz internal clock. - /// - /// - **Total monotonic period:** ~48 days - /// - **Time precision:** ~977 μs - /// - /// See the [`rtc`](crate::rtc) module for more details. - #[macro_export] - macro_rules! rtc_mode0_monotonic_1k_ext { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode0, - $crate::rtc::rtic::v2::CLOCK_FREQ_1K, - Ext1k - ); - }; + pub trait RtcClockRate { + const RATE: u32; } - /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt - /// for it with a 32.768 kHz internal clock. - /// - /// - **Total monotonic period:** ~36 hours - /// - **Time precision:** ~31 μs - /// - /// See the [`rtc`](crate::rtc) module for more details. - #[macro_export] - macro_rules! rtc_mode0_monotonic_32k_int { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode0, - $crate::rtc::rtic::v2::CLOCK_FREQ_32K, - Int32k - ); - }; + pub enum Clock32k {} + impl RtcClockRate for Clock32k { + const RATE: u32 = 32_768; } - /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt - /// for it with a 32.768 kHz internal clock. - /// - /// - **Total monotonic period:** ~36 hours - /// - **Time precision:** ~31 μs - /// - /// See the [`rtc`](crate::rtc) module for more details. - #[macro_export] - macro_rules! rtc_mode0_monotonic_32k_ext { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode0, - $crate::rtc::rtic::v2::CLOCK_FREQ_32K, - Ext32k - ); - }; + pub enum Clock1k {} + impl RtcClockRate for Clock1k { + const RATE: u32 = 1_024; } - /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt - /// for it with a 1.024 kHz internal clock. - /// - /// - **Total monotonic period:** ~571 million years - /// - **Time precision:** ~977 μs - /// - /// See the [`rtc`](crate::rtc) module for more details. - #[macro_export] - macro_rules! rtc_mode1_monotonic_1k_int { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode1, - $crate::rtc::rtic::v2::CLOCK_FREQ_1K, - Int1k - ); - }; - } + pub trait RtcClockSource {} - /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt - /// for it with a 1.024 kHz external clock. - /// - /// - **Total monotonic period:** ~571 million years - /// - **Time precision:** ~977 μs - /// - /// See the [`rtc`](crate::rtc) module for more details. - #[macro_export] - macro_rules! rtc_mode1_monotonic_1k_ext { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode1, - $crate::rtc::rtic::v2::CLOCK_FREQ_1K, - Ext1k - ); - }; - } + pub enum ClockInternal {} + impl RtcClockSource for ClockInternal {} - /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt - /// for it with a 32.768 kHz internal clock. - /// - /// - **Total monotonic period:** ~17.8 million years - /// - **Time precision:** ~31 μs - /// - /// See the [`rtc`](crate::rtc) module for more details. - #[macro_export] - macro_rules! rtc_mode1_monotonic_32k_int { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode1, - $crate::rtc::rtic::v2::CLOCK_FREQ_32K, - Int32k - ); - }; - } + pub enum ClockExternal {} + impl RtcClockSource for ClockExternal {} - /// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt - /// for it with a 32.768 kHz external clock. - /// - /// - **Total monotonic period:** ~17.8 million years - /// - **Time precision:** ~31 μs - /// - /// See the [`rtc`](crate::rtc) module for more details. - #[macro_export] - macro_rules! rtc_mode1_monotonic_32k_ext { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode1, - $crate::rtc::rtic::v2::CLOCK_FREQ_32K, - Ext32k - ); - }; + pub trait RtcClockSetter { + fn set_source(reg: &mut W) -> &mut W; } - // TODO: Suppress doc probably - // TODO: Move register write here - // TODO: Can we use type programming enum instead? - pub enum ClockSource { - Int1k, - Ext1k, - Int32k, - Ext32k, + pub struct ClockSetter { + rate: PhantomData, + source: PhantomData, + } + impl RtcClockSetter for ClockSetter { + #[inline] + fn set_source(reg: &mut W) -> &mut W { + reg.rtcsel().ulp1k() + } + } + impl RtcClockSetter for ClockSetter { + #[inline] + fn set_source(reg: &mut W) -> &mut W { + reg.rtcsel().xosc1k() + } } - pub mod mode0 { - use super::*; + impl RtcClockSetter for ClockSetter { + #[inline] + fn set_source(reg: &mut W) -> &mut W { + reg.rtcsel().ulp32k() + } + } + impl RtcClockSetter for ClockSetter { + #[inline] + fn set_source(reg: &mut W) -> &mut W { + reg.rtcsel().xosc32k() + } + } +} - /// RTC based [`TimerQueueBackend`]. - pub struct RtcBackend; +#[doc(hidden)] +#[macro_export] +macro_rules! __internal_create_rtc_interrupt { + ($backend:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + unsafe extern "C" fn RTC() { + use $crate::rtic_time::timer_queue::TimerQueueBackend; + $crate::rtc::rtic::$backend::timer_queue().on_monotonic_interrupt(); + } + }; +} - static RTC_TQ: TimerQueue = TimerQueue::new(); +#[doc(hidden)] +#[macro_export] +macro_rules! __internal_create_rtc_struct { + ($name:ident, $backend:ident, $clock_rate:ty, $clock_source:ty) => { + /// A `Monotonic` based on the RTC peripheral. + pub struct $name; - #[hal_macro_helper] - impl RtcBackend { - #[inline] - fn sync() { - let rtc = unsafe { &pac::Rtc::steal() }; + impl $name { + /// Starts the `Monotonic`. + /// + /// This method must be called only once. + pub fn start( + rtc: $crate::pac::Rtc, + mclk: &mut $crate::pac::Mclk, + osc32kctrl: &mut $crate::pac::Osc32kctrl, + ) { + use $crate::rtc::rtic::rtc_clock::*; + $crate::__internal_create_rtc_interrupt!($backend); - #[hal_cfg("rtc-d5x")] - while rtc.mode0().syncbusy().read().bits() != 0 {} - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - while rtc.mode0().status().read().syncbusy().bit_is_set() {} + $crate::rtc::rtic::$backend::_start::>( + rtc, mclk, osc32kctrl, + ); } + } - pub fn raw_count() -> u32 { - let rtc = unsafe { &pac::Rtc::steal() }; + use $crate::rtc::rtic::rtc_clock::RtcClockRate; + + impl $crate::rtic_time::monotonic::TimerQueueBasedMonotonic for $name { + type Backend = $crate::rtc::rtic::$backend; + type Instant = $crate::fugit::Instant< + ::Ticks, + 1, + { <$clock_rate>::RATE }, + >; + type Duration = $crate::fugit::Duration< + ::Ticks, + 1, + { <$clock_rate>::RATE }, + >; + } - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - { - rtc.mode0().readreq().modify(|_, w| w.rcont().set_bit()); - Self::sync(); - } - // NOTE: Sync is automatic on SAMD5x chips. - rtc.mode0().count().read().bits() - } + $crate::rtic_time::impl_embedded_hal_delay_fugit!($name); + $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name); + }; +} - /// Starts the clock. - /// - /// **Do not use this function directly.** - /// - /// TODO: Update - /// Use the [`rtc_monotonic`] macro instead. - pub fn _start( - rtc: pac::Rtc, - mclk: &mut pac::Mclk, - osc32kctrl: &mut pac::Osc32kctrl, - clock_source: ClockSource, - ) { - // Enables the APBA clock for the RTC. - mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); +/// Create an RTIC v2 monotonic based on RTC in mode 0. +/// TODO: Where do we put more documentation? Maybe should be here. +/// For sure document the clock types. +#[macro_export] +macro_rules! rtc_mode0_monotonic { + ($name:ident, $clock_rate: ty, $clock_source: ty) => { + $crate::__internal_create_rtc_struct!($name, RtcMode0Backend, $clock_rate, $clock_source); + }; +} - // It is necessary to disable the RTC before resetting it. - // NOTE: This register and field are the same in all modes. - rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); - Self::sync(); +/// Create an RTIC v2 monotonic based on RTC in mode 1. +/// TODO: Where do we put more documentation? Maybe should be here. +/// For sure document the clock types. +#[macro_export] +macro_rules! rtc_mode1_monotonic { + ($name:ident, $clock_rate: ty, $clock_source: ty) => { + $crate::__internal_create_rtc_struct!($name, RtcMode1Backend, $clock_rate, $clock_source); + }; +} - // Initialize the timer queue - RTC_TQ.initialize(Self {}); +// TODO: Given how this is done now, move these numbers to some documentation +// for some other item, a table for each macro +/* /* /// Create an RTC based monotonic for RTIC v2 and register the RTC + * interrupt for it with a 1.024 kHz internal clock. + * + * - **Total monotonic period:** ~48 days + * - **Time precision:** ~977 μs + * + * See the [`rtc`](crate::rtc) module for more details. */ +#[macro_export] +macro_rules! rtc_mode0_monotonic_1k_int { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode0, + $crate::rtc::rtic::Clock1k, + Int1k + ); + }; +} - // Reset RTC back to initial settings, which disables it and enters mode 0. - rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); - // Wait for the reset to complete - while rtc.mode0().ctrla().read().swrst().bit_is_set() {} +/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt +/// for it with a 1.024 kHz internal clock. +/// +/// - **Total monotonic period:** ~48 days +/// - **Time precision:** ~977 μs +/// +/// See the [`rtc`](crate::rtc) module for more details. +#[macro_export] +macro_rules! rtc_mode0_monotonic_1k_ext { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode0, + $crate::rtc::rtic::Clock1k, + Ext1k + ); + }; +} - // Set the RTC clock source. - match clock_source { - ClockSource::Int1k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().ulp1k()), - ClockSource::Ext1k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().xosc1k()), - ClockSource::Int32k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().ulp32k()), - ClockSource::Ext32k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().xosc32k()), - } +/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt +/// for it with a 32.768 kHz internal clock. +/// +/// - **Total monotonic period:** ~36 hours +/// - **Time precision:** ~31 μs +/// +/// See the [`rtc`](crate::rtc) module for more details. +#[macro_export] +macro_rules! rtc_mode0_monotonic_32k_int { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode0, + $crate::rtc::rtic::Clock32k, + Int32k + ); + }; +} - // Set the the initial compare - unsafe { - rtc.mode0().comp(0).write(|w| w.comp().bits(0)); - } - Self::sync(); +/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt +/// for it with a 32.768 kHz internal clock. +/// +/// - **Total monotonic period:** ~36 hours +/// - **Time precision:** ~31 μs +/// +/// See the [`rtc`](crate::rtc) module for more details. +#[macro_export] +macro_rules! rtc_mode0_monotonic_32k_ext { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode0, + $crate::rtc::rtic::Clock32k, + Ext32k + ); + }; +} - // Timing critical, make sure we don't get interrupted. - critical_section::with(|_| { - // Start the RTC counter. - rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); - Self::sync(); - - // Enable counter sync on SAMx5x, the counter cannot be read otherwise. - #[hal_cfg("rtc-d5x")] - { - rtc.mode0().ctrla().modify(|_, w| w.countsync().set_bit()); - - // Errata: The first read of the count is incorrect so we need to read it - // then wait for it to change. - let count = Self::now(); - while Self::now() == count {} - - // Clear the triggered compare flag - Self::clear_compare_flag(); - - // Clear the compare flag and enable the interrupt - rtc.mode0().intenset().write(|w| w.cmp0().set_bit()); - } - - // Enable the RTC interrupt at the highest priority in the NVIC. - unsafe { - set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); - pac::NVIC::unmask(pac::Interrupt::RTC); - } - }); - } +/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt +/// for it with a 1.024 kHz internal clock. +/// +/// - **Total monotonic period:** ~571 million years +/// - **Time precision:** ~977 μs +/// +/// See the [`rtc`](crate::rtc) module for more details. +#[macro_export] +macro_rules! rtc_mode1_monotonic_1k_int { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode1, + $crate::rtc::rtic::Clock1k, + Int1k + ); + }; +} + +/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt +/// for it with a 1.024 kHz external clock. +/// +/// - **Total monotonic period:** ~571 million years +/// - **Time precision:** ~977 μs +/// +/// See the [`rtc`](crate::rtc) module for more details. +#[macro_export] +macro_rules! rtc_mode1_monotonic_1k_ext { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode1, + $crate::rtc::rtic::Clock1k, + Ext1k + ); + }; +} + +/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt +/// for it with a 32.768 kHz internal clock. +/// +/// - **Total monotonic period:** ~17.8 million years +/// - **Time precision:** ~31 μs +/// +/// See the [`rtc`](crate::rtc) module for more details. +#[macro_export] +macro_rules! rtc_mode1_monotonic_32k_int { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode1, + $crate::rtc::rtic::Clock32k, + Int32k + ); + }; +} + +/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt +/// for it with a 32.768 kHz external clock. +/// +/// - **Total monotonic period:** ~17.8 million years +/// - **Time precision:** ~31 μs +/// +/// See the [`rtc`](crate::rtc) module for more details. +#[macro_export] +macro_rules! rtc_mode1_monotonic_32k_ext { + ($name:ident) => { + $crate::__internal_create_rtc_struct!( + $name, + mode1, + $crate::rtc::rtic::Clock32k, + Ext32k + ); + }; +} */ + +/// RTC based [`TimerQueueBackend`]. +pub struct RtcMode0Backend; + +static RTC_MODE0_TQ: TimerQueue = TimerQueue::new(); + +#[hal_macro_helper] +impl RtcMode0Backend { + #[inline] + fn sync() { + let rtc = unsafe { &pac::Rtc::steal() }; + + #[hal_cfg("rtc-d5x")] + while rtc.mode0().syncbusy().read().bits() != 0 {} + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + while rtc.mode0().status().read().syncbusy().bit_is_set() {} + } + + pub fn raw_count() -> u32 { + let rtc = unsafe { &pac::Rtc::steal() }; + + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + { + rtc.mode0().readreq().modify(|_, w| w.rcont().set_bit()); + Self::sync(); } + // NOTE: Sync is automatic on SAMD5x chips. + rtc.mode0().count().read().bits() + } - impl TimerQueueBackend for RtcBackend { - type Ticks = u32; + /// Starts the clock. + /// + /// **Do not use this function directly.** + /// + /// TODO: Update + /// Use the [`rtc_mode0_monotonic`] macro instead. + pub fn _start( + rtc: pac::Rtc, + mclk: &mut pac::Mclk, + osc32kctrl: &mut pac::Osc32kctrl, + ) { + // Enables the APBA clock for the RTC. + mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); - #[hal_macro_helper] - fn now() -> Self::Ticks { - Self::raw_count() - } + // It is necessary to disable the RTC before resetting it. + // NOTE: This register and field are the same in all modes. + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); - fn enable_timer() { - let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); - Self::sync(); - } + // Initialize the timer queue + RTC_MODE0_TQ.initialize(Self {}); - fn disable_timer() { - let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); - Self::sync(); - } + // Reset RTC back to initial settings, which disables it and enters mode 0. + rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); + // Wait for the reset to complete + while rtc.mode0().ctrla().read().swrst().bit_is_set() {} - fn on_interrupt() { - let rtc = unsafe { pac::Rtc::steal() }; - - // For some strange reason that was unable to be determined, often the compare - // interrupt will trigger, but the count will be less than the compare value, - // even after syncing, which causes the TimerQueue to not register that the - // timeout is up. There is nothing about this in the chip errata or - // documentation. - // - // Testing showed that usually the count is only one less than - // the compare. We correct for this here by waiting until the counter reaches - // the compare value. - let compare = rtc.mode0().comp(0).read().bits(); - loop { - let counter = Self::raw_count(); - - if counter >= compare { - break; - } - } - } + // Set the RTC clock source. + osc32kctrl.rtcctrl().write(S::set_source); - fn set_compare(mut instant: Self::Ticks) { - let rtc = unsafe { pac::Rtc::steal() }; + // Set the the initial compare + unsafe { + rtc.mode0().comp(0).write(|w| w.comp().bits(0)); + } + Self::sync(); - // Evidently the compare interrupt will not trigger if the instant is within a - // couple of ticks, so delay it a bit if it is too close. - // This is not mentioned in the documentation or errata, but is known to be an - // issue for other microcontrollers as well (e.g. nRF family). - if instant.saturating_sub(Self::now()) < MIN_COMPARE_TICKS { - instant = instant.wrapping_add(MIN_COMPARE_TICKS) - } + // Timing critical, make sure we don't get interrupted. + critical_section::with(|_| { + // Start the RTC counter. + rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); - unsafe { rtc.mode0().comp(0).write(|w| w.comp().bits(instant as u32)) }; - Self::sync(); - } + // Enable counter sync on SAMx5x, the counter cannot be read otherwise. + #[hal_cfg("rtc-d5x")] + { + rtc.mode0().ctrla().modify(|_, w| w.countsync().set_bit()); - fn clear_compare_flag() { - let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode0().intflag().modify(|_, w| w.cmp0().set_bit()); - // NOTE: Should not need to sync here - } + // Errata: The first read of the count is incorrect so we need to read it + // then wait for it to change. + let count = Self::now(); + while Self::now() == count {} + + // Clear the triggered compare flag + Self::clear_compare_flag(); - fn pend_interrupt() { - pac::NVIC::pend(pac::Interrupt::RTC); + // Clear the compare flag and enable the interrupt + rtc.mode0().intenset().write(|w| w.cmp0().set_bit()); } - fn timer_queue() -> &'static TimerQueue { - &RTC_TQ + // Enable the RTC interrupt in the NVIC and set its priority. + // SAFETY: We take full ownership of the peripheral and interrupt vector, + // plus we are not using any external shared resources so we won't impact + // basepri/source masking based critical sections. + unsafe { + set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); + pac::NVIC::unmask(pac::Interrupt::RTC); } - } + }); } +} + +impl TimerQueueBackend for RtcMode0Backend { + type Ticks = u32; - pub mod mode1 { - use core::sync::atomic::Ordering; + #[hal_macro_helper] + fn now() -> Self::Ticks { + Self::raw_count() + } - use super::*; - use portable_atomic::AtomicU64; - use rtic_time::half_period_counter::calculate_now; + fn enable_timer() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); + } - struct TimerValueU16(u16); - impl rtic_time::half_period_counter::TimerValue for TimerValueU16 { - const BITS: u32 = 16; - } - impl From for u64 { - fn from(value: TimerValueU16) -> Self { - Self::from(value.0) + fn disable_timer() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); + } + + fn on_interrupt() { + let rtc = unsafe { pac::Rtc::steal() }; + + // For some strange reason that was unable to be determined, often the compare + // interrupt will trigger, but the count will be less than the compare value, + // even after syncing, which causes the TimerQueue to not register that the + // timeout is up. There is nothing about this in the chip errata or + // documentation. + // + // Testing showed that usually the count is only one less than + // the compare. We correct for this here by waiting until the counter reaches + // the compare value. + let compare = rtc.mode0().comp(0).read().bits(); + loop { + let counter = Self::raw_count(); + + if counter >= compare { + break; } } + } - /// RTC based [`TimerQueueBackend`]. - pub struct RtcBackend; - - static RTC_PERIOD_COUNT: AtomicU64 = AtomicU64::new(0); - static RTC_TQ: TimerQueue = TimerQueue::new(); + fn set_compare(mut instant: Self::Ticks) { + let rtc = unsafe { pac::Rtc::steal() }; - #[hal_macro_helper] - impl RtcBackend { - #[inline] - fn sync() { - let rtc = unsafe { &pac::Rtc::steal() }; + // Evidently the compare interrupt will not trigger if the instant is within a + // couple of ticks, so delay it a bit if it is too close. + // This is not mentioned in the documentation or errata, but is known to be an + // issue for other microcontrollers as well (e.g. nRF family). + if instant.saturating_sub(Self::now()) < MIN_COMPARE_TICKS { + instant = instant.wrapping_add(MIN_COMPARE_TICKS) + } - #[hal_cfg("rtc-d5x")] - while rtc.mode1().syncbusy().read().bits() != 0 {} - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - while rtc.mode1().status().read().syncbusy().bit_is_set() {} - } + unsafe { rtc.mode0().comp(0).write(|w| w.comp().bits(instant as u32)) }; + Self::sync(); + } - pub fn raw_count() -> u16 { - let rtc = unsafe { &pac::Rtc::steal() }; + fn clear_compare_flag() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode0().intflag().modify(|_, w| w.cmp0().set_bit()); + // NOTE: Should not need to sync here + } - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - { - rtc.mode1().readreq().modify(|_, w| w.rcont().set_bit()); - Self::sync(); - } - // NOTE: Sync is automatic on SAMD5x chips. + fn pend_interrupt() { + pac::NVIC::pend(pac::Interrupt::RTC); + } - rtc.mode1().count().read().bits() - } + fn timer_queue() -> &'static TimerQueue { + &RTC_MODE0_TQ + } +} - /// Starts the clock. - /// - /// **Do not use this function directly.** - /// - /// TODO: Update - /// Use the [`rtc_monotonic`] macro instead. - pub fn _start( - rtc: pac::Rtc, - mclk: &mut pac::Mclk, - osc32kctrl: &mut pac::Osc32kctrl, - clock_source: ClockSource, - ) { - // Enables the APBA clock for the RTC. - mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); +struct TimerValueU16(u16); +impl rtic_time::half_period_counter::TimerValue for TimerValueU16 { + const BITS: u32 = 16; +} +impl From for u64 { + fn from(value: TimerValueU16) -> Self { + Self::from(value.0) + } +} - // It is necessary to disable the RTC before resetting it. - // NOTE: This register and field are the same in all modes. - rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); - Self::sync(); +/// RTC based [`TimerQueueBackend`]. +pub struct RtcMode1Backend; - // Reset RTC back to initial settings, which disables it and enters mode 0. - rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); - // Wait for the reset to complete - while rtc.mode0().ctrla().read().swrst().bit_is_set() {} - - // Set the RTC clock source. - match clock_source { - ClockSource::Int1k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().ulp1k()), - ClockSource::Ext1k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().xosc1k()), - ClockSource::Int32k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().ulp32k()), - ClockSource::Ext32k => osc32kctrl.rtcctrl().write(|w| w.rtcsel().xosc32k()), - } +static RTC_MODE1_PERIOD_COUNT: AtomicU64 = AtomicU64::new(0); +static RTC_MODE1_TQ: TimerQueue = TimerQueue::new(); - // Set mode 1 (16 bit counter) - rtc.mode0().ctrla().modify(|_, w| w.mode().count16()); +#[hal_macro_helper] +impl RtcMode1Backend { + #[inline] + fn sync() { + let rtc = unsafe { &pac::Rtc::steal() }; - // Set the mode 1 period - unsafe { rtc.mode1().per().write(|w| w.bits(0xFFFF)) }; + #[hal_cfg("rtc-d5x")] + while rtc.mode1().syncbusy().read().bits() != 0 {} + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + while rtc.mode1().status().read().syncbusy().bit_is_set() {} + } - // Configure the compare registers - unsafe { - rtc.mode1().comp(0).write(|w| w.bits(0)); // Dynamic wakeup - rtc.mode1().comp(1).write(|w| w.bits(0x8000)); // Half-period - } - Self::sync(); + pub fn raw_count() -> u16 { + let rtc = unsafe { &pac::Rtc::steal() }; - // Timing critical, make sure we don't get interrupted. - critical_section::with(|_| { - // Start the RTC counter. - rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); - Self::sync(); - - // Enable counter sync on SAMx5x, the counter cannot be read otherwise. - #[hal_cfg("rtc-d5x")] - { - rtc.mode1().ctrla().modify(|_, w| w.countsync().set_bit()); - Self::sync(); - - // Errata: The first read of the count is incorrect so we need to read it - // then wait for it to change. - let count = Self::raw_count(); - while Self::raw_count() == count {} - } - - // Make sure period counter is synced with the timer value - RTC_PERIOD_COUNT.store(0, Ordering::SeqCst); - - // Initialize the timer queue - RTC_TQ.initialize(Self {}); - - // Clear the triggered compare flag - Self::clear_compare_flag(); - - // Enable the compare and overflow interrupts. - rtc.mode1() - .intenset() - .write(|w| w.ovf().set_bit().cmp0().set_bit().cmp1().set_bit()); - - // Enable the RTC interrupt in the NVIC and set its priority. - // SAFETY: We take full ownership of the peripheral and interrupt vector, - // plus we are not using any external shared resources so we won't impact - // basepri/source masking based critical sections. - unsafe { - set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); - pac::NVIC::unmask(pac::Interrupt::RTC); - } - }); - } + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + { + rtc.mode1().readreq().modify(|_, w| w.rcont().set_bit()); + Self::sync(); } + // NOTE: Sync is automatic on SAMD5x chips. - impl TimerQueueBackend for RtcBackend { - type Ticks = u64; + rtc.mode1().count().read().bits() + } - #[hal_macro_helper] - fn now() -> Self::Ticks { - calculate_now( - || RTC_PERIOD_COUNT.load(Ordering::Relaxed), - || TimerValueU16(Self::raw_count()), - ) - } + /// Starts the clock. + /// + /// **Do not use this function directly.** + /// + /// TODO: Update + /// Use the [`rtc_mode1_monotonic`] macro instead. + pub fn _start( + rtc: pac::Rtc, + mclk: &mut pac::Mclk, + osc32kctrl: &mut pac::Osc32kctrl, + ) { + // Enables the APBA clock for the RTC. + mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); - fn enable_timer() { - let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); - Self::sync(); - } + // It is necessary to disable the RTC before resetting it. + // NOTE: This register and field are the same in all modes. + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); + + // Reset RTC back to initial settings, which disables it and enters mode 0. + rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); + // Wait for the reset to complete + while rtc.mode0().ctrla().read().swrst().bit_is_set() {} + + // Set the RTC clock source. + osc32kctrl.rtcctrl().write(S::set_source); - fn disable_timer() { - let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode1().ctrla().modify(|_, w| w.enable().clear_bit()); + // Set mode 1 (16 bit counter) + rtc.mode0().ctrla().modify(|_, w| w.mode().count16()); + + // Set the mode 1 period + unsafe { rtc.mode1().per().write(|w| w.bits(0xFFFF)) }; + + // Configure the compare registers + unsafe { + rtc.mode1().comp(0).write(|w| w.bits(0)); // Dynamic wakeup + rtc.mode1().comp(1).write(|w| w.bits(0x8000)); // Half-period + } + Self::sync(); + + // Timing critical, make sure we don't get interrupted. + critical_section::with(|_| { + // Start the RTC counter. + rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); + + // Enable counter sync on SAMx5x, the counter cannot be read otherwise. + #[hal_cfg("rtc-d5x")] + { + rtc.mode1().ctrla().modify(|_, w| w.countsync().set_bit()); Self::sync(); + + // Errata: The first read of the count is incorrect so we need to read it + // then wait for it to change. + let count = Self::raw_count(); + while Self::raw_count() == count {} } - fn on_interrupt() { - let rtc: pac::Rtc = unsafe { pac::Rtc::steal() }; - let intflag = rtc.mode1().intflag().read(); + // Make sure period counter is synced with the timer value + RTC_MODE1_PERIOD_COUNT.store(0, Ordering::SeqCst); - if intflag.ovf().bit_is_set() { - // This was an overflow interrupt - rtc.mode1().intflag().modify(|_, w| w.ovf().set_bit()); - let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); - assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!"); - } - if intflag.cmp1().bit_is_set() { - // This was half-period interrupt - rtc.mode1().intflag().modify(|_, w| w.cmp1().set_bit()); - let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); - assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!"); - } + // Initialize the timer queue + RTC_MODE1_TQ.initialize(Self {}); - // For some strange reason that was unable to be determined, often the - // interrupt will trigger, but the count will be less the value that is - // supposed to trigger the interrupt, even after syncing. This causes - // now() to return an incorrect time, which causes the TimerQueue lots - // of problems. - // - // Testing showed that usually the count is only one less than the - // expected value. We correct for this here by waiting until the counter - // reaches the compare value. - let expected_compare = if intflag.ovf().bit_is_set() { - 0 - } else if intflag.cmp1().bit_is_set() { - rtc.mode1().comp(1).read().bits() - } else { - // The cmp0 flag has already been cleared by this point, but if it - // is neither of the others, than the interrupt must have been triggered - // by a cmp0 - rtc.mode1().comp(0).read().bits() - }; - - loop { - let counter = Self::raw_count(); - - if expected_compare < 0x1000 { - // Wait for the counter to roll over - if counter < 0x8000 && counter >= expected_compare { - break; - } - } else { - // Wait for the counter to catch up to the compare value - if counter >= expected_compare { - break; - } - } - } - } + // Clear the triggered compare flag + Self::clear_compare_flag(); - fn set_compare(mut instant: Self::Ticks) { - let rtc = unsafe { pac::Rtc::steal() }; - - const MAX: u64 = 0xFFFF; - - // Disable interrupts because this section is timing critical. - // We rely on the fact that this entire section runs within one - // RTC clock tick. (which it will do easily if it doesn't get - // interrupted) - critical_section::with(|_| { - let now = Self::now(); - // wrapping_sub deals with the u64 overflow corner case - let diff = instant.wrapping_sub(now); - let val = if diff <= MAX { - // Now we know `instant` will happen within one `MAX` time duration. - - // Evidently the compare interrupt will not trigger if the instant is within - // a couple of ticks, so delay it a bit if it is too - // close. This is not mentioned in the documentation - // or errata, but is known to be an issue for other - // microcontrollers as well (e.g. nRF family). - if diff < MIN_COMPARE_TICKS.into() { - instant = instant.wrapping_add(MIN_COMPARE_TICKS.into()) - } - - (instant & MAX) as u16 - } else { - 0 - }; - - unsafe { rtc.mode1().comp(0).write(|w| w.comp().bits(val)) }; - Self::sync(); - }); - } + // Enable the compare and overflow interrupts. + rtc.mode1() + .intenset() + .write(|w| w.ovf().set_bit().cmp0().set_bit().cmp1().set_bit()); - fn clear_compare_flag() { - let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode1().intflag().write(|w| w.cmp0().set_bit()); - // NOTE: Should not need to sync here + // Enable the RTC interrupt in the NVIC and set its priority. + // SAFETY: We take full ownership of the peripheral and interrupt vector, + // plus we are not using any external shared resources so we won't impact + // basepri/source masking based critical sections. + unsafe { + set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); + pac::NVIC::unmask(pac::Interrupt::RTC); } + }); + } +} - fn pend_interrupt() { - pac::NVIC::pend(pac::Interrupt::RTC); - } +impl TimerQueueBackend for RtcMode1Backend { + type Ticks = u64; - fn timer_queue() -> &'static TimerQueue { - &RTC_TQ - } - } + #[hal_macro_helper] + fn now() -> Self::Ticks { + calculate_now( + || RTC_MODE1_PERIOD_COUNT.load(Ordering::Relaxed), + || TimerValueU16(Self::raw_count()), + ) } - const fn cortex_logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { - ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) + fn enable_timer() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); } - unsafe fn set_monotonic_prio( - prio_bits: u8, - interrupt: impl cortex_m::interrupt::InterruptNumber, - ) { - extern "C" { - static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8; - } + fn disable_timer() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode1().ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); + } - let max_prio = RTIC_ASYNC_MAX_LOGICAL_PRIO.max(1).min(1 << prio_bits); - let hw_prio = cortex_logical2hw(max_prio, prio_bits); + fn on_interrupt() { + let rtc: pac::Rtc = unsafe { pac::Rtc::steal() }; + let intflag = rtc.mode1().intflag().read(); - // We take ownership of the entire IRQ and all settings to it, we only change - // settings for the IRQ we control. - // This will also compile-error in case the NVIC changes in size. - let mut nvic: cortex_m::peripheral::NVIC = core::mem::transmute(()); + if intflag.ovf().bit_is_set() { + // This was an overflow interrupt + rtc.mode1().intflag().modify(|_, w| w.ovf().set_bit()); + let prev = RTC_MODE1_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); + assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!"); + } + if intflag.cmp1().bit_is_set() { + // This was half-period interrupt + rtc.mode1().intflag().modify(|_, w| w.cmp1().set_bit()); + let prev = RTC_MODE1_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); + assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!"); + } - nvic.set_priority(interrupt, hw_prio); - } -} + // For some strange reason that was unable to be determined, often the + // interrupt will trigger, but the count will be less the value that is + // supposed to trigger the interrupt, even after syncing. This causes + // now() to return an incorrect time, which causes the TimerQueue lots + // of problems. + // + // Testing showed that usually the count is only one less than the + // expected value. We correct for this here by waiting until the counter + // reaches the compare value. + let expected_compare = if intflag.ovf().bit_is_set() { + 0 + } else if intflag.cmp1().bit_is_set() { + rtc.mode1().comp(1).read().bits() + } else { + // The cmp0 flag has already been cleared by this point, but if it + // is neither of the others, than the interrupt must have been triggered + // by a cmp0 + rtc.mode1().comp(0).read().bits() + }; -// TODO: Delete? -/* mod v2 { - use super::*; - use crate::pac; - use portable_atomic::{AtomicU64, Ordering}; - use rtic_time::{ - half_period_counter::calculate_now, - timer_queue::{TimerQueue, TimerQueueBackend}, - }; + loop { + let counter = Self::raw_count(); - #[doc(hidden)] - #[macro_export] - macro_rules! __internal_create_rtc_interrupt { - () => { - #[no_mangle] - #[allow(non_snake_case)] - unsafe extern "C" fn RTC() { - use $crate::rtic_time::timer_queue::TimerQueueBackend; - $crate::rtc::rtic::RtcBackend::timer_queue().on_monotonic_interrupt(); + if expected_compare < 0x1000 { + // Wait for the counter to roll over + if counter < 0x8000 && counter >= expected_compare { + break; + } + } else { + // Wait for the counter to catch up to the compare value + if counter >= expected_compare { + break; + } } - }; + } } - #[doc(hidden)] - #[macro_export] - macro_rules! __internal_create_rtc_struct { - ($name:ident, $clock_rate:literal, $clock_source:ident) => { - /// A `Monotonic` based on the RTC peripheral. - pub struct $name; - - impl $name { - /// Starts the `Monotonic`. - /// - /// This method must be called only once. - pub fn start( - rtc: $crate::pac::Rtc, - mclk: &mut $crate::pac::Mclk, - osc32kctrl: &mut $crate::pac::Osc32kctrl, - ) { - $crate::__internal_create_rtc_interrupt!(); - - $crate::rtc::rtic::RtcBackend::_start( - rtc, - mclk, - osc32kctrl, - $crate::rtc::rtic::ClockSource::$clock_source, - ); + fn set_compare(mut instant: Self::Ticks) { + let rtc = unsafe { pac::Rtc::steal() }; + + const MAX: u64 = 0xFFFF; + + // Disable interrupts because this section is timing critical. + // We rely on the fact that this entire section runs within one + // RTC clock tick. (which it will do easily if it doesn't get + // interrupted) + critical_section::with(|_| { + let now = Self::now(); + // wrapping_sub deals with the u64 overflow corner case + let diff = instant.wrapping_sub(now); + let val = if diff <= MAX { + // Now we know `instant` will happen within one `MAX` time duration. + + // Evidently the compare interrupt will not trigger if the instant is within + // a couple of ticks, so delay it a bit if it is too + // close. This is not mentioned in the documentation + // or errata, but is known to be an issue for other + // microcontrollers as well (e.g. nRF family). + if diff < MIN_COMPARE_TICKS.into() { + instant = instant.wrapping_add(MIN_COMPARE_TICKS.into()) } - } - impl $crate::rtic_time::monotonic::TimerQueueBasedMonotonic for $name { - type Backend = $crate::rtc::rtic::RtcBackend; - type Instant = $crate::fugit::Instant< - ::Ticks, - 1, - $clock_rate, - >; - type Duration = $crate::fugit::Duration< - ::Ticks, - 1, - $clock_rate, - >; - } + (instant & MAX) as u16 + } else { + 0 + }; - $crate::rtic_time::impl_embedded_hal_delay_fugit!($name); - $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name); - }; + unsafe { rtc.mode1().comp(0).write(|w| w.comp().bits(val)) }; + Self::sync(); + }); } + fn clear_compare_flag() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode1().intflag().write(|w| w.cmp0().set_bit()); + // NOTE: Should not need to sync here + } -} */ + fn pend_interrupt() { + pac::NVIC::pend(pac::Interrupt::RTC); + } + + fn timer_queue() -> &'static TimerQueue { + &RTC_MODE1_TQ + } +} + +const fn cortex_logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { + ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) +} + +unsafe fn set_monotonic_prio(prio_bits: u8, interrupt: impl cortex_m::interrupt::InterruptNumber) { + extern "C" { + static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8; + } + + let max_prio = RTIC_ASYNC_MAX_LOGICAL_PRIO.max(1).min(1 << prio_bits); + let hw_prio = cortex_logical2hw(max_prio, prio_bits); + + // We take ownership of the entire IRQ and all settings to it, we only change + // settings for the IRQ we control. + // This will also compile-error in case the NVIC changes in size. + let mut nvic: cortex_m::peripheral::NVIC = core::mem::transmute(()); + + nvic.set_priority(interrupt, hw_prio); +} From 1485589cc82e5dcb2eb4ab9e159f6f29e77a3874 Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Sat, 16 Nov 2024 22:44:16 -0500 Subject: [PATCH 03/13] feat(rtic-v2-rtc-monotonics): Further prepares the monotonics for publication in the HAL. * Moves the two monotonics into their own files so that the `rtc::rtic` main module file is not so large and unwieldy. * Adds thorough documentation to the `rtc::rtic` module and its items, discussing the differences between the two monotonics, and how to use the macros to create them. --- hal/Cargo.toml | 2 +- hal/src/rtc/rtic.rs | 837 -------------------------------------- hal/src/rtc/rtic/mod.rs | 331 +++++++++++++++ hal/src/rtc/rtic/mode0.rs | 176 ++++++++ hal/src/rtc/rtic/mode1.rs | 261 ++++++++++++ 5 files changed, 769 insertions(+), 838 deletions(-) delete mode 100644 hal/src/rtc/rtic.rs create mode 100644 hal/src/rtc/rtic/mod.rs create mode 100644 hal/src/rtc/rtic/mode0.rs create mode 100644 hal/src/rtc/rtic/mode1.rs diff --git a/hal/Cargo.toml b/hal/Cargo.toml index 6cd220649f0..65f42eb0210 100644 --- a/hal/Cargo.toml +++ b/hal/Cargo.toml @@ -23,7 +23,7 @@ rust-version = "1.77.2" version = "0.20.2" [package.metadata.docs.rs] -features = ["samd21g", "samd21g-rt", "usb", "dma", "async"] +features = ["samd21g", "samd21g-rt", "usb", "dma", "async", "rtic"] #=============================================================================== # Required depdendencies diff --git a/hal/src/rtc/rtic.rs b/hal/src/rtc/rtic.rs deleted file mode 100644 index 5174f5fd91a..00000000000 --- a/hal/src/rtc/rtic.rs +++ /dev/null @@ -1,837 +0,0 @@ -//! [`Monotonic`](rtic_time::Monotonic) implementations for the Real Time -//! Clock (RTC). -//! -//! TODO: Somewhere make a note about u32 implementation limited to 36 hours and -//! 24 minutes. -//! -//! TODO: Mention v1 and v2. -//! TODO: Have prelude for monotonic that includes extension traits like in. -//! TODO: Written with the intention of no HAL dependence for move. -//! `rtic-monotonics`? -//! -//! # Example -//! -//! ``` -//! // TODO: Update this -//! use atsamd_hal::rtc::rtic::prelude::*; -//! rtc_monotonic!(Mono); -//! -//! fn init() { -//! # // This is normally provided by the selected PAC -//! # let rtc = unsafe { core::mem::transmute(()) }; -//! # let mut mclk = unsafe { core::mem::transmute(()) }; -//! // Start the monotonic -//! Mono::start(rtc, &mut mclk); -//! } -//! -//! async fn usage() { -//! loop { -//! // Use the monotonic -//! let timestamp = Mono::now(); -//! Mono::delay(100.millis()).await; -//! } -//! } -//! ``` - -// TODO: Before HAL PR -// - Make sure every chip compiles -// - Complete documentation - -mod v1 { - use super::super::{Count32Mode, Rtc}; - use rtic_monotonic::Monotonic; - - /// The RTC clock frequency in Hz. - pub const CLOCK_FREQ: u32 = 32_768; - - type Instant = fugit::Instant; - type Duration = fugit::Duration; - - impl Monotonic for Rtc { - type Instant = Instant; - type Duration = Duration; - unsafe fn reset(&mut self) { - // Since reset is only called once, we use it to enable the interrupt generation - // bit. - self.mode0().intenset().write(|w| w.cmp0().set_bit()); - } - - fn now(&mut self) -> Self::Instant { - Self::Instant::from_ticks(self.count32()) - } - - fn zero() -> Self::Instant { - Self::Instant::from_ticks(0) - } - - fn set_compare(&mut self, instant: Self::Instant) { - unsafe { - self.mode0() - .comp(0) - .write(|w| w.comp().bits(instant.ticks())) - } - } - - fn clear_compare_flag(&mut self) { - self.mode0().intflag().write(|w| w.cmp0().set_bit()); - } - } -} - -use crate::pac; -use atsamd_hal_macros::hal_macro_helper; -use core::sync::atomic::Ordering; -use portable_atomic::AtomicU64; -use rtic_time::{ - half_period_counter::calculate_now, - timer_queue::{TimerQueue, TimerQueueBackend}, -}; - -const MIN_COMPARE_TICKS: u32 = 5; - -pub mod prelude { - pub use super::rtc_clock; - pub use crate::{rtc_mode0_monotonic, rtc_mode1_monotonic}; - pub use rtic_time::Monotonic; - - pub use fugit::{self, ExtU32, ExtU32Ceil, ExtU64, ExtU64Ceil}; -} - -// Note about -pub mod rtc_clock { - use crate::pac::{generic::W, osc32kctrl::rtcctrl::RtcctrlSpec}; - use core::marker::PhantomData; - - pub trait RtcClockRate { - const RATE: u32; - } - - pub enum Clock32k {} - impl RtcClockRate for Clock32k { - const RATE: u32 = 32_768; - } - - pub enum Clock1k {} - impl RtcClockRate for Clock1k { - const RATE: u32 = 1_024; - } - - pub trait RtcClockSource {} - - pub enum ClockInternal {} - impl RtcClockSource for ClockInternal {} - - pub enum ClockExternal {} - impl RtcClockSource for ClockExternal {} - - pub trait RtcClockSetter { - fn set_source(reg: &mut W) -> &mut W; - } - - pub struct ClockSetter { - rate: PhantomData, - source: PhantomData, - } - impl RtcClockSetter for ClockSetter { - #[inline] - fn set_source(reg: &mut W) -> &mut W { - reg.rtcsel().ulp1k() - } - } - impl RtcClockSetter for ClockSetter { - #[inline] - fn set_source(reg: &mut W) -> &mut W { - reg.rtcsel().xosc1k() - } - } - - impl RtcClockSetter for ClockSetter { - #[inline] - fn set_source(reg: &mut W) -> &mut W { - reg.rtcsel().ulp32k() - } - } - impl RtcClockSetter for ClockSetter { - #[inline] - fn set_source(reg: &mut W) -> &mut W { - reg.rtcsel().xosc32k() - } - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __internal_create_rtc_interrupt { - ($backend:ident) => { - #[no_mangle] - #[allow(non_snake_case)] - unsafe extern "C" fn RTC() { - use $crate::rtic_time::timer_queue::TimerQueueBackend; - $crate::rtc::rtic::$backend::timer_queue().on_monotonic_interrupt(); - } - }; -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __internal_create_rtc_struct { - ($name:ident, $backend:ident, $clock_rate:ty, $clock_source:ty) => { - /// A `Monotonic` based on the RTC peripheral. - pub struct $name; - - impl $name { - /// Starts the `Monotonic`. - /// - /// This method must be called only once. - pub fn start( - rtc: $crate::pac::Rtc, - mclk: &mut $crate::pac::Mclk, - osc32kctrl: &mut $crate::pac::Osc32kctrl, - ) { - use $crate::rtc::rtic::rtc_clock::*; - $crate::__internal_create_rtc_interrupt!($backend); - - $crate::rtc::rtic::$backend::_start::>( - rtc, mclk, osc32kctrl, - ); - } - } - - use $crate::rtc::rtic::rtc_clock::RtcClockRate; - - impl $crate::rtic_time::monotonic::TimerQueueBasedMonotonic for $name { - type Backend = $crate::rtc::rtic::$backend; - type Instant = $crate::fugit::Instant< - ::Ticks, - 1, - { <$clock_rate>::RATE }, - >; - type Duration = $crate::fugit::Duration< - ::Ticks, - 1, - { <$clock_rate>::RATE }, - >; - } - - $crate::rtic_time::impl_embedded_hal_delay_fugit!($name); - $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name); - }; -} - -/// Create an RTIC v2 monotonic based on RTC in mode 0. -/// TODO: Where do we put more documentation? Maybe should be here. -/// For sure document the clock types. -#[macro_export] -macro_rules! rtc_mode0_monotonic { - ($name:ident, $clock_rate: ty, $clock_source: ty) => { - $crate::__internal_create_rtc_struct!($name, RtcMode0Backend, $clock_rate, $clock_source); - }; -} - -/// Create an RTIC v2 monotonic based on RTC in mode 1. -/// TODO: Where do we put more documentation? Maybe should be here. -/// For sure document the clock types. -#[macro_export] -macro_rules! rtc_mode1_monotonic { - ($name:ident, $clock_rate: ty, $clock_source: ty) => { - $crate::__internal_create_rtc_struct!($name, RtcMode1Backend, $clock_rate, $clock_source); - }; -} - -// TODO: Given how this is done now, move these numbers to some documentation -// for some other item, a table for each macro -/* /* /// Create an RTC based monotonic for RTIC v2 and register the RTC - * interrupt for it with a 1.024 kHz internal clock. - * - * - **Total monotonic period:** ~48 days - * - **Time precision:** ~977 μs - * - * See the [`rtc`](crate::rtc) module for more details. */ -#[macro_export] -macro_rules! rtc_mode0_monotonic_1k_int { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode0, - $crate::rtc::rtic::Clock1k, - Int1k - ); - }; -} - -/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt -/// for it with a 1.024 kHz internal clock. -/// -/// - **Total monotonic period:** ~48 days -/// - **Time precision:** ~977 μs -/// -/// See the [`rtc`](crate::rtc) module for more details. -#[macro_export] -macro_rules! rtc_mode0_monotonic_1k_ext { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode0, - $crate::rtc::rtic::Clock1k, - Ext1k - ); - }; -} - -/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt -/// for it with a 32.768 kHz internal clock. -/// -/// - **Total monotonic period:** ~36 hours -/// - **Time precision:** ~31 μs -/// -/// See the [`rtc`](crate::rtc) module for more details. -#[macro_export] -macro_rules! rtc_mode0_monotonic_32k_int { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode0, - $crate::rtc::rtic::Clock32k, - Int32k - ); - }; -} - -/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt -/// for it with a 32.768 kHz internal clock. -/// -/// - **Total monotonic period:** ~36 hours -/// - **Time precision:** ~31 μs -/// -/// See the [`rtc`](crate::rtc) module for more details. -#[macro_export] -macro_rules! rtc_mode0_monotonic_32k_ext { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode0, - $crate::rtc::rtic::Clock32k, - Ext32k - ); - }; -} - -/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt -/// for it with a 1.024 kHz internal clock. -/// -/// - **Total monotonic period:** ~571 million years -/// - **Time precision:** ~977 μs -/// -/// See the [`rtc`](crate::rtc) module for more details. -#[macro_export] -macro_rules! rtc_mode1_monotonic_1k_int { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode1, - $crate::rtc::rtic::Clock1k, - Int1k - ); - }; -} - -/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt -/// for it with a 1.024 kHz external clock. -/// -/// - **Total monotonic period:** ~571 million years -/// - **Time precision:** ~977 μs -/// -/// See the [`rtc`](crate::rtc) module for more details. -#[macro_export] -macro_rules! rtc_mode1_monotonic_1k_ext { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode1, - $crate::rtc::rtic::Clock1k, - Ext1k - ); - }; -} - -/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt -/// for it with a 32.768 kHz internal clock. -/// -/// - **Total monotonic period:** ~17.8 million years -/// - **Time precision:** ~31 μs -/// -/// See the [`rtc`](crate::rtc) module for more details. -#[macro_export] -macro_rules! rtc_mode1_monotonic_32k_int { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode1, - $crate::rtc::rtic::Clock32k, - Int32k - ); - }; -} - -/// Create an RTC based monotonic for RTIC v2 and register the RTC interrupt -/// for it with a 32.768 kHz external clock. -/// -/// - **Total monotonic period:** ~17.8 million years -/// - **Time precision:** ~31 μs -/// -/// See the [`rtc`](crate::rtc) module for more details. -#[macro_export] -macro_rules! rtc_mode1_monotonic_32k_ext { - ($name:ident) => { - $crate::__internal_create_rtc_struct!( - $name, - mode1, - $crate::rtc::rtic::Clock32k, - Ext32k - ); - }; -} */ - -/// RTC based [`TimerQueueBackend`]. -pub struct RtcMode0Backend; - -static RTC_MODE0_TQ: TimerQueue = TimerQueue::new(); - -#[hal_macro_helper] -impl RtcMode0Backend { - #[inline] - fn sync() { - let rtc = unsafe { &pac::Rtc::steal() }; - - #[hal_cfg("rtc-d5x")] - while rtc.mode0().syncbusy().read().bits() != 0 {} - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - while rtc.mode0().status().read().syncbusy().bit_is_set() {} - } - - pub fn raw_count() -> u32 { - let rtc = unsafe { &pac::Rtc::steal() }; - - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - { - rtc.mode0().readreq().modify(|_, w| w.rcont().set_bit()); - Self::sync(); - } - // NOTE: Sync is automatic on SAMD5x chips. - rtc.mode0().count().read().bits() - } - - /// Starts the clock. - /// - /// **Do not use this function directly.** - /// - /// TODO: Update - /// Use the [`rtc_mode0_monotonic`] macro instead. - pub fn _start( - rtc: pac::Rtc, - mclk: &mut pac::Mclk, - osc32kctrl: &mut pac::Osc32kctrl, - ) { - // Enables the APBA clock for the RTC. - mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); - - // It is necessary to disable the RTC before resetting it. - // NOTE: This register and field are the same in all modes. - rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); - Self::sync(); - - // Initialize the timer queue - RTC_MODE0_TQ.initialize(Self {}); - - // Reset RTC back to initial settings, which disables it and enters mode 0. - rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); - // Wait for the reset to complete - while rtc.mode0().ctrla().read().swrst().bit_is_set() {} - - // Set the RTC clock source. - osc32kctrl.rtcctrl().write(S::set_source); - - // Set the the initial compare - unsafe { - rtc.mode0().comp(0).write(|w| w.comp().bits(0)); - } - Self::sync(); - - // Timing critical, make sure we don't get interrupted. - critical_section::with(|_| { - // Start the RTC counter. - rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); - Self::sync(); - - // Enable counter sync on SAMx5x, the counter cannot be read otherwise. - #[hal_cfg("rtc-d5x")] - { - rtc.mode0().ctrla().modify(|_, w| w.countsync().set_bit()); - - // Errata: The first read of the count is incorrect so we need to read it - // then wait for it to change. - let count = Self::now(); - while Self::now() == count {} - - // Clear the triggered compare flag - Self::clear_compare_flag(); - - // Clear the compare flag and enable the interrupt - rtc.mode0().intenset().write(|w| w.cmp0().set_bit()); - } - - // Enable the RTC interrupt in the NVIC and set its priority. - // SAFETY: We take full ownership of the peripheral and interrupt vector, - // plus we are not using any external shared resources so we won't impact - // basepri/source masking based critical sections. - unsafe { - set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); - pac::NVIC::unmask(pac::Interrupt::RTC); - } - }); - } -} - -impl TimerQueueBackend for RtcMode0Backend { - type Ticks = u32; - - #[hal_macro_helper] - fn now() -> Self::Ticks { - Self::raw_count() - } - - fn enable_timer() { - let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); - Self::sync(); - } - - fn disable_timer() { - let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); - Self::sync(); - } - - fn on_interrupt() { - let rtc = unsafe { pac::Rtc::steal() }; - - // For some strange reason that was unable to be determined, often the compare - // interrupt will trigger, but the count will be less than the compare value, - // even after syncing, which causes the TimerQueue to not register that the - // timeout is up. There is nothing about this in the chip errata or - // documentation. - // - // Testing showed that usually the count is only one less than - // the compare. We correct for this here by waiting until the counter reaches - // the compare value. - let compare = rtc.mode0().comp(0).read().bits(); - loop { - let counter = Self::raw_count(); - - if counter >= compare { - break; - } - } - } - - fn set_compare(mut instant: Self::Ticks) { - let rtc = unsafe { pac::Rtc::steal() }; - - // Evidently the compare interrupt will not trigger if the instant is within a - // couple of ticks, so delay it a bit if it is too close. - // This is not mentioned in the documentation or errata, but is known to be an - // issue for other microcontrollers as well (e.g. nRF family). - if instant.saturating_sub(Self::now()) < MIN_COMPARE_TICKS { - instant = instant.wrapping_add(MIN_COMPARE_TICKS) - } - - unsafe { rtc.mode0().comp(0).write(|w| w.comp().bits(instant as u32)) }; - Self::sync(); - } - - fn clear_compare_flag() { - let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode0().intflag().modify(|_, w| w.cmp0().set_bit()); - // NOTE: Should not need to sync here - } - - fn pend_interrupt() { - pac::NVIC::pend(pac::Interrupt::RTC); - } - - fn timer_queue() -> &'static TimerQueue { - &RTC_MODE0_TQ - } -} - -struct TimerValueU16(u16); -impl rtic_time::half_period_counter::TimerValue for TimerValueU16 { - const BITS: u32 = 16; -} -impl From for u64 { - fn from(value: TimerValueU16) -> Self { - Self::from(value.0) - } -} - -/// RTC based [`TimerQueueBackend`]. -pub struct RtcMode1Backend; - -static RTC_MODE1_PERIOD_COUNT: AtomicU64 = AtomicU64::new(0); -static RTC_MODE1_TQ: TimerQueue = TimerQueue::new(); - -#[hal_macro_helper] -impl RtcMode1Backend { - #[inline] - fn sync() { - let rtc = unsafe { &pac::Rtc::steal() }; - - #[hal_cfg("rtc-d5x")] - while rtc.mode1().syncbusy().read().bits() != 0 {} - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - while rtc.mode1().status().read().syncbusy().bit_is_set() {} - } - - pub fn raw_count() -> u16 { - let rtc = unsafe { &pac::Rtc::steal() }; - - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - { - rtc.mode1().readreq().modify(|_, w| w.rcont().set_bit()); - Self::sync(); - } - // NOTE: Sync is automatic on SAMD5x chips. - - rtc.mode1().count().read().bits() - } - - /// Starts the clock. - /// - /// **Do not use this function directly.** - /// - /// TODO: Update - /// Use the [`rtc_mode1_monotonic`] macro instead. - pub fn _start( - rtc: pac::Rtc, - mclk: &mut pac::Mclk, - osc32kctrl: &mut pac::Osc32kctrl, - ) { - // Enables the APBA clock for the RTC. - mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); - - // It is necessary to disable the RTC before resetting it. - // NOTE: This register and field are the same in all modes. - rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); - Self::sync(); - - // Reset RTC back to initial settings, which disables it and enters mode 0. - rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); - // Wait for the reset to complete - while rtc.mode0().ctrla().read().swrst().bit_is_set() {} - - // Set the RTC clock source. - osc32kctrl.rtcctrl().write(S::set_source); - - // Set mode 1 (16 bit counter) - rtc.mode0().ctrla().modify(|_, w| w.mode().count16()); - - // Set the mode 1 period - unsafe { rtc.mode1().per().write(|w| w.bits(0xFFFF)) }; - - // Configure the compare registers - unsafe { - rtc.mode1().comp(0).write(|w| w.bits(0)); // Dynamic wakeup - rtc.mode1().comp(1).write(|w| w.bits(0x8000)); // Half-period - } - Self::sync(); - - // Timing critical, make sure we don't get interrupted. - critical_section::with(|_| { - // Start the RTC counter. - rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); - Self::sync(); - - // Enable counter sync on SAMx5x, the counter cannot be read otherwise. - #[hal_cfg("rtc-d5x")] - { - rtc.mode1().ctrla().modify(|_, w| w.countsync().set_bit()); - Self::sync(); - - // Errata: The first read of the count is incorrect so we need to read it - // then wait for it to change. - let count = Self::raw_count(); - while Self::raw_count() == count {} - } - - // Make sure period counter is synced with the timer value - RTC_MODE1_PERIOD_COUNT.store(0, Ordering::SeqCst); - - // Initialize the timer queue - RTC_MODE1_TQ.initialize(Self {}); - - // Clear the triggered compare flag - Self::clear_compare_flag(); - - // Enable the compare and overflow interrupts. - rtc.mode1() - .intenset() - .write(|w| w.ovf().set_bit().cmp0().set_bit().cmp1().set_bit()); - - // Enable the RTC interrupt in the NVIC and set its priority. - // SAFETY: We take full ownership of the peripheral and interrupt vector, - // plus we are not using any external shared resources so we won't impact - // basepri/source masking based critical sections. - unsafe { - set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); - pac::NVIC::unmask(pac::Interrupt::RTC); - } - }); - } -} - -impl TimerQueueBackend for RtcMode1Backend { - type Ticks = u64; - - #[hal_macro_helper] - fn now() -> Self::Ticks { - calculate_now( - || RTC_MODE1_PERIOD_COUNT.load(Ordering::Relaxed), - || TimerValueU16(Self::raw_count()), - ) - } - - fn enable_timer() { - let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); - Self::sync(); - } - - fn disable_timer() { - let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode1().ctrla().modify(|_, w| w.enable().clear_bit()); - Self::sync(); - } - - fn on_interrupt() { - let rtc: pac::Rtc = unsafe { pac::Rtc::steal() }; - let intflag = rtc.mode1().intflag().read(); - - if intflag.ovf().bit_is_set() { - // This was an overflow interrupt - rtc.mode1().intflag().modify(|_, w| w.ovf().set_bit()); - let prev = RTC_MODE1_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); - assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!"); - } - if intflag.cmp1().bit_is_set() { - // This was half-period interrupt - rtc.mode1().intflag().modify(|_, w| w.cmp1().set_bit()); - let prev = RTC_MODE1_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); - assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!"); - } - - // For some strange reason that was unable to be determined, often the - // interrupt will trigger, but the count will be less the value that is - // supposed to trigger the interrupt, even after syncing. This causes - // now() to return an incorrect time, which causes the TimerQueue lots - // of problems. - // - // Testing showed that usually the count is only one less than the - // expected value. We correct for this here by waiting until the counter - // reaches the compare value. - let expected_compare = if intflag.ovf().bit_is_set() { - 0 - } else if intflag.cmp1().bit_is_set() { - rtc.mode1().comp(1).read().bits() - } else { - // The cmp0 flag has already been cleared by this point, but if it - // is neither of the others, than the interrupt must have been triggered - // by a cmp0 - rtc.mode1().comp(0).read().bits() - }; - - loop { - let counter = Self::raw_count(); - - if expected_compare < 0x1000 { - // Wait for the counter to roll over - if counter < 0x8000 && counter >= expected_compare { - break; - } - } else { - // Wait for the counter to catch up to the compare value - if counter >= expected_compare { - break; - } - } - } - } - - fn set_compare(mut instant: Self::Ticks) { - let rtc = unsafe { pac::Rtc::steal() }; - - const MAX: u64 = 0xFFFF; - - // Disable interrupts because this section is timing critical. - // We rely on the fact that this entire section runs within one - // RTC clock tick. (which it will do easily if it doesn't get - // interrupted) - critical_section::with(|_| { - let now = Self::now(); - // wrapping_sub deals with the u64 overflow corner case - let diff = instant.wrapping_sub(now); - let val = if diff <= MAX { - // Now we know `instant` will happen within one `MAX` time duration. - - // Evidently the compare interrupt will not trigger if the instant is within - // a couple of ticks, so delay it a bit if it is too - // close. This is not mentioned in the documentation - // or errata, but is known to be an issue for other - // microcontrollers as well (e.g. nRF family). - if diff < MIN_COMPARE_TICKS.into() { - instant = instant.wrapping_add(MIN_COMPARE_TICKS.into()) - } - - (instant & MAX) as u16 - } else { - 0 - }; - - unsafe { rtc.mode1().comp(0).write(|w| w.comp().bits(val)) }; - Self::sync(); - }); - } - - fn clear_compare_flag() { - let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode1().intflag().write(|w| w.cmp0().set_bit()); - // NOTE: Should not need to sync here - } - - fn pend_interrupt() { - pac::NVIC::pend(pac::Interrupt::RTC); - } - - fn timer_queue() -> &'static TimerQueue { - &RTC_MODE1_TQ - } -} - -const fn cortex_logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { - ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) -} - -unsafe fn set_monotonic_prio(prio_bits: u8, interrupt: impl cortex_m::interrupt::InterruptNumber) { - extern "C" { - static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8; - } - - let max_prio = RTIC_ASYNC_MAX_LOGICAL_PRIO.max(1).min(1 << prio_bits); - let hw_prio = cortex_logical2hw(max_prio, prio_bits); - - // We take ownership of the entire IRQ and all settings to it, we only change - // settings for the IRQ we control. - // This will also compile-error in case the NVIC changes in size. - let mut nvic: cortex_m::peripheral::NVIC = core::mem::transmute(()); - - nvic.set_priority(interrupt, hw_prio); -} diff --git a/hal/src/rtc/rtic/mod.rs b/hal/src/rtc/rtic/mod.rs new file mode 100644 index 00000000000..4c8422264d9 --- /dev/null +++ b/hal/src/rtc/rtic/mod.rs @@ -0,0 +1,331 @@ +//! [`Monotonic`](rtic_time::Monotonic) implementations for the Real Time +//! Clock (RTC). +//! +//! Enabling the `rtic` feature is required to use this module. +//! +//! For RTIC v1, the old [`rtic_monotonic::Monotonic`] trait is implemented for +//! [`Rtc`](crate::rtc::Rtc) in [`Count32Mode`](crate::rtc::Count32Mode). +//! +//! The items here provide monotonics for RTIC v2. Two monotonics are provided: +//! one that uses the RTC in mode 0, and another that uses the RTC in mode 1. +//! The mode 0 monotonic uses a 32-bit hardware counter with no [half-period +//! counting](rtic_time::half_period_counter), whereas the mode 1 monotonic uses +//! a 16-bit counter but with half-period counting. +//! +//! The mode 0 monotonic has the advantage that the only interrupts that occur +//! are for the RTIC tasks, but the disadvantage is that the lack of half-period +//! counting means that the monotonic wraps in a much shorter time than the mode +//! 1 monotonic. The mode 1 monotonic, however is less efficient in the sense +//! that additional interrupts are required for half-period counting, which can +//! be quite frequent depending on the selected clock rate. The monotonic should +//! be chosen based on the specific use case, bearing in mind that monotonics +//! are never supposed to actually roll over back to zero time (i.e. they are +//! supposed to count time _monotonically_). Doing so may result in strange +//! behavior for tasks scheduled near the time of the rollover. +//! +//! The overall monotonic period (i.e. the time before the monotonic rolls over +//! back to zero time) is as follows: +//! +//! | | 1 kHz clock | 32 kHz clock | +//! | ---------- | ------------------ | ------------------- | +//! | **Mode 0** | ~48 days | ~36 hours | +//! | **Mode 1** | ~571 million years | ~17.8 million years | +//! +//! The time precision (i.e. the RTC tick time and shortest delay time) is as +//! follows: +//! +//! | | 1 kHz clock | 32 kHz clock | +//! | ------------ | ----------- | ------------ | +//! | **Any mode** | ~977 μs | ~31 μs | +//! +//! A monotonic using the desired RTC mode should be created with the +//! appropriate [macro](crate::rtc::rtic::prelude). The RTC clock rate and +//! source must also be specified when calling the macro, using the types in +//! [`rtc_clock`]. The first macro argument is the name of the global structure +//! that will implement [`Monotonic`](rtic_time::Monotonic). The second argument +//! must be a clock rate type that implements +//! [`RtcClockRate`](rtc_clock::RtcClockRate), and the third argument must be a +//! type clock source implementing +//! [`RtcClockSource`](rtc_clock::RtcClockSource). See below for an example. +//! +//! NOTE: These monotonics currently live in the HAL for testing and refinement +//! purposes. The intention is to eventually move them to the +//! [`rtic-monotonics`](https://docs.rs/rtic-monotonics/latest/rtic_monotonics/) crate, which is a central location for peripheral-based +//! RTIC monotonics for various microcontroller families. As such, be aware that +//! this module could disappear at any time in the future. +//! +//! NOTE: Some SAMD chips will support half-period counting in mode 0, which +//! would greatly extend the overall monotonic period to that of the mode 1 +//! monotonic, while also still being very interrupt-efficient. It is planned to +//! add this to the mode 0 monotonic for chips that support it. +//! +//! NOTE: The mode 1 monotonic currently has a robustness issue that is being +//! worked on. Refer to [Issue #765](https://github.com/atsamd-rs/atsamd/issues/765) for details. +//! +//! # Example +//! +//! ``` +//! use atsamd_hal::prelude::*; +//! rtc_mode0_monotonic!(Mono, rtc_clock::Clock32k, rtc_clock::ClockInternal); +//! +//! fn init() { +//! # // This is normally provided by the selected PAC +//! # let rtc = unsafe { core::mem::transmute(()) }; +//! # let mut mclk = unsafe { core::mem::transmute(()) }; +//! # let mut osc32kctrl = unsafe { core::mem::transmute(()) }; +//! // Start the monotonic +//! Mono::start(rtc, &mut mclk, &mut osc32kctrl); +//! } +//! +//! async fn usage() { +//! loop { +//! // Use the monotonic +//! let timestamp = Mono::now(); +//! Mono::delay(100u32.millis()).await; +//! } +//! } +//! ``` + +// TODO: Before HAL PR +// - Make sure every chip at least compiles + +mod v1 { + use crate::rtc::{Count32Mode, Rtc}; + use rtic_monotonic::Monotonic; + + /// The RTC clock frequency in Hz. + pub const CLOCK_FREQ: u32 = 32_768; + + type Instant = fugit::Instant; + type Duration = fugit::Duration; + + impl Monotonic for Rtc { + type Instant = Instant; + type Duration = Duration; + unsafe fn reset(&mut self) { + // Since reset is only called once, we use it to enable the interrupt generation + // bit. + self.mode0().intenset().write(|w| w.cmp0().set_bit()); + } + + fn now(&mut self) -> Self::Instant { + Self::Instant::from_ticks(self.count32()) + } + + fn zero() -> Self::Instant { + Self::Instant::from_ticks(0) + } + + fn set_compare(&mut self, instant: Self::Instant) { + unsafe { + self.mode0() + .comp(0) + .write(|w| w.comp().bits(instant.ticks())) + } + } + + fn clear_compare_flag(&mut self) { + self.mode0().intflag().write(|w| w.cmp0().set_bit()); + } + } +} + +mod mode0; +mod mode1; + +pub use mode0::RtcBackend as RtcMode0Backend; +pub use mode1::RtcBackend as RtcMode1Backend; + +const MIN_COMPARE_TICKS: u32 = 5; + +pub mod prelude { + pub use super::rtc_clock; + pub use crate::{rtc_mode0_monotonic, rtc_mode1_monotonic}; + pub use rtic_time::Monotonic; + + pub use fugit::{self, ExtU32, ExtU32Ceil, ExtU64, ExtU64Ceil}; +} + +/// Types used to specify the RTC clock source at compile time when creating the +/// monotonics. +/// +/// These types utilize [type-level programming](crate::typelevel) +/// techniques and are passed to the [monotonic creation +/// macros](crate::rtc::rtic::prelude) +/// +/// NOTE: This could probably be accomplished using the items from +/// [`clock::v2::rtcosc`](crate::clock::v2::rtcosc), but we want to avoid +/// dependencies from other parts of the HAL since this will move to [`rtic-monotonics`](https://docs.rs/rtic-monotonics/latest/rtic_monotonics/). +pub mod rtc_clock { + use crate::pac::{generic::W, osc32kctrl::rtcctrl::RtcctrlSpec}; + use core::marker::PhantomData; + + /// Type-level enum for available RTC clock rates. + pub trait RtcClockRate { + const RATE: u32; + } + + /// Type level [`RtcClockRate`] variant for the 32.768 kHz clock rate. + pub enum Clock32k {} + impl RtcClockRate for Clock32k { + const RATE: u32 = 32_768; + } + + /// Type level [`RtcClockRate`] variant for the 1.024 kHz clock rate. + pub enum Clock1k {} + impl RtcClockRate for Clock1k { + const RATE: u32 = 1_024; + } + + /// Type-level enum for available RTC sources. + pub trait RtcClockSource {} + + /// Type level [`RtcClockSource`] variant for an internal clock source. + pub enum ClockInternal {} + impl RtcClockSource for ClockInternal {} + + /// Type level [`RtcClockSource`] variant for an external clock source. + pub enum ClockExternal {} + impl RtcClockSource for ClockExternal {} + + /// Types that can configure the RTC clock. + pub trait RtcClockSetter { + /// Sets the RTC clock source by modifying the register. + fn set_source(reg: &mut W) -> &mut W; + } + + /// Collection forming a complete RTC clock source, and that can configure + /// the clock source as an [`RtcClockSetter`]. + pub struct ClockSetter { + rate: PhantomData, + source: PhantomData, + } + impl RtcClockSetter for ClockSetter { + #[inline] + fn set_source(reg: &mut W) -> &mut W { + reg.rtcsel().ulp1k() + } + } + impl RtcClockSetter for ClockSetter { + #[inline] + fn set_source(reg: &mut W) -> &mut W { + reg.rtcsel().xosc1k() + } + } + + impl RtcClockSetter for ClockSetter { + #[inline] + fn set_source(reg: &mut W) -> &mut W { + reg.rtcsel().ulp32k() + } + } + impl RtcClockSetter for ClockSetter { + #[inline] + fn set_source(reg: &mut W) -> &mut W { + reg.rtcsel().xosc32k() + } + } +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __internal_create_rtc_interrupt { + ($backend:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + unsafe extern "C" fn RTC() { + use $crate::rtic_time::timer_queue::TimerQueueBackend; + $crate::rtc::rtic::$backend::timer_queue().on_monotonic_interrupt(); + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __internal_create_rtc_struct { + ($name:ident, $backend:ident, $clock_rate:ty, $clock_source:ty) => { + /// A `Monotonic` based on the RTC peripheral. + pub struct $name; + + impl $name { + /// Starts the `Monotonic`. + /// + /// This method must be called only once. + pub fn start( + rtc: $crate::pac::Rtc, + mclk: &mut $crate::pac::Mclk, + osc32kctrl: &mut $crate::pac::Osc32kctrl, + ) { + use $crate::rtc::rtic::rtc_clock::*; + $crate::__internal_create_rtc_interrupt!($backend); + + $crate::rtc::rtic::$backend::_start::>( + rtc, mclk, osc32kctrl, + ); + } + } + + use $crate::rtc::rtic::rtc_clock::RtcClockRate; + + impl $crate::rtic_time::monotonic::TimerQueueBasedMonotonic for $name { + type Backend = $crate::rtc::rtic::$backend; + type Instant = $crate::fugit::Instant< + ::Ticks, + 1, + { <$clock_rate>::RATE }, + >; + type Duration = $crate::fugit::Duration< + ::Ticks, + 1, + { <$clock_rate>::RATE }, + >; + } + + $crate::rtic_time::impl_embedded_hal_delay_fugit!($name); + $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name); + }; +} + +/// Create an RTIC v2 monotonic using the RTC in mode 0. +/// +/// See the [`rtic`](crate::rtc::rtic) module for details. +#[macro_export] +macro_rules! rtc_mode0_monotonic { + ($name:ident, $clock_rate: ty, $clock_source: ty) => { + $crate::__internal_create_rtc_struct!($name, RtcMode0Backend, $clock_rate, $clock_source); + }; +} + +/// Create an RTIC v2 monotonic based on RTC in mode 1. +/// +/// See the [`rtic`](crate::rtc::rtic) module for details. +#[macro_export] +macro_rules! rtc_mode1_monotonic { + ($name:ident, $clock_rate: ty, $clock_source: ty) => { + $crate::__internal_create_rtc_struct!($name, RtcMode1Backend, $clock_rate, $clock_source); + }; +} + +/// This function was copied from the private function in `rtic-monotonics`, +/// so that should be used when the monotonics move there. +const fn cortex_logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { + ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) +} + +/// This function was copied from the private function in `rtic-monotonics`, +/// so that should be used when the monotonics move there. +unsafe fn set_monotonic_prio(prio_bits: u8, interrupt: impl cortex_m::interrupt::InterruptNumber) { + extern "C" { + static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8; + } + + let max_prio = RTIC_ASYNC_MAX_LOGICAL_PRIO.max(1).min(1 << prio_bits); + let hw_prio = cortex_logical2hw(max_prio, prio_bits); + + // We take ownership of the entire IRQ and all settings to it, we only change + // settings for the IRQ we control. + // This will also compile-error in case the NVIC changes in size. + let mut nvic: cortex_m::peripheral::NVIC = core::mem::transmute(()); + + nvic.set_priority(interrupt, hw_prio); +} diff --git a/hal/src/rtc/rtic/mode0.rs b/hal/src/rtc/rtic/mode0.rs new file mode 100644 index 00000000000..4d60ac6909e --- /dev/null +++ b/hal/src/rtc/rtic/mode0.rs @@ -0,0 +1,176 @@ +use super::MIN_COMPARE_TICKS; +use crate::pac; +use atsamd_hal_macros::hal_macro_helper; +use rtic_time::timer_queue::{TimerQueue, TimerQueueBackend}; + +/// RTC mode 0 based [`TimerQueueBackend`]. +pub struct RtcBackend; + +static RTC_TQ: TimerQueue = TimerQueue::new(); + +#[hal_macro_helper] +impl RtcBackend { + #[inline] + fn sync() { + let rtc = unsafe { &pac::Rtc::steal() }; + + #[hal_cfg("rtc-d5x")] + while rtc.mode0().syncbusy().read().bits() != 0 {} + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + while rtc.mode0().status().read().syncbusy().bit_is_set() {} + } + + pub fn raw_count() -> u32 { + let rtc = unsafe { &pac::Rtc::steal() }; + + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + { + rtc.mode0().readreq().modify(|_, w| w.rcont().set_bit()); + Self::sync(); + } + // NOTE: Sync is automatic on SAMD5x chips. + rtc.mode0().count().read().bits() + } + + /// Starts the clock. + /// + /// **Do not use this function directly.** + /// + /// Use the [`rtc_mode0_monotonic`](crate::rtc_mode0_monotonic) macro + /// instead and then call `start` on the monotonic. + pub fn _start( + rtc: pac::Rtc, + mclk: &mut pac::Mclk, + osc32kctrl: &mut pac::Osc32kctrl, + ) { + // Enables the APBA clock for the RTC. + mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); + + // It is necessary to disable the RTC before resetting it. + // NOTE: This register and field are the same in all modes. + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); + + // Initialize the timer queue + RTC_TQ.initialize(Self {}); + + // Reset RTC back to initial settings, which disables it and enters mode 0. + rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); + // Wait for the reset to complete + while rtc.mode0().ctrla().read().swrst().bit_is_set() {} + + // Set the RTC clock source. + osc32kctrl.rtcctrl().write(S::set_source); + + // Set the the initial compare + unsafe { + rtc.mode0().comp(0).write(|w| w.comp().bits(0)); + } + Self::sync(); + + // Timing critical, make sure we don't get interrupted. + critical_section::with(|_| { + // Start the RTC counter. + rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); + + // Enable counter sync on SAMx5x, the counter cannot be read otherwise. + #[hal_cfg("rtc-d5x")] + { + rtc.mode0().ctrla().modify(|_, w| w.countsync().set_bit()); + + // Errata: The first read of the count is incorrect so we need to read it + // then wait for it to change. + let count = Self::now(); + while Self::now() == count {} + + // Clear the triggered compare flag + Self::clear_compare_flag(); + + // Clear the compare flag and enable the interrupt + rtc.mode0().intenset().write(|w| w.cmp0().set_bit()); + } + + // Enable the RTC interrupt in the NVIC and set its priority. + // SAFETY: We take full ownership of the peripheral and interrupt vector, + // plus we are not using any external shared resources so we won't impact + // basepri/source masking based critical sections. + unsafe { + super::set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); + pac::NVIC::unmask(pac::Interrupt::RTC); + } + }); + } +} + +impl TimerQueueBackend for RtcBackend { + type Ticks = u32; + + #[hal_macro_helper] + fn now() -> Self::Ticks { + Self::raw_count() + } + + fn enable_timer() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); + } + + fn disable_timer() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); + } + + fn on_interrupt() { + let rtc = unsafe { pac::Rtc::steal() }; + + // For some strange reason that was unable to be determined, often the compare + // interrupt will trigger, but the count will be less than the compare value, + // even after syncing, which causes the TimerQueue to not register that the + // timeout is up. There is nothing about this in the chip errata or + // documentation. + // + // Testing showed that usually the count is only one less than + // the compare. We correct for this here by waiting until the counter reaches + // the compare value. + let compare = rtc.mode0().comp(0).read().bits(); + loop { + let counter = Self::raw_count(); + + if counter >= compare { + break; + } + } + } + + fn set_compare(mut instant: Self::Ticks) { + let rtc = unsafe { pac::Rtc::steal() }; + + // Evidently the compare interrupt will not trigger if the instant is within a + // couple of ticks, so delay it a bit if it is too close. + // This is not mentioned in the documentation or errata, but is known to be an + // issue for other microcontrollers as well (e.g. nRF family). + if instant.saturating_sub(Self::now()) < MIN_COMPARE_TICKS { + instant = instant.wrapping_add(MIN_COMPARE_TICKS) + } + + unsafe { rtc.mode0().comp(0).write(|w| w.comp().bits(instant as u32)) }; + Self::sync(); + } + + fn clear_compare_flag() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode0().intflag().modify(|_, w| w.cmp0().set_bit()); + // NOTE: Should not need to sync here + } + + fn pend_interrupt() { + pac::NVIC::pend(pac::Interrupt::RTC); + } + + fn timer_queue() -> &'static TimerQueue { + &RTC_TQ + } +} diff --git a/hal/src/rtc/rtic/mode1.rs b/hal/src/rtc/rtic/mode1.rs new file mode 100644 index 00000000000..d7554b451f5 --- /dev/null +++ b/hal/src/rtc/rtic/mode1.rs @@ -0,0 +1,261 @@ +use super::MIN_COMPARE_TICKS; +use crate::pac; +use atsamd_hal_macros::hal_macro_helper; +use core::sync::atomic::Ordering; +use portable_atomic::AtomicU64; +use rtic_time::{ + half_period_counter::calculate_now, + timer_queue::{TimerQueue, TimerQueueBackend}, +}; + +struct TimerValueU16(u16); +impl rtic_time::half_period_counter::TimerValue for TimerValueU16 { + const BITS: u32 = 16; +} +impl From for u64 { + fn from(value: TimerValueU16) -> Self { + Self::from(value.0) + } +} + +/// RTC mode 1 based [`TimerQueueBackend`]. +pub struct RtcBackend; + +static RTC_PERIOD_COUNT: AtomicU64 = AtomicU64::new(0); +static RTC_TQ: TimerQueue = TimerQueue::new(); + +#[hal_macro_helper] +impl RtcBackend { + #[inline] + fn sync() { + let rtc = unsafe { &pac::Rtc::steal() }; + + #[hal_cfg("rtc-d5x")] + while rtc.mode1().syncbusy().read().bits() != 0 {} + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + while rtc.mode1().status().read().syncbusy().bit_is_set() {} + } + + pub fn raw_count() -> u16 { + let rtc = unsafe { &pac::Rtc::steal() }; + + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + { + rtc.mode1().readreq().modify(|_, w| w.rcont().set_bit()); + Self::sync(); + } + // NOTE: Sync is automatic on SAMD5x chips. + + rtc.mode1().count().read().bits() + } + + /// Starts the clock. + /// + /// **Do not use this function directly.** + /// + /// Use the [`rtc_mode1_monotonic`](crate::rtc_mode1_monotonic) macro + /// instead and then call `start` on the monotonic. + pub fn _start( + rtc: pac::Rtc, + mclk: &mut pac::Mclk, + osc32kctrl: &mut pac::Osc32kctrl, + ) { + // Enables the APBA clock for the RTC. + mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); + + // It is necessary to disable the RTC before resetting it. + // NOTE: This register and field are the same in all modes. + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); + + // Reset RTC back to initial settings, which disables it and enters mode 0. + rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); + // Wait for the reset to complete + while rtc.mode0().ctrla().read().swrst().bit_is_set() {} + + // Set the RTC clock source. + osc32kctrl.rtcctrl().write(S::set_source); + + // Set mode 1 (16 bit counter) + rtc.mode0().ctrla().modify(|_, w| w.mode().count16()); + + // Set the mode 1 period + unsafe { rtc.mode1().per().write(|w| w.bits(0xFFFF)) }; + + // Configure the compare registers + unsafe { + rtc.mode1().comp(0).write(|w| w.bits(0)); // Dynamic wakeup + rtc.mode1().comp(1).write(|w| w.bits(0x8000)); // Half-period + } + Self::sync(); + + // Timing critical, make sure we don't get interrupted. + critical_section::with(|_| { + // Start the RTC counter. + rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); + + // Enable counter sync on SAMx5x, the counter cannot be read otherwise. + #[hal_cfg("rtc-d5x")] + { + rtc.mode1().ctrla().modify(|_, w| w.countsync().set_bit()); + Self::sync(); + + // Errata: The first read of the count is incorrect so we need to read it + // then wait for it to change. + let count = Self::raw_count(); + while Self::raw_count() == count {} + } + + // Make sure period counter is synced with the timer value + RTC_PERIOD_COUNT.store(0, Ordering::SeqCst); + + // Initialize the timer queue + RTC_TQ.initialize(Self {}); + + // Clear the triggered compare flag + Self::clear_compare_flag(); + + // Enable the compare and overflow interrupts. + rtc.mode1() + .intenset() + .write(|w| w.ovf().set_bit().cmp0().set_bit().cmp1().set_bit()); + + // Enable the RTC interrupt in the NVIC and set its priority. + // SAFETY: We take full ownership of the peripheral and interrupt vector, + // plus we are not using any external shared resources so we won't impact + // basepri/source masking based critical sections. + unsafe { + super::set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); + pac::NVIC::unmask(pac::Interrupt::RTC); + } + }); + } +} + +impl TimerQueueBackend for RtcBackend { + type Ticks = u64; + + #[hal_macro_helper] + fn now() -> Self::Ticks { + calculate_now( + || RTC_PERIOD_COUNT.load(Ordering::Relaxed), + || TimerValueU16(Self::raw_count()), + ) + } + + fn enable_timer() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); + Self::sync(); + } + + fn disable_timer() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode1().ctrla().modify(|_, w| w.enable().clear_bit()); + Self::sync(); + } + + fn on_interrupt() { + let rtc: pac::Rtc = unsafe { pac::Rtc::steal() }; + let intflag = rtc.mode1().intflag().read(); + + if intflag.ovf().bit_is_set() { + // This was an overflow interrupt + rtc.mode1().intflag().modify(|_, w| w.ovf().set_bit()); + let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); + assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!"); + } + if intflag.cmp1().bit_is_set() { + // This was half-period interrupt + rtc.mode1().intflag().modify(|_, w| w.cmp1().set_bit()); + let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); + assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!"); + } + + // For some strange reason that was unable to be determined, often the + // interrupt will trigger, but the count will be less the value that is + // supposed to trigger the interrupt, even after syncing. This causes + // now() to return an incorrect time, which causes the TimerQueue lots + // of problems. + // + // Testing showed that usually the count is only one less than the + // expected value. We correct for this here by waiting until the counter + // reaches the compare value. + let expected_compare = if intflag.ovf().bit_is_set() { + 0 + } else if intflag.cmp1().bit_is_set() { + rtc.mode1().comp(1).read().bits() + } else { + // The cmp0 flag has already been cleared by this point, but if it + // is neither of the others, than the interrupt must have been triggered + // by a cmp0 + rtc.mode1().comp(0).read().bits() + }; + + loop { + let counter = Self::raw_count(); + + if expected_compare < 0x1000 { + // Wait for the counter to roll over + if counter < 0x8000 && counter >= expected_compare { + break; + } + } else { + // Wait for the counter to catch up to the compare value + if counter >= expected_compare { + break; + } + } + } + } + + fn set_compare(mut instant: Self::Ticks) { + let rtc = unsafe { pac::Rtc::steal() }; + + const MAX: u64 = 0xFFFF; + + // Disable interrupts because this section is timing critical. + // We rely on the fact that this entire section runs within one + // RTC clock tick. (which it will do easily if it doesn't get + // interrupted) + critical_section::with(|_| { + let now = Self::now(); + // wrapping_sub deals with the u64 overflow corner case + let diff = instant.wrapping_sub(now); + let val = if diff <= MAX { + // Now we know `instant` will happen within one `MAX` time duration. + + // Evidently the compare interrupt will not trigger if the instant is within + // a couple of ticks, so delay it a bit if it is too + // close. This is not mentioned in the documentation + // or errata, but is known to be an issue for other + // microcontrollers as well (e.g. nRF family). + if diff < MIN_COMPARE_TICKS.into() { + instant = instant.wrapping_add(MIN_COMPARE_TICKS.into()) + } + + (instant & MAX) as u16 + } else { + 0 + }; + + unsafe { rtc.mode1().comp(0).write(|w| w.comp().bits(val)) }; + Self::sync(); + }); + } + + fn clear_compare_flag() { + let rtc = unsafe { pac::Rtc::steal() }; + rtc.mode1().intflag().write(|w| w.cmp0().set_bit()); + // NOTE: Should not need to sync here + } + + fn pend_interrupt() { + pac::NVIC::pend(pac::Interrupt::RTC); + } + + fn timer_queue() -> &'static TimerQueue { + &RTC_TQ + } +} From ba8d0e4940ba281534fe144dbb1394bfa3d67044 Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Sun, 24 Nov 2024 23:13:35 -0500 Subject: [PATCH 04/13] feat(rtic-v2-rtc-monotonics): Further prepares the monotonics for publication. * Finally achieves ostensibly robust operation for both monotonics in which they can operate solidly for at least 12 hours under very heavy task load. * Adds in instrumentation to monitor the monotonics and panic with useful debugging messages if an abnormal condition or stall is detected. --- hal/Cargo.toml | 8 +- hal/src/rtc/rtic/mod.rs | 16 ++- hal/src/rtc/rtic/mode0.rs | 217 +++++++++++++++++++++++++--------- hal/src/rtc/rtic/mode1.rs | 241 +++++++++++++++++++++++++++----------- 4 files changed, 353 insertions(+), 129 deletions(-) diff --git a/hal/Cargo.toml b/hal/Cargo.toml index 65f42eb0210..5b99f71a3fb 100644 --- a/hal/Cargo.toml +++ b/hal/Cargo.toml @@ -48,7 +48,7 @@ nb = "1.1" num-traits = {version = "0.2.19", default-features = false} opaque-debug = "0.3.0" paste = "1.0.15" -portable-atomic = {version = "1.9.0", optional = true, default-features = false, features = ["critical-section"]} +portable-atomic = {version = "1.10.0", optional = true, features = ["critical-section"]} rand_core = "0.6" seq-macro = "0.3" typenum = "1.12.0" @@ -63,12 +63,10 @@ defmt = { version = "0.3.8", optional = true} embassy-sync = {version = "0.6.0", optional = true} embedded-hal-async = {version = "1.0.0", optional = true} embedded-io-async = {version = "0.6.1", optional = true} -critical-section = {version = "1.2.0", optional = true} embedded-sdmmc = {version = "0.3", optional = true} futures = {version = "0.3.31", default-features = false, features = ["async-await"], optional = true} jlink_rtt = {version = "0.2", optional = true} mcan-core = {version = "0.2", optional = true} -portable-atomic = {version = "1.9.0", optional = true} rtic-monotonic = {version = "1.0", optional = true} usb-device = {version = "0.3.2", optional = true} rtic-time = {version = "2.0", optional = true} @@ -189,7 +187,7 @@ defmt = ["dep:defmt"] dma = [] enable_unsafe_aes_newblock_cipher = [] max-channels = ["dma"] -rtic = ["rtic-monotonic", "rtic-time", "critical-section", "portable-atomic"] +rtic = ["rtic-monotonic", "rtic-time", "portable-atomic"] sdmmc = ["embedded-sdmmc"] usb = ["usb-device"] use_rtt = ["jlink_rtt"] @@ -211,6 +209,4 @@ async = [ # The `device` feature tells the HAL that a device has been selected from the # feature list. It exists mostly to provide better error messages. -critical-section = ["dep:critical-section"] device = [] -rtic-time = ["dep:rtic-time"] diff --git a/hal/src/rtc/rtic/mod.rs b/hal/src/rtc/rtic/mod.rs index 4c8422264d9..7bdd4957b63 100644 --- a/hal/src/rtc/rtic/mod.rs +++ b/hal/src/rtc/rtic/mod.rs @@ -234,8 +234,7 @@ macro_rules! __internal_create_rtc_interrupt { #[no_mangle] #[allow(non_snake_case)] unsafe extern "C" fn RTC() { - use $crate::rtic_time::timer_queue::TimerQueueBackend; - $crate::rtc::rtic::$backend::timer_queue().on_monotonic_interrupt(); + $crate::rtc::rtic::$backend::interrupt_handler(); } }; } @@ -329,3 +328,16 @@ unsafe fn set_monotonic_prio(prio_bits: u8, interrupt: impl cortex_m::interrupt: nvic.set_priority(interrupt, hw_prio); } + +// TODO: Test code +#[derive(Default)] +struct LoopChecker { + count: usize, +} +impl LoopChecker { + pub fn too_many(&mut self) -> bool { + self.count += 1; + + self.count > 0x800000 + } +} diff --git a/hal/src/rtc/rtic/mode0.rs b/hal/src/rtc/rtic/mode0.rs index 4d60ac6909e..f92736b1744 100644 --- a/hal/src/rtc/rtic/mode0.rs +++ b/hal/src/rtc/rtic/mode0.rs @@ -1,4 +1,4 @@ -use super::MIN_COMPARE_TICKS; +use super::{LoopChecker, MIN_COMPARE_TICKS}; use crate::pac; use atsamd_hal_macros::hal_macro_helper; use rtic_time::timer_queue::{TimerQueue, TimerQueueBackend}; @@ -8,28 +8,124 @@ pub struct RtcBackend; static RTC_TQ: TimerQueue = TimerQueue::new(); +// NOTE: TODO: Move this elsewhere so it is not duplicated? +// As explained in the datasheet, reading a read-synced register may result in +// an old value, which we try to avoid by ensuring that SYNCBUSY is clear before +// reading. A write to a write-synced register will be discarded if syncing is +// happening during the write. As such we also ensure that SYNCBUSY is clear +// before writing to a synced register. Every register access should be prefaced +// by a SYNC comment indicating the required synchronization, which indicates +// that this access was checked and accounted for. #[hal_macro_helper] impl RtcBackend { + /// RTC interrupt handler called before control passes to RTIC. + #[inline] + pub unsafe fn interrupt_handler() { + let rtc = pac::Rtc::steal(); + + /// Returns whether a < b, taking wrapping into account and assuming + /// that the difference is less than 0x80000000. + #[inline] + fn less_than_with_wrap(a: u32, b: u32) -> bool { + let d = a.wrapping_sub(b); + + d >= 0x80000000 + } + + // Ensure that the COUNT is at least the compare value + // Due to syncing delay this may not be the case initially + // Note that this has to be done here because RTIC will clear the cmp0 flag + // before `RtcBackend::on_interrupt` is called. + if rtc.mode0().intflag().read().cmp0().bit_is_set() { + let compare = rtc.mode0().comp(0).read().bits(); + while less_than_with_wrap(Self::now(), compare) {} + } + + Self::timer_queue().on_monotonic_interrupt(); + } + + // TODO: Test code + fn check_loop(checker: &mut LoopChecker, item: &str) { + let rtc = unsafe { &pac::Rtc::steal() }; + + if checker.too_many() { + Self::sync(); + panic!( + "Took too long in '{item}' with count 0x{:X} and cmp 0x{:X}. Int flags: 0x{:X}, Enabled: {:?}", + rtc.mode0().count().read().bits(), + rtc.mode0().comp(0).read().bits(), + rtc.mode0().intflag().read().bits(), + rtc.mode0().ctrla().read().enable().bit_is_set(), + ); + } + } + + /// Waits until the SYNCBUSY register is clear. #[inline] fn sync() { let rtc = unsafe { &pac::Rtc::steal() }; + let mut loop_checker = LoopChecker::default(); + + // SYNC: None #[hal_cfg("rtc-d5x")] - while rtc.mode0().syncbusy().read().bits() != 0 {} + while rtc.mode0().syncbusy().read().bits() != 0 { + Self::check_loop(&mut loop_checker, "sync"); + } + // SYNC: None #[hal_cfg(any("rtc-d11", "rtc-d21"))] - while rtc.mode0().status().read().syncbusy().bit_is_set() {} + while rtc.mode0().status().read().syncbusy().bit_is_set() { + Self::check_loop(&mut loop_checker, "sync"); + } } - pub fn raw_count() -> u32 { + // TODO: Test? + #[inline] + fn wait_for_count_change() -> u32 { let rtc = unsafe { &pac::Rtc::steal() }; - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - { - rtc.mode0().readreq().modify(|_, w| w.rcont().set_bit()); - Self::sync(); + let mut last_count = Self::now(); + + // TODO: It sounds like we need to ensure everywhere that + // a) Sync read registered are ensured not to be syncing before reading + // b) Sync write registers are also ensured not to be syncing before writing + // since this will discard the write. + + // TODO: Also the count by 4 of the COUNT may be due to synchronization delay: + // https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-13/GUID-0C52DB00-4BF6-4F41-85B5-B76529875364.html#GUID-0C52DB00-4BF6-4F41-85B5-B76529875364 + // Though according to calculations this should only be between 2 and 3 RTC + // clock periods, not 4? But could take a little over 3, hence 4. It says 2 is + // typical though. + + // TODO: If the clock is disabled then just continue. + // This can happen if a new task pends the interrupt while the queue is empty so + // that the timer is disabled, which can otherwise result in waiting + // forever. + // SYNC: Write (we just read though) + if !rtc.mode0().ctrla().read().enable().bit_is_set() { + return last_count; + } + + let mut loop_checker = LoopChecker::default(); + loop { + let count = Self::now(); + + if count != last_count { + if last_count > 0 && count != last_count.wrapping_add(4) { + // TODO: Test code + // SYNC: Write (we just read though) + let compare = rtc.mode0().comp(0).read().bits(); + + panic!("Clock anomaly at 0x{count:X} with previous step at 0x{last_count:X} with cmp 0x{compare:X}"); + } + + break count; + } + + Self::check_loop(&mut loop_checker, "wait_for_count_change"); + + last_count = count; } - // NOTE: Sync is automatic on SAMD5x chips. - rtc.mode0().count().read().bits() } /// Starts the clock. @@ -43,53 +139,71 @@ impl RtcBackend { mclk: &mut pac::Mclk, osc32kctrl: &mut pac::Osc32kctrl, ) { - // Enables the APBA clock for the RTC. - mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); - - // It is necessary to disable the RTC before resetting it. + // Disable the RTC. // NOTE: This register and field are the same in all modes. - rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + // SYNC: Write Self::sync(); + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); - // Initialize the timer queue - RTC_TQ.initialize(Self {}); + // Set the RTC clock source. + osc32kctrl.rtcctrl().write(S::set_source); + + // Enable the APBA clock for the RTC. + mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); // Reset RTC back to initial settings, which disables it and enters mode 0. + // NOTE: This register and field are the same in all modes. + // SYNC: Write + Self::sync(); rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); + // Wait for the reset to complete - while rtc.mode0().ctrla().read().swrst().bit_is_set() {} + let mut loop_checker = LoopChecker::default(); + // SYNC: Write (we just read though) + while rtc.mode0().ctrla().read().swrst().bit_is_set() { + Self::check_loop(&mut loop_checker, "reset"); + } - // Set the RTC clock source. - osc32kctrl.rtcctrl().write(S::set_source); + // TODO: Test code + // Set the prescalar + //rtc.mode0().ctrla().modify(|_, w| w.prescaler().div4()); // Set the the initial compare + // SYNC: Write + Self::sync(); unsafe { rtc.mode0().comp(0).write(|w| w.comp().bits(0)); } - Self::sync(); // Timing critical, make sure we don't get interrupted. critical_section::with(|_| { // Start the RTC counter. - rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); + // SYNC: Write Self::sync(); + rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); // Enable counter sync on SAMx5x, the counter cannot be read otherwise. #[hal_cfg("rtc-d5x")] { + // Enable counter synchronization + // SYNC: Write + Self::sync(); rtc.mode0().ctrla().modify(|_, w| w.countsync().set_bit()); // Errata: The first read of the count is incorrect so we need to read it // then wait for it to change. - let count = Self::now(); - while Self::now() == count {} + Self::wait_for_count_change(); + } - // Clear the triggered compare flag - Self::clear_compare_flag(); + // Clear the triggered compare flag + Self::clear_compare_flag(); - // Clear the compare flag and enable the interrupt - rtc.mode0().intenset().write(|w| w.cmp0().set_bit()); - } + // Enable the compare interrupt + // SYNC: None + rtc.mode0().intenset().write(|w| w.cmp0().set_bit()); + + // Initialize the timer queue + RTC_TQ.initialize(Self {}); // Enable the RTC interrupt in the NVIC and set its priority. // SAFETY: We take full ownership of the peripheral and interrupt vector, @@ -108,41 +222,35 @@ impl TimerQueueBackend for RtcBackend { #[hal_macro_helper] fn now() -> Self::Ticks { - Self::raw_count() + let rtc = unsafe { &pac::Rtc::steal() }; + + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + { + // SYNC: TODO: Need to check for these other chips + rtc.mode0().readreq().modify(|_, w| w.rcont().set_bit()); + } + + // SYNC: Read/Write + Self::sync(); + rtc.mode0().count().read().bits() } fn enable_timer() { let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); + // SYNC: Write Self::sync(); + rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); } fn disable_timer() { let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + // SYNC: Write Self::sync(); + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); } fn on_interrupt() { - let rtc = unsafe { pac::Rtc::steal() }; - - // For some strange reason that was unable to be determined, often the compare - // interrupt will trigger, but the count will be less than the compare value, - // even after syncing, which causes the TimerQueue to not register that the - // timeout is up. There is nothing about this in the chip errata or - // documentation. - // - // Testing showed that usually the count is only one less than - // the compare. We correct for this here by waiting until the counter reaches - // the compare value. - let compare = rtc.mode0().comp(0).read().bits(); - loop { - let counter = Self::raw_count(); - - if counter >= compare { - break; - } - } + // There is nothing we need to do here } fn set_compare(mut instant: Self::Ticks) { @@ -156,14 +264,17 @@ impl TimerQueueBackend for RtcBackend { instant = instant.wrapping_add(MIN_COMPARE_TICKS) } - unsafe { rtc.mode0().comp(0).write(|w| w.comp().bits(instant as u32)) }; + // Set the compare register and wait for sync + // SYNC: Write Self::sync(); + unsafe { rtc.mode0().comp(0).write(|w| w.comp().bits(instant)) }; } fn clear_compare_flag() { let rtc = unsafe { pac::Rtc::steal() }; + + // SYNC: None rtc.mode0().intflag().modify(|_, w| w.cmp0().set_bit()); - // NOTE: Should not need to sync here } fn pend_interrupt() { diff --git a/hal/src/rtc/rtic/mode1.rs b/hal/src/rtc/rtic/mode1.rs index d7554b451f5..39482d660a0 100644 --- a/hal/src/rtc/rtic/mode1.rs +++ b/hal/src/rtc/rtic/mode1.rs @@ -1,4 +1,4 @@ -use super::MIN_COMPARE_TICKS; +use super::{LoopChecker, MIN_COMPARE_TICKS}; use crate::pac; use atsamd_hal_macros::hal_macro_helper; use core::sync::atomic::Ordering; @@ -24,31 +24,129 @@ pub struct RtcBackend; static RTC_PERIOD_COUNT: AtomicU64 = AtomicU64::new(0); static RTC_TQ: TimerQueue = TimerQueue::new(); +// NOTE: TODO: Move this elsewhere so it is not duplicated? +// As explained in the datasheet, reading a read-synced register may result in +// an old value, which we try to avoid by ensuring that SYNCBUSY is clear before +// reading. A write to a write-synced register will be discarded if syncing is +// happening during the write. As such we also ensure that SYNCBUSY is clear +// before writing to a synced register. Every register access should be prefaced +// by a SYNC comment indicating the required synchronization, which indicates +// that this access was checked and accounted for. #[hal_macro_helper] impl RtcBackend { + /// RTC interrupt handler called before control passes to RTIC. + #[inline] + pub unsafe fn interrupt_handler() { + let rtc = pac::Rtc::steal(); + + /// Returns whether a < b, taking wrapping into account and assuming + /// that the difference is less than 0x8000. + #[inline] + fn less_than_with_wrap(a: u16, b: u16) -> bool { + let d = a.wrapping_sub(b); + + d >= 0x8000 + } + + // Ensure that the COUNT is at least the compare value + // Due to syncing delay this may not be the case initially + // Note that this has to be done here because RTIC will clear the cmp0 flag + // before `RtcBackend::on_interrupt` is called. + if rtc.mode1().intflag().read().cmp0().bit_is_set() { + let compare = rtc.mode1().comp(0).read().bits(); + while less_than_with_wrap(Self::raw_count(), compare) {} + } + + Self::timer_queue().on_monotonic_interrupt(); + } + + // TODO: Test code + fn check_loop(checker: &mut LoopChecker, item: &str) { + let rtc = unsafe { &pac::Rtc::steal() }; + + if checker.too_many() { + Self::sync(); + panic!( + "Took too long in '{item}' with count 0x{:X} and cmp 0x{:X}. Int flags: 0x{:X}, Enabled: {:?}", + rtc.mode1().count().read().bits(), + rtc.mode1().comp(0).read().bits(), + rtc.mode1().intflag().read().bits(), + rtc.mode1().ctrla().read().enable().bit_is_set(), + ); + } + } + #[inline] fn sync() { let rtc = unsafe { &pac::Rtc::steal() }; + let mut loop_checker = LoopChecker::default(); + + // SYNC: None #[hal_cfg("rtc-d5x")] - while rtc.mode1().syncbusy().read().bits() != 0 {} + while rtc.mode1().syncbusy().read().bits() != 0 { + Self::check_loop(&mut loop_checker, "sync"); + } + // SYNC: None #[hal_cfg(any("rtc-d11", "rtc-d21"))] - while rtc.mode1().status().read().syncbusy().bit_is_set() {} + while rtc.mode1().status().read().syncbusy().bit_is_set() { + Self::check_loop(&mut loop_checker, "sync"); + } } + // TODO: This will ultimately not need to be public pub fn raw_count() -> u16 { let rtc = unsafe { &pac::Rtc::steal() }; #[hal_cfg(any("rtc-d11", "rtc-d21"))] { + // SYNC: TODO: Need to check for these other chips rtc.mode1().readreq().modify(|_, w| w.rcont().set_bit()); Self::sync(); } - // NOTE: Sync is automatic on SAMD5x chips. + // SYNC: Read/Write + Self::sync(); rtc.mode1().count().read().bits() } + // TODO: Test? + fn wait_for_count_change() -> u16 { + let rtc = unsafe { &pac::Rtc::steal() }; + + let mut last_count = Self::raw_count(); + + // TODO: If the clock is disabled then just continue. + // This can happen if a new task pends the interrupt while the queue is empty so + // that the timer is disabled, which can otherwise result in waiting + // forever. + // SYNC: Write (we just read though) + if !rtc.mode1().ctrla().read().enable().bit_is_set() { + return last_count; + } + + let mut loop_checker = LoopChecker::default(); + loop { + let count = Self::raw_count(); + + if count != last_count { + if last_count > 0 && count != last_count.wrapping_add(4) { + // TODO: Test code + // SYNC: Write (we just read though) + let compare = rtc.mode1().comp(0).read().bits(); + + panic!("Clock anomaly at 0x{count:X} with previous step at 0x{last_count:X} with cmp 0x{compare:X}"); + } + + break count; + } + + Self::check_loop(&mut loop_checker, "wait_for_count_change"); + + last_count = count; + } + } + /// Starts the clock. /// /// **Do not use this function directly.** @@ -60,63 +158,88 @@ impl RtcBackend { mclk: &mut pac::Mclk, osc32kctrl: &mut pac::Osc32kctrl, ) { - // Enables the APBA clock for the RTC. - mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); - - // It is necessary to disable the RTC before resetting it. + // Disable the RTC. // NOTE: This register and field are the same in all modes. - rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + // SYNC: Write Self::sync(); + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + + // Set the RTC clock source. + osc32kctrl.rtcctrl().write(S::set_source); + + // Enable the APBA clock for the RTC. + mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); // Reset RTC back to initial settings, which disables it and enters mode 0. + // NOTE: This register and field are the same in all modes. + // SYNC: Write + Self::sync(); rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); - // Wait for the reset to complete - while rtc.mode0().ctrla().read().swrst().bit_is_set() {} - // Set the RTC clock source. - osc32kctrl.rtcctrl().write(S::set_source); + // Wait for the reset to complete + let mut loop_checker = LoopChecker::default(); + // SYNC: Write (we just read though) + while rtc.mode0().ctrla().read().swrst().bit_is_set() { + Self::check_loop(&mut loop_checker, "reset"); + } // Set mode 1 (16 bit counter) + // SYNC: Write + Self::sync(); rtc.mode0().ctrla().modify(|_, w| w.mode().count16()); + // TODO: Test code + // Set the prescalar + //rtc.mode1().ctrla().modify(|_, w| w.prescaler().div4()); + // Set the mode 1 period + // SYNC: Write + Self::sync(); unsafe { rtc.mode1().per().write(|w| w.bits(0xFFFF)) }; // Configure the compare registers unsafe { - rtc.mode1().comp(0).write(|w| w.bits(0)); // Dynamic wakeup - rtc.mode1().comp(1).write(|w| w.bits(0x8000)); // Half-period + // RTIC tasks waker + // SYNC: Write + Self::sync(); + rtc.mode1().comp(0).write(|w| w.bits(0)); + // Half period + // SYNC: Write + Self::sync(); + rtc.mode1().comp(1).write(|w| w.bits(0x8000)); } - Self::sync(); // Timing critical, make sure we don't get interrupted. critical_section::with(|_| { // Start the RTC counter. - rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); + // SYNC: Write Self::sync(); + rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); // Enable counter sync on SAMx5x, the counter cannot be read otherwise. #[hal_cfg("rtc-d5x")] { - rtc.mode1().ctrla().modify(|_, w| w.countsync().set_bit()); + // Enable counter synchronization + // SYNC: Write Self::sync(); + rtc.mode1().ctrla().modify(|_, w| w.countsync().set_bit()); // Errata: The first read of the count is incorrect so we need to read it // then wait for it to change. - let count = Self::raw_count(); - while Self::raw_count() == count {} + Self::wait_for_count_change(); } + // Clear the triggered compare flag + Self::clear_compare_flag(); + // Make sure period counter is synced with the timer value RTC_PERIOD_COUNT.store(0, Ordering::SeqCst); // Initialize the timer queue RTC_TQ.initialize(Self {}); - // Clear the triggered compare flag - Self::clear_compare_flag(); - // Enable the compare and overflow interrupts. + // SYNC: None rtc.mode1() .intenset() .write(|w| w.ovf().set_bit().cmp0().set_bit().cmp1().set_bit()); @@ -146,67 +269,47 @@ impl TimerQueueBackend for RtcBackend { fn enable_timer() { let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); + // SYNC: Write Self::sync(); + rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); } fn disable_timer() { let rtc = unsafe { pac::Rtc::steal() }; - rtc.mode1().ctrla().modify(|_, w| w.enable().clear_bit()); + // SYNC: Write Self::sync(); + rtc.mode1().ctrla().modify(|_, w| w.enable().clear_bit()); } fn on_interrupt() { let rtc: pac::Rtc = unsafe { pac::Rtc::steal() }; + + // Capture the interrupt flags + // SYNC: None let intflag = rtc.mode1().intflag().read(); - if intflag.ovf().bit_is_set() { - // This was an overflow interrupt - rtc.mode1().intflag().modify(|_, w| w.ovf().set_bit()); - let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); - assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!"); - } + // NOTE: The cmp0 flag is cleared when RTIC calls `clear_compare_flag`. if intflag.cmp1().bit_is_set() { // This was half-period interrupt + // SYNC: None rtc.mode1().intflag().modify(|_, w| w.cmp1().set_bit()); let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!"); - } - - // For some strange reason that was unable to be determined, often the - // interrupt will trigger, but the count will be less the value that is - // supposed to trigger the interrupt, even after syncing. This causes - // now() to return an incorrect time, which causes the TimerQueue lots - // of problems. - // - // Testing showed that usually the count is only one less than the - // expected value. We correct for this here by waiting until the counter - // reaches the compare value. - let expected_compare = if intflag.ovf().bit_is_set() { - 0 - } else if intflag.cmp1().bit_is_set() { - rtc.mode1().comp(1).read().bits() - } else { - // The cmp0 flag has already been cleared by this point, but if it - // is neither of the others, than the interrupt must have been triggered - // by a cmp0 - rtc.mode1().comp(0).read().bits() - }; - loop { - let counter = Self::raw_count(); + // Ensure that the COUNT has crossed + // Due to syncing delay this may not be the case initially + while Self::raw_count() < 0x8000 {} + } + if intflag.ovf().bit_is_set() { + // This was an overflow interrupt + // SYNC: None + rtc.mode1().intflag().modify(|_, w| w.ovf().set_bit()); + let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); + assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!"); - if expected_compare < 0x1000 { - // Wait for the counter to roll over - if counter < 0x8000 && counter >= expected_compare { - break; - } - } else { - // Wait for the counter to catch up to the compare value - if counter >= expected_compare { - break; - } - } + // Ensure that the COUNT has wrapped + // Due to syncing delay this may not be the case initially + while Self::raw_count() > 0x8000 {} } } @@ -221,7 +324,8 @@ impl TimerQueueBackend for RtcBackend { // interrupted) critical_section::with(|_| { let now = Self::now(); - // wrapping_sub deals with the u64 overflow corner case + + // Wrapping_sub deals with the u64 overflow corner case let diff = instant.wrapping_sub(now); let val = if diff <= MAX { // Now we know `instant` will happen within one `MAX` time duration. @@ -240,15 +344,16 @@ impl TimerQueueBackend for RtcBackend { 0 }; - unsafe { rtc.mode1().comp(0).write(|w| w.comp().bits(val)) }; + // SYNC: Write Self::sync(); + unsafe { rtc.mode1().comp(0).write(|w| w.comp().bits(val)) }; }); } fn clear_compare_flag() { let rtc = unsafe { pac::Rtc::steal() }; + // SYNC: None rtc.mode1().intflag().write(|w| w.cmp0().set_bit()); - // NOTE: Should not need to sync here } fn pend_interrupt() { From 8cf97caa7fe04e2f2e3769742c370b1335ee79c3 Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Wed, 4 Dec 2024 08:21:07 -0500 Subject: [PATCH 05/13] feat(rtic-v2-rtc-monotonics): Further prepares the monotonics for publication. * Major refactor to decouple the RTC mode from the monotonic backend type (basic or half-period counting). * This enables using half-period counting in mode 0 for SAMx5x chips without duplicating as much mode. * Updates and expands the documentation to account for the changes. * Leaves in the monitor instrumentation so it is captured in the new form. --- hal/src/rtc/rtic/backends.rs | 327 ++++++++++++++++++++++++++ hal/src/rtc/rtic/mod.rs | 264 ++++++++++----------- hal/src/rtc/rtic/mode0.rs | 287 ----------------------- hal/src/rtc/rtic/mode1.rs | 366 ----------------------------- hal/src/rtc/rtic/modes.rs | 442 +++++++++++++++++++++++++++++++++++ 5 files changed, 891 insertions(+), 795 deletions(-) create mode 100644 hal/src/rtc/rtic/backends.rs delete mode 100644 hal/src/rtc/rtic/mode0.rs delete mode 100644 hal/src/rtc/rtic/mode1.rs create mode 100644 hal/src/rtc/rtic/modes.rs diff --git a/hal/src/rtc/rtic/backends.rs b/hal/src/rtc/rtic/backends.rs new file mode 100644 index 00000000000..d4b7b1b8e2f --- /dev/null +++ b/hal/src/rtc/rtic/backends.rs @@ -0,0 +1,327 @@ +#[doc(hidden)] +#[macro_export] +macro_rules! __internal_backend_methods { + { + mode = $mode:ty; + rtic_int = $rtic_int:ty; + rtc_pac = $rtc_pac: ident; + init_compares = $init_compares:block + statics = $statics:block + enable_interrupts = $enable_interrupts:block + } + => { + /// RTC interrupt handler called before control passes to the + /// [`TimerQueue` + /// handler`](rtic_time::timer_queue::TimerQueue::on_monotonic_interrupt). + /// + /// # Safety + /// This should only be called from the RTC interrupt handler. + #[inline] + pub unsafe fn interrupt_handler() { + let rtc = pac::Rtc::steal(); + + /// Returns whether a < b, taking wrapping into account and assuming + /// that the difference is less than a half period. + #[inline] + fn less_than_with_wrap( + a: <$mode as RtcMode>::Count, + b: <$mode as RtcMode>::Count, + ) -> bool { + let d = a.wrapping_sub(b); + + d >= <$mode>::HALF_PERIOD + } + + // Ensure that the COUNT is at least the compare value + // Due to syncing delay this may not be the case initially + // Note that this has to be done here because RTIC will clear the cmp0 flag + // before `RtcBackend::on_interrupt` is called. + if <$mode>::check_interrupt_flag::<$rtic_int>(&rtc) { + let compare = <$mode>::get_compare(&rtc, 0); + while less_than_with_wrap(<$mode>::count(&rtc), compare) {} + } + + Self::timer_queue().on_monotonic_interrupt(); + } + + /// Starts the clock. + /// + /// **Do not use this function directly.** + /// + /// Use the crate level macros instead, then call `start` on the monotonic. + pub fn _start($rtc_pac: pac::Rtc) { + // Disable the RTC. + <$mode>::disable(&$rtc_pac); + + // Reset RTC back to initial settings, which disables it and enters mode 0. + <$mode>::reset(&$rtc_pac); + + unsafe { + // Set the RTC mode + <$mode>::set_mode(&$rtc_pac); + + $init_compares + } + + // Timing critical, make sure we don't get interrupted. + critical_section::with(|_| { + // Start the timer and initialize it + <$mode>::start_and_initialize(&$rtc_pac); + + // Clear the triggered compare flag + <$mode>::clear_interrupt_flag::<$rtic_int>(&$rtc_pac); + + // Enable the compare interrupt + <$mode>::enable_interrupt::<$rtic_int>(&$rtc_pac); + + $statics + + $enable_interrupts + + // Enable the RTC interrupt in the NVIC and set its priority. + // SAFETY: We take full ownership of the peripheral and interrupt vector, + // plus we are not using any external shared resources so we won't impact + // basepri/source masking based critical sections. + unsafe { + $crate::rtc::rtic::set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); + pac::NVIC::unmask(pac::Interrupt::RTC); + } + }); + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __internal_basic_backend { + ($name:ident, $mode:ty, $rtic_int:ty) => { + use atsamd_hal_macros::hal_macro_helper; + use rtic_time::timer_queue::{TimerQueue, TimerQueueBackend}; + use $crate::pac; + use $crate::rtc::rtic::modes::RtcMode; + + /// Basic RTC-based [`TimerQueueBackend`] without period counting. + pub struct $name; + + static RTC_TQ: TimerQueue<$name> = TimerQueue::new(); + + #[hal_macro_helper] + impl $name { + $crate::__internal_backend_methods! { + mode = $mode; + rtic_int = $rtic_int; + rtc_pac = rtc; + init_compares = { + // Set the the initial compare + <$mode>::set_compare(&rtc, 0, 0); + } + statics = { + // Initialize the timer queue + RTC_TQ.initialize(Self); + } + enable_interrupts = { + // Enable the compare interrupt + <$mode>::enable_interrupt::<$rtic_int>(&rtc); + } + } + } + + impl TimerQueueBackend for $name { + type Ticks = <$mode as RtcMode>::Count; + + #[hal_macro_helper] + fn now() -> Self::Ticks { + <$mode>::count(unsafe { &pac::Rtc::steal() }) + } + + fn enable_timer() { + <$mode>::enable(unsafe { &pac::Rtc::steal() }); + } + + fn disable_timer() { + <$mode>::disable(unsafe { &pac::Rtc::steal() }); + } + + fn on_interrupt() { + // There is nothing we need to do here + } + + fn set_compare(mut instant: Self::Ticks) { + let rtc = unsafe { pac::Rtc::steal() }; + + // Evidently the compare interrupt will not trigger if the instant is within a + // couple of ticks, so delay it a bit if it is too close. + // This is not mentioned in the documentation or errata, but is known to be an + // issue for other microcontrollers as well (e.g. nRF family). + if instant.saturating_sub(Self::now()) < <$mode>::MIN_COMPARE_TICKS { + instant = instant.wrapping_add(<$mode>::MIN_COMPARE_TICKS) + } + + unsafe { <$mode>::set_compare(&rtc, 0, instant) }; + } + + fn clear_compare_flag() { + <$mode>::clear_interrupt_flag::<$rtic_int>(unsafe { &pac::Rtc::steal() }); + } + + fn pend_interrupt() { + pac::NVIC::pend(pac::Interrupt::RTC); + } + + fn timer_queue() -> &'static TimerQueue { + &RTC_TQ + } + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __internal_half_period_counting_backend { + ($name:ident, $mode:ty, $rtic_int:ty, $half_period_int:ty, $overflow_int:ty) => { + use atsamd_hal_macros::hal_macro_helper; + use core::sync::atomic::Ordering; + use portable_atomic::AtomicU64; + use rtic_time::{ + half_period_counter::calculate_now, + timer_queue::{TimerQueue, TimerQueueBackend}, + }; + use $crate::pac; + use $crate::rtc::rtic::modes::RtcMode; + + struct TimerValue(<$mode as RtcMode>::Count); + impl rtic_time::half_period_counter::TimerValue for TimerValue { + const BITS: u32 = <$mode as RtcMode>::Count::BITS; + } + impl From for u64 { + fn from(value: TimerValue) -> Self { + Self::from(value.0) + } + } + + /// An RTC-based [`TimerQueueBackend`] that vastly extends the overall monotonic + /// period using [half-period counting](rtic_time::half_period_counter). + pub struct $name; + + static RTC_PERIOD_COUNT: AtomicU64 = AtomicU64::new(0); + static RTC_TQ: TimerQueue<$name> = TimerQueue::new(); + + #[hal_macro_helper] + impl $name { + $crate::__internal_backend_methods! { + mode = $mode; + rtic_int = $rtic_int; + rtc_pac = rtc; + init_compares = { + // Configure the compare registers + <$mode>::set_compare(&rtc, 0, 0); + <$mode>::set_compare(&rtc, 1, <$mode>::HALF_PERIOD); + } + statics = { + // Make sure period counter is synced with the timer value + RTC_PERIOD_COUNT.store(0, Ordering::SeqCst); + + // Initialize the timer queue + RTC_TQ.initialize(Self); + } + enable_interrupts = { + // Enable the compare and overflow interrupts. + <$mode>::enable_interrupt::<$rtic_int>(&rtc); + <$mode>::enable_interrupt::<$half_period_int>(&rtc); + <$mode>::enable_interrupt::<$overflow_int>(&rtc); + } + } + } + + impl TimerQueueBackend for RtcBackend { + type Ticks = u64; + + #[hal_macro_helper] + fn now() -> Self::Ticks { + calculate_now( + || RTC_PERIOD_COUNT.load(Ordering::Relaxed), + || TimerValue(<$mode>::count(unsafe { &pac::Rtc::steal() })), + ) + } + + fn enable_timer() { + <$mode>::enable(unsafe { &pac::Rtc::steal() }); + } + + fn disable_timer() { + <$mode>::disable(unsafe { &pac::Rtc::steal() }); + } + + fn on_interrupt() { + let rtc: pac::Rtc = unsafe { pac::Rtc::steal() }; + + // NOTE: The cmp0 flag is cleared when RTIC calls `clear_compare_flag`. + if <$mode>::check_interrupt_flag::<$half_period_int>(&rtc) { + <$mode>::clear_interrupt_flag::<$half_period_int>(&rtc); + let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); + assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!"); + + // Ensure that the COUNT has crossed + // Due to syncing delay this may not be the case initially + while <$mode>::count(&rtc) < <$mode>::HALF_PERIOD {} + } + if <$mode>::check_interrupt_flag::<$overflow_int>(&rtc) { + <$mode>::clear_interrupt_flag::<$overflow_int>(&rtc); + let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); + assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!"); + + // Ensure that the COUNT has wrapped + // Due to syncing delay this may not be the case initially + while <$mode>::count(&rtc) > <$mode>::HALF_PERIOD {} + } + } + + fn set_compare(mut instant: Self::Ticks) { + let rtc = unsafe { pac::Rtc::steal() }; + + const MAX: u64 = <$mode as RtcMode>::Count::MAX as u64; + + // Disable interrupts because this section is timing critical. + // We rely on the fact that this entire section runs within one + // RTC clock tick. (which it will do easily if it doesn't get + // interrupted) + critical_section::with(|_| { + let now = Self::now(); + + // Wrapping_sub deals with the u64 overflow corner case + let diff = instant.wrapping_sub(now); + let val = if diff <= MAX { + // Now we know `instant` will happen within one `MAX` time duration. + + // Evidently the compare interrupt will not trigger if the instant is within + // a couple of ticks, so delay it a bit if it is too + // close. This is not mentioned in the documentation + // or errata, but is known to be an issue for other + // microcontrollers as well (e.g. nRF family). + if diff < <$mode>::MIN_COMPARE_TICKS.into() { + instant = instant.wrapping_add(<$mode>::MIN_COMPARE_TICKS.into()) + } + + (instant & MAX) as <$mode as RtcMode>::Count + } else { + 0 + }; + + unsafe { <$mode>::set_compare(&rtc, 0, val) }; + }); + } + + fn clear_compare_flag() { + <$mode>::clear_interrupt_flag::<$rtic_int>(unsafe { &pac::Rtc::steal() }); + } + + fn pend_interrupt() { + pac::NVIC::pend(pac::Interrupt::RTC); + } + + fn timer_queue() -> &'static TimerQueue { + &RTC_TQ + } + } + }; +} diff --git a/hal/src/rtc/rtic/mod.rs b/hal/src/rtc/rtic/mod.rs index 7bdd4957b63..4b1b973d386 100644 --- a/hal/src/rtc/rtic/mod.rs +++ b/hal/src/rtc/rtic/mod.rs @@ -1,4 +1,4 @@ -//! [`Monotonic`](rtic_time::Monotonic) implementations for the Real Time +//! [`Monotonic`](rtic_time::Monotonic) implementations using the Real Time //! Clock (RTC). //! //! Enabling the `rtic` feature is required to use this module. @@ -7,87 +7,136 @@ //! [`Rtc`](crate::rtc::Rtc) in [`Count32Mode`](crate::rtc::Count32Mode). //! //! The items here provide monotonics for RTIC v2. Two monotonics are provided: -//! one that uses the RTC in mode 0, and another that uses the RTC in mode 1. -//! The mode 0 monotonic uses a 32-bit hardware counter with no [half-period -//! counting](rtic_time::half_period_counter), whereas the mode 1 monotonic uses -//! a 16-bit counter but with half-period counting. +//! one that uses the RTC in mode 0 (32-bit hardware counter), and another that +//! uses the RTC in mode 1 (16-bit hardware counter). //! -//! The mode 0 monotonic has the advantage that the only interrupts that occur -//! are for the RTIC tasks, but the disadvantage is that the lack of half-period -//! counting means that the monotonic wraps in a much shorter time than the mode -//! 1 monotonic. The mode 1 monotonic, however is less efficient in the sense -//! that additional interrupts are required for half-period counting, which can -//! be quite frequent depending on the selected clock rate. The monotonic should -//! be chosen based on the specific use case, bearing in mind that monotonics -//! are never supposed to actually roll over back to zero time (i.e. they are -//! supposed to count time _monotonically_). Doing so may result in strange -//! behavior for tasks scheduled near the time of the rollover. +//! Which mode is used can influence the total monotonic period before the its +//! time counter overflows and rolls back to zero time. This rollover violates +//! the contract of the monotonic, which is supposed to count time +//! _monotonically_. As such, the rollover can cause strange behavior for tasks +//! scheduled near the time of the rollover. Hence, the monotonic should be +//! chosen to match the use case such that the monotonic will never roll over +//! during the expected total duration of program execution. +//! +//! For all chip variants, the mode 1 monotonic uses [half-period +//! counting](rtic_time::half_period_counter) to extend the monotonic counter to +//! 64-bits. This is enabled by the fact that the RTC for every variant has +//! at least two compare registers in mode 1. This greatly extends the total +//! monotonic period. However, in this mode, half-period counting interrupts +//! occur more frequently (see below) due to the hardware counter being only 16 +//! bits wide, so it is less efficient in that regard. +//! +//! For SAMD11/21 chips, the mode 0 monotonic only has a single compare register +//! so that half-period counting is not possible. This significantly reduces the +//! total monotonic period. The SAMx5x chips, however, feature two compare +//! registers in mode 0 so that half-period counting can be done. In the latter +//! case, the mode 0 monotonic has extremely infrequent half-period counting +//! interrupts and so is more efficient. In fact, the mode 1 monotonic on SAMx5x +//! platforms offers nothing except more undesirable more frequent interrupts, +//! so its use is not recommended. +//! +//! NOTE: These monotonics currently live in the HAL for testing and refinement +//! purposes. The intention is to eventually move them to the +//! [`rtic-monotonics`](https://docs.rs/rtic-monotonics/latest/rtic_monotonics/) crate, +//! which is a central location for peripheral-based RTIC monotonics for various +//! microcontroller families. As such, be aware that this module could disappear +//! at any time in the future. +//! +//! # Choosing a mode //! //! The overall monotonic period (i.e. the time before the monotonic rolls over //! back to zero time) is as follows: //! -//! | | 1 kHz clock | 32 kHz clock | -//! | ---------- | ------------------ | ------------------- | -//! | **Mode 0** | ~48 days | ~36 hours | -//! | **Mode 1** | ~571 million years | ~17.8 million years | +//! | | 1 kHz clock | 32 kHz clock | +//! | ---------------------- | ------------------ | ------------------- | +//! | **Mode 0 (SAMD11/21)** | ~48 days | ~36 hours | +//! | **Mode 0 (SAMx5x)** | ~571 million years | ~17.8 million years | +//! | **Mode 1** | ~571 million years | ~17.8 million years | //! -//! The time precision (i.e. the RTC tick time and shortest delay time) is as +//! The half-period counting interrupt periods for the monotonics are: +//! +//! | | 1 kHz clock | 32 kHz clock | +//! | ---------------------- | ----------- | ------------ | +//! | **Mode 0 (SAMD11/21)** | Never | Never | +//! | **Mode 0 (SAMx5x)** | ~24 days | ~18 hours | +//! | **Mode 1** | 32 seconds | 1 second | +//! +//! The time resolution (i.e. the RTC tick time and shortest delay time) is as //! follows: //! //! | | 1 kHz clock | 32 kHz clock | //! | ------------ | ----------- | ------------ | //! | **Any mode** | ~977 μs | ~31 μs | //! -//! A monotonic using the desired RTC mode should be created with the -//! appropriate [macro](crate::rtc::rtic::prelude). The RTC clock rate and -//! source must also be specified when calling the macro, using the types in -//! [`rtc_clock`]. The first macro argument is the name of the global structure -//! that will implement [`Monotonic`](rtic_time::Monotonic). The second argument -//! must be a clock rate type that implements -//! [`RtcClockRate`](rtc_clock::RtcClockRate), and the third argument must be a -//! type clock source implementing -//! [`RtcClockSource`](rtc_clock::RtcClockSource). See below for an example. +//! It should be apparent from these numbers that the mode 1 monotonic offers +//! nothing but more frequent interrupts on SAMx5x platforms. //! -//! NOTE: These monotonics currently live in the HAL for testing and refinement -//! purposes. The intention is to eventually move them to the -//! [`rtic-monotonics`](https://docs.rs/rtic-monotonics/latest/rtic_monotonics/) crate, which is a central location for peripheral-based -//! RTIC monotonics for various microcontroller families. As such, be aware that -//! this module could disappear at any time in the future. +//! # RTC clock selection //! -//! NOTE: Some SAMD chips will support half-period counting in mode 0, which -//! would greatly extend the overall monotonic period to that of the mode 1 -//! monotonic, while also still being very interrupt-efficient. It is planned to -//! add this to the mode 0 monotonic for chips that support it. +//! Prior to starting the monotonic, the RTC clock source must be configured +//! using the [`atsamd-hal`](https://docs.rs/atsamd-hal/latest/atsamd_hal/index.html) +//! crate. On SAMD11/21 platforms, the RTC clock must be setup as a +//! [generic clock](https://docs.rs/atsamd-hal/latest/atsamd_hal/clock/struct.GenericClockController.html). +//! On SAMx5x platforms the RTC clock must be selected from either the 1.1024 +//! kHz clock or the 32.768 kHz clock, either of which can be internal or +//! external. //! -//! NOTE: The mode 1 monotonic currently has a robustness issue that is being -//! worked on. Refer to [Issue #765](https://github.com/atsamd-rs/atsamd/issues/765) for details. +//! TODO: Update HAL links and [should proof of RTC clock setup be required?](https://github.com/atsamd-rs/atsamd/issues/765#issuecomment-2526974751) +//! +//! # Usage +//! +//! A monotonic using the desired RTC mode should be created with the +//! appropriate [macro](crate::rtc::rtic::prelude). The first macro argument is +//! the name of the global structure that will implement +//! [`Monotonic`](rtic_time::Monotonic). The RTC clock rate must be +//! known at compile time, and so the appropriate type from [`rtc_clock`] must +//! be passed to the macro as the second argument. +//! +//! Sometime during initialization, the monotonic also must be started by called +//! the `start` method on the created monotonic. The [`Rtc`](pac::Rtc) struct +//! must be passed to the `start` to ensure that the monotonic has complete +//! control of the RTC. //! //! # Example //! //! ``` //! use atsamd_hal::prelude::*; -//! rtc_mode0_monotonic!(Mono, rtc_clock::Clock32k, rtc_clock::ClockInternal); +//! +//! // Create the monotonic struct named `Mono` +//! rtc_mode0_monotonic!(Mono, rtc_clock::Clock32k); //! //! fn init() { //! # // This is normally provided by the selected PAC //! # let rtc = unsafe { core::mem::transmute(()) }; //! # let mut mclk = unsafe { core::mem::transmute(()) }; //! # let mut osc32kctrl = unsafe { core::mem::transmute(()) }; +//! // Here the RTC clock source should be configured using the HAL +//! //! // Start the monotonic -//! Mono::start(rtc, &mut mclk, &mut osc32kctrl); +//! Mono::start(rtc); //! } //! //! async fn usage() { //! loop { //! // Use the monotonic //! let timestamp = Mono::now(); +//! +//! Mono::delay_until(timestamp + 2u32.secs()).await; //! Mono::delay(100u32.millis()).await; //! } //! } //! ``` - -// TODO: Before HAL PR -// - Make sure every chip at least compiles +//! +//! # Other notes +//! +//! The number returned by +//! [`Monotonic::now().ticks()`](rtic_monotonic::Monotonic::now) will always +//! increase (barring monotonic rollover). However, due to the register +//! [synchronization delay](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-0C52DB00-4BF6-4F41-85B5-B76529875364.html), +//! the number returned may not always increment by one every time it changes. +//! In fact, testing shows that it typically increments by four every time it +//! changes. This is true regardless of the clock rate used, as the +//! synchronization delay scales along with the clock period. mod v1 { use crate::rtc::{Count32Mode, Rtc}; @@ -130,13 +179,11 @@ mod v1 { } } -mod mode0; -mod mode1; - -pub use mode0::RtcBackend as RtcMode0Backend; -pub use mode1::RtcBackend as RtcMode1Backend; +mod backends; +mod modes; -const MIN_COMPARE_TICKS: u32 = 5; +pub use modes::mode0::RtcBackend as RtcMode0Backend; +pub use modes::mode1::RtcBackend as RtcMode1Backend; pub mod prelude { pub use super::rtc_clock; @@ -146,84 +193,38 @@ pub mod prelude { pub use fugit::{self, ExtU32, ExtU32Ceil, ExtU64, ExtU64Ceil}; } -/// Types used to specify the RTC clock source at compile time when creating the +/// Types used to specify the RTC clock rate at compile time when creating the /// monotonics. /// /// These types utilize [type-level programming](crate::typelevel) /// techniques and are passed to the [monotonic creation -/// macros](crate::rtc::rtic::prelude) -/// -/// NOTE: This could probably be accomplished using the items from -/// [`clock::v2::rtcosc`](crate::clock::v2::rtcosc), but we want to avoid -/// dependencies from other parts of the HAL since this will move to [`rtic-monotonics`](https://docs.rs/rtic-monotonics/latest/rtic_monotonics/). +/// macros](crate::rtc::rtic::prelude). +/// The clock rate must be specified at compile time so that the `Instant` and +/// `Duration` types in +/// [`TimerQueueBasedMonotonic`](rtic_time::monotonic::TimerQueueBasedMonotonic) +/// can be specified. pub mod rtc_clock { - use crate::pac::{generic::W, osc32kctrl::rtcctrl::RtcctrlSpec}; - use core::marker::PhantomData; - /// Type-level enum for available RTC clock rates. pub trait RtcClockRate { - const RATE: u32; + const RATE_HZ: u32; } /// Type level [`RtcClockRate`] variant for the 32.768 kHz clock rate. pub enum Clock32k {} impl RtcClockRate for Clock32k { - const RATE: u32 = 32_768; + const RATE_HZ: u32 = 32_768; } /// Type level [`RtcClockRate`] variant for the 1.024 kHz clock rate. pub enum Clock1k {} impl RtcClockRate for Clock1k { - const RATE: u32 = 1_024; + const RATE_HZ: u32 = 1_024; } - /// Type-level enum for available RTC sources. - pub trait RtcClockSource {} - - /// Type level [`RtcClockSource`] variant for an internal clock source. - pub enum ClockInternal {} - impl RtcClockSource for ClockInternal {} - - /// Type level [`RtcClockSource`] variant for an external clock source. - pub enum ClockExternal {} - impl RtcClockSource for ClockExternal {} - - /// Types that can configure the RTC clock. - pub trait RtcClockSetter { - /// Sets the RTC clock source by modifying the register. - fn set_source(reg: &mut W) -> &mut W; - } - - /// Collection forming a complete RTC clock source, and that can configure - /// the clock source as an [`RtcClockSetter`]. - pub struct ClockSetter { - rate: PhantomData, - source: PhantomData, - } - impl RtcClockSetter for ClockSetter { - #[inline] - fn set_source(reg: &mut W) -> &mut W { - reg.rtcsel().ulp1k() - } - } - impl RtcClockSetter for ClockSetter { - #[inline] - fn set_source(reg: &mut W) -> &mut W { - reg.rtcsel().xosc1k() - } - } - - impl RtcClockSetter for ClockSetter { - #[inline] - fn set_source(reg: &mut W) -> &mut W { - reg.rtcsel().ulp32k() - } - } - impl RtcClockSetter for ClockSetter { - #[inline] - fn set_source(reg: &mut W) -> &mut W { - reg.rtcsel().xosc32k() - } + /// Type level [`RtcClockRate`] variant for a custom clock rate + pub enum ClockCustom {} + impl RtcClockRate for ClockCustom { + const RATE_HZ: u32 = RATE_HZ; } } @@ -242,25 +243,17 @@ macro_rules! __internal_create_rtc_interrupt { #[doc(hidden)] #[macro_export] macro_rules! __internal_create_rtc_struct { - ($name:ident, $backend:ident, $clock_rate:ty, $clock_source:ty) => { + ($name:ident, $backend:ident, $clock_rate:ty) => { /// A `Monotonic` based on the RTC peripheral. pub struct $name; impl $name { - /// Starts the `Monotonic`. - /// /// This method must be called only once. - pub fn start( - rtc: $crate::pac::Rtc, - mclk: &mut $crate::pac::Mclk, - osc32kctrl: &mut $crate::pac::Osc32kctrl, - ) { + pub fn start(rtc: $crate::pac::Rtc) { use $crate::rtc::rtic::rtc_clock::*; $crate::__internal_create_rtc_interrupt!($backend); - $crate::rtc::rtic::$backend::_start::>( - rtc, mclk, osc32kctrl, - ); + $crate::rtc::rtic::$backend::_start(rtc); } } @@ -271,12 +264,12 @@ macro_rules! __internal_create_rtc_struct { type Instant = $crate::fugit::Instant< ::Ticks, 1, - { <$clock_rate>::RATE }, + { <$clock_rate>::RATE_HZ }, >; type Duration = $crate::fugit::Duration< ::Ticks, 1, - { <$clock_rate>::RATE }, + { <$clock_rate>::RATE_HZ }, >; } @@ -285,23 +278,23 @@ macro_rules! __internal_create_rtc_struct { }; } -/// Create an RTIC v2 monotonic using the RTC in mode 0. +/// Create an RTIC v2 monotonic that uses the RTC in mode 0. /// /// See the [`rtic`](crate::rtc::rtic) module for details. #[macro_export] macro_rules! rtc_mode0_monotonic { - ($name:ident, $clock_rate: ty, $clock_source: ty) => { - $crate::__internal_create_rtc_struct!($name, RtcMode0Backend, $clock_rate, $clock_source); + ($name:ident, $clock_rate: ty) => { + $crate::__internal_create_rtc_struct!($name, RtcMode0Backend, $clock_rate); }; } -/// Create an RTIC v2 monotonic based on RTC in mode 1. +/// Create an RTIC v2 monotonic that uses the RTC in mode 1. /// /// See the [`rtic`](crate::rtc::rtic) module for details. #[macro_export] macro_rules! rtc_mode1_monotonic { - ($name:ident, $clock_rate: ty, $clock_source: ty) => { - $crate::__internal_create_rtc_struct!($name, RtcMode1Backend, $clock_rate, $clock_source); + ($name:ident, $clock_rate: ty) => { + $crate::__internal_create_rtc_struct!($name, RtcMode1Backend, $clock_rate); }; } @@ -328,16 +321,3 @@ unsafe fn set_monotonic_prio(prio_bits: u8, interrupt: impl cortex_m::interrupt: nvic.set_priority(interrupt, hw_prio); } - -// TODO: Test code -#[derive(Default)] -struct LoopChecker { - count: usize, -} -impl LoopChecker { - pub fn too_many(&mut self) -> bool { - self.count += 1; - - self.count > 0x800000 - } -} diff --git a/hal/src/rtc/rtic/mode0.rs b/hal/src/rtc/rtic/mode0.rs deleted file mode 100644 index f92736b1744..00000000000 --- a/hal/src/rtc/rtic/mode0.rs +++ /dev/null @@ -1,287 +0,0 @@ -use super::{LoopChecker, MIN_COMPARE_TICKS}; -use crate::pac; -use atsamd_hal_macros::hal_macro_helper; -use rtic_time::timer_queue::{TimerQueue, TimerQueueBackend}; - -/// RTC mode 0 based [`TimerQueueBackend`]. -pub struct RtcBackend; - -static RTC_TQ: TimerQueue = TimerQueue::new(); - -// NOTE: TODO: Move this elsewhere so it is not duplicated? -// As explained in the datasheet, reading a read-synced register may result in -// an old value, which we try to avoid by ensuring that SYNCBUSY is clear before -// reading. A write to a write-synced register will be discarded if syncing is -// happening during the write. As such we also ensure that SYNCBUSY is clear -// before writing to a synced register. Every register access should be prefaced -// by a SYNC comment indicating the required synchronization, which indicates -// that this access was checked and accounted for. -#[hal_macro_helper] -impl RtcBackend { - /// RTC interrupt handler called before control passes to RTIC. - #[inline] - pub unsafe fn interrupt_handler() { - let rtc = pac::Rtc::steal(); - - /// Returns whether a < b, taking wrapping into account and assuming - /// that the difference is less than 0x80000000. - #[inline] - fn less_than_with_wrap(a: u32, b: u32) -> bool { - let d = a.wrapping_sub(b); - - d >= 0x80000000 - } - - // Ensure that the COUNT is at least the compare value - // Due to syncing delay this may not be the case initially - // Note that this has to be done here because RTIC will clear the cmp0 flag - // before `RtcBackend::on_interrupt` is called. - if rtc.mode0().intflag().read().cmp0().bit_is_set() { - let compare = rtc.mode0().comp(0).read().bits(); - while less_than_with_wrap(Self::now(), compare) {} - } - - Self::timer_queue().on_monotonic_interrupt(); - } - - // TODO: Test code - fn check_loop(checker: &mut LoopChecker, item: &str) { - let rtc = unsafe { &pac::Rtc::steal() }; - - if checker.too_many() { - Self::sync(); - panic!( - "Took too long in '{item}' with count 0x{:X} and cmp 0x{:X}. Int flags: 0x{:X}, Enabled: {:?}", - rtc.mode0().count().read().bits(), - rtc.mode0().comp(0).read().bits(), - rtc.mode0().intflag().read().bits(), - rtc.mode0().ctrla().read().enable().bit_is_set(), - ); - } - } - - /// Waits until the SYNCBUSY register is clear. - #[inline] - fn sync() { - let rtc = unsafe { &pac::Rtc::steal() }; - - let mut loop_checker = LoopChecker::default(); - - // SYNC: None - #[hal_cfg("rtc-d5x")] - while rtc.mode0().syncbusy().read().bits() != 0 { - Self::check_loop(&mut loop_checker, "sync"); - } - // SYNC: None - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - while rtc.mode0().status().read().syncbusy().bit_is_set() { - Self::check_loop(&mut loop_checker, "sync"); - } - } - - // TODO: Test? - #[inline] - fn wait_for_count_change() -> u32 { - let rtc = unsafe { &pac::Rtc::steal() }; - - let mut last_count = Self::now(); - - // TODO: It sounds like we need to ensure everywhere that - // a) Sync read registered are ensured not to be syncing before reading - // b) Sync write registers are also ensured not to be syncing before writing - // since this will discard the write. - - // TODO: Also the count by 4 of the COUNT may be due to synchronization delay: - // https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-13/GUID-0C52DB00-4BF6-4F41-85B5-B76529875364.html#GUID-0C52DB00-4BF6-4F41-85B5-B76529875364 - // Though according to calculations this should only be between 2 and 3 RTC - // clock periods, not 4? But could take a little over 3, hence 4. It says 2 is - // typical though. - - // TODO: If the clock is disabled then just continue. - // This can happen if a new task pends the interrupt while the queue is empty so - // that the timer is disabled, which can otherwise result in waiting - // forever. - // SYNC: Write (we just read though) - if !rtc.mode0().ctrla().read().enable().bit_is_set() { - return last_count; - } - - let mut loop_checker = LoopChecker::default(); - loop { - let count = Self::now(); - - if count != last_count { - if last_count > 0 && count != last_count.wrapping_add(4) { - // TODO: Test code - // SYNC: Write (we just read though) - let compare = rtc.mode0().comp(0).read().bits(); - - panic!("Clock anomaly at 0x{count:X} with previous step at 0x{last_count:X} with cmp 0x{compare:X}"); - } - - break count; - } - - Self::check_loop(&mut loop_checker, "wait_for_count_change"); - - last_count = count; - } - } - - /// Starts the clock. - /// - /// **Do not use this function directly.** - /// - /// Use the [`rtc_mode0_monotonic`](crate::rtc_mode0_monotonic) macro - /// instead and then call `start` on the monotonic. - pub fn _start( - rtc: pac::Rtc, - mclk: &mut pac::Mclk, - osc32kctrl: &mut pac::Osc32kctrl, - ) { - // Disable the RTC. - // NOTE: This register and field are the same in all modes. - // SYNC: Write - Self::sync(); - rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); - - // Set the RTC clock source. - osc32kctrl.rtcctrl().write(S::set_source); - - // Enable the APBA clock for the RTC. - mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); - - // Reset RTC back to initial settings, which disables it and enters mode 0. - // NOTE: This register and field are the same in all modes. - // SYNC: Write - Self::sync(); - rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); - - // Wait for the reset to complete - let mut loop_checker = LoopChecker::default(); - // SYNC: Write (we just read though) - while rtc.mode0().ctrla().read().swrst().bit_is_set() { - Self::check_loop(&mut loop_checker, "reset"); - } - - // TODO: Test code - // Set the prescalar - //rtc.mode0().ctrla().modify(|_, w| w.prescaler().div4()); - - // Set the the initial compare - // SYNC: Write - Self::sync(); - unsafe { - rtc.mode0().comp(0).write(|w| w.comp().bits(0)); - } - - // Timing critical, make sure we don't get interrupted. - critical_section::with(|_| { - // Start the RTC counter. - // SYNC: Write - Self::sync(); - rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); - - // Enable counter sync on SAMx5x, the counter cannot be read otherwise. - #[hal_cfg("rtc-d5x")] - { - // Enable counter synchronization - // SYNC: Write - Self::sync(); - rtc.mode0().ctrla().modify(|_, w| w.countsync().set_bit()); - - // Errata: The first read of the count is incorrect so we need to read it - // then wait for it to change. - Self::wait_for_count_change(); - } - - // Clear the triggered compare flag - Self::clear_compare_flag(); - - // Enable the compare interrupt - // SYNC: None - rtc.mode0().intenset().write(|w| w.cmp0().set_bit()); - - // Initialize the timer queue - RTC_TQ.initialize(Self {}); - - // Enable the RTC interrupt in the NVIC and set its priority. - // SAFETY: We take full ownership of the peripheral and interrupt vector, - // plus we are not using any external shared resources so we won't impact - // basepri/source masking based critical sections. - unsafe { - super::set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); - pac::NVIC::unmask(pac::Interrupt::RTC); - } - }); - } -} - -impl TimerQueueBackend for RtcBackend { - type Ticks = u32; - - #[hal_macro_helper] - fn now() -> Self::Ticks { - let rtc = unsafe { &pac::Rtc::steal() }; - - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - { - // SYNC: TODO: Need to check for these other chips - rtc.mode0().readreq().modify(|_, w| w.rcont().set_bit()); - } - - // SYNC: Read/Write - Self::sync(); - rtc.mode0().count().read().bits() - } - - fn enable_timer() { - let rtc = unsafe { pac::Rtc::steal() }; - // SYNC: Write - Self::sync(); - rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); - } - - fn disable_timer() { - let rtc = unsafe { pac::Rtc::steal() }; - // SYNC: Write - Self::sync(); - rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); - } - - fn on_interrupt() { - // There is nothing we need to do here - } - - fn set_compare(mut instant: Self::Ticks) { - let rtc = unsafe { pac::Rtc::steal() }; - - // Evidently the compare interrupt will not trigger if the instant is within a - // couple of ticks, so delay it a bit if it is too close. - // This is not mentioned in the documentation or errata, but is known to be an - // issue for other microcontrollers as well (e.g. nRF family). - if instant.saturating_sub(Self::now()) < MIN_COMPARE_TICKS { - instant = instant.wrapping_add(MIN_COMPARE_TICKS) - } - - // Set the compare register and wait for sync - // SYNC: Write - Self::sync(); - unsafe { rtc.mode0().comp(0).write(|w| w.comp().bits(instant)) }; - } - - fn clear_compare_flag() { - let rtc = unsafe { pac::Rtc::steal() }; - - // SYNC: None - rtc.mode0().intflag().modify(|_, w| w.cmp0().set_bit()); - } - - fn pend_interrupt() { - pac::NVIC::pend(pac::Interrupt::RTC); - } - - fn timer_queue() -> &'static TimerQueue { - &RTC_TQ - } -} diff --git a/hal/src/rtc/rtic/mode1.rs b/hal/src/rtc/rtic/mode1.rs deleted file mode 100644 index 39482d660a0..00000000000 --- a/hal/src/rtc/rtic/mode1.rs +++ /dev/null @@ -1,366 +0,0 @@ -use super::{LoopChecker, MIN_COMPARE_TICKS}; -use crate::pac; -use atsamd_hal_macros::hal_macro_helper; -use core::sync::atomic::Ordering; -use portable_atomic::AtomicU64; -use rtic_time::{ - half_period_counter::calculate_now, - timer_queue::{TimerQueue, TimerQueueBackend}, -}; - -struct TimerValueU16(u16); -impl rtic_time::half_period_counter::TimerValue for TimerValueU16 { - const BITS: u32 = 16; -} -impl From for u64 { - fn from(value: TimerValueU16) -> Self { - Self::from(value.0) - } -} - -/// RTC mode 1 based [`TimerQueueBackend`]. -pub struct RtcBackend; - -static RTC_PERIOD_COUNT: AtomicU64 = AtomicU64::new(0); -static RTC_TQ: TimerQueue = TimerQueue::new(); - -// NOTE: TODO: Move this elsewhere so it is not duplicated? -// As explained in the datasheet, reading a read-synced register may result in -// an old value, which we try to avoid by ensuring that SYNCBUSY is clear before -// reading. A write to a write-synced register will be discarded if syncing is -// happening during the write. As such we also ensure that SYNCBUSY is clear -// before writing to a synced register. Every register access should be prefaced -// by a SYNC comment indicating the required synchronization, which indicates -// that this access was checked and accounted for. -#[hal_macro_helper] -impl RtcBackend { - /// RTC interrupt handler called before control passes to RTIC. - #[inline] - pub unsafe fn interrupt_handler() { - let rtc = pac::Rtc::steal(); - - /// Returns whether a < b, taking wrapping into account and assuming - /// that the difference is less than 0x8000. - #[inline] - fn less_than_with_wrap(a: u16, b: u16) -> bool { - let d = a.wrapping_sub(b); - - d >= 0x8000 - } - - // Ensure that the COUNT is at least the compare value - // Due to syncing delay this may not be the case initially - // Note that this has to be done here because RTIC will clear the cmp0 flag - // before `RtcBackend::on_interrupt` is called. - if rtc.mode1().intflag().read().cmp0().bit_is_set() { - let compare = rtc.mode1().comp(0).read().bits(); - while less_than_with_wrap(Self::raw_count(), compare) {} - } - - Self::timer_queue().on_monotonic_interrupt(); - } - - // TODO: Test code - fn check_loop(checker: &mut LoopChecker, item: &str) { - let rtc = unsafe { &pac::Rtc::steal() }; - - if checker.too_many() { - Self::sync(); - panic!( - "Took too long in '{item}' with count 0x{:X} and cmp 0x{:X}. Int flags: 0x{:X}, Enabled: {:?}", - rtc.mode1().count().read().bits(), - rtc.mode1().comp(0).read().bits(), - rtc.mode1().intflag().read().bits(), - rtc.mode1().ctrla().read().enable().bit_is_set(), - ); - } - } - - #[inline] - fn sync() { - let rtc = unsafe { &pac::Rtc::steal() }; - - let mut loop_checker = LoopChecker::default(); - - // SYNC: None - #[hal_cfg("rtc-d5x")] - while rtc.mode1().syncbusy().read().bits() != 0 { - Self::check_loop(&mut loop_checker, "sync"); - } - // SYNC: None - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - while rtc.mode1().status().read().syncbusy().bit_is_set() { - Self::check_loop(&mut loop_checker, "sync"); - } - } - - // TODO: This will ultimately not need to be public - pub fn raw_count() -> u16 { - let rtc = unsafe { &pac::Rtc::steal() }; - - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - { - // SYNC: TODO: Need to check for these other chips - rtc.mode1().readreq().modify(|_, w| w.rcont().set_bit()); - Self::sync(); - } - - // SYNC: Read/Write - Self::sync(); - rtc.mode1().count().read().bits() - } - - // TODO: Test? - fn wait_for_count_change() -> u16 { - let rtc = unsafe { &pac::Rtc::steal() }; - - let mut last_count = Self::raw_count(); - - // TODO: If the clock is disabled then just continue. - // This can happen if a new task pends the interrupt while the queue is empty so - // that the timer is disabled, which can otherwise result in waiting - // forever. - // SYNC: Write (we just read though) - if !rtc.mode1().ctrla().read().enable().bit_is_set() { - return last_count; - } - - let mut loop_checker = LoopChecker::default(); - loop { - let count = Self::raw_count(); - - if count != last_count { - if last_count > 0 && count != last_count.wrapping_add(4) { - // TODO: Test code - // SYNC: Write (we just read though) - let compare = rtc.mode1().comp(0).read().bits(); - - panic!("Clock anomaly at 0x{count:X} with previous step at 0x{last_count:X} with cmp 0x{compare:X}"); - } - - break count; - } - - Self::check_loop(&mut loop_checker, "wait_for_count_change"); - - last_count = count; - } - } - - /// Starts the clock. - /// - /// **Do not use this function directly.** - /// - /// Use the [`rtc_mode1_monotonic`](crate::rtc_mode1_monotonic) macro - /// instead and then call `start` on the monotonic. - pub fn _start( - rtc: pac::Rtc, - mclk: &mut pac::Mclk, - osc32kctrl: &mut pac::Osc32kctrl, - ) { - // Disable the RTC. - // NOTE: This register and field are the same in all modes. - // SYNC: Write - Self::sync(); - rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); - - // Set the RTC clock source. - osc32kctrl.rtcctrl().write(S::set_source); - - // Enable the APBA clock for the RTC. - mclk.apbamask().modify(|_, w| w.rtc_().set_bit()); - - // Reset RTC back to initial settings, which disables it and enters mode 0. - // NOTE: This register and field are the same in all modes. - // SYNC: Write - Self::sync(); - rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); - - // Wait for the reset to complete - let mut loop_checker = LoopChecker::default(); - // SYNC: Write (we just read though) - while rtc.mode0().ctrla().read().swrst().bit_is_set() { - Self::check_loop(&mut loop_checker, "reset"); - } - - // Set mode 1 (16 bit counter) - // SYNC: Write - Self::sync(); - rtc.mode0().ctrla().modify(|_, w| w.mode().count16()); - - // TODO: Test code - // Set the prescalar - //rtc.mode1().ctrla().modify(|_, w| w.prescaler().div4()); - - // Set the mode 1 period - // SYNC: Write - Self::sync(); - unsafe { rtc.mode1().per().write(|w| w.bits(0xFFFF)) }; - - // Configure the compare registers - unsafe { - // RTIC tasks waker - // SYNC: Write - Self::sync(); - rtc.mode1().comp(0).write(|w| w.bits(0)); - // Half period - // SYNC: Write - Self::sync(); - rtc.mode1().comp(1).write(|w| w.bits(0x8000)); - } - - // Timing critical, make sure we don't get interrupted. - critical_section::with(|_| { - // Start the RTC counter. - // SYNC: Write - Self::sync(); - rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); - - // Enable counter sync on SAMx5x, the counter cannot be read otherwise. - #[hal_cfg("rtc-d5x")] - { - // Enable counter synchronization - // SYNC: Write - Self::sync(); - rtc.mode1().ctrla().modify(|_, w| w.countsync().set_bit()); - - // Errata: The first read of the count is incorrect so we need to read it - // then wait for it to change. - Self::wait_for_count_change(); - } - - // Clear the triggered compare flag - Self::clear_compare_flag(); - - // Make sure period counter is synced with the timer value - RTC_PERIOD_COUNT.store(0, Ordering::SeqCst); - - // Initialize the timer queue - RTC_TQ.initialize(Self {}); - - // Enable the compare and overflow interrupts. - // SYNC: None - rtc.mode1() - .intenset() - .write(|w| w.ovf().set_bit().cmp0().set_bit().cmp1().set_bit()); - - // Enable the RTC interrupt in the NVIC and set its priority. - // SAFETY: We take full ownership of the peripheral and interrupt vector, - // plus we are not using any external shared resources so we won't impact - // basepri/source masking based critical sections. - unsafe { - super::set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); - pac::NVIC::unmask(pac::Interrupt::RTC); - } - }); - } -} - -impl TimerQueueBackend for RtcBackend { - type Ticks = u64; - - #[hal_macro_helper] - fn now() -> Self::Ticks { - calculate_now( - || RTC_PERIOD_COUNT.load(Ordering::Relaxed), - || TimerValueU16(Self::raw_count()), - ) - } - - fn enable_timer() { - let rtc = unsafe { pac::Rtc::steal() }; - // SYNC: Write - Self::sync(); - rtc.mode1().ctrla().modify(|_, w| w.enable().set_bit()); - } - - fn disable_timer() { - let rtc = unsafe { pac::Rtc::steal() }; - // SYNC: Write - Self::sync(); - rtc.mode1().ctrla().modify(|_, w| w.enable().clear_bit()); - } - - fn on_interrupt() { - let rtc: pac::Rtc = unsafe { pac::Rtc::steal() }; - - // Capture the interrupt flags - // SYNC: None - let intflag = rtc.mode1().intflag().read(); - - // NOTE: The cmp0 flag is cleared when RTIC calls `clear_compare_flag`. - if intflag.cmp1().bit_is_set() { - // This was half-period interrupt - // SYNC: None - rtc.mode1().intflag().modify(|_, w| w.cmp1().set_bit()); - let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); - assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!"); - - // Ensure that the COUNT has crossed - // Due to syncing delay this may not be the case initially - while Self::raw_count() < 0x8000 {} - } - if intflag.ovf().bit_is_set() { - // This was an overflow interrupt - // SYNC: None - rtc.mode1().intflag().modify(|_, w| w.ovf().set_bit()); - let prev = RTC_PERIOD_COUNT.fetch_add(1, Ordering::Relaxed); - assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!"); - - // Ensure that the COUNT has wrapped - // Due to syncing delay this may not be the case initially - while Self::raw_count() > 0x8000 {} - } - } - - fn set_compare(mut instant: Self::Ticks) { - let rtc = unsafe { pac::Rtc::steal() }; - - const MAX: u64 = 0xFFFF; - - // Disable interrupts because this section is timing critical. - // We rely on the fact that this entire section runs within one - // RTC clock tick. (which it will do easily if it doesn't get - // interrupted) - critical_section::with(|_| { - let now = Self::now(); - - // Wrapping_sub deals with the u64 overflow corner case - let diff = instant.wrapping_sub(now); - let val = if diff <= MAX { - // Now we know `instant` will happen within one `MAX` time duration. - - // Evidently the compare interrupt will not trigger if the instant is within - // a couple of ticks, so delay it a bit if it is too - // close. This is not mentioned in the documentation - // or errata, but is known to be an issue for other - // microcontrollers as well (e.g. nRF family). - if diff < MIN_COMPARE_TICKS.into() { - instant = instant.wrapping_add(MIN_COMPARE_TICKS.into()) - } - - (instant & MAX) as u16 - } else { - 0 - }; - - // SYNC: Write - Self::sync(); - unsafe { rtc.mode1().comp(0).write(|w| w.comp().bits(val)) }; - }); - } - - fn clear_compare_flag() { - let rtc = unsafe { pac::Rtc::steal() }; - // SYNC: None - rtc.mode1().intflag().write(|w| w.cmp0().set_bit()); - } - - fn pend_interrupt() { - pac::NVIC::pend(pac::Interrupt::RTC); - } - - fn timer_queue() -> &'static TimerQueue { - &RTC_TQ - } -} diff --git a/hal/src/rtc/rtic/modes.rs b/hal/src/rtc/rtic/modes.rs new file mode 100644 index 00000000000..eb170d97a1a --- /dev/null +++ b/hal/src/rtc/rtic/modes.rs @@ -0,0 +1,442 @@ +//! Abstraction of basic RTC functions for each mode. +//! +//! As explained in the [datasheets](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-ABE2D37F-8125-4279-9955-BC3900046CFF.html), +//! reading a read-synced register may result in +//! an old value, which we try to avoid by ensuring that SYNCBUSY is clear +//! before reading. A write to a write-synced register will be discarded if +//! syncing is happening during the write. As such, we also ensure that SYNCBUSY +//! is clear before writing to a synced register. Every register access should +//! be prefaced by a `SYNC`` comment indicating the required synchronization, +//! the presence of this comment indicates that this access was checked and +//! accounted for. + +use crate::pac; +use atsamd_hal_macros::hal_macro_helper; +use pac::Rtc; + +// TODO: Test code +#[derive(Default)] +pub struct LoopChecker { + count: usize, +} +impl LoopChecker { + pub fn too_many(&mut self) -> bool { + self.count += 1; + + self.count > 0x800000 + } +} + +/// Type-level enum for RTC interrupts. +pub trait RtcInterrupt { + /// Enable this interrupt. + fn enable(rtc: &Rtc); + /// Returns whether the interrupt has been triggered. + fn check_flag(rtc: &Rtc) -> bool; + /// Clears the interrupt flag so the ISR will not be called again + /// immediately. + fn clear_flag(rtc: &Rtc); +} + +/// Macro to easily declare an RTC interrupt. +macro_rules! create_rtc_interrupt { + ($mode:ident, $name:ident, $bit:ident) => { + /// Type-level variant for the $name interrupt in $mode. + enum $name {} + impl RtcInterrupt for $name { + #[inline] + fn enable(rtc: &Rtc) { + // SYNC: None + rtc.$mode().intenset().write(|w| w.$bit().set_bit()); + } + + #[inline] + fn check_flag(rtc: &Rtc) -> bool { + // SYNC: None + rtc.$mode().intflag().read().$bit().bit_is_set() + } + + #[inline] + fn clear_flag(rtc: &Rtc) { + // SYNC: None + rtc.$mode().intflag().write(|w| w.$bit().set_bit()); + } + } + }; +} + +/// An abstraction of an RTC in a particular mode that provides low-level +/// access and handles all register syncing issues using only associated +/// functions. +#[hal_macro_helper] +pub trait RtcMode { + /// The type of the COUNT register. + type Count: Copy + PartialEq + Eq; + /// The COUNT value representing a half period. + const HALF_PERIOD: Self::Count; + /// The minimum number of ticks that compares need to be ahead of the COUNT + /// in order to trigger. + const MIN_COMPARE_TICKS: Self::Count; + + // TODO: Test code + fn check_loop(rtc: &Rtc, checker: &mut LoopChecker, item: &str); + fn check_for_clock_anomaly(rtc: &Rtc, last_count: Self::Count, count: Self::Count); + + /// Sets this mode in the CTRL register. + unsafe fn set_mode(rtc: &Rtc); + /// Sets a compare value. + unsafe fn set_compare(rtc: &Rtc, number: usize, value: Self::Count); + /// Retrieves a compare from the register. + fn get_compare(rtc: &Rtc, number: usize) -> Self::Count; + /// Starts the RTC and does any required initialization for this mode. + fn start_and_initialize(rtc: &Rtc); + /// Returns the current synced COUNT value. + fn count(rtc: &Rtc) -> Self::Count; + /// Returns whether register syncing is currently happening. + fn sync_busy(rtc: &Rtc) -> bool; + + /// Resets the RTC, leaving it disabled in MODE0. + #[inline] + fn reset(rtc: &Rtc) { + // Reset RTC back to initial settings, which disables it and enters mode 0. + // NOTE: This register and field are the same in all modes. + // SYNC: Write + Self::sync(rtc); + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + rtc.mode0().ctrl().modify(|_, w| w.swrst().set_bit()); + #[hal_cfg("rtc-d5x")] + rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); + + // Wait for the reset to complete + let mut loop_checker = LoopChecker::default(); + // SYNC: Write (we just read though) + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + while rtc.mode0().ctrl().read().swrst().bit_is_set() { + Self::check_loop(rtc, &mut loop_checker, "reset"); + } + #[hal_cfg("rtc-d5x")] + while rtc.mode0().ctrla().read().swrst().bit_is_set() { + Self::check_loop(rtc, &mut loop_checker, "reset"); + } + } + + /// Enables an RTC interrupt. + #[inline] + fn enable_interrupt(rtc: &Rtc) { + I::enable(rtc); + } + + /// Returns whether an RTC interrupt has been triggered. + #[inline] + fn check_interrupt_flag(rtc: &Rtc) -> bool { + I::check_flag(rtc) + } + + /// Clears an RTC interrupt flag so the ISR will not be called again + /// immediately. + #[inline] + fn clear_interrupt_flag(rtc: &Rtc) { + I::clear_flag(rtc); + } + + /// Waits for any register syncing to be completed, or returns immediately + /// if no currently syncing. + #[inline] + fn sync(rtc: &Rtc) { + let mut loop_checker = LoopChecker::default(); + + while Self::sync_busy(rtc) { + Self::check_loop(rtc, &mut loop_checker, "sync"); + } + } + + /// Disables the RTC. + #[inline] + fn disable(rtc: &Rtc) { + // SYNC: Write + Self::sync(rtc); + // NOTE: This register and field are the same in all modes. + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + rtc.mode0().ctrl().modify(|_, w| w.enable().clear_bit()); + #[hal_cfg("rtc-d5x")] + rtc.mode0().ctrla().modify(|_, w| w.enable().clear_bit()); + } + + /// Enables the RTC. + #[inline] + fn enable(rtc: &Rtc) { + // SYNC: Write + Self::sync(rtc); + // NOTE: This register and field are the same in all modes. + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + rtc.mode0().ctrl().modify(|_, w| w.enable().set_bit()); + #[hal_cfg("rtc-d5x")] + rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); + } + + /// Returns whether the RTC is enabled. + #[inline] + fn is_enabled(rtc: &Rtc) -> bool { + // SYNC: Write (we just read though) + // NOTE: This register and field are the same in all modes. + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + return rtc.mode0().ctrl().read().enable().bit_is_set(); + #[hal_cfg("rtc-d5x")] + return rtc.mode0().ctrla().read().enable().bit_is_set(); + } + + /// Waits until the COUNT register changes. + /// + /// Note that this may not necessarily be the next tick due sync delay. + /// Beware that this will wait forever if the RTC is disabled! + #[inline] + fn wait_for_count_change(rtc: &Rtc) -> Self::Count { + let mut last_count = Self::count(rtc); + + let mut loop_checker = LoopChecker::default(); + loop { + let count = Self::count(rtc); + + if count != last_count { + Self::check_for_clock_anomaly(rtc, last_count, count); + + break count; + } + + Self::check_loop(rtc, &mut loop_checker, "wait_for_count_change"); + + last_count = count; + } + } +} + +#[hal_macro_helper] +pub mod mode0 { + use super::*; + + create_rtc_interrupt!(mode0, Compare0, cmp0); + #[hal_cfg("rtc-d5x")] + create_rtc_interrupt!(mode0, Compare1, cmp1); + #[hal_cfg("rtc-d5x")] + create_rtc_interrupt!(mode0, Overflow, ovf); + + /// The RTC operating in MODE0 (23-bit COUNT) + pub struct RtcMode0; + + impl RtcMode for RtcMode0 { + type Count = u32; + const HALF_PERIOD: Self::Count = 0x8000_0000; + const MIN_COMPARE_TICKS: Self::Count = 5; + + // TODO: Test code + fn check_loop(rtc: &Rtc, checker: &mut LoopChecker, item: &str) { + if checker.too_many() { + panic!( + "Took too long in '{item}' with count 0x{:X} and cmp 0x{:X}. Int flags: 0x{:X}, Enabled: {:?}", + Self::count(rtc), + Self::get_compare(rtc, 0), + rtc.mode0().intflag().read().bits(), + Self::is_enabled(rtc), + ); + } + } + + // TODO: Test code + fn check_for_clock_anomaly(rtc: &Rtc, last_count: Self::Count, count: Self::Count) { + if last_count > 0 && count != last_count.wrapping_add(4) { + panic!("Clock anomaly at 0x{count:X} with previous step at 0x{last_count:X} with cmp 0x{:X}", Self::get_compare(rtc, 0)); + } + } + + #[inline] + unsafe fn set_mode(rtc: &Rtc) { + // SYNC: Write + Self::sync(rtc); + // NOTE: This register and field are the same in all modes. + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + rtc.mode0().ctrl().modify(|_, w| w.mode().count32()); + #[hal_cfg("rtc-d5x")] + rtc.mode0().ctrla().modify(|_, w| w.mode().count32()); + } + + #[inline] + unsafe fn set_compare(rtc: &Rtc, number: usize, value: Self::Count) { + // SYNC: Write + Self::sync(rtc); + rtc.mode0().comp(number).write(|w| w.comp().bits(value)); + } + + #[inline] + fn get_compare(rtc: &Rtc, number: usize) -> Self::Count { + // SYNC: Write (we just read though) + rtc.mode0().comp(number).read().bits() + } + + #[inline] + fn start_and_initialize(rtc: &Rtc) { + Self::enable(rtc); + + // Enable counter sync on SAMx5x, the counter cannot be read otherwise. + #[hal_cfg("rtc-d5x")] + { + // Enable counter synchronization + // SYNC: Write + Self::sync(rtc); + #[hal_cfg("rtc-d5x")] + rtc.mode0().ctrla().modify(|_, w| w.countsync().set_bit()); + + // Errata: The first read of the count is incorrect so we need to read it + // then wait for it to change. + Self::wait_for_count_change(rtc); + } + } + + #[inline] + fn count(rtc: &Rtc) -> Self::Count { + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + { + // Request syncing of the COUNT register. + // SYNC: None + rtc.mode0().readreq().modify(|_, w| w.rreq().set_bit()); + } + + // SYNC: Read/Write + Self::sync(rtc); + rtc.mode0().count().read().bits() + } + + #[inline] + fn sync_busy(rtc: &Rtc) -> bool { + // SYNC: None + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + return rtc.mode0().status().read().syncbusy().bit_is_set(); + + // SYNC: None + #[hal_cfg("rtc-d5x")] + return rtc.mode0().syncbusy().read().bits() != 0; + } + } + + // The SAMD11/21 chips do not feature a second compare in MODE0. + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + crate::__internal_basic_backend!(RtcBackend, RtcMode0, Compare0); + + #[hal_cfg("rtc-d5x")] + crate::__internal_half_period_counting_backend!( + RtcBackend, RtcMode0, Compare0, Compare1, Overflow + ); +} + +#[hal_macro_helper] +pub mod mode1 { + use super::*; + + create_rtc_interrupt!(mode1, Compare0, cmp0); + create_rtc_interrupt!(mode1, Compare1, cmp1); + create_rtc_interrupt!(mode1, Overflow, ovf); + + /// The RTC operating in MODE1 (16-bit COUNT) + pub struct RtcMode1; + + impl RtcMode for RtcMode1 { + type Count = u16; + const HALF_PERIOD: Self::Count = 0x8000; + const MIN_COMPARE_TICKS: Self::Count = 5; + + // TODO: Test code + fn check_loop(rtc: &Rtc, checker: &mut LoopChecker, item: &str) { + if checker.too_many() { + panic!( + "Took too long in '{item}' with count 0x{:X} and cmp 0x{:X}. Int flags: 0x{:X}, Enabled: {:?}", + Self::count(rtc), + Self::get_compare(rtc, 0), + rtc.mode1().intflag().read().bits(), + Self::is_enabled(rtc), + ); + } + } + + // TODO: Test code + fn check_for_clock_anomaly(rtc: &Rtc, last_count: Self::Count, count: Self::Count) { + if last_count > 0 && count != last_count.wrapping_add(4) { + panic!("Clock anomaly at 0x{count:X} with previous step at 0x{last_count:X} with cmp 0x{:X}", Self::get_compare(rtc, 0)); + } + } + + #[inline] + unsafe fn set_mode(rtc: &Rtc) { + // SYNC: Write + Self::sync(rtc); + // NOTE: This register and field are the same in all modes. + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + rtc.mode0().ctrl().modify(|_, w| w.mode().count16()); + #[hal_cfg("rtc-d5x")] + rtc.mode0().ctrla().modify(|_, w| w.mode().count16()); + + // Set the mode 1 period + // SYNC: Write + Self::sync(rtc); + rtc.mode1().per().write(|w| w.bits(0xFFFF)); + } + + #[inline] + unsafe fn set_compare(rtc: &Rtc, number: usize, value: Self::Count) { + // SYNC: Write + Self::sync(rtc); + rtc.mode1().comp(number).write(|w| w.comp().bits(value)); + } + + #[inline] + fn get_compare(rtc: &Rtc, number: usize) -> Self::Count { + // SYNC: Write (we just read though) + rtc.mode1().comp(number).read().bits() + } + + #[inline] + fn start_and_initialize(rtc: &Rtc) { + Self::enable(rtc); + + // Enable counter sync on SAMx5x, the counter cannot be read otherwise. + #[hal_cfg("rtc-d5x")] + { + // Enable counter synchronization + // SYNC: Write + Self::sync(rtc); + rtc.mode1().ctrla().modify(|_, w| w.countsync().set_bit()); + + // Errata: The first read of the count is incorrect so we need to read it + // then wait for it to change. + Self::wait_for_count_change(rtc); + } + } + + #[inline] + fn count(rtc: &Rtc) -> Self::Count { + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + { + // Request syncing of the COUNT register. + // SYNC: None + rtc.mode1().readreq().modify(|_, w| w.rreq().set_bit()); + } + + // SYNC: Read/Write + Self::sync(rtc); + rtc.mode1().count().read().bits() + } + + #[inline] + fn sync_busy(rtc: &Rtc) -> bool { + // SYNC: None + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + return rtc.mode1().status().read().syncbusy().bit_is_set(); + + // SYNC: None + #[hal_cfg("rtc-d5x")] + return rtc.mode1().syncbusy().read().bits() != 0; + } + } + + crate::__internal_half_period_counting_backend!( + RtcBackend, RtcMode1, Compare0, Compare1, Overflow + ); +} From d5a42201ec6dd14c5b6f82bad5ac7a3c9f64cd7e Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Wed, 11 Dec 2024 13:49:30 -0500 Subject: [PATCH 06/13] feat(rtic-v2-rtc-monotonics): Removes the monitor instrumentation now that the monotonics have been thoroughly tested. --- hal/src/rtc/rtic/modes.rs | 88 ++------------------------------------- 1 file changed, 3 insertions(+), 85 deletions(-) diff --git a/hal/src/rtc/rtic/modes.rs b/hal/src/rtc/rtic/modes.rs index eb170d97a1a..12d55e87194 100644 --- a/hal/src/rtc/rtic/modes.rs +++ b/hal/src/rtc/rtic/modes.rs @@ -14,19 +14,6 @@ use crate::pac; use atsamd_hal_macros::hal_macro_helper; use pac::Rtc; -// TODO: Test code -#[derive(Default)] -pub struct LoopChecker { - count: usize, -} -impl LoopChecker { - pub fn too_many(&mut self) -> bool { - self.count += 1; - - self.count > 0x800000 - } -} - /// Type-level enum for RTC interrupts. pub trait RtcInterrupt { /// Enable this interrupt. @@ -78,10 +65,6 @@ pub trait RtcMode { /// in order to trigger. const MIN_COMPARE_TICKS: Self::Count; - // TODO: Test code - fn check_loop(rtc: &Rtc, checker: &mut LoopChecker, item: &str); - fn check_for_clock_anomaly(rtc: &Rtc, last_count: Self::Count, count: Self::Count); - /// Sets this mode in the CTRL register. unsafe fn set_mode(rtc: &Rtc); /// Sets a compare value. @@ -108,16 +91,11 @@ pub trait RtcMode { rtc.mode0().ctrla().modify(|_, w| w.swrst().set_bit()); // Wait for the reset to complete - let mut loop_checker = LoopChecker::default(); // SYNC: Write (we just read though) #[hal_cfg(any("rtc-d11", "rtc-d21"))] - while rtc.mode0().ctrl().read().swrst().bit_is_set() { - Self::check_loop(rtc, &mut loop_checker, "reset"); - } + while rtc.mode0().ctrl().read().swrst().bit_is_set() {} #[hal_cfg("rtc-d5x")] - while rtc.mode0().ctrla().read().swrst().bit_is_set() { - Self::check_loop(rtc, &mut loop_checker, "reset"); - } + while rtc.mode0().ctrla().read().swrst().bit_is_set() {} } /// Enables an RTC interrupt. @@ -143,11 +121,7 @@ pub trait RtcMode { /// if no currently syncing. #[inline] fn sync(rtc: &Rtc) { - let mut loop_checker = LoopChecker::default(); - - while Self::sync_busy(rtc) { - Self::check_loop(rtc, &mut loop_checker, "sync"); - } + while Self::sync_busy(rtc) {} } /// Disables the RTC. @@ -174,17 +148,6 @@ pub trait RtcMode { rtc.mode0().ctrla().modify(|_, w| w.enable().set_bit()); } - /// Returns whether the RTC is enabled. - #[inline] - fn is_enabled(rtc: &Rtc) -> bool { - // SYNC: Write (we just read though) - // NOTE: This register and field are the same in all modes. - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - return rtc.mode0().ctrl().read().enable().bit_is_set(); - #[hal_cfg("rtc-d5x")] - return rtc.mode0().ctrla().read().enable().bit_is_set(); - } - /// Waits until the COUNT register changes. /// /// Note that this may not necessarily be the next tick due sync delay. @@ -193,18 +156,13 @@ pub trait RtcMode { fn wait_for_count_change(rtc: &Rtc) -> Self::Count { let mut last_count = Self::count(rtc); - let mut loop_checker = LoopChecker::default(); loop { let count = Self::count(rtc); if count != last_count { - Self::check_for_clock_anomaly(rtc, last_count, count); - break count; } - Self::check_loop(rtc, &mut loop_checker, "wait_for_count_change"); - last_count = count; } } @@ -228,26 +186,6 @@ pub mod mode0 { const HALF_PERIOD: Self::Count = 0x8000_0000; const MIN_COMPARE_TICKS: Self::Count = 5; - // TODO: Test code - fn check_loop(rtc: &Rtc, checker: &mut LoopChecker, item: &str) { - if checker.too_many() { - panic!( - "Took too long in '{item}' with count 0x{:X} and cmp 0x{:X}. Int flags: 0x{:X}, Enabled: {:?}", - Self::count(rtc), - Self::get_compare(rtc, 0), - rtc.mode0().intflag().read().bits(), - Self::is_enabled(rtc), - ); - } - } - - // TODO: Test code - fn check_for_clock_anomaly(rtc: &Rtc, last_count: Self::Count, count: Self::Count) { - if last_count > 0 && count != last_count.wrapping_add(4) { - panic!("Clock anomaly at 0x{count:X} with previous step at 0x{last_count:X} with cmp 0x{:X}", Self::get_compare(rtc, 0)); - } - } - #[inline] unsafe fn set_mode(rtc: &Rtc) { // SYNC: Write @@ -343,26 +281,6 @@ pub mod mode1 { const HALF_PERIOD: Self::Count = 0x8000; const MIN_COMPARE_TICKS: Self::Count = 5; - // TODO: Test code - fn check_loop(rtc: &Rtc, checker: &mut LoopChecker, item: &str) { - if checker.too_many() { - panic!( - "Took too long in '{item}' with count 0x{:X} and cmp 0x{:X}. Int flags: 0x{:X}, Enabled: {:?}", - Self::count(rtc), - Self::get_compare(rtc, 0), - rtc.mode1().intflag().read().bits(), - Self::is_enabled(rtc), - ); - } - } - - // TODO: Test code - fn check_for_clock_anomaly(rtc: &Rtc, last_count: Self::Count, count: Self::Count) { - if last_count > 0 && count != last_count.wrapping_add(4) { - panic!("Clock anomaly at 0x{count:X} with previous step at 0x{last_count:X} with cmp 0x{:X}", Self::get_compare(rtc, 0)); - } - } - #[inline] unsafe fn set_mode(rtc: &Rtc) { // SYNC: Write From 2cdf373310f52498da25fa5c92e85e9348137471 Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Thu, 19 Dec 2024 16:14:29 -0500 Subject: [PATCH 07/13] feat(rtic-v2-rtc-monotonics): Factors out the low-level RTC abstractions and moves RTIC-specific things, so they can be used for other purposes as well. --- hal/src/rtc/mod.rs | 2 + hal/src/rtc/{rtic => }/modes.rs | 112 ++++++++++++++------------------ hal/src/rtc/rtic/backends.rs | 31 ++++----- hal/src/rtc/rtic/mod.rs | 80 +++++++++++++++++------ 4 files changed, 128 insertions(+), 97 deletions(-) rename hal/src/rtc/{rtic => }/modes.rs (77%) diff --git a/hal/src/rtc/mod.rs b/hal/src/rtc/mod.rs index a3a559a7e4c..5520d587900 100644 --- a/hal/src/rtc/mod.rs +++ b/hal/src/rtc/mod.rs @@ -14,6 +14,8 @@ use core::marker::PhantomData; #[cfg(feature = "sdmmc")] use embedded_sdmmc::{TimeSource, Timestamp}; +mod modes; + #[cfg(feature = "rtic")] pub mod rtic; diff --git a/hal/src/rtc/rtic/modes.rs b/hal/src/rtc/modes.rs similarity index 77% rename from hal/src/rtc/rtic/modes.rs rename to hal/src/rtc/modes.rs index 12d55e87194..687ce7bf2cb 100644 --- a/hal/src/rtc/rtic/modes.rs +++ b/hal/src/rtc/modes.rs @@ -1,14 +1,32 @@ -//! Abstraction of basic RTC functions for each mode. +//! Provides low-level access to the [Real Time Clock (RTC)](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-E17D8859-D42B-4B0E-9B81-76168A0C38AC.html) peripheral on ATSAMD chips. +//! The main abstraction is the [`RtcMode`] trait, which exposes +//! static/associated functions to use the RTC in in a particular mode. All functions are marked as [`inline`](https://matklad.github.io/2021/07/09/inline-in-rust.html) +//! so that this should be a zero cost abstraction. //! -//! As explained in the [datasheets](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-ABE2D37F-8125-4279-9955-BC3900046CFF.html), -//! reading a read-synced register may result in -//! an old value, which we try to avoid by ensuring that SYNCBUSY is clear -//! before reading. A write to a write-synced register will be discarded if -//! syncing is happening during the write. As such, we also ensure that SYNCBUSY -//! is clear before writing to a synced register. Every register access should -//! be prefaced by a `SYNC`` comment indicating the required synchronization, -//! the presence of this comment indicates that this access was checked and -//! accounted for. +//! This module is intended to serve as the basis for the safe +//! [`Rtc`](crate::rtc::Rtc) abstraction as well as RTIC and embassy time +//! drivers. +//! +//! Abstraction benefits: +//! - Handles all RTC register accesses. +//! - Handles RTC [register synchronization](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-ABE2D37F-8125-4279-9955-BC3900046CFF.html) +//! - Handles ATSAMD chip variations. +//! +//! The idea is that various higher-level users of these abstractions will not +//! have to handle these low-level aspects of using the RTC. However, this +//! module does not present a safe interface. For example, many of the methods +//! in [`RtcMode`] assume that the RTC has already been put into the correct +//! mode (using [`RtcMode::set_mode`]), but without enforcing this in any way. + +// As explained in the [datasheets](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-ABE2D37F-8125-4279-9955-BC3900046CFF.html), +// reading a read-synced register may result in +// an old value, which we try to avoid by ensuring that SYNCBUSY is clear +// before reading. A write to a write-synced register will be discarded if +// syncing is happening during the write. As such, we also ensure that SYNCBUSY +// is clear before writing to a synced register. Throughout the crate, every +// register access should be prefaced by a `SYNC` comment indicating the +// required synchronization. The presence of this comment signals that this +// access was checked in the datasheet and accounted for. use crate::pac; use atsamd_hal_macros::hal_macro_helper; @@ -29,7 +47,7 @@ pub trait RtcInterrupt { macro_rules! create_rtc_interrupt { ($mode:ident, $name:ident, $bit:ident) => { /// Type-level variant for the $name interrupt in $mode. - enum $name {} + pub enum $name {} impl RtcInterrupt for $name { #[inline] fn enable(rtc: &Rtc) { @@ -71,8 +89,6 @@ pub trait RtcMode { unsafe fn set_compare(rtc: &Rtc, number: usize, value: Self::Count); /// Retrieves a compare from the register. fn get_compare(rtc: &Rtc, number: usize) -> Self::Count; - /// Starts the RTC and does any required initialization for this mode. - fn start_and_initialize(rtc: &Rtc); /// Returns the current synced COUNT value. fn count(rtc: &Rtc) -> Self::Count; /// Returns whether register syncing is currently happening. @@ -98,6 +114,26 @@ pub trait RtcMode { while rtc.mode0().ctrla().read().swrst().bit_is_set() {} } + /// Starts the RTC and does any required initialization for this mode. + #[inline] + fn start_and_initialize(rtc: &Rtc) { + Self::enable(rtc); + + // Enable counter sync on SAMx5x, the counter cannot be read otherwise. + #[hal_cfg("rtc-d5x")] + { + // Enable counter synchronization + // NOTE: This register and field are the same in all modes. + // SYNC: Write + Self::sync(rtc); + rtc.mode0().ctrla().modify(|_, w| w.countsync().set_bit()); + + // Errata: The first read of the count is incorrect so we need to read it + // then wait for it to change. + Self::wait_for_count_change(rtc); + } + } + /// Enables an RTC interrupt. #[inline] fn enable_interrupt(rtc: &Rtc) { @@ -210,25 +246,6 @@ pub mod mode0 { rtc.mode0().comp(number).read().bits() } - #[inline] - fn start_and_initialize(rtc: &Rtc) { - Self::enable(rtc); - - // Enable counter sync on SAMx5x, the counter cannot be read otherwise. - #[hal_cfg("rtc-d5x")] - { - // Enable counter synchronization - // SYNC: Write - Self::sync(rtc); - #[hal_cfg("rtc-d5x")] - rtc.mode0().ctrla().modify(|_, w| w.countsync().set_bit()); - - // Errata: The first read of the count is incorrect so we need to read it - // then wait for it to change. - Self::wait_for_count_change(rtc); - } - } - #[inline] fn count(rtc: &Rtc) -> Self::Count { #[hal_cfg(any("rtc-d11", "rtc-d21"))] @@ -254,15 +271,6 @@ pub mod mode0 { return rtc.mode0().syncbusy().read().bits() != 0; } } - - // The SAMD11/21 chips do not feature a second compare in MODE0. - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - crate::__internal_basic_backend!(RtcBackend, RtcMode0, Compare0); - - #[hal_cfg("rtc-d5x")] - crate::__internal_half_period_counting_backend!( - RtcBackend, RtcMode0, Compare0, Compare1, Overflow - ); } #[hal_macro_helper] @@ -310,24 +318,6 @@ pub mod mode1 { rtc.mode1().comp(number).read().bits() } - #[inline] - fn start_and_initialize(rtc: &Rtc) { - Self::enable(rtc); - - // Enable counter sync on SAMx5x, the counter cannot be read otherwise. - #[hal_cfg("rtc-d5x")] - { - // Enable counter synchronization - // SYNC: Write - Self::sync(rtc); - rtc.mode1().ctrla().modify(|_, w| w.countsync().set_bit()); - - // Errata: The first read of the count is incorrect so we need to read it - // then wait for it to change. - Self::wait_for_count_change(rtc); - } - } - #[inline] fn count(rtc: &Rtc) -> Self::Count { #[hal_cfg(any("rtc-d11", "rtc-d21"))] @@ -353,8 +343,4 @@ pub mod mode1 { return rtc.mode1().syncbusy().read().bits() != 0; } } - - crate::__internal_half_period_counting_backend!( - RtcBackend, RtcMode1, Compare0, Compare1, Overflow - ); } diff --git a/hal/src/rtc/rtic/backends.rs b/hal/src/rtc/rtic/backends.rs index d4b7b1b8e2f..ca5063d312c 100644 --- a/hal/src/rtc/rtic/backends.rs +++ b/hal/src/rtc/rtic/backends.rs @@ -29,7 +29,7 @@ macro_rules! __internal_backend_methods { ) -> bool { let d = a.wrapping_sub(b); - d >= <$mode>::HALF_PERIOD + d >= <$mode as RtcModeMonotonic>::HALF_PERIOD } // Ensure that the COUNT is at least the compare value @@ -94,13 +94,13 @@ macro_rules! __internal_backend_methods { #[doc(hidden)] #[macro_export] macro_rules! __internal_basic_backend { - ($name:ident, $mode:ty, $rtic_int:ty) => { + ($name:ident, $mode:ty, $mode_num:literal, $rtic_int:ty) => { use atsamd_hal_macros::hal_macro_helper; use rtic_time::timer_queue::{TimerQueue, TimerQueueBackend}; use $crate::pac; - use $crate::rtc::rtic::modes::RtcMode; + use $crate::rtc::modes::RtcMode; - /// Basic RTC-based [`TimerQueueBackend`] without period counting. + #[doc = concat!("Basic RTC-based [`TimerQueueBackend`] without period counting that uses the RTC in mode ", stringify!($mode_num), ".")] pub struct $name; static RTC_TQ: TimerQueue<$name> = TimerQueue::new(); @@ -153,8 +153,10 @@ macro_rules! __internal_basic_backend { // couple of ticks, so delay it a bit if it is too close. // This is not mentioned in the documentation or errata, but is known to be an // issue for other microcontrollers as well (e.g. nRF family). - if instant.saturating_sub(Self::now()) < <$mode>::MIN_COMPARE_TICKS { - instant = instant.wrapping_add(<$mode>::MIN_COMPARE_TICKS) + if instant.saturating_sub(Self::now()) + < <$mode as RtcModeMonotonic>::MIN_COMPARE_TICKS + { + instant = instant.wrapping_add(<$mode as RtcModeMonotonic>::MIN_COMPARE_TICKS) } unsafe { <$mode>::set_compare(&rtc, 0, instant) }; @@ -178,7 +180,7 @@ macro_rules! __internal_basic_backend { #[doc(hidden)] #[macro_export] macro_rules! __internal_half_period_counting_backend { - ($name:ident, $mode:ty, $rtic_int:ty, $half_period_int:ty, $overflow_int:ty) => { + ($name:ident, $mode:ty, $mode_num:literal, $rtic_int:ty, $half_period_int:ty, $overflow_int:ty) => { use atsamd_hal_macros::hal_macro_helper; use core::sync::atomic::Ordering; use portable_atomic::AtomicU64; @@ -187,7 +189,6 @@ macro_rules! __internal_half_period_counting_backend { timer_queue::{TimerQueue, TimerQueueBackend}, }; use $crate::pac; - use $crate::rtc::rtic::modes::RtcMode; struct TimerValue(<$mode as RtcMode>::Count); impl rtic_time::half_period_counter::TimerValue for TimerValue { @@ -199,8 +200,7 @@ macro_rules! __internal_half_period_counting_backend { } } - /// An RTC-based [`TimerQueueBackend`] that vastly extends the overall monotonic - /// period using [half-period counting](rtic_time::half_period_counter). + #[doc = concat!("An RTC-based [`TimerQueueBackend`] using [half-period counting](rtic_time::half_period_counter) that uses the RTC in mode ", stringify!($mode_num), ".")] pub struct $name; static RTC_PERIOD_COUNT: AtomicU64 = AtomicU64::new(0); @@ -215,7 +215,7 @@ macro_rules! __internal_half_period_counting_backend { init_compares = { // Configure the compare registers <$mode>::set_compare(&rtc, 0, 0); - <$mode>::set_compare(&rtc, 1, <$mode>::HALF_PERIOD); + <$mode>::set_compare(&rtc, 1, <$mode as RtcModeMonotonic>::HALF_PERIOD); } statics = { // Make sure period counter is synced with the timer value @@ -263,7 +263,7 @@ macro_rules! __internal_half_period_counting_backend { // Ensure that the COUNT has crossed // Due to syncing delay this may not be the case initially - while <$mode>::count(&rtc) < <$mode>::HALF_PERIOD {} + while <$mode>::count(&rtc) < <$mode as RtcModeMonotonic>::HALF_PERIOD {} } if <$mode>::check_interrupt_flag::<$overflow_int>(&rtc) { <$mode>::clear_interrupt_flag::<$overflow_int>(&rtc); @@ -272,7 +272,7 @@ macro_rules! __internal_half_period_counting_backend { // Ensure that the COUNT has wrapped // Due to syncing delay this may not be the case initially - while <$mode>::count(&rtc) > <$mode>::HALF_PERIOD {} + while <$mode>::count(&rtc) > <$mode as RtcModeMonotonic>::HALF_PERIOD {} } } @@ -298,8 +298,9 @@ macro_rules! __internal_half_period_counting_backend { // close. This is not mentioned in the documentation // or errata, but is known to be an issue for other // microcontrollers as well (e.g. nRF family). - if diff < <$mode>::MIN_COMPARE_TICKS.into() { - instant = instant.wrapping_add(<$mode>::MIN_COMPARE_TICKS.into()) + if diff < <$mode as RtcModeMonotonic>::MIN_COMPARE_TICKS.into() { + instant = instant + .wrapping_add(<$mode as RtcModeMonotonic>::MIN_COMPARE_TICKS.into()) } (instant & MAX) as <$mode as RtcMode>::Count diff --git a/hal/src/rtc/rtic/mod.rs b/hal/src/rtc/rtic/mod.rs index 4b1b973d386..cf07d18c655 100644 --- a/hal/src/rtc/rtic/mod.rs +++ b/hal/src/rtc/rtic/mod.rs @@ -35,13 +35,6 @@ //! platforms offers nothing except more undesirable more frequent interrupts, //! so its use is not recommended. //! -//! NOTE: These monotonics currently live in the HAL for testing and refinement -//! purposes. The intention is to eventually move them to the -//! [`rtic-monotonics`](https://docs.rs/rtic-monotonics/latest/rtic_monotonics/) crate, -//! which is a central location for peripheral-based RTIC monotonics for various -//! microcontroller families. As such, be aware that this module could disappear -//! at any time in the future. -//! //! # Choosing a mode //! //! The overall monotonic period (i.e. the time before the monotonic rolls over @@ -74,14 +67,15 @@ //! # RTC clock selection //! //! Prior to starting the monotonic, the RTC clock source must be configured -//! using the [`atsamd-hal`](https://docs.rs/atsamd-hal/latest/atsamd_hal/index.html) -//! crate. On SAMD11/21 platforms, the RTC clock must be setup as a -//! [generic clock](https://docs.rs/atsamd-hal/latest/atsamd_hal/clock/struct.GenericClockController.html). +//! using [`clocks`](crate::clock). On SAMD11/21 platforms, the RTC clock must +//! be setup as a [generic clock](crate::clock::GenericClockController). //! On SAMx5x platforms the RTC clock must be selected from either the 1.1024 //! kHz clock or the 32.768 kHz clock, either of which can be internal or //! external. //! -//! TODO: Update HAL links and [should proof of RTC clock setup be required?](https://github.com/atsamd-rs/atsamd/issues/765#issuecomment-2526974751) +//! NOTE: Eventually, starting the monotonic will require proof that the RTC +//! clock has been configured. However, this requires v2 of the clock API for +//! SAMx5x chips, which is not yet fully supported in the rest of the HAL. //! //! # Usage //! @@ -93,9 +87,13 @@ //! be passed to the macro as the second argument. //! //! Sometime during initialization, the monotonic also must be started by called -//! the `start` method on the created monotonic. The [`Rtc`](pac::Rtc) struct -//! must be passed to the `start` to ensure that the monotonic has complete -//! control of the RTC. +//! the `start` method on the created monotonic. The [`Rtc`](crate::pac::Rtc) +//! peripheral struct must be passed to `start` to ensure that the monotonic +//! has complete control of the RTC. +//! +//! Note that the macro creates the RTC interrupt handler, and starting the +//! monotonic enables RTC interrupts in the NVIC, so that this does not need to +//! be done manually. //! //! # Example //! @@ -138,6 +136,8 @@ //! changes. This is true regardless of the clock rate used, as the //! synchronization delay scales along with the clock period. +// TODO: Need to revisit this and either modernize (e.g. it does not use period +// counting) it or remove it. mod v1 { use crate::rtc::{Count32Mode, Rtc}; use rtic_monotonic::Monotonic; @@ -180,10 +180,9 @@ mod v1 { } mod backends; -mod modes; -pub use modes::mode0::RtcBackend as RtcMode0Backend; -pub use modes::mode1::RtcBackend as RtcMode1Backend; +use super::modes::{mode0::RtcMode0, mode1::RtcMode1, RtcMode}; +use atsamd_hal_macros::hal_macro_helper; pub mod prelude { pub use super::rtc_clock; @@ -228,6 +227,50 @@ pub mod rtc_clock { } } +trait RtcModeMonotonic: RtcMode { + /// The COUNT value representing a half period. + const HALF_PERIOD: Self::Count; + /// The minimum number of ticks that compares need to be ahead of the COUNT + /// in order to trigger. + const MIN_COMPARE_TICKS: Self::Count; +} +impl RtcModeMonotonic for RtcMode0 { + const HALF_PERIOD: Self::Count = 0x8000_0000; + const MIN_COMPARE_TICKS: Self::Count = 5; +} +impl RtcModeMonotonic for RtcMode1 { + const HALF_PERIOD: Self::Count = 0x8000; + const MIN_COMPARE_TICKS: Self::Count = 5; +} + +#[hal_macro_helper] +mod mode0 { + use super::*; + use crate::rtc::modes::mode0::Compare0; + #[hal_cfg("rtc-d5x")] + use crate::rtc::modes::mode0::{Compare1, Overflow}; + + // The SAMD11/21 chips do not feature a second compare in MODE0. + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + crate::__internal_basic_backend!(RtcBackend, RtcMode0, 0, Compare0); + #[hal_cfg("rtc-d5x")] + crate::__internal_half_period_counting_backend!( + RtcBackend, RtcMode0, 0, Compare0, Compare1, Overflow + ); +} + +mod mode1 { + use super::*; + use crate::rtc::modes::mode1::{Compare0, Compare1, Overflow}; + + crate::__internal_half_period_counting_backend!( + RtcBackend, RtcMode1, 1, Compare0, Compare1, Overflow + ); +} + +pub use mode0::RtcBackend as RtcMode0Backend; +pub use mode1::RtcBackend as RtcMode1Backend; + #[doc(hidden)] #[macro_export] macro_rules! __internal_create_rtc_interrupt { @@ -304,8 +347,7 @@ const fn cortex_logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) } -/// This function was copied from the private function in `rtic-monotonics`, -/// so that should be used when the monotonics move there. +/// This function was copied from the private function in `rtic-monotonics`. unsafe fn set_monotonic_prio(prio_bits: u8, interrupt: impl cortex_m::interrupt::InterruptNumber) { extern "C" { static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8; From 305d05f7f7094b3de69fd034bada0c798914d875 Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Fri, 20 Dec 2024 16:28:49 -0500 Subject: [PATCH 08/13] feat(rtic-v2-rtc-monotonics): Only includes a single RTC monotonic, using the best RTC mode for the chip variant. Updates the documentation accordingly. --- hal/src/rtc/rtic/mod.rs | 164 ++++++++++++++++------------------------ 1 file changed, 65 insertions(+), 99 deletions(-) diff --git a/hal/src/rtc/rtic/mod.rs b/hal/src/rtc/rtic/mod.rs index cf07d18c655..8e37c48ae72 100644 --- a/hal/src/rtc/rtic/mod.rs +++ b/hal/src/rtc/rtic/mod.rs @@ -4,65 +4,8 @@ //! Enabling the `rtic` feature is required to use this module. //! //! For RTIC v1, the old [`rtic_monotonic::Monotonic`] trait is implemented for -//! [`Rtc`](crate::rtc::Rtc) in [`Count32Mode`](crate::rtc::Count32Mode). -//! -//! The items here provide monotonics for RTIC v2. Two monotonics are provided: -//! one that uses the RTC in mode 0 (32-bit hardware counter), and another that -//! uses the RTC in mode 1 (16-bit hardware counter). -//! -//! Which mode is used can influence the total monotonic period before the its -//! time counter overflows and rolls back to zero time. This rollover violates -//! the contract of the monotonic, which is supposed to count time -//! _monotonically_. As such, the rollover can cause strange behavior for tasks -//! scheduled near the time of the rollover. Hence, the monotonic should be -//! chosen to match the use case such that the monotonic will never roll over -//! during the expected total duration of program execution. -//! -//! For all chip variants, the mode 1 monotonic uses [half-period -//! counting](rtic_time::half_period_counter) to extend the monotonic counter to -//! 64-bits. This is enabled by the fact that the RTC for every variant has -//! at least two compare registers in mode 1. This greatly extends the total -//! monotonic period. However, in this mode, half-period counting interrupts -//! occur more frequently (see below) due to the hardware counter being only 16 -//! bits wide, so it is less efficient in that regard. -//! -//! For SAMD11/21 chips, the mode 0 monotonic only has a single compare register -//! so that half-period counting is not possible. This significantly reduces the -//! total monotonic period. The SAMx5x chips, however, feature two compare -//! registers in mode 0 so that half-period counting can be done. In the latter -//! case, the mode 0 monotonic has extremely infrequent half-period counting -//! interrupts and so is more efficient. In fact, the mode 1 monotonic on SAMx5x -//! platforms offers nothing except more undesirable more frequent interrupts, -//! so its use is not recommended. -//! -//! # Choosing a mode -//! -//! The overall monotonic period (i.e. the time before the monotonic rolls over -//! back to zero time) is as follows: -//! -//! | | 1 kHz clock | 32 kHz clock | -//! | ---------------------- | ------------------ | ------------------- | -//! | **Mode 0 (SAMD11/21)** | ~48 days | ~36 hours | -//! | **Mode 0 (SAMx5x)** | ~571 million years | ~17.8 million years | -//! | **Mode 1** | ~571 million years | ~17.8 million years | -//! -//! The half-period counting interrupt periods for the monotonics are: -//! -//! | | 1 kHz clock | 32 kHz clock | -//! | ---------------------- | ----------- | ------------ | -//! | **Mode 0 (SAMD11/21)** | Never | Never | -//! | **Mode 0 (SAMx5x)** | ~24 days | ~18 hours | -//! | **Mode 1** | 32 seconds | 1 second | -//! -//! The time resolution (i.e. the RTC tick time and shortest delay time) is as -//! follows: -//! -//! | | 1 kHz clock | 32 kHz clock | -//! | ------------ | ----------- | ------------ | -//! | **Any mode** | ~977 μs | ~31 μs | -//! -//! It should be apparent from these numbers that the mode 1 monotonic offers -//! nothing but more frequent interrupts on SAMx5x platforms. +//! [`Rtc`](crate::rtc::Rtc) in [`Count32Mode`](crate::rtc::Count32Mode). A +//! monotonic for RTIC v2 is provided here. //! //! # RTC clock selection //! @@ -73,23 +16,59 @@ //! kHz clock or the 32.768 kHz clock, either of which can be internal or //! external. //! -//! NOTE: Eventually, starting the monotonic will require proof that the RTC +//! **NOTE: Eventually, starting the monotonic will require proof that the RTC //! clock has been configured. However, this requires v2 of the clock API for -//! SAMx5x chips, which is not yet fully supported in the rest of the HAL. +//! SAMx5x chips, which is not yet fully supported in the rest of the HAL.** +//! +//! # RTC modes +//! +//! The RTC on all chip variants has two counter modes: mode 0 features a 32-bit +//! hardware counter, and mode 1 features a a 16-bit hardware counter but some +//! additional features. Part of the [`Monotonic`](rtic_time::Monotonic) +//! contract is that the monotonic should always count up and never roll over +//! back to time zero. However, even the 32-bit hardware counter will overflow +//! after about 36 hours using the faster clock rate, which is not acceptable. +//! +//! A technique known as [half-period counting +//! (HPC)](rtic_time::half_period_counter) is used to effectively increase the +//! montononic counter to be 64 bits wide in either mode. The result is a +//! monotonic that effectively counts up forever without rolling over. This +//! technique requires two compare registers, one for waking RTIC tasks and one +//! for HPC. The number of compare registers available on ATSAMD chips is as +//! follows: +//! +//! | | SAMD11/21 | SAMx5x | +//! | -----------| --------- | ------ | +//! | **Mode 0** | 1 | 2 | +//! | **Mode 1** | 2 | 4 | +//! +//! As a result, HPC can be done in mode 0 for SAMx5x chips but requires mode 1 +//! for SAMD11/21 variants. The monotonic provided for each variant uses the +//! appropriate RTC mode. +//! +//! The monotonics have the following specifications: +//! +//! | | 1 kHz clock | 32 kHz clock | +//! | ------------------------------------ | ------------------ | ------------------- | +//! | **Rollover period** | ~571 million years | ~17.8 million years | +//! | **HPC interrupt period (SAMD11/21)** | 32 seconds | 1 second | +//! | **HPC interrupt period (SAMx5x)** | ~24 days | ~18 hours | +//! | **Time resolution** | ~977 μs | ~31 μs | //! //! # Usage //! -//! A monotonic using the desired RTC mode should be created with the -//! appropriate [macro](crate::rtc::rtic::prelude). The first macro argument is -//! the name of the global structure that will implement +//! The monotonic should be created using the +//! [macro](prelude::rtc_monotonic), which is included in the +//! [prelude](crate::prelude). The first macro argument is the name of +//! the global structure that will implement //! [`Monotonic`](rtic_time::Monotonic). The RTC clock rate must be //! known at compile time, and so the appropriate type from [`rtc_clock`] must //! be passed to the macro as the second argument. //! -//! Sometime during initialization, the monotonic also must be started by called -//! the `start` method on the created monotonic. The [`Rtc`](crate::pac::Rtc) -//! peripheral struct must be passed to `start` to ensure that the monotonic -//! has complete control of the RTC. +//! Sometime during initialization, the monotonic also must be started by +//! calling the `start` method on the created monotonic. The +//! [`Rtc`](crate::pac::Rtc) peripheral struct must be passed to `start` to +//! ensure that the monotonic has complete control of the RTC. //! //! Note that the macro creates the RTC interrupt handler, and starting the //! monotonic enables RTC interrupts in the NVIC, so that this does not need to @@ -101,14 +80,14 @@ //! use atsamd_hal::prelude::*; //! //! // Create the monotonic struct named `Mono` -//! rtc_mode0_monotonic!(Mono, rtc_clock::Clock32k); +//! rtc_monotonic!(Mono, rtc_clock::Clock32k); //! //! fn init() { //! # // This is normally provided by the selected PAC //! # let rtc = unsafe { core::mem::transmute(()) }; //! # let mut mclk = unsafe { core::mem::transmute(()) }; //! # let mut osc32kctrl = unsafe { core::mem::transmute(()) }; -//! // Here the RTC clock source should be configured using the HAL +//! // Here the RTC clock source should be configured using the clocks API //! //! // Start the monotonic //! Mono::start(rtc); @@ -186,7 +165,7 @@ use atsamd_hal_macros::hal_macro_helper; pub mod prelude { pub use super::rtc_clock; - pub use crate::{rtc_mode0_monotonic, rtc_mode1_monotonic}; + pub use crate::rtc_monotonic; pub use rtic_time::Monotonic; pub use fugit::{self, ExtU32, ExtU32Ceil, ExtU64, ExtU64Ceil}; @@ -244,32 +223,29 @@ impl RtcModeMonotonic for RtcMode1 { } #[hal_macro_helper] -mod mode0 { +mod backend { use super::*; - use crate::rtc::modes::mode0::Compare0; - #[hal_cfg("rtc-d5x")] - use crate::rtc::modes::mode0::{Compare1, Overflow}; - // The SAMD11/21 chips do not feature a second compare in MODE0. + // For SAMD11/21 chips mode 1 is the only sensible option + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + use crate::rtc::modes::mode1::{Compare0, Compare1, Overflow}; + #[hal_cfg(any("rtc-d11", "rtc-d21"))] - crate::__internal_basic_backend!(RtcBackend, RtcMode0, 0, Compare0); - #[hal_cfg("rtc-d5x")] crate::__internal_half_period_counting_backend!( - RtcBackend, RtcMode0, 0, Compare0, Compare1, Overflow + RtcBackend, RtcMode1, 1, Compare0, Compare1, Overflow ); -} -mod mode1 { - use super::*; - use crate::rtc::modes::mode1::{Compare0, Compare1, Overflow}; + // For SAMx5x mode 0 is the best option + #[hal_cfg("rtc-d5x")] + use crate::rtc::modes::mode0::{Compare0, Compare1, Overflow}; + #[hal_cfg("rtc-d5x")] crate::__internal_half_period_counting_backend!( - RtcBackend, RtcMode1, 1, Compare0, Compare1, Overflow + RtcBackend, RtcMode0, 0, Compare0, Compare1, Overflow ); } -pub use mode0::RtcBackend as RtcMode0Backend; -pub use mode1::RtcBackend as RtcMode1Backend; +pub use backend::RtcBackend; #[doc(hidden)] #[macro_export] @@ -321,23 +297,13 @@ macro_rules! __internal_create_rtc_struct { }; } -/// Create an RTIC v2 monotonic that uses the RTC in mode 0. -/// -/// See the [`rtic`](crate::rtc::rtic) module for details. -#[macro_export] -macro_rules! rtc_mode0_monotonic { - ($name:ident, $clock_rate: ty) => { - $crate::__internal_create_rtc_struct!($name, RtcMode0Backend, $clock_rate); - }; -} - -/// Create an RTIC v2 monotonic that uses the RTC in mode 1. +/// Create an RTIC v2 monotonic that uses the RTC. /// /// See the [`rtic`](crate::rtc::rtic) module for details. #[macro_export] -macro_rules! rtc_mode1_monotonic { +macro_rules! rtc_monotonic { ($name:ident, $clock_rate: ty) => { - $crate::__internal_create_rtc_struct!($name, RtcMode1Backend, $clock_rate); + $crate::__internal_create_rtc_struct!($name, RtcBackend, $clock_rate); }; } From 77a863696886c1ae962ce208093963504e7a3e8d Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Fri, 20 Dec 2024 19:00:08 -0500 Subject: [PATCH 09/13] feat(rtic-v2-rtc-monotonics): Addresses some minor issues when checking things with the `build-all.py` script. * Renames the `wait_for_count_change` to `_wait_for_count_change` function name to suppress an unused warning for SAMD11/21 chips. * Fixes the `metro_m0` example `blinky_rtic.rs` to fix an RTIC v1 item location change. --- boards/metro_m0/examples/blinky_rtic.rs | 2 +- hal/src/rtc/mod.rs | 1 + hal/src/rtc/modes.rs | 4 ++-- hal/src/rtc/rtic/mod.rs | 14 +++++++++----- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/boards/metro_m0/examples/blinky_rtic.rs b/boards/metro_m0/examples/blinky_rtic.rs index a812a4ac357..0b21f48299c 100644 --- a/boards/metro_m0/examples/blinky_rtic.rs +++ b/boards/metro_m0/examples/blinky_rtic.rs @@ -19,7 +19,7 @@ mod app { use hal::clock::{ClockGenId, ClockSource, GenericClockController}; use hal::pac::Peripherals; use hal::prelude::*; - use hal::rtc::{Count32Mode, Duration, Rtc}; + use hal::rtc::{rtic::v1::Duration, Count32Mode, Rtc}; #[local] struct Local {} diff --git a/hal/src/rtc/mod.rs b/hal/src/rtc/mod.rs index 5520d587900..1551b531453 100644 --- a/hal/src/rtc/mod.rs +++ b/hal/src/rtc/mod.rs @@ -14,6 +14,7 @@ use core::marker::PhantomData; #[cfg(feature = "sdmmc")] use embedded_sdmmc::{TimeSource, Timestamp}; +#[cfg(feature = "rtic")] mod modes; #[cfg(feature = "rtic")] diff --git a/hal/src/rtc/modes.rs b/hal/src/rtc/modes.rs index 687ce7bf2cb..296eabfe90b 100644 --- a/hal/src/rtc/modes.rs +++ b/hal/src/rtc/modes.rs @@ -130,7 +130,7 @@ pub trait RtcMode { // Errata: The first read of the count is incorrect so we need to read it // then wait for it to change. - Self::wait_for_count_change(rtc); + Self::_wait_for_count_change(rtc); } } @@ -189,7 +189,7 @@ pub trait RtcMode { /// Note that this may not necessarily be the next tick due sync delay. /// Beware that this will wait forever if the RTC is disabled! #[inline] - fn wait_for_count_change(rtc: &Rtc) -> Self::Count { + fn _wait_for_count_change(rtc: &Rtc) -> Self::Count { let mut last_count = Self::count(rtc); loop { diff --git a/hal/src/rtc/rtic/mod.rs b/hal/src/rtc/rtic/mod.rs index 8e37c48ae72..374b6be786c 100644 --- a/hal/src/rtc/rtic/mod.rs +++ b/hal/src/rtc/rtic/mod.rs @@ -115,17 +115,21 @@ //! changes. This is true regardless of the clock rate used, as the //! synchronization delay scales along with the clock period. -// TODO: Need to revisit this and either modernize (e.g. it does not use period -// counting) it or remove it. -mod v1 { +/// Items for RTIC v1. +/// +/// TODO: This probably needs to be modernized (e.g. it does not implement +/// half-period counting) or deprecated/removed. +pub mod v1 { use crate::rtc::{Count32Mode, Rtc}; use rtic_monotonic::Monotonic; /// The RTC clock frequency in Hz. pub const CLOCK_FREQ: u32 = 32_768; - type Instant = fugit::Instant; - type Duration = fugit::Duration; + /// The [`fugit`] time instant. + pub type Instant = fugit::Instant; + /// The [`fugit`] time duration. + pub type Duration = fugit::Duration; impl Monotonic for Rtc { type Instant = Instant; From 1774c084db88cb3f228a96d76d556b44bd073233 Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Sun, 22 Dec 2024 18:39:33 -0500 Subject: [PATCH 10/13] feat(rtic-v2-rtc-monotonics): Extends the `MIN_COMPARE_TICKS` to address a freezing issue in release builds. --- hal/src/rtc/modes.rs | 9 --------- hal/src/rtc/rtic/backends.rs | 31 +++++++++++++++---------------- hal/src/rtc/rtic/mod.rs | 4 ++-- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/hal/src/rtc/modes.rs b/hal/src/rtc/modes.rs index 296eabfe90b..4f99605bc80 100644 --- a/hal/src/rtc/modes.rs +++ b/hal/src/rtc/modes.rs @@ -77,11 +77,6 @@ macro_rules! create_rtc_interrupt { pub trait RtcMode { /// The type of the COUNT register. type Count: Copy + PartialEq + Eq; - /// The COUNT value representing a half period. - const HALF_PERIOD: Self::Count; - /// The minimum number of ticks that compares need to be ahead of the COUNT - /// in order to trigger. - const MIN_COMPARE_TICKS: Self::Count; /// Sets this mode in the CTRL register. unsafe fn set_mode(rtc: &Rtc); @@ -219,8 +214,6 @@ pub mod mode0 { impl RtcMode for RtcMode0 { type Count = u32; - const HALF_PERIOD: Self::Count = 0x8000_0000; - const MIN_COMPARE_TICKS: Self::Count = 5; #[inline] unsafe fn set_mode(rtc: &Rtc) { @@ -286,8 +279,6 @@ pub mod mode1 { impl RtcMode for RtcMode1 { type Count = u16; - const HALF_PERIOD: Self::Count = 0x8000; - const MIN_COMPARE_TICKS: Self::Count = 5; #[inline] unsafe fn set_mode(rtc: &Rtc) { diff --git a/hal/src/rtc/rtic/backends.rs b/hal/src/rtc/rtic/backends.rs index ca5063d312c..e5eaa54e4cf 100644 --- a/hal/src/rtc/rtic/backends.rs +++ b/hal/src/rtc/rtic/backends.rs @@ -29,7 +29,7 @@ macro_rules! __internal_backend_methods { ) -> bool { let d = a.wrapping_sub(b); - d >= <$mode as RtcModeMonotonic>::HALF_PERIOD + d >= <$mode>::HALF_PERIOD } // Ensure that the COUNT is at least the compare value @@ -38,6 +38,7 @@ macro_rules! __internal_backend_methods { // before `RtcBackend::on_interrupt` is called. if <$mode>::check_interrupt_flag::<$rtic_int>(&rtc) { let compare = <$mode>::get_compare(&rtc, 0); + while less_than_with_wrap(<$mode>::count(&rtc), compare) {} } @@ -129,7 +130,6 @@ macro_rules! __internal_basic_backend { impl TimerQueueBackend for $name { type Ticks = <$mode as RtcMode>::Count; - #[hal_macro_helper] fn now() -> Self::Ticks { <$mode>::count(unsafe { &pac::Rtc::steal() }) } @@ -154,9 +154,9 @@ macro_rules! __internal_basic_backend { // This is not mentioned in the documentation or errata, but is known to be an // issue for other microcontrollers as well (e.g. nRF family). if instant.saturating_sub(Self::now()) - < <$mode as RtcModeMonotonic>::MIN_COMPARE_TICKS + < <$mode>::MIN_COMPARE_TICKS { - instant = instant.wrapping_add(<$mode as RtcModeMonotonic>::MIN_COMPARE_TICKS) + instant = instant.wrapping_add(<$mode>::MIN_COMPARE_TICKS) } unsafe { <$mode>::set_compare(&rtc, 0, instant) }; @@ -215,7 +215,7 @@ macro_rules! __internal_half_period_counting_backend { init_compares = { // Configure the compare registers <$mode>::set_compare(&rtc, 0, 0); - <$mode>::set_compare(&rtc, 1, <$mode as RtcModeMonotonic>::HALF_PERIOD); + <$mode>::set_compare(&rtc, 1, <$mode>::HALF_PERIOD); } statics = { // Make sure period counter is synced with the timer value @@ -236,7 +236,6 @@ macro_rules! __internal_half_period_counting_backend { impl TimerQueueBackend for RtcBackend { type Ticks = u64; - #[hal_macro_helper] fn now() -> Self::Ticks { calculate_now( || RTC_PERIOD_COUNT.load(Ordering::Relaxed), @@ -263,7 +262,7 @@ macro_rules! __internal_half_period_counting_backend { // Ensure that the COUNT has crossed // Due to syncing delay this may not be the case initially - while <$mode>::count(&rtc) < <$mode as RtcModeMonotonic>::HALF_PERIOD {} + while <$mode>::count(&rtc) < <$mode>::HALF_PERIOD {} } if <$mode>::check_interrupt_flag::<$overflow_int>(&rtc) { <$mode>::clear_interrupt_flag::<$overflow_int>(&rtc); @@ -272,7 +271,7 @@ macro_rules! __internal_half_period_counting_backend { // Ensure that the COUNT has wrapped // Due to syncing delay this may not be the case initially - while <$mode>::count(&rtc) > <$mode as RtcModeMonotonic>::HALF_PERIOD {} + while <$mode>::count(&rtc) > <$mode>::HALF_PERIOD {} } } @@ -293,19 +292,19 @@ macro_rules! __internal_half_period_counting_backend { let val = if diff <= MAX { // Now we know `instant` will happen within one `MAX` time duration. - // Evidently the compare interrupt will not trigger if the instant is within - // a couple of ticks, so delay it a bit if it is too - // close. This is not mentioned in the documentation - // or errata, but is known to be an issue for other - // microcontrollers as well (e.g. nRF family). - if diff < <$mode as RtcModeMonotonic>::MIN_COMPARE_TICKS.into() { + // Evidently the compare interrupt will not trigger if the instant is within a + // couple of ticks, so delay it a bit if it is too close. + // This is not mentioned in the documentation or errata, but is known to be an + // issue for other microcontrollers as well (e.g. nRF family). + if diff < <$mode>::MIN_COMPARE_TICKS.into() { instant = instant - .wrapping_add(<$mode as RtcModeMonotonic>::MIN_COMPARE_TICKS.into()) + .wrapping_add(<$mode>::MIN_COMPARE_TICKS.into()); } (instant & MAX) as <$mode as RtcMode>::Count } else { - 0 + // Just wait a full hardware counter period + <$mode>::count(&rtc).wrapping_sub(1) }; unsafe { <$mode>::set_compare(&rtc, 0, val) }; diff --git a/hal/src/rtc/rtic/mod.rs b/hal/src/rtc/rtic/mod.rs index 374b6be786c..4b4d59ca3a3 100644 --- a/hal/src/rtc/rtic/mod.rs +++ b/hal/src/rtc/rtic/mod.rs @@ -219,11 +219,11 @@ trait RtcModeMonotonic: RtcMode { } impl RtcModeMonotonic for RtcMode0 { const HALF_PERIOD: Self::Count = 0x8000_0000; - const MIN_COMPARE_TICKS: Self::Count = 5; + const MIN_COMPARE_TICKS: Self::Count = 8; } impl RtcModeMonotonic for RtcMode1 { const HALF_PERIOD: Self::Count = 0x8000; - const MIN_COMPARE_TICKS: Self::Count = 5; + const MIN_COMPARE_TICKS: Self::Count = 8; } #[hal_macro_helper] From 42f3ad958fa59634c041a06bf50d255beec0806f Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Thu, 2 Jan 2025 16:47:15 -0500 Subject: [PATCH 11/13] feat(rtic-v2-rtc-monotonics): Addresses some issues raised about merging the PR (#804) * Deprecates the entire `rtc::rtic::v1` module, which primarily just implements the old RTIC v1 `Monotonic` trait for `Rtc`. * Adds #[hal_macro_helper] to functions as required and removes them from other, non-function items. * Adds documentation to `rtc::rtic::set_monotonic_prio` about the dependence on the RTIC static variable `RTIC_ASYNC_MAX_LOGICAL_PRIO`. --- hal/src/rtc/mod.rs | 6 +++++- hal/src/rtc/modes.rs | 15 +++++++++++---- hal/src/rtc/rtic/backends.rs | 6 ++---- hal/src/rtc/rtic/mod.rs | 25 ++++++++++++++++++------- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/hal/src/rtc/mod.rs b/hal/src/rtc/mod.rs index 1551b531453..66d1cfe7e51 100644 --- a/hal/src/rtc/mod.rs +++ b/hal/src/rtc/mod.rs @@ -100,7 +100,6 @@ pub struct Rtc { _mode: PhantomData, } -#[hal_macro_helper] impl Rtc { // --- Helper Functions for M0 vs M4 targets #[inline] @@ -114,6 +113,7 @@ impl Rtc { } #[inline] + #[hal_macro_helper] fn mode0_ctrla(&self) -> &Mode0CtrlA { #[hal_cfg("rtc-d5x")] return self.mode0().ctrla(); @@ -122,6 +122,7 @@ impl Rtc { } #[inline] + #[hal_macro_helper] fn mode2_ctrla(&self) -> &Mode2CtrlA { #[hal_cfg("rtc-d5x")] return self.mode2().ctrla(); @@ -130,6 +131,7 @@ impl Rtc { } #[inline] + #[hal_macro_helper] fn sync(&self) { #[hal_cfg("rtc-d5x")] while self.mode2().syncbusy().read().bits() != 0 {} @@ -166,6 +168,7 @@ impl Rtc { } /// Reonfigures the peripheral for 32bit counter mode. + #[hal_macro_helper] pub fn into_count32_mode(mut self) -> Rtc { self.enable(false); self.sync(); @@ -192,6 +195,7 @@ impl Rtc { /// Reconfigures the peripheral for clock/calendar mode. Requires the source /// clock to be running at 1024 Hz. + #[hal_macro_helper] pub fn into_clock_mode(mut self) -> Rtc { // The max divisor is 1024, so to get 1 Hz, we need a 1024 Hz source. assert_eq!( diff --git a/hal/src/rtc/modes.rs b/hal/src/rtc/modes.rs index 4f99605bc80..530ddee268d 100644 --- a/hal/src/rtc/modes.rs +++ b/hal/src/rtc/modes.rs @@ -29,7 +29,7 @@ // access was checked in the datasheet and accounted for. use crate::pac; -use atsamd_hal_macros::hal_macro_helper; +use atsamd_hal_macros::{hal_cfg, hal_macro_helper}; use pac::Rtc; /// Type-level enum for RTC interrupts. @@ -73,7 +73,6 @@ macro_rules! create_rtc_interrupt { /// An abstraction of an RTC in a particular mode that provides low-level /// access and handles all register syncing issues using only associated /// functions. -#[hal_macro_helper] pub trait RtcMode { /// The type of the COUNT register. type Count: Copy + PartialEq + Eq; @@ -91,6 +90,7 @@ pub trait RtcMode { /// Resets the RTC, leaving it disabled in MODE0. #[inline] + #[hal_macro_helper] fn reset(rtc: &Rtc) { // Reset RTC back to initial settings, which disables it and enters mode 0. // NOTE: This register and field are the same in all modes. @@ -111,6 +111,7 @@ pub trait RtcMode { /// Starts the RTC and does any required initialization for this mode. #[inline] + #[hal_macro_helper] fn start_and_initialize(rtc: &Rtc) { Self::enable(rtc); @@ -157,6 +158,7 @@ pub trait RtcMode { /// Disables the RTC. #[inline] + #[hal_macro_helper] fn disable(rtc: &Rtc) { // SYNC: Write Self::sync(rtc); @@ -169,6 +171,7 @@ pub trait RtcMode { /// Enables the RTC. #[inline] + #[hal_macro_helper] fn enable(rtc: &Rtc) { // SYNC: Write Self::sync(rtc); @@ -199,7 +202,6 @@ pub trait RtcMode { } } -#[hal_macro_helper] pub mod mode0 { use super::*; @@ -216,6 +218,7 @@ pub mod mode0 { type Count = u32; #[inline] + #[hal_macro_helper] unsafe fn set_mode(rtc: &Rtc) { // SYNC: Write Self::sync(rtc); @@ -240,6 +243,7 @@ pub mod mode0 { } #[inline] + #[hal_macro_helper] fn count(rtc: &Rtc) -> Self::Count { #[hal_cfg(any("rtc-d11", "rtc-d21"))] { @@ -254,6 +258,7 @@ pub mod mode0 { } #[inline] + #[hal_macro_helper] fn sync_busy(rtc: &Rtc) -> bool { // SYNC: None #[hal_cfg(any("rtc-d11", "rtc-d21"))] @@ -266,7 +271,6 @@ pub mod mode0 { } } -#[hal_macro_helper] pub mod mode1 { use super::*; @@ -281,6 +285,7 @@ pub mod mode1 { type Count = u16; #[inline] + #[hal_macro_helper] unsafe fn set_mode(rtc: &Rtc) { // SYNC: Write Self::sync(rtc); @@ -310,6 +315,7 @@ pub mod mode1 { } #[inline] + #[hal_macro_helper] fn count(rtc: &Rtc) -> Self::Count { #[hal_cfg(any("rtc-d11", "rtc-d21"))] { @@ -324,6 +330,7 @@ pub mod mode1 { } #[inline] + #[hal_macro_helper] fn sync_busy(rtc: &Rtc) -> bool { // SYNC: None #[hal_cfg(any("rtc-d11", "rtc-d21"))] diff --git a/hal/src/rtc/rtic/backends.rs b/hal/src/rtc/rtic/backends.rs index e5eaa54e4cf..687f112421d 100644 --- a/hal/src/rtc/rtic/backends.rs +++ b/hal/src/rtc/rtic/backends.rs @@ -96,7 +96,7 @@ macro_rules! __internal_backend_methods { #[macro_export] macro_rules! __internal_basic_backend { ($name:ident, $mode:ty, $mode_num:literal, $rtic_int:ty) => { - use atsamd_hal_macros::hal_macro_helper; + use atsamd_hal_macros::hal_cfg; use rtic_time::timer_queue::{TimerQueue, TimerQueueBackend}; use $crate::pac; use $crate::rtc::modes::RtcMode; @@ -106,7 +106,6 @@ macro_rules! __internal_basic_backend { static RTC_TQ: TimerQueue<$name> = TimerQueue::new(); - #[hal_macro_helper] impl $name { $crate::__internal_backend_methods! { mode = $mode; @@ -181,7 +180,7 @@ macro_rules! __internal_basic_backend { #[macro_export] macro_rules! __internal_half_period_counting_backend { ($name:ident, $mode:ty, $mode_num:literal, $rtic_int:ty, $half_period_int:ty, $overflow_int:ty) => { - use atsamd_hal_macros::hal_macro_helper; + use atsamd_hal_macros::hal_cfg; use core::sync::atomic::Ordering; use portable_atomic::AtomicU64; use rtic_time::{ @@ -206,7 +205,6 @@ macro_rules! __internal_half_period_counting_backend { static RTC_PERIOD_COUNT: AtomicU64 = AtomicU64::new(0); static RTC_TQ: TimerQueue<$name> = TimerQueue::new(); - #[hal_macro_helper] impl $name { $crate::__internal_backend_methods! { mode = $mode; diff --git a/hal/src/rtc/rtic/mod.rs b/hal/src/rtc/rtic/mod.rs index 4b4d59ca3a3..7881a8df7e5 100644 --- a/hal/src/rtc/rtic/mod.rs +++ b/hal/src/rtc/rtic/mod.rs @@ -4,8 +4,8 @@ //! Enabling the `rtic` feature is required to use this module. //! //! For RTIC v1, the old [`rtic_monotonic::Monotonic`] trait is implemented for -//! [`Rtc`](crate::rtc::Rtc) in [`Count32Mode`](crate::rtc::Count32Mode). A -//! monotonic for RTIC v2 is provided here. +//! [`Rtc`](crate::rtc::Rtc) in [`Count32Mode`](crate::rtc::Count32Mode) in the +//! [`v1`] module. A monotonic for RTIC v2 is provided here. //! //! # RTC clock selection //! @@ -117,8 +117,11 @@ /// Items for RTIC v1. /// -/// TODO: This probably needs to be modernized (e.g. it does not implement -/// half-period counting) or deprecated/removed. +/// This mainly implements [`rtic_monotonic::Monotonic`] for +/// [`Rtc`](crate::rtc::Rtc). +/// +/// This will be removed in a future release, users should migrate to RTIC v2. +#[deprecated] pub mod v1 { use crate::rtc::{Count32Mode, Rtc}; use rtic_monotonic::Monotonic; @@ -165,7 +168,7 @@ pub mod v1 { mod backends; use super::modes::{mode0::RtcMode0, mode1::RtcMode1, RtcMode}; -use atsamd_hal_macros::hal_macro_helper; +use atsamd_hal_macros::hal_cfg; pub mod prelude { pub use super::rtc_clock; @@ -226,7 +229,6 @@ impl RtcModeMonotonic for RtcMode1 { const MIN_COMPARE_TICKS: Self::Count = 8; } -#[hal_macro_helper] mod backend { use super::*; @@ -317,7 +319,16 @@ const fn cortex_logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) } -/// This function was copied from the private function in `rtic-monotonics`. +/// This function was modified from the private function in `rtic-monotonics`. +/// +/// Note that this depends on the static variable `RTIC_ASYNC_MAX_LOGICAL_PRIO` +/// defined as part of RTIC. Should this ever be used without linking with RTIC, +/// the user would need to define this as follows: +/// +/// ``` +/// #[no_mangle] +/// pub static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8 = (something); +/// ``` unsafe fn set_monotonic_prio(prio_bits: u8, interrupt: impl cortex_m::interrupt::InterruptNumber) { extern "C" { static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8; From 0b8aff58b0f81d7f8562583c3a1896d711553324 Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Tue, 7 Jan 2025 17:17:02 -0500 Subject: [PATCH 12/13] feat(rtic-v2-rtc-monotonics): Addresses some issues raised about merging the PR (#804) * Adds safety documentation to the `RtcMode` trait methods, and makes all methods safe to call. * Removes the RTIC-specific prelude and adjusts RTIC items in the crate-level prelude. * Updates the `rtic` module example to include notes about the `RTIC_ASYNC_MAX_LOGICAL_PRIO` static variable. --- hal/src/lib.rs | 6 +- hal/src/prelude.rs | 5 +- hal/src/rtc/modes.rs | 141 +++++++++++++++++++++++++---------- hal/src/rtc/rtic/backends.rs | 10 +-- hal/src/rtc/rtic/mod.rs | 36 ++++----- 5 files changed, 128 insertions(+), 70 deletions(-) diff --git a/hal/src/lib.rs b/hal/src/lib.rs index db675493bdd..193ba897aed 100644 --- a/hal/src/lib.rs +++ b/hal/src/lib.rs @@ -14,6 +14,9 @@ pub use embedded_hal_async as ehal_async; #[cfg(feature = "async")] pub use embedded_io_async; +#[cfg(feature = "rtic")] +pub use rtic_time; + pub mod typelevel; mod util; @@ -91,9 +94,6 @@ pub mod timer_traits; #[cfg(feature = "dma")] pub mod dmac; -#[cfg(feature = "rtic")] -pub use rtic_time; - #[doc(hidden)] mod peripherals; #[doc(inline)] diff --git a/hal/src/prelude.rs b/hal/src/prelude.rs index 1360b965940..d7f1b3cb135 100644 --- a/hal/src/prelude.rs +++ b/hal/src/prelude.rs @@ -13,4 +13,7 @@ pub use crate::ehal_02::digital::v2::ToggleableOutputPin as _atsamd_hal_embedded pub use crate::ehal_02::prelude::*; #[cfg(feature = "rtic")] -pub use crate::rtc::rtic::prelude::*; +pub use rtic_time::Monotonic as _; + +#[cfg(feature = "rtic")] +pub use fugit::{ExtU64, ExtU64Ceil}; diff --git a/hal/src/rtc/modes.rs b/hal/src/rtc/modes.rs index 530ddee268d..c4874fdb035 100644 --- a/hal/src/rtc/modes.rs +++ b/hal/src/rtc/modes.rs @@ -1,4 +1,5 @@ //! Provides low-level access to the [Real Time Clock (RTC)](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-E17D8859-D42B-4B0E-9B81-76168A0C38AC.html) peripheral on ATSAMD chips. +//! //! The main abstraction is the [`RtcMode`] trait, which exposes //! static/associated functions to use the RTC in in a particular mode. All functions are marked as [`inline`](https://matklad.github.io/2021/07/09/inline-in-rust.html) //! so that this should be a zero cost abstraction. @@ -9,7 +10,7 @@ //! //! Abstraction benefits: //! - Handles all RTC register accesses. -//! - Handles RTC [register synchronization](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-ABE2D37F-8125-4279-9955-BC3900046CFF.html) +//! - Handles RTC [register synchronization](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-ABE2D37F-8125-4279-9955-BC3900046CFF.html). //! - Handles ATSAMD chip variations. //! //! The idea is that various higher-level users of these abstractions will not @@ -46,7 +47,7 @@ pub trait RtcInterrupt { /// Macro to easily declare an RTC interrupt. macro_rules! create_rtc_interrupt { ($mode:ident, $name:ident, $bit:ident) => { - /// Type-level variant for the $name interrupt in $mode. + #[doc = concat!("Type-level variant for the ", stringify!($name), " interrupt in ", stringify!($mode))] pub enum $name {} impl RtcInterrupt for $name { #[inline] @@ -78,17 +79,59 @@ pub trait RtcMode { type Count: Copy + PartialEq + Eq; /// Sets this mode in the CTRL register. - unsafe fn set_mode(rtc: &Rtc); + /// + /// # Safety + /// + /// This can be called any time but is typically only called once before + /// calling most other methods. + fn set_mode(rtc: &Rtc); + /// Sets a compare value. - unsafe fn set_compare(rtc: &Rtc, number: usize, value: Self::Count); + /// + /// # Safety + /// + /// Should be called only after setting the RTC mode using + /// [`set_mode`](RtcMode::set_mode). + fn set_compare(rtc: &Rtc, number: usize, value: Self::Count); + /// Retrieves a compare from the register. + /// + /// # Safety + /// + /// Should be called only after setting the RTC mode using + /// [`set_mode`](RtcMode::set_mode). fn get_compare(rtc: &Rtc, number: usize) -> Self::Count; + /// Returns the current synced COUNT value. + /// + /// # Safety + /// + /// Should be called only after setting the RTC mode using + /// [`set_mode`](RtcMode::set_mode). fn count(rtc: &Rtc) -> Self::Count; + /// Returns whether register syncing is currently happening. - fn sync_busy(rtc: &Rtc) -> bool; + /// + /// # Safety + /// + /// Can be called any time. + #[inline] + #[hal_macro_helper] + fn sync_busy(rtc: &Rtc) -> bool { + // NOTE: This register and field are the same in all modes. + // SYNC: None + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + return rtc.mode0().status().read().syncbusy().bit_is_set(); + // SYNC: None + #[hal_cfg("rtc-d5x")] + return rtc.mode0().syncbusy().read().bits() != 0; + } /// Resets the RTC, leaving it disabled in MODE0. + /// + /// # Safety + /// + /// Can be called any time. #[inline] #[hal_macro_helper] fn reset(rtc: &Rtc) { @@ -110,6 +153,11 @@ pub trait RtcMode { } /// Starts the RTC and does any required initialization for this mode. + /// + /// # Safety + /// + /// Should be called only after setting the RTC mode using + /// [`set_mode`](RtcMode::set_mode). #[inline] #[hal_macro_helper] fn start_and_initialize(rtc: &Rtc) { @@ -131,12 +179,22 @@ pub trait RtcMode { } /// Enables an RTC interrupt. + /// + /// # Safety + /// + /// Should be called only after setting the RTC mode using + /// [`set_mode`](RtcMode::set_mode). #[inline] fn enable_interrupt(rtc: &Rtc) { I::enable(rtc); } /// Returns whether an RTC interrupt has been triggered. + /// + /// # Safety + /// + /// Should be called only after setting the RTC mode using + /// [`set_mode`](RtcMode::set_mode). #[inline] fn check_interrupt_flag(rtc: &Rtc) -> bool { I::check_flag(rtc) @@ -144,19 +202,32 @@ pub trait RtcMode { /// Clears an RTC interrupt flag so the ISR will not be called again /// immediately. + /// + /// # Safety + /// + /// Should be called only after setting the RTC mode using + /// [`set_mode`](RtcMode::set_mode). #[inline] fn clear_interrupt_flag(rtc: &Rtc) { I::clear_flag(rtc); } /// Waits for any register syncing to be completed, or returns immediately - /// if no currently syncing. + /// if not currently syncing. + /// + /// # Safety + /// + /// Can be called any time. #[inline] fn sync(rtc: &Rtc) { while Self::sync_busy(rtc) {} } /// Disables the RTC. + /// + /// # Safety + /// + /// Can be called any time. #[inline] #[hal_macro_helper] fn disable(rtc: &Rtc) { @@ -170,6 +241,10 @@ pub trait RtcMode { } /// Enables the RTC. + /// + /// # Safety + /// + /// Can be called any time. #[inline] #[hal_macro_helper] fn enable(rtc: &Rtc) { @@ -184,8 +259,14 @@ pub trait RtcMode { /// Waits until the COUNT register changes. /// - /// Note that this may not necessarily be the next tick due sync delay. - /// Beware that this will wait forever if the RTC is disabled! + /// Note that this may not necessarily be the next tick numerically due sync + /// delay. + /// + /// # Safety + /// + /// Should be called only after setting the RTC mode using + /// [`set_mode`](RtcMode::set_mode). This will halt forever if called when + /// the RTC is disabled. #[inline] fn _wait_for_count_change(rtc: &Rtc) -> Self::Count { let mut last_count = Self::count(rtc); @@ -202,6 +283,7 @@ pub trait RtcMode { } } +/// Interface for using the RTC in MODE0 (32-bit COUNT) pub mod mode0 { use super::*; @@ -211,7 +293,7 @@ pub mod mode0 { #[hal_cfg("rtc-d5x")] create_rtc_interrupt!(mode0, Overflow, ovf); - /// The RTC operating in MODE0 (23-bit COUNT) + /// The RTC operating in MODE0 (32-bit COUNT) pub struct RtcMode0; impl RtcMode for RtcMode0 { @@ -219,7 +301,7 @@ pub mod mode0 { #[inline] #[hal_macro_helper] - unsafe fn set_mode(rtc: &Rtc) { + fn set_mode(rtc: &Rtc) { // SYNC: Write Self::sync(rtc); // NOTE: This register and field are the same in all modes. @@ -230,10 +312,12 @@ pub mod mode0 { } #[inline] - unsafe fn set_compare(rtc: &Rtc, number: usize, value: Self::Count) { + fn set_compare(rtc: &Rtc, number: usize, value: Self::Count) { // SYNC: Write Self::sync(rtc); - rtc.mode0().comp(number).write(|w| w.comp().bits(value)); + unsafe { + rtc.mode0().comp(number).write(|w| w.comp().bits(value)); + } } #[inline] @@ -256,21 +340,10 @@ pub mod mode0 { Self::sync(rtc); rtc.mode0().count().read().bits() } - - #[inline] - #[hal_macro_helper] - fn sync_busy(rtc: &Rtc) -> bool { - // SYNC: None - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - return rtc.mode0().status().read().syncbusy().bit_is_set(); - - // SYNC: None - #[hal_cfg("rtc-d5x")] - return rtc.mode0().syncbusy().read().bits() != 0; - } } } +/// Interface for using the RTC in MODE1 (16-bit COUNT) pub mod mode1 { use super::*; @@ -286,7 +359,7 @@ pub mod mode1 { #[inline] #[hal_macro_helper] - unsafe fn set_mode(rtc: &Rtc) { + fn set_mode(rtc: &Rtc) { // SYNC: Write Self::sync(rtc); // NOTE: This register and field are the same in all modes. @@ -298,14 +371,14 @@ pub mod mode1 { // Set the mode 1 period // SYNC: Write Self::sync(rtc); - rtc.mode1().per().write(|w| w.bits(0xFFFF)); + unsafe { rtc.mode1().per().write(|w| w.bits(0xFFFF)) }; } #[inline] - unsafe fn set_compare(rtc: &Rtc, number: usize, value: Self::Count) { + fn set_compare(rtc: &Rtc, number: usize, value: Self::Count) { // SYNC: Write Self::sync(rtc); - rtc.mode1().comp(number).write(|w| w.comp().bits(value)); + unsafe { rtc.mode1().comp(number).write(|w| w.comp().bits(value)) }; } #[inline] @@ -328,17 +401,5 @@ pub mod mode1 { Self::sync(rtc); rtc.mode1().count().read().bits() } - - #[inline] - #[hal_macro_helper] - fn sync_busy(rtc: &Rtc) -> bool { - // SYNC: None - #[hal_cfg(any("rtc-d11", "rtc-d21"))] - return rtc.mode1().status().read().syncbusy().bit_is_set(); - - // SYNC: None - #[hal_cfg("rtc-d5x")] - return rtc.mode1().syncbusy().read().bits() != 0; - } } } diff --git a/hal/src/rtc/rtic/backends.rs b/hal/src/rtc/rtic/backends.rs index 687f112421d..82bb23e32d5 100644 --- a/hal/src/rtc/rtic/backends.rs +++ b/hal/src/rtc/rtic/backends.rs @@ -57,12 +57,10 @@ macro_rules! __internal_backend_methods { // Reset RTC back to initial settings, which disables it and enters mode 0. <$mode>::reset(&$rtc_pac); - unsafe { - // Set the RTC mode - <$mode>::set_mode(&$rtc_pac); + // Set the RTC mode + <$mode>::set_mode(&$rtc_pac); - $init_compares - } + $init_compares // Timing critical, make sure we don't get interrupted. critical_section::with(|_| { @@ -305,7 +303,7 @@ macro_rules! __internal_half_period_counting_backend { <$mode>::count(&rtc).wrapping_sub(1) }; - unsafe { <$mode>::set_compare(&rtc, 0, val) }; + <$mode>::set_compare(&rtc, 0, val); }); } diff --git a/hal/src/rtc/rtic/mod.rs b/hal/src/rtc/rtic/mod.rs index 7881a8df7e5..8ddeba16ca0 100644 --- a/hal/src/rtc/rtic/mod.rs +++ b/hal/src/rtc/rtic/mod.rs @@ -58,8 +58,7 @@ //! # Usage //! //! The monotonic should be created using the -//! [macro](prelude::rtc_monotonic), which is included in the -//! [prelude](crate::prelude). The first macro argument is the name of +//! [macro](crate::rtc_monotonic). The first macro argument is the name of //! the global structure that will implement //! [`Monotonic`](rtic_time::Monotonic). The RTC clock rate must be //! known at compile time, and so the appropriate type from [`rtc_clock`] must @@ -78,10 +77,20 @@ //! //! ``` //! use atsamd_hal::prelude::*; +//! use atsamd_hal::rtc::rtic::rtc_clock; //! //! // Create the monotonic struct named `Mono` //! rtc_monotonic!(Mono, rtc_clock::Clock32k); //! +//! // Uncomment if not using the RTIC RTOS: +//! // #[no_mangle] +//! // static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8 = 1; +//! // +//! // This tells the monotonic driver the maximum interrupt +//! // priority it's allowed to use. RTIC sets it automatically, +//! // but you need to set it manually if you're writing +//! // a RTIC-less app. +//! //! fn init() { //! # // This is normally provided by the selected PAC //! # let rtc = unsafe { core::mem::transmute(()) }; @@ -170,22 +179,14 @@ mod backends; use super::modes::{mode0::RtcMode0, mode1::RtcMode1, RtcMode}; use atsamd_hal_macros::hal_cfg; -pub mod prelude { - pub use super::rtc_clock; - pub use crate::rtc_monotonic; - pub use rtic_time::Monotonic; - - pub use fugit::{self, ExtU32, ExtU32Ceil, ExtU64, ExtU64Ceil}; -} - /// Types used to specify the RTC clock rate at compile time when creating the /// monotonics. /// /// These types utilize [type-level programming](crate::typelevel) /// techniques and are passed to the [monotonic creation -/// macros](crate::rtc::rtic::prelude). -/// The clock rate must be specified at compile time so that the `Instant` and -/// `Duration` types in +/// macro](crate::rtc_monotonic). +/// The RTC clock rate must be specified at compile time so that the `Instant` +/// and `Duration` types in /// [`TimerQueueBasedMonotonic`](rtic_time::monotonic::TimerQueueBasedMonotonic) /// can be specified. pub mod rtc_clock { @@ -322,13 +323,8 @@ const fn cortex_logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { /// This function was modified from the private function in `rtic-monotonics`. /// /// Note that this depends on the static variable `RTIC_ASYNC_MAX_LOGICAL_PRIO` -/// defined as part of RTIC. Should this ever be used without linking with RTIC, -/// the user would need to define this as follows: -/// -/// ``` -/// #[no_mangle] -/// pub static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8 = (something); -/// ``` +/// defined as part of RTIC. Refer to the example in the [`rtic` +/// module](crate::rtc::rtic) documentation for more details. unsafe fn set_monotonic_prio(prio_bits: u8, interrupt: impl cortex_m::interrupt::InterruptNumber) { extern "C" { static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8; From c11b7d1e12f2ce92485bf326d1a5716b5809557d Mon Sep 17 00:00:00 2001 From: Dan Whitman Date: Mon, 13 Jan 2025 12:20:27 -0500 Subject: [PATCH 13/13] feat(rtic-v2-rtc-monotonics): Addresses the remaining issue raised about merging the PR (#804) * Adds the `from_numeric` method to `interrupt::Priority` to create a variant from a numeric priority. * Removes the `rtc::rtic::cortex_logical2hw` function, instead using `interrupt::Priority::logical2hw`. --- hal/src/interrupt.rs | 9 +++++++++ hal/src/rtc/rtic/backends.rs | 2 +- hal/src/rtc/rtic/mod.rs | 18 ++++++++---------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/hal/src/interrupt.rs b/hal/src/interrupt.rs index ec79802b0b3..607bec95b41 100644 --- a/hal/src/interrupt.rs +++ b/hal/src/interrupt.rs @@ -42,6 +42,15 @@ pub enum Priority { } impl Priority { + /// Creates the `Priority` from a numeric priority if possible. + pub const fn from_numeric(prio: u8) -> Option { + if prio >= 1 && prio <= 8 { + Some(unsafe { core::mem::transmute::(prio) }) + } else { + None + } + } + /// Convert a logical priority (where higher priority number = higher /// priority level) to a hardware priority level (where lower priority /// number = higher priority level). diff --git a/hal/src/rtc/rtic/backends.rs b/hal/src/rtc/rtic/backends.rs index 82bb23e32d5..88a00078e48 100644 --- a/hal/src/rtc/rtic/backends.rs +++ b/hal/src/rtc/rtic/backends.rs @@ -82,7 +82,7 @@ macro_rules! __internal_backend_methods { // plus we are not using any external shared resources so we won't impact // basepri/source masking based critical sections. unsafe { - $crate::rtc::rtic::set_monotonic_prio(pac::NVIC_PRIO_BITS, pac::Interrupt::RTC); + $crate::rtc::rtic::set_monotonic_prio(pac::Interrupt::RTC); pac::NVIC::unmask(pac::Interrupt::RTC); } }); diff --git a/hal/src/rtc/rtic/mod.rs b/hal/src/rtc/rtic/mod.rs index 8ddeba16ca0..242c4fb8122 100644 --- a/hal/src/rtc/rtic/mod.rs +++ b/hal/src/rtc/rtic/mod.rs @@ -177,6 +177,7 @@ pub mod v1 { mod backends; use super::modes::{mode0::RtcMode0, mode1::RtcMode1, RtcMode}; +use crate::interrupt::{Priority, NVIC_PRIO_BITS}; use atsamd_hal_macros::hal_cfg; /// Types used to specify the RTC clock rate at compile time when creating the @@ -314,24 +315,21 @@ macro_rules! rtc_monotonic { }; } -/// This function was copied from the private function in `rtic-monotonics`, -/// so that should be used when the monotonics move there. -const fn cortex_logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { - ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) -} - -/// This function was modified from the private function in `rtic-monotonics`. +/// This function was modified from the private function in `rtic-monotonics`, +/// part of the [`rtic`](https://github.com/rtic-rs/rtic) project. /// /// Note that this depends on the static variable `RTIC_ASYNC_MAX_LOGICAL_PRIO` /// defined as part of RTIC. Refer to the example in the [`rtic` /// module](crate::rtc::rtic) documentation for more details. -unsafe fn set_monotonic_prio(prio_bits: u8, interrupt: impl cortex_m::interrupt::InterruptNumber) { +/// +/// See LICENSE-MIT and LICENSE-APACHE for the licenses. +unsafe fn set_monotonic_prio(interrupt: impl cortex_m::interrupt::InterruptNumber) { extern "C" { static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8; } - let max_prio = RTIC_ASYNC_MAX_LOGICAL_PRIO.max(1).min(1 << prio_bits); - let hw_prio = cortex_logical2hw(max_prio, prio_bits); + let max_prio = RTIC_ASYNC_MAX_LOGICAL_PRIO.clamp(1, 1 << NVIC_PRIO_BITS); + let hw_prio = Priority::from_numeric(max_prio).unwrap().logical2hw(); // We take ownership of the entire IRQ and all settings to it, we only change // settings for the IRQ we control.