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