cortex_m_systick_countdown/lib.rs
1#![no_std]
2#![warn(clippy::all)]
3
4//! Wrappers around the Cortex-M SysTick peripheral for making
5//! [`embedded_hal::timer::CountDown`](https://docs.rs/embedded-hal/0.2.3/embedded_hal/timer/trait.CountDown.html)
6//! instances.
7//!
8//! The `CountDown` trait is by default non-blocking, but can be made blocking
9//! with [`nb::block!`](https://docs.rs/nb/0.1.2/nb/macro.block.html).
10//!
11//! ## Usage
12//!
13//! Create an instance of [`PollingSysTick`](struct.PollingSysTick.html) after
14//! you have configured your clocks. It consumes the `SYST` peripheral in order
15//! to get exclusive control over it.
16//!
17//! You can use the [`embedded_hal::blocking::delay::DelayMs`
18//! trait](https://docs.rs/embedded-hal/0.2.3/embedded_hal/blocking/delay/trait.DelayMs.html)
19//! on `PollingSysTick` directly, or you can use `PollingSysTick` to make
20//! `MillisCountDown` instances that are independent, non-blocking
21//! counters.
22
23use core::{num::Wrapping, time::Duration};
24
25use cortex_m::peripheral::{syst::SystClkSource, SYST};
26
27use embedded_hal::blocking::delay::DelayMs;
28use embedded_hal::timer::CountDown;
29
30use nb;
31use void::Void;
32
33use core::cell::UnsafeCell;
34
35/// Trait that abstracts a counter that increases as milliseconds go by.
36///
37/// Factored out to leave the door open for different SysTick counters, such as
38/// counting via interrupts.
39pub trait CountsMillis {
40 /// Returns a value that must not increment faster than once per
41 /// millisecond, and will wrap around.
42 fn count(&self) -> Wrapping<u32>;
43}
44
45/// Configuration information for setting the SysTick reload value.
46pub struct SysTickCalibration {
47 /// The number of ticks of the SysTick’s clock source to get to 1ms.
48 ///
49 /// Note that reload values should typically be one fewer than the number of
50 /// clock cycles, since an extra one is always needed to detect the rollover
51 /// and reload the counter.
52 pub ticks_per_ms: u32,
53}
54
55impl SysTickCalibration {
56 /// Gets the calibration from the chip’s built-in "ticks per 10ms" value.
57 ///
58 /// Returns an `Option` because this value is is not present on all devices
59 /// (SAMD51 for example seems to not have it, since its processor speed is
60 /// configurable). In those cases, use
61 /// [`from_clock_hz`](#method.from_clock_hz) instead.
62 pub fn built_in() -> Option<SysTickCalibration> {
63 let calibrated_tick_value = SYST::get_ticks_per_10ms();
64
65 if calibrated_tick_value == 0 {
66 None
67 } else {
68 Some(SysTickCalibration {
69 // Leave one clock cycle for checking the overflow
70 ticks_per_ms: (calibrated_tick_value + 1) / 10 - 1,
71 })
72 }
73 }
74
75 /// Creates a calibration from the underlying frequency of the clock that
76 /// drives SysTick. This typically seems to be the same frequency that the
77 /// processor is currently running at.
78 ///
79 /// For SAMD51 processors, if you don’t want to hard-code a known size, you
80 /// can get this from the gclck0 frequency.
81 pub fn from_clock_hz(hz: u32) -> SysTickCalibration {
82 SysTickCalibration {
83 ticks_per_ms: hz / 1_000 - 1,
84 }
85 }
86}
87
88/// Millisecond counter based on SysTick
89///
90/// Effectively a singleton because this struct will consume the only SYST value
91/// in the program. (Use [`free`](#method.free) if you need to get it back.)
92///
93/// ## Usage
94///
95/// For simple blocking delays, use the
96/// [`embedded_hal::blocking::delay::DelayMs`
97/// trait](https://docs.rs/embedded-hal/0.2.3/embedded_hal/blocking/delay/trait.DelayMs.html)
98/// to pause the program for a certain amount of time.
99///
100/// For timeouts or other non-blocking operations, create a
101/// [`MillisCountDown`](struct.MillisCountDown.html) instance and use its
102/// `start` and `wait` methods. You can have multiple `MillisCountDown`
103/// instances active at the same time.
104///
105/// Because this uses polling for measuring SysTick, it will work even during
106/// periods where interrupts are disabled.
107///
108/// ## Implementation
109///
110/// We configure SysTick’s reload value to a count that will take 1ms to
111/// decrement to. When we detect that this count has wrapped over we increment
112/// our internal count of the milliseconds that have ellapsed.
113///
114/// We use the polling pattern for querying the time, rather than relying on
115/// interrupts, which means that our count is only guaranteed to be _no faster_
116/// than SysTick. We only keep accurate count while the [`count`](#method.count)
117/// method is being actively called, and may experience some jitter depending on
118/// where SysTick is in its count when you start a timer.
119///
120/// This also means we need to use internal mutability so that we can access the
121/// SYST.has_wrapped() method (which mutates on read) and update our counter.
122pub struct PollingSysTick {
123 syst: UnsafeCell<SYST>,
124 counter: UnsafeCell<Wrapping<u32>>,
125}
126
127impl PollingSysTick {
128 /// Configures SysTick based on the values provided in the calibration.
129 pub fn new(mut syst: SYST, calibration: &SysTickCalibration) -> Self {
130 syst.disable_interrupt();
131 syst.set_clock_source(SystClkSource::Core);
132 syst.set_reload(calibration.ticks_per_ms);
133 syst.enable_counter();
134
135 PollingSysTick {
136 syst: UnsafeCell::new(syst),
137 counter: UnsafeCell::default(),
138 }
139 }
140
141 /// Turns this value back into the underlying SysTick.
142 pub fn free(self) -> SYST {
143 self.syst.into_inner()
144 }
145}
146
147impl CountsMillis for PollingSysTick {
148 /// Returns a number that goes up no faster than once per millisecond. This
149 /// value will not increment unless polled (this is so it can operate
150 /// during critical sections when interrupts are disabled).
151 fn count(&self) -> Wrapping<u32> {
152 // This is all unsafe because incrementing the internal count happens as
153 // a side effect of reading it. We’re ok with that, because we know that
154 // we have sole control over the SYST singleton, so we’re the only ones
155 // who will see the wrapping.
156 if unsafe { (*self.syst.get()).has_wrapped() } {
157 // Disabled interrupts because += is non-atomic.
158 cortex_m::interrupt::free(|_| unsafe {
159 (*self.counter.get()) += Wrapping(1);
160 });
161 }
162
163 unsafe { *self.counter.get() }
164 }
165}
166
167impl DelayMs<u32> for PollingSysTick {
168 fn delay_ms(&mut self, ms: u32) {
169 let mut count_down = MillisCountDown::new(self);
170 count_down.start_ms(ms);
171 nb::block!(count_down.wait()).unwrap();
172 }
173}
174
175/// `CountDown` that uses an underlying `CountsMillis` (probably
176/// `PollingSysTick`).
177pub struct MillisCountDown<'a, CM: CountsMillis> {
178 counter: &'a CM,
179 target_millis: Option<Wrapping<u32>>,
180}
181
182impl<'a, CM: CountsMillis> MillisCountDown<'a, CM> {
183 /// Creates a `MillisCountDown` from a `CountsMillis` source.
184 ///
185 /// `CountsMillis` is probably going to be your instance of
186 /// [`PollingSysTick`](struct.PollingSysTick.html).
187 pub fn new(counter: &'a CM) -> Self {
188 MillisCountDown {
189 target_millis: None,
190 counter,
191 }
192 }
193
194 /// Underlying version of `CountDown`’s `start` that takes a `u32` of
195 /// milliseconds rather than a `Duration`.
196 ///
197 /// Use this if you want to avoid the `u64`s in `Duration`.
198 pub fn start_ms(&mut self, ms: u32) {
199 self.target_millis = Some(self.counter.count() + Wrapping(ms));
200 }
201
202 /// Underlying implementation of `CountDown`’s `wait` that works directly on
203 /// our underlying u32 ms values and can be used by any `CountDown` trait
204 /// implementations.
205 ///
206 /// Calling this method before `start`, or after it has already returned
207 /// `Ok` will panic.
208 pub fn wait_ms(&mut self) -> Result<(), nb::Error<Void>> {
209 // Rollover-safe duration check derived from:
210 // https://playground.arduino.cc/Code/TimingRollover/
211 if (self.counter.count() - self.target_millis.unwrap()).0 as i32 > 0 {
212 self.target_millis.take();
213 Ok(())
214 } else {
215 Err(nb::Error::WouldBlock)
216 }
217 }
218}
219
220impl<'a, CM: CountsMillis> CountDown for MillisCountDown<'a, CM> {
221 type Time = Duration;
222
223 /// Starts timing the given `Duration`.
224 ///
225 /// [`wait`](#method.wait) will return
226 /// [`nb::Error::WouldBlock`](https://docs.rs/nb/0.1.2/nb/enum.Error.html#variant.WouldBlock)
227 /// until this amount of time has passed.
228 ///
229 /// Calling this method before the time has fully ellapsed will reset the
230 /// timer.
231 fn start<T>(&mut self, count: T)
232 where
233 T: Into<Self::Time>,
234 {
235 let dur: Self::Time = count.into();
236 let millis = (dur.as_secs() as u32) * 1000 + dur.subsec_millis() as u32;
237 self.start_ms(millis);
238 }
239
240 /// Returns
241 /// [`nb::Error::WillBlock`](https://docs.rs/nb/0.1.2/nb/enum.Error.html#variant.WouldBlock)
242 /// while the timer runs, then will return `Result::Ok`.
243 ///
244 /// Calling this method before `start`, or after it has already returned
245 /// `Ok` will panic.
246 fn wait(&mut self) -> Result<(), nb::Error<Void>> {
247 self.wait_ms()
248 }
249}