rtic_monotonics/imxrt.rs
1//! [`Monotonic`](rtic_time::Monotonic) implementations for i.MX RT's GPT peripherals.
2//!
3//! # Example
4//!
5//! ```
6//! use rtic_monotonics::imxrt::prelude::*;
7//!
8//! // Create the type `Mono`. It will manage the GPT1 timer, and
9//! // run with a resolution of 1 µs (1,000,000 ticks per second).
10//! imxrt_gpt1_monotonic!(Mono, 1_000_000);
11//!
12//! fn init() {
13//! // Obtain ownership of the timer register block.
14//! let gpt1 = unsafe { imxrt_ral::gpt::GPT1::instance() };
15//!
16//! // Configure the timer tick rate as specified earlier
17//! todo!("Configure the gpt1 peripheral to a tick rate of 1_000_000");
18//!
19//! // Start the monotonic
20//! Mono::start(gpt1);
21//! }
22//!
23//! async fn usage() {
24//! loop {
25//! // You can use the monotonic to get the time...
26//! let timestamp = Mono::now();
27//! // ...and you can use it to add a delay to this async function
28//! Mono::delay(100.millis()).await;
29//! }
30//! }
31//! ```
32
33use portable_atomic::{AtomicU32, Ordering};
34use rtic_time::{
35 half_period_counter::calculate_now,
36 timer_queue::{TimerQueue, TimerQueueBackend},
37};
38
39pub use imxrt_ral as ral;
40
41/// Common definitions and traits for using the i.MX RT monotonics
42pub mod prelude {
43 #[cfg(feature = "imxrt_gpt1")]
44 pub use crate::imxrt_gpt1_monotonic;
45 #[cfg(feature = "imxrt_gpt2")]
46 pub use crate::imxrt_gpt2_monotonic;
47
48 pub use crate::Monotonic;
49 pub use fugit::{self, ExtU64, ExtU64Ceil};
50}
51
52#[doc(hidden)]
53#[macro_export]
54macro_rules! __internal_create_imxrt_timer_interrupt {
55 ($mono_backend:ident, $timer:ident) => {
56 #[no_mangle]
57 #[allow(non_snake_case)]
58 unsafe extern "C" fn $timer() {
59 use $crate::TimerQueueBackend;
60 $crate::imxrt::$mono_backend::timer_queue().on_monotonic_interrupt();
61 }
62 };
63}
64
65#[doc(hidden)]
66#[macro_export]
67macro_rules! __internal_create_imxrt_timer_struct {
68 ($name:ident, $mono_backend:ident, $timer:ident, $tick_rate_hz:expr) => {
69 /// A `Monotonic` based on the GPT peripheral.
70 pub struct $name;
71
72 impl $name {
73 /// Starts the `Monotonic`.
74 ///
75 /// This method must be called only once.
76 pub fn start(gpt: $crate::imxrt::ral::gpt::$timer) {
77 $crate::__internal_create_imxrt_timer_interrupt!($mono_backend, $timer);
78
79 $crate::imxrt::$mono_backend::_start(gpt);
80 }
81 }
82
83 impl $crate::TimerQueueBasedMonotonic for $name {
84 type Backend = $crate::imxrt::$mono_backend;
85 type Instant = $crate::fugit::Instant<
86 <Self::Backend as $crate::TimerQueueBackend>::Ticks,
87 1,
88 { $tick_rate_hz },
89 >;
90 type Duration = $crate::fugit::Duration<
91 <Self::Backend as $crate::TimerQueueBackend>::Ticks,
92 1,
93 { $tick_rate_hz },
94 >;
95 }
96
97 $crate::rtic_time::impl_embedded_hal_delay_fugit!($name);
98 $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name);
99 };
100}
101
102/// Create a GPT1 based monotonic and register the GPT1 interrupt for it.
103///
104/// See [`crate::imxrt`] for more details.
105///
106/// # Arguments
107///
108/// * `name` - The name that the monotonic type will have.
109/// * `tick_rate_hz` - The tick rate of the timer peripheral. It's the user's responsibility
110/// to configure the peripheral to the given frequency before starting the
111/// monotonic.
112#[cfg(feature = "imxrt_gpt1")]
113#[macro_export]
114macro_rules! imxrt_gpt1_monotonic {
115 ($name:ident, $tick_rate_hz:expr) => {
116 $crate::__internal_create_imxrt_timer_struct!($name, Gpt1Backend, GPT1, $tick_rate_hz);
117 };
118}
119
120/// Create a GPT2 based monotonic and register the GPT2 interrupt for it.
121///
122/// See [`crate::imxrt`] for more details.
123///
124/// # Arguments
125///
126/// * `name` - The name that the monotonic type will have.
127/// * `tick_rate_hz` - The tick rate of the timer peripheral. It's the user's responsibility
128/// to configure the peripheral to the given frequency before starting the
129/// monotonic.
130#[cfg(feature = "imxrt_gpt2")]
131#[macro_export]
132macro_rules! imxrt_gpt2_monotonic {
133 ($name:ident, $tick_rate_hz:expr) => {
134 $crate::__internal_create_imxrt_timer_struct!($name, Gpt2Backend, GPT2, $tick_rate_hz);
135 };
136}
137
138macro_rules! make_timer {
139 ($mono_name:ident, $backend_name:ident, $timer:ident, $period:ident, $tq:ident$(, doc: ($($doc:tt)*))?) => {
140 /// GPT based [`TimerQueueBackend`].
141 $(
142 #[cfg_attr(docsrs, doc(cfg($($doc)*)))]
143 )?
144
145 pub struct $backend_name;
146
147 use ral::gpt::$timer;
148
149 /// Number of 2^31 periods elapsed since boot.
150 static $period: AtomicU32 = AtomicU32::new(0);
151 static $tq: TimerQueue<$backend_name> = TimerQueue::new();
152
153 impl $backend_name {
154 /// Starts the timer.
155 ///
156 /// **Do not use this function directly.**
157 ///
158 /// Use the prelude macros instead.
159 pub fn _start(gpt: $timer) {
160
161 // Disable the timer.
162 ral::modify_reg!(ral::gpt, gpt, CR, EN: 0);
163 // Clear all status registers.
164 ral::write_reg!(ral::gpt, gpt, SR, 0b11_1111);
165
166 // Base configuration
167 ral::modify_reg!(ral::gpt, gpt, CR,
168 ENMOD: 1, // Clear timer state
169 FRR: 1, // Free-Run mode
170 );
171
172 // Reset period
173 $period.store(0, Ordering::SeqCst);
174
175 // Enable interrupts
176 ral::write_reg!(ral::gpt, gpt, IR,
177 ROVIE: 1, // Rollover interrupt
178 OF1IE: 1, // Timer compare 1 interrupt (for half-periods)
179 OF2IE: 1, // Timer compare 2 interrupt (for dynamic wakeup)
180 );
181
182 // Configure half-period interrupt
183 ral::write_reg!(ral::gpt, gpt, OCR[0], 0x8000_0000);
184
185 // Dynamic interrupt register; for now initialize to zero
186 // so it gets combined with rollover interrupt
187 ral::write_reg!(ral::gpt, gpt, OCR[1], 0x0000_0000);
188
189 // Initialize timer queue
190 $tq.initialize(Self {});
191
192 // Enable the timer
193 ral::modify_reg!(ral::gpt, gpt, CR, EN: 1);
194 ral::modify_reg!(ral::gpt, gpt, CR,
195 ENMOD: 0, // Keep state when disabled
196 );
197
198 // SAFETY: We take full ownership of the peripheral and interrupt vector,
199 // plus we are not using any external shared resources so we won't impact
200 // basepri/source masking based critical sections.
201 unsafe {
202 crate::set_monotonic_prio(ral::NVIC_PRIO_BITS, ral::Interrupt::$timer);
203 cortex_m::peripheral::NVIC::unmask(ral::Interrupt::$timer);
204 }
205 }
206 }
207
208 impl TimerQueueBackend for $backend_name {
209 type Ticks = u64;
210
211 fn now() -> Self::Ticks {
212 let gpt = unsafe{ $timer::instance() };
213
214 calculate_now(
215 || $period.load(Ordering::Relaxed),
216 || ral::read_reg!(ral::gpt, gpt, CNT)
217 )
218 }
219
220 fn set_compare(instant: Self::Ticks) {
221 let gpt = unsafe{ $timer::instance() };
222
223 // Set the timer regardless of whether it is multiple periods in the future,
224 // or even already in the past.
225 // The worst thing that can happen is a spurious wakeup, and with a timer
226 // period of half an hour, this is hardly a problem.
227
228 let ticks_wrapped = instant as u32;
229
230 ral::write_reg!(ral::gpt, gpt, OCR[1], ticks_wrapped);
231 }
232
233 fn clear_compare_flag() {
234 let gpt = unsafe{ $timer::instance() };
235 ral::write_reg!(ral::gpt, gpt, SR, OF2: 1);
236 }
237
238 fn pend_interrupt() {
239 cortex_m::peripheral::NVIC::pend(ral::Interrupt::$timer);
240 }
241
242 fn on_interrupt() {
243 let gpt = unsafe{ $timer::instance() };
244
245 let (rollover, half_rollover) = ral::read_reg!(ral::gpt, gpt, SR, ROV, OF1);
246
247 if rollover != 0 {
248 let prev = $period.fetch_add(1, Ordering::Relaxed);
249 ral::write_reg!(ral::gpt, gpt, SR, ROV: 1);
250 assert!(prev % 2 == 1, "Monotonic must have skipped an interrupt!");
251 }
252
253 if half_rollover != 0 {
254 let prev = $period.fetch_add(1, Ordering::Relaxed);
255 ral::write_reg!(ral::gpt, gpt, SR, OF1: 1);
256 assert!(prev % 2 == 0, "Monotonic must have skipped an interrupt!");
257 }
258 }
259
260 fn timer_queue() -> &'static TimerQueue<Self> {
261 &$tq
262 }
263 }
264 };
265}
266
267#[cfg(feature = "imxrt_gpt1")]
268make_timer!(Gpt1, Gpt1Backend, GPT1, GPT1_HALFPERIODS, GPT1_TQ);
269
270#[cfg(feature = "imxrt_gpt2")]
271make_timer!(Gpt2, Gpt2Backend, GPT2, GPT2_HALFPERIODS, GPT2_TQ);