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}