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);