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/Cargo.toml b/hal/Cargo.toml index b771ed9c7f7..5b99f71a3fb 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 @@ -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" @@ -69,6 +69,7 @@ jlink_rtt = {version = "0.2", optional = true} mcan-core = {version = "0.2", 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 +81,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 +183,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", "portable-atomic"] sdmmc = ["embedded-sdmmc"] usb = ["usb-device"] use_rtt = ["jlink_rtt"] diff --git a/hal/src/lib.rs b/hal/src/lib.rs index 91ad7ca0af7..7352b37c9da 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; diff --git a/hal/src/prelude.rs b/hal/src/prelude.rs index 1a78e5211cb..d7f1b3cb135 100644 --- a/hal/src/prelude.rs +++ b/hal/src/prelude.rs @@ -11,3 +11,9 @@ 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 rtic_time::Monotonic as _; + +#[cfg(feature = "rtic")] +pub use fugit::{ExtU64, ExtU64Ceil}; 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..66d1cfe7e51 100644 --- a/hal/src/rtc.rs +++ b/hal/src/rtc/mod.rs @@ -15,11 +15,10 @@ 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; +mod modes; + #[cfg(feature = "rtic")] -use rtic_monotonic::Monotonic; +pub mod rtic; // SAMx5x imports #[hal_cfg("rtc-d5x")] @@ -101,7 +100,6 @@ pub struct Rtc { _mode: PhantomData, } -#[hal_macro_helper] impl Rtc { // --- Helper Functions for M0 vs M4 targets #[inline] @@ -115,6 +113,7 @@ impl Rtc { } #[inline] + #[hal_macro_helper] fn mode0_ctrla(&self) -> &Mode0CtrlA { #[hal_cfg("rtc-d5x")] return self.mode0().ctrla(); @@ -123,6 +122,7 @@ impl Rtc { } #[inline] + #[hal_macro_helper] fn mode2_ctrla(&self) -> &Mode2CtrlA { #[hal_cfg("rtc-d5x")] return self.mode2().ctrla(); @@ -131,6 +131,7 @@ impl Rtc { } #[inline] + #[hal_macro_helper] fn sync(&self) { #[hal_cfg("rtc-d5x")] while self.mode2().syncbusy().read().bits() != 0 {} @@ -167,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(); @@ -193,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!( @@ -238,6 +241,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 +488,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/modes.rs b/hal/src/rtc/modes.rs new file mode 100644 index 00000000000..c4874fdb035 --- /dev/null +++ b/hal/src/rtc/modes.rs @@ -0,0 +1,405 @@ +//! 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. +//! +//! 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_cfg, hal_macro_helper}; +use pac::Rtc; + +/// 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) => { + #[doc = concat!("Type-level variant for the ", stringify!($name), " interrupt in ", stringify!($mode))] + pub 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. +pub trait RtcMode { + /// The type of the COUNT register. + type Count: Copy + PartialEq + Eq; + + /// Sets this mode in the CTRL register. + /// + /// # 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. + /// + /// # 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. + /// + /// # 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) { + // 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 + // SYNC: Write (we just read though) + #[hal_cfg(any("rtc-d11", "rtc-d21"))] + while rtc.mode0().ctrl().read().swrst().bit_is_set() {} + #[hal_cfg("rtc-d5x")] + while rtc.mode0().ctrla().read().swrst().bit_is_set() {} + } + + /// 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) { + 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. + /// + /// # 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) + } + + /// 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 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) { + // 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. + /// + /// # Safety + /// + /// Can be called any time. + #[inline] + #[hal_macro_helper] + 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()); + } + + /// Waits until the COUNT register changes. + /// + /// 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); + + loop { + let count = Self::count(rtc); + + if count != last_count { + break count; + } + + last_count = count; + } + } +} + +/// Interface for using the RTC in MODE0 (32-bit COUNT) +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 (32-bit COUNT) + pub struct RtcMode0; + + impl RtcMode for RtcMode0 { + type Count = u32; + + #[inline] + #[hal_macro_helper] + 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] + fn set_compare(rtc: &Rtc, number: usize, value: Self::Count) { + // SYNC: Write + Self::sync(rtc); + unsafe { + 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] + #[hal_macro_helper] + 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() + } + } +} + +/// Interface for using the RTC in MODE1 (16-bit COUNT) +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; + + #[inline] + #[hal_macro_helper] + 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); + unsafe { rtc.mode1().per().write(|w| w.bits(0xFFFF)) }; + } + + #[inline] + fn set_compare(rtc: &Rtc, number: usize, value: Self::Count) { + // SYNC: Write + Self::sync(rtc); + unsafe { 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] + #[hal_macro_helper] + 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() + } + } +} diff --git a/hal/src/rtc/rtic/backends.rs b/hal/src/rtc/rtic/backends.rs new file mode 100644 index 00000000000..82bb23e32d5 --- /dev/null +++ b/hal/src/rtc/rtic/backends.rs @@ -0,0 +1,323 @@ +#[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); + + // 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, $mode_num:literal, $rtic_int:ty) => { + use atsamd_hal_macros::hal_cfg; + use rtic_time::timer_queue::{TimerQueue, TimerQueueBackend}; + use $crate::pac; + use $crate::rtc::modes::RtcMode; + + #[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(); + + 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; + + 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, $mode_num:literal, $rtic_int:ty, $half_period_int:ty, $overflow_int:ty) => { + use atsamd_hal_macros::hal_cfg; + use core::sync::atomic::Ordering; + use portable_atomic::AtomicU64; + use rtic_time::{ + half_period_counter::calculate_now, + timer_queue::{TimerQueue, TimerQueueBackend}, + }; + use $crate::pac; + + 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) + } + } + + #[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); + static RTC_TQ: TimerQueue<$name> = TimerQueue::new(); + + 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; + + 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 { + // Just wait a full hardware counter period + <$mode>::count(&rtc).wrapping_sub(1) + }; + + <$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 new file mode 100644 index 00000000000..8ddeba16ca0 --- /dev/null +++ b/hal/src/rtc/rtic/mod.rs @@ -0,0 +1,342 @@ +//! [`Monotonic`](rtic_time::Monotonic) implementations using 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) in the +//! [`v1`] module. A monotonic for RTIC v2 is provided here. +//! +//! # RTC clock selection +//! +//! Prior to starting the monotonic, the RTC clock source must be configured +//! 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. +//! +//! **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.** +//! +//! # 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 +//! +//! The monotonic should be created using the +//! [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 +//! be passed to the macro as the second argument. +//! +//! 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 +//! be done manually. +//! +//! # Example +//! +//! ``` +//! 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(()) }; +//! # let mut mclk = unsafe { core::mem::transmute(()) }; +//! # let mut osc32kctrl = unsafe { core::mem::transmute(()) }; +//! // Here the RTC clock source should be configured using the clocks API +//! +//! // Start the monotonic +//! 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; +//! } +//! } +//! ``` +//! +//! # 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. + +/// Items for RTIC v1. +/// +/// 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; + + /// The RTC clock frequency in Hz. + pub const CLOCK_FREQ: u32 = 32_768; + + /// 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; + 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 backends; + +use super::modes::{mode0::RtcMode0, mode1::RtcMode1, RtcMode}; +use atsamd_hal_macros::hal_cfg; + +/// 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 +/// 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 { + /// Type-level enum for available RTC clock rates. + pub trait RtcClockRate { + const RATE_HZ: u32; + } + + /// Type level [`RtcClockRate`] variant for the 32.768 kHz clock rate. + pub enum Clock32k {} + impl RtcClockRate for Clock32k { + 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_HZ: u32 = 1_024; + } + + /// Type level [`RtcClockRate`] variant for a custom clock rate + pub enum ClockCustom {} + impl RtcClockRate for ClockCustom { + const RATE_HZ: u32 = RATE_HZ; + } +} + +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 = 8; +} +impl RtcModeMonotonic for RtcMode1 { + const HALF_PERIOD: Self::Count = 0x8000; + const MIN_COMPARE_TICKS: Self::Count = 8; +} + +mod backend { + use super::*; + + // 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_half_period_counting_backend!( + RtcBackend, RtcMode1, 1, 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, RtcMode0, 0, Compare0, Compare1, Overflow + ); +} + +pub use backend::RtcBackend; + +#[doc(hidden)] +#[macro_export] +macro_rules! __internal_create_rtc_interrupt { + ($backend:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + unsafe extern "C" fn RTC() { + $crate::rtc::rtic::$backend::interrupt_handler(); + } + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __internal_create_rtc_struct { + ($name:ident, $backend:ident, $clock_rate:ty) => { + /// A `Monotonic` based on the RTC peripheral. + pub struct $name; + + impl $name { + /// This method must be called only once. + 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); + } + } + + 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_HZ }, + >; + type Duration = $crate::fugit::Duration< + ::Ticks, + 1, + { <$clock_rate>::RATE_HZ }, + >; + } + + $crate::rtic_time::impl_embedded_hal_delay_fugit!($name); + $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name); + }; +} + +/// Create an RTIC v2 monotonic that uses the RTC. +/// +/// See the [`rtic`](crate::rtc::rtic) module for details. +#[macro_export] +macro_rules! rtc_monotonic { + ($name:ident, $clock_rate: ty) => { + $crate::__internal_create_rtc_struct!($name, RtcBackend, $clock_rate); + }; +} + +/// 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`. +/// +/// 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) { + 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); +}