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}