Skip to main content

atsamd_hal/rtc/rtic/
mod.rs

1//! [`Monotonic`](rtic_time::Monotonic) implementations using the Real Time
2//! Clock (RTC).
3//!
4//! Enabling the `rtic` feature is required to use this module.
5//!
6//! For RTIC v1, the old [`rtic_monotonic::Monotonic`] trait is implemented for
7//! [`Rtc`](crate::rtc::Rtc) in [`Count32Mode`](crate::rtc::Count32Mode) in the
8//! [`v1`] module. A monotonic for RTIC v2 is provided here.
9//!
10//! # RTC clock selection
11//!
12//! Prior to starting the monotonic, the RTC clock source must be configured
13//! using [`clocks`](crate::clock). On SAMD11/21 platforms, the RTC clock must
14//! be setup as a [generic clock](crate::clock::GenericClockController).
15//! On SAMx5x platforms the RTC clock must be selected from either the 1.1024
16//! kHz clock or the 32.768 kHz clock, either of which can be internal or
17//! external.
18//!
19//! **NOTE: Eventually, starting the monotonic will require proof that the RTC
20//! clock has been configured. However, this requires v2 of the clock API for
21//! SAMx5x chips, which is not yet fully supported in the rest of the HAL.**
22//!
23//! # RTC modes
24//!
25//! The RTC on all chip variants has two counter modes: mode 0 features a 32-bit
26//! hardware counter, and mode 1 features a a 16-bit hardware counter but some
27//! additional features. Part of the [`Monotonic`](rtic_time::Monotonic)
28//! contract is that the monotonic should always count up and never roll over
29//! back to time zero. However, even the 32-bit hardware counter will overflow
30//! after about 36 hours using the faster clock rate, which is not acceptable.
31//!
32//! A technique known as [half-period counting
33//! (HPC)](rtic_time::half_period_counter) is used to effectively increase the
34//! montononic counter to be 64 bits wide in either mode. The result is a
35//! monotonic that effectively counts up forever without rolling over. This
36//! technique requires two compare registers, one for waking RTIC tasks and one
37//! for HPC. The number of compare registers available on ATSAMD chips is as
38//! follows:
39//!
40//! |            | SAMD11/21 | SAMx5x |
41//! | -----------| --------- | ------ |
42//! | **Mode 0** | 1         | 2      |
43//! | **Mode 1** | 2         | 4      |
44//!
45//! As a result, HPC can be done in mode 0 for SAMx5x chips but requires mode 1
46//! for SAMD11/21 variants. The monotonic provided for each variant uses the
47//! appropriate RTC mode.
48//!
49//! The monotonics have the following specifications:
50//!
51//! |                                      | 1 kHz clock        | 32 kHz clock        |
52//! | ------------------------------------ | ------------------ | ------------------- |
53//! | **Rollover period**                  | ~571 million years | ~17.8 million years |
54//! | **HPC interrupt period (SAMD11/21)** | 32 seconds         | 1 second            |
55//! | **HPC interrupt period (SAMx5x)**    | ~24 days           | ~18 hours           |
56//! | **Time resolution**                  | ~977 μs            | ~31 μs              |
57//!
58//! # Usage
59//!
60//! The monotonic should be created using the
61//! [macro](crate::rtc_monotonic). The first macro argument is the name of
62//! the global structure that will implement
63//! [`Monotonic`](rtic_time::Monotonic). The RTC clock rate must be
64//! known at compile time, and so the appropriate type from [`rtc_clock`] must
65//! be passed to the macro as the second argument.
66//!
67//! Sometime during initialization, the monotonic also must be started by
68//! calling the `start` method on the created monotonic. The
69//! [`Rtc`](crate::pac::Rtc) peripheral struct must be passed to `start` to
70//! ensure that the monotonic has complete control of the RTC.
71//!
72//! Note that the macro creates the RTC interrupt handler, and starting the
73//! monotonic enables RTC interrupts in the NVIC, so that this does not need to
74//! be done manually.
75//!
76//! # Example
77//!
78//! ```
79//! use atsamd_hal::prelude::*;
80//! use atsamd_hal::rtc::rtic::rtc_clock;
81//!
82//! // Create the monotonic struct named `Mono`
83//! rtc_monotonic!(Mono, rtc_clock::Clock32k);
84//!
85//! // Uncomment if not using the RTIC RTOS:
86//! // #[unsafe(no_mangle)]
87//! // static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8 = 1;
88//! //
89//! // This tells the monotonic driver the maximum interrupt
90//! // priority it's allowed to use. RTIC sets it automatically,
91//! // but you need to set it manually if you're writing
92//! // a RTIC-less app.
93//!
94//! fn init() {
95//!     # // This is normally provided by the selected PAC
96//!     # let rtc = unsafe { core::mem::transmute(()) };
97//!     # let mut mclk = unsafe { core::mem::transmute(()) };
98//!     # let mut osc32kctrl = unsafe { core::mem::transmute(()) };
99//!     // Here the RTC clock source should be configured using the clocks API
100//!
101//!     // Start the monotonic
102//!     Mono::start(rtc);
103//! }
104//!
105//! async fn usage() {
106//!     loop {
107//!          // Use the monotonic
108//!          let timestamp = Mono::now();
109//!
110//!          Mono::delay_until(timestamp + 2u32.secs()).await;
111//!          Mono::delay(100u32.millis()).await;
112//!     }
113//! }
114//! ```
115//!
116//! # Other notes
117//!
118//! The number returned by
119//! [`Monotonic::now().ticks()`](rtic_monotonic::Monotonic::now) will always
120//! increase (barring monotonic rollover). However, due to the register
121//! [synchronization delay](https://onlinedocs.microchip.com/oxy/GUID-F5813793-E016-46F5-A9E2-718D8BCED496-en-US-14/GUID-0C52DB00-4BF6-4F41-85B5-B76529875364.html),
122//! the number returned may not always increment by one every time it changes.
123//! In fact, testing shows that it typically increments by four every time it
124//! changes. This is true regardless of the clock rate used, as the
125//! synchronization delay scales along with the clock period.
126
127/// Items for RTIC v1.
128///
129/// This mainly implements [`rtic_monotonic::Monotonic`] for
130/// [`Rtc<Count32Mode>`](crate::rtc::Rtc).
131///
132/// This will be removed in a future release, users should migrate to RTIC v2.
133#[deprecated]
134pub mod v1 {
135    use crate::rtc::{
136        Count32Mode, Rtc,
137        modes::{
138            RtcMode,
139            mode0::{Compare0, RtcMode0},
140        },
141    };
142    use rtic_monotonic::Monotonic;
143
144    /// The RTC clock frequency in Hz.
145    pub const CLOCK_FREQ: u32 = 32_768;
146
147    /// The [`fugit`] time instant.
148    pub type Instant = fugit::Instant<u32, 1, CLOCK_FREQ>;
149    /// The [`fugit`] time duration.
150    pub type Duration = fugit::Duration<u32, 1, CLOCK_FREQ>;
151
152    impl Monotonic for Rtc<Count32Mode> {
153        type Instant = Instant;
154        type Duration = Duration;
155        unsafe fn reset(&mut self) {
156            // Since reset is only called once, we use it to enable the interrupt generation
157            // bit.
158            RtcMode0::enable_interrupt::<Compare0>(&self.rtc);
159        }
160
161        fn now(&mut self) -> Self::Instant {
162            Self::Instant::from_ticks(self.count32())
163        }
164
165        fn zero() -> Self::Instant {
166            Self::Instant::from_ticks(0)
167        }
168
169        fn set_compare(&mut self, instant: Self::Instant) {
170            RtcMode0::set_compare(&self.rtc, 0, instant.ticks());
171        }
172
173        fn clear_compare_flag(&mut self) {
174            RtcMode0::clear_interrupt_flag::<Compare0>(&self.rtc);
175        }
176    }
177}
178
179mod backends;
180
181#[hal_cfg("rtc-d5x")]
182use super::modes::{RtcMode, mode0::RtcMode0};
183#[hal_cfg(any("rtc-d11", "rtc-d21"))]
184use super::modes::{RtcMode, mode1::RtcMode1};
185use crate::interrupt::{NVIC_PRIO_BITS, Priority};
186use atsamd_hal_macros::hal_cfg;
187
188/// Types used to specify the RTC clock rate at compile time when creating the
189/// monotonics.
190///
191/// These types utilize [type-level programming](crate::typelevel)
192/// techniques and are passed to the [monotonic creation
193/// macro](crate::rtc_monotonic).
194/// The RTC clock rate must be specified at compile time so that the `Instant`
195/// and `Duration` types in
196/// [`TimerQueueBasedMonotonic`](rtic_time::monotonic::TimerQueueBasedMonotonic)
197/// can be specified.
198pub mod rtc_clock {
199    /// Type-level enum for available RTC clock rates.
200    pub trait RtcClockRate {
201        const RATE_HZ: u32;
202    }
203
204    /// Type level [`RtcClockRate`] variant for the 32.768 kHz clock rate.
205    pub enum Clock32k {}
206    impl RtcClockRate for Clock32k {
207        const RATE_HZ: u32 = 32_768;
208    }
209
210    /// Type level [`RtcClockRate`] variant for the 1.024 kHz clock rate.
211    pub enum Clock1k {}
212    impl RtcClockRate for Clock1k {
213        const RATE_HZ: u32 = 1_024;
214    }
215
216    /// Type level [`RtcClockRate`] variant for a custom clock rate
217    pub enum ClockCustom<const RATE_HZ: u32> {}
218    impl<const RATE_HZ: u32> RtcClockRate for ClockCustom<RATE_HZ> {
219        const RATE_HZ: u32 = RATE_HZ;
220    }
221}
222
223trait RtcModeMonotonic: RtcMode {
224    /// The COUNT value representing a half period.
225    const HALF_PERIOD: Self::Count;
226    /// Slack covering the COUNT register synchronization horizon.
227    ///
228    /// A read of COUNT returns the last synchronized value, which can lag the
229    /// counter by up to two synchronization periods of 5-6 CLK_RTC cycles
230    /// each (DS60001507M 13.3.3/13.3.7), i.e. ~12 ticks at DIV1. This bounds
231    /// both how far a compare that actually fired can be ahead of a read
232    /// (the interrupt handler's catch-up wait) and the minimum margin a
233    /// compare must be armed ahead of the COUNT in order to reliably trigger.
234    const SYNC_SLACK_TICKS: Self::Count;
235}
236
237#[hal_cfg("rtc-d5x")]
238impl RtcModeMonotonic for RtcMode0 {
239    const HALF_PERIOD: Self::Count = 0x8000_0000;
240    const SYNC_SLACK_TICKS: Self::Count = 12;
241}
242#[hal_cfg(any("rtc-d11", "rtc-d21"))]
243impl RtcModeMonotonic for RtcMode1 {
244    const HALF_PERIOD: Self::Count = 0x8000;
245    const SYNC_SLACK_TICKS: Self::Count = 12;
246}
247
248mod backend {
249    use super::*;
250
251    // For SAMD11/21 chips mode 1 is the only sensible option
252    #[hal_cfg(any("rtc-d11", "rtc-d21"))]
253    use crate::rtc::modes::mode1::{Compare0, Compare1, Overflow};
254
255    #[hal_cfg(any("rtc-d11", "rtc-d21"))]
256    crate::__internal_half_period_counting_backend!(
257        RtcBackend, RtcMode1, 1, Compare0, Compare1, Overflow
258    );
259
260    // For SAMx5x mode 0 is the best option
261    #[hal_cfg("rtc-d5x")]
262    use crate::rtc::modes::mode0::{Compare0, Compare1, Overflow};
263
264    #[hal_cfg("rtc-d5x")]
265    crate::__internal_half_period_counting_backend!(
266        RtcBackend, RtcMode0, 0, Compare0, Compare1, Overflow
267    );
268}
269
270pub use backend::RtcBackend;
271
272#[doc(hidden)]
273#[macro_export]
274macro_rules! __internal_create_rtc_interrupt {
275    ($backend:ident) => {
276        #[unsafe(no_mangle)]
277        #[allow(non_snake_case)]
278        unsafe extern "C" fn RTC() {
279            $crate::rtc::rtic::$backend::interrupt_handler();
280        }
281    };
282}
283
284#[doc(hidden)]
285#[macro_export]
286macro_rules! __internal_create_rtc_struct {
287    ($name:ident, $backend:ident, $clock_rate:ty) => {
288        /// A `Monotonic` based on the RTC peripheral.
289        pub struct $name;
290
291        impl $name {
292            /// This method must be called only once.
293            pub fn start(rtc: $crate::pac::Rtc) {
294                use $crate::rtc::rtic::rtc_clock::*;
295                $crate::__internal_create_rtc_interrupt!($backend);
296
297                $crate::rtc::rtic::$backend::_start(rtc);
298            }
299        }
300
301        use $crate::rtc::rtic::rtc_clock::RtcClockRate;
302
303        impl $crate::rtic_time::monotonic::TimerQueueBasedMonotonic for $name {
304            type Backend = $crate::rtc::rtic::$backend;
305            type Instant = $crate::fugit::Instant<
306                <Self::Backend as $crate::rtic_time::timer_queue::TimerQueueBackend>::Ticks,
307                1,
308                { <$clock_rate>::RATE_HZ },
309            >;
310            type Duration = $crate::fugit::Duration<
311                <Self::Backend as $crate::rtic_time::timer_queue::TimerQueueBackend>::Ticks,
312                1,
313                { <$clock_rate>::RATE_HZ },
314            >;
315        }
316
317        $crate::rtic_time::impl_embedded_hal_delay_fugit!($name);
318        $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name);
319    };
320}
321
322/// Create an RTIC v2 monotonic that uses the RTC.
323///
324/// See the [`rtic`](crate::rtc::rtic) module for details.
325#[macro_export]
326macro_rules! rtc_monotonic {
327    ($name:ident, $clock_rate: ty) => {
328        $crate::__internal_create_rtc_struct!($name, RtcBackend, $clock_rate);
329    };
330}
331
332/// This function was modified from the private function in `rtic-monotonics`,
333/// part of the [`rtic`](https://github.com/rtic-rs/rtic) project.
334///
335/// Note that this depends on the static variable `RTIC_ASYNC_MAX_LOGICAL_PRIO`
336/// defined as part of RTIC. Refer to the example in the [`rtic`
337/// module](crate::rtc::rtic) documentation for more details.
338///
339/// See LICENSE-MIT and LICENSE-APACHE for the licenses.
340unsafe fn set_monotonic_prio(interrupt: impl cortex_m::interrupt::InterruptNumber) {
341    unsafe extern "C" {
342        static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8;
343    }
344
345    unsafe {
346        let max_prio = RTIC_ASYNC_MAX_LOGICAL_PRIO.clamp(1, 1 << NVIC_PRIO_BITS);
347        let hw_prio = Priority::from_numeric(max_prio).unwrap().logical2hw();
348
349        // We take ownership of the entire IRQ and all settings to it, we only change
350        // settings for the IRQ we control.
351        // This will also compile-error in case the NVIC changes in size.
352        let mut nvic: cortex_m::peripheral::NVIC = core::mem::transmute(());
353
354        nvic.set_priority(interrupt, hw_prio);
355    }
356}