rtic_monotonics/
systick.rs

1//! [`Monotonic`](rtic_time::Monotonic) based on Cortex-M SysTick.
2//!
3//! Note: this implementation is inefficient as it
4//! ticks and generates interrupts at a constant rate.
5//!
6//! # Example
7//!
8//! ```
9//! use rtic_monotonics::systick::prelude::*;
10//!
11//! // Create the type `Mono`. It will manage the SysTick timer, and use it to
12//! // generate 1000 interrupts per second.
13//! systick_monotonic!(Mono, 1_000);
14//!
15//! fn init() {
16//!     let core_peripherals = cortex_m::Peripherals::take().unwrap();
17//!     // Start the monotonic using the cortex-m crate's Systick driver.
18//!     // We tell it we have a system clock of 12 MHz.
19//!     Mono::start(core_peripherals.SYST, 12_000_000);
20//! }
21//!
22//! async fn usage() {
23//!     loop {
24//!          // You can use the monotonic to get the time...
25//!          let timestamp = Mono::now();
26//!          // ...and you can use it to add a delay to this async function
27//!          Mono::delay(100.millis()).await;
28//!     }
29//! }
30//! ```
31//!
32//! By default, SysTick uses [`SystClkSource::Core`] as the clock source.
33//! To set the SysTick clock source, use `start_with_clock_source`:
34//!
35//! ```
36//! # use rtic_monotonics::systick::prelude::*;
37//! # systick_monotonic!(Mono, 1_000);
38//! use rtic_monotonics::systick::SystClkSource;
39//!
40//! fn init() {
41//!     let core_peripherals = cortex_m::Peripherals::take().unwrap();
42//!     // Start the monotonic using the cortex-m crate's Systick driver.
43//!     // We tell it we have a 12MHz external clock source.
44//!     Mono::start_with_clock_source(
45//!         core_peripherals.SYST,
46//!         12_000_000,
47//!         SystClkSource::External,
48//!     );
49//! }
50//! ```
51
52/// Common definitions and traits for using the systick monotonic
53pub mod prelude {
54    pub use crate::systick_monotonic;
55
56    pub use crate::Monotonic;
57
58    cfg_if::cfg_if! {
59        if #[cfg(feature = "systick-64bit")] {
60            pub use fugit::{self, ExtU64, ExtU64Ceil};
61        } else {
62            pub use fugit::{self, ExtU32, ExtU32Ceil};
63        }
64    }
65}
66
67pub use cortex_m::peripheral::{syst::SystClkSource, SYST};
68
69use portable_atomic::Ordering;
70use rtic_time::timer_queue::TimerQueue;
71
72use crate::TimerQueueBackend;
73
74cfg_if::cfg_if! {
75    if #[cfg(feature = "systick-64bit")] {
76        use portable_atomic::AtomicU64;
77        static SYSTICK_CNT: AtomicU64 = AtomicU64::new(0);
78    } else {
79        use portable_atomic::AtomicU32;
80        static SYSTICK_CNT: AtomicU32 = AtomicU32::new(0);
81    }
82}
83
84static SYSTICK_TIMER_QUEUE: TimerQueue<SystickBackend> = TimerQueue::new();
85
86/// Systick based [`TimerQueueBackend`].
87pub struct SystickBackend;
88
89impl SystickBackend {
90    /// Starts the monotonic timer.
91    ///
92    /// **Do not use this function directly.**
93    ///
94    /// Use the prelude macros instead.
95    pub fn _start(mut systick: SYST, sysclk: u32, timer_hz: u32, clk_source: SystClkSource) {
96        assert!(
97            sysclk.is_multiple_of(timer_hz),
98            "timer_hz cannot evenly divide sysclk! Please adjust the timer or sysclk frequency."
99        );
100        let reload = sysclk / timer_hz - 1;
101
102        assert!(reload <= 0x00ff_ffff);
103        assert!(reload > 0);
104
105        systick.disable_counter();
106        systick.set_clock_source(clk_source);
107        systick.set_reload(reload);
108        systick.enable_interrupt();
109        systick.enable_counter();
110
111        SYSTICK_TIMER_QUEUE.initialize(SystickBackend {});
112    }
113
114    fn systick() -> SYST {
115        unsafe { core::mem::transmute::<(), SYST>(()) }
116    }
117}
118
119impl TimerQueueBackend for SystickBackend {
120    cfg_if::cfg_if! {
121        if #[cfg(feature = "systick-64bit")] {
122            type Ticks = u64;
123        } else {
124            type Ticks = u32;
125        }
126    }
127
128    fn now() -> Self::Ticks {
129        if Self::systick().has_wrapped() {
130            SYSTICK_CNT.fetch_add(1, Ordering::AcqRel);
131        }
132
133        SYSTICK_CNT.load(Ordering::Relaxed)
134    }
135
136    fn set_compare(_: Self::Ticks) {
137        // No need to do something here, we get interrupts anyway.
138    }
139
140    fn clear_compare_flag() {
141        // NOOP with SysTick interrupt
142    }
143
144    fn pend_interrupt() {
145        cortex_m::peripheral::SCB::set_pendst();
146    }
147
148    fn on_interrupt() {
149        if Self::systick().has_wrapped() {
150            SYSTICK_CNT.fetch_add(1, Ordering::AcqRel);
151        }
152    }
153
154    fn timer_queue() -> &'static TimerQueue<Self> {
155        &SYSTICK_TIMER_QUEUE
156    }
157}
158
159/// Create a Systick based monotonic and register the Systick interrupt for it.
160///
161/// This macro expands to produce a new type called `$name`, which has a `fn
162/// start()` function for you to call. The type has an implementation of the
163/// `rtic_monotonics::TimerQueueBasedMonotonic` trait, the
164/// `embedded_hal::delay::DelayNs` trait and the
165/// `embedded_hal_async::delay::DelayNs` trait.
166///
167/// This macro also produces an interrupt handler for the SysTick interrupt, by
168/// creating an `extern "C" fn SysTick() { ... }`.
169///
170/// See [`crate::systick`] for more details.
171///
172/// # Arguments
173///
174/// * `name` - The name that the monotonic type will have.
175/// * `tick_rate_hz` - The tick rate of the timer peripheral.
176///   Can be omitted; defaults to 1kHz.
177#[macro_export]
178macro_rules! systick_monotonic {
179    ($name:ident) => {
180        $crate::systick_monotonic!($name, 1_000);
181    };
182    ($name:ident, $tick_rate_hz:expr) => {
183        /// A `Monotonic` based on SysTick.
184        pub struct $name;
185
186        impl $name {
187            /// Starts the `Monotonic`.
188            ///
189            /// The `sysclk` parameter is the speed at which SysTick runs at. This value should come from
190            /// the clock generation function of the used HAL.
191            ///
192            /// Panics if it is impossible to achieve the desired monotonic tick rate based
193            /// on the given `sysclk` parameter. If that happens, adjust the desired monotonic tick rate.
194            ///
195            /// This method, or `start_with_clock_source`, must be called only once.
196            pub fn start(systick: $crate::systick::SYST, sysclk: u32) {
197                Self::start_with_clock_source(systick, sysclk, $crate::systick::SystClkSource::Core)
198            }
199
200            /// Starts the `Monotonic` with your preferred SYST clock source.
201            ///
202            /// The `sysclk` parameter is the speed at which SysTick runs at. This value should come from
203            /// the clock generation function of the used HAL, depending on your `clk_source`.
204            ///
205            /// Panics if it is impossible to achieve the desired monotonic tick rate based
206            /// on the given `sysclk` parameter. If that happens, adjust the desired monotonic tick rate.
207            ///
208            /// This method, or `start`, must be called only once.
209            pub fn start_with_clock_source(
210                systick: $crate::systick::SYST,
211                sysclk: u32,
212                clk_source: $crate::systick::SystClkSource,
213            ) {
214                #[no_mangle]
215                #[allow(non_snake_case)]
216                unsafe extern "C" fn SysTick() {
217                    use $crate::TimerQueueBackend;
218                    $crate::systick::SystickBackend::timer_queue().on_monotonic_interrupt();
219                }
220
221                $crate::systick::SystickBackend::_start(systick, sysclk, $tick_rate_hz, clk_source);
222            }
223        }
224
225        impl $crate::TimerQueueBasedMonotonic for $name {
226            type Backend = $crate::systick::SystickBackend;
227            type Instant = $crate::fugit::Instant<
228                <Self::Backend as $crate::TimerQueueBackend>::Ticks,
229                1,
230                { $tick_rate_hz },
231            >;
232            type Duration = $crate::fugit::Duration<
233                <Self::Backend as $crate::TimerQueueBackend>::Ticks,
234                1,
235                { $tick_rate_hz },
236            >;
237        }
238
239        $crate::rtic_time::impl_embedded_hal_delay_fugit!($name);
240        $crate::rtic_time::impl_embedded_hal_async_delay_fugit!($name);
241    };
242}