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    /// The minimum number of ticks that compares need to be ahead of the COUNT
227    /// in order to trigger.
228    const MIN_COMPARE_TICKS: Self::Count;
229}
230
231#[hal_cfg("rtc-d5x")]
232impl RtcModeMonotonic for RtcMode0 {
233    const HALF_PERIOD: Self::Count = 0x8000_0000;
234    const MIN_COMPARE_TICKS: Self::Count = 8;
235}
236#[hal_cfg(any("rtc-d11", "rtc-d21"))]
237impl RtcModeMonotonic for RtcMode1 {
238    const HALF_PERIOD: Self::Count = 0x8000;
239    const MIN_COMPARE_TICKS: Self::Count = 8;
240}
241
242mod backend {
243    use super::*;
244
245    // For SAMD11/21 chips mode 1 is the only sensible option
246    #[hal_cfg(any("rtc-d11", "rtc-d21"))]
247    use crate::rtc::modes::mode1::{Compare0, Compare1, Overflow};
248
249    #[hal_cfg(any("rtc-d11", "rtc-d21"))]
250    crate::__internal_half_period_counting_backend!(
251        RtcBackend, RtcMode1, 1, Compare0, Compare1, Overflow
252    );
253
254    // For SAMx5x mode 0 is the best option
255    #[hal_cfg("rtc-d5x")]
256    use crate::rtc::modes::mode0::{Compare0, Compare1, Overflow};
257
258    #[hal_cfg("rtc-d5x")]
259    crate::__internal_half_period_counting_backend!(
260        RtcBackend, RtcMode0, 0, Compare0, Compare1, Overflow
261    );
262}
263
264pub use backend::RtcBackend;
265
266#[doc(hidden)]
267#[macro_export]
268macro_rules! __internal_create_rtc_interrupt {
269    ($backend:ident) => {
270        #[unsafe(no_mangle)]
271        #[allow(non_snake_case)]
272        unsafe extern "C" fn RTC() {
273            $crate::rtc::rtic::$backend::interrupt_handler();
274        }
275    };
276}
277
278#[doc(hidden)]
279#[macro_export]
280macro_rules! __internal_create_rtc_struct {
281    ($name:ident, $backend:ident, $clock_rate:ty) => {
282        /// A `Monotonic` based on the RTC peripheral.
283        pub struct $name;
284
285        impl $name {
286            /// This method must be called only once.
287            pub fn start(rtc: $crate::pac::Rtc) {
288                use $crate::rtc::rtic::rtc_clock::*;
289                $crate::__internal_create_rtc_interrupt!($backend);
290
291                $crate::rtc::rtic::$backend::_start(rtc);
292            }
293        }
294
295        use $crate::rtc::rtic::rtc_clock::RtcClockRate;
296
297        impl $crate::rtic_time::monotonic::TimerQueueBasedMonotonic for $name {
298            type Backend = $crate::rtc::rtic::$backend;
299            type Instant = $crate::fugit::Instant<
300                <Self::Backend as $crate::rtic_time::timer_queue::TimerQueueBackend>::Ticks,
301                1,
302                { <$clock_rate>::RATE_HZ },
303            >;
304            type Duration = $crate::fugit::Duration<
305                <Self::Backend as $crate::rtic_time::timer_queue::TimerQueueBackend>::Ticks,
306                1,
307                { <$clock_rate>::RATE_HZ },
308            >;
309        }
310
311        $crate::rtic_time::impl_embedded_hal_delay_fugit!($name);
312        $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name);
313    };
314}
315
316/// Create an RTIC v2 monotonic that uses the RTC.
317///
318/// See the [`rtic`](crate::rtc::rtic) module for details.
319#[macro_export]
320macro_rules! rtc_monotonic {
321    ($name:ident, $clock_rate: ty) => {
322        $crate::__internal_create_rtc_struct!($name, RtcBackend, $clock_rate);
323    };
324}
325
326/// This function was modified from the private function in `rtic-monotonics`,
327/// part of the [`rtic`](https://github.com/rtic-rs/rtic) project.
328///
329/// Note that this depends on the static variable `RTIC_ASYNC_MAX_LOGICAL_PRIO`
330/// defined as part of RTIC. Refer to the example in the [`rtic`
331/// module](crate::rtc::rtic) documentation for more details.
332///
333/// See LICENSE-MIT and LICENSE-APACHE for the licenses.
334unsafe fn set_monotonic_prio(interrupt: impl cortex_m::interrupt::InterruptNumber) {
335    unsafe extern "C" {
336        static RTIC_ASYNC_MAX_LOGICAL_PRIO: u8;
337    }
338
339    unsafe {
340        let max_prio = RTIC_ASYNC_MAX_LOGICAL_PRIO.clamp(1, 1 << NVIC_PRIO_BITS);
341        let hw_prio = Priority::from_numeric(max_prio).unwrap().logical2hw();
342
343        // We take ownership of the entire IRQ and all settings to it, we only change
344        // settings for the IRQ we control.
345        // This will also compile-error in case the NVIC changes in size.
346        let mut nvic: cortex_m::peripheral::NVIC = core::mem::transmute(());
347
348        nvic.set_priority(interrupt, hw_prio);
349    }
350}