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}