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}