embedded_async_timer/impls/stm32l4x6_lptim1.rs
1//! A AsyncTimer implementation for the LPTIM1 peripheral in the STM32L4x6 running at 32kHz.
2//!
3//! This module provides it's own RTC implementation on top of the LPTIM1 which extends its 16 bit counter to 32 bits,
4//! and provides alarms.
5//!
6//! This implementation assumes:
7//! * LSI on.
8//! * 32kHz granularity.
9//! * Interrupts are handled at least every second.
10//! * ~1.5 days maximum operation time.
11//!
12//! **Warning**: time will not observably progress (from `get_time()`) unless interrupts are handled.
13//!
14//! ```
15//! let p = stm32l4xx_hal::stm32::Peripherals::take().unwrap();
16//! let timer = AsyncTimer::new(Lptim1Rtc::new(p.LPTIM1, &p.RCC)));
17//! let mut flash = p.FLASH.constrain();
18//! let mut rcc = p.RCC.constrain();
19//! rcc.cfgr.lsi(true).freeze(&mut flash.acr);
20//!```
21//!
22//! Do not forget to call `handle_interrupt` in the interrupt handler for `LPTIM1`.
23
24use crate::{AsyncTimer, Timer};
25use core::num::NonZeroU32;
26use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU16, Ordering};
27use cortex_m::interrupt::CriticalSection;
28use stm32l4xx_hal::stm32::{Interrupt, LPTIM1, NVIC, RCC};
29
30const ALARM_TICKS_PER_SECOND: u64 = 32000;
31
32/// Ring size for timer. Timer will overflow during this value.
33///
34/// You might consider the values for ALARM_SEGMENT and 0 in fragments to be equal.
35const ALARM_SEGMENT: u32 = 0xff00;
36
37/// Any part between ALARM_SEGMENT and ALARM_DIRTY_ZONE will yield previous ALARM_SEGMENT as time.
38/// The ARRM interrupt **MUST** trigger within this zone to make this logic work.
39/// In our case that is within ~1Hz.
40const ALARM_DIRTY_ZONE: u32 = ALARM_SEGMENT / 2;
41
42/// Configurated ARR value; timer counter will reset during ALARM_ARR+1 and yield 0 sometimes instead.
43const ALARM_ARR: u32 = ALARM_SEGMENT - 1;
44
45/// A low-level Real Time Clock with alarms implemented with the LPTIM1 peripheral.
46///
47/// *Note:* call `handle_interrupt` in the LPTIM1 interrupt.
48///
49/// Uses the LSI to yield a 32Khz operating speed. Uses 16 bit time segments to create 32 bit time at 32Khz, yielding a total operating time of 1.5 days.
50/// The time will wrap around when saturated, but care must be taken to properly wrap around deadlines as well.
51/// Currently wrapped around deadlines will trigger immediately.
52/// During the lifetime of this timer 3 interrupts will be triggered:
53/// * ARR, once the 16 bit internal counter wraps around.
54/// * CMP, to handle deadlines.
55/// * CMPOK, when new deadlines have been uploaded to the timer.
56///
57/// You can extend the 1.5 days operating time by either making time an U64, or by adjusting the prescaler or source clock to a lower operating frequency.
58///
59/// Operates fine with the STOP2 sleep mode.
60pub struct Lptim1Rtc {
61 /// The Lower Power Timer 1 register block.
62 inner: LPTIM1,
63 /// Our time in current amount of fractions.
64 time: AtomicU16,
65 /// Our most significant bits (i.e. `time`) have not yet been updated.
66 dirty: AtomicBool,
67 /// Indication whether the CMP register is being written to.
68 /// Can only write the CMP register when uploading is not busy.
69 uploading: AtomicBool,
70 /// Deadline for an alarm, if set.
71 deadline: Option<NonZeroU32>,
72}
73
74/// Error to indicate that the just scheduled deadline has already passed.
75#[derive(Debug)]
76pub struct DeadlinePassed;
77
78impl Lptim1Rtc {
79 /// Set up the heartbeat timer and interrupt.
80 ///
81 /// Wakes the CPU at least every 2 seconds to update the time, and more often when necessary.
82 /// Enables casually running sleep or stop2 whilst using async delays.
83 pub fn new(timer: LPTIM1, rcc: &RCC) -> Self {
84 rcc.apb1enr1.modify(|_, w| w.lptim1en().set_bit());
85 rcc.apb1rstr1.modify(|_, w| w.lptim1rst().set_bit());
86 rcc.apb1rstr1.modify(|_, w| w.lptim1rst().clear_bit());
87 rcc.apb1smenr1.modify(|_, w| w.lptim1smen().set_bit()); // Power LPTIM1 in LP mode
88
89 // Set clock to LSI, which should be 32KHz
90 rcc.ccipr.modify(|_, w| unsafe { w.lptim1sel().bits(0b01) });
91
92 Self {
93 inner: timer,
94 time: AtomicU16::new(0),
95 // We start with dirty, and will trigger a cmp soon.
96 dirty: AtomicBool::new(true),
97 uploading: AtomicBool::new(false),
98 deadline: None,
99 }
100 }
101
102 /// Start the timer, starting with fraction `count`.
103 ///
104 /// We will expect a CMPOK interrupt soon after this call. Hence make sure that the handle_interrupt is already callable
105 /// from the interrupt when calling `start_timer`.
106 pub fn start_timer(&mut self) {
107 // Disable the LPTIM device such that we can configure it & the interrupts.
108 self.inner.cr.modify(|_, w| w.enable().clear_bit());
109 NVIC::mask(Interrupt::LPTIM1);
110
111 self.inner.cfgr.modify(|_, w| {
112 w.cksel().clear_bit(); // internal clocksource
113 w.enc().clear_bit(); // no encoder mode
114 w.preload().clear_bit(); // load at any register write
115 w.countmode().clear_bit(); // the counter is incremented following each internal clock pulse
116 w.timout().set_bit();
117 unsafe {
118 w.presc().bits(0b000); // x1
119 w.ckpol().bits(0b00);
120 w.trigen().bits(0b00)
121 }
122 });
123
124 // Enable ARRM, CMPM and CMPOK events, though we only use CMPM when a deadline has been set.
125 self.inner
126 .ier
127 .write(|w| w.arrmie().set_bit().cmpmie().set_bit().cmpokie().set_bit());
128
129 unsafe { NVIC::unmask(Interrupt::LPTIM1) };
130
131 // Enable the LPTIM device.
132 self.inner.cr.modify(|_, w| w.enable().set_bit());
133
134 // Set ARR and CMP to default reset values.
135 self.inner.arr.write(|w| unsafe { w.bits(ALARM_ARR) });
136 self.set_cmp(ALARM_DIRTY_ZONE);
137
138 // Await until it is configured.
139 compiler_fence(Ordering::SeqCst);
140
141 self.inner.cr.modify(|_, w| w.cntstrt().set_bit());
142 }
143
144 /// Get the current time.
145 pub fn get_time(&self) -> u32 {
146 // Get the time, but the answer might be unstable because
147 // we need to perform multiple (albeit atomic) operations to reach the time.
148 let get_time_unstable = || {
149 let frac: u32 = self.get_fraction(); // Might take a few cycles.
150
151 let frac = if frac >= ALARM_SEGMENT {
152 ALARM_SEGMENT
153 } else if frac < ALARM_DIRTY_ZONE {
154 frac + ALARM_SEGMENT
155 } else if self.dirty.load(Ordering::Acquire) {
156 // Implicitly somewhere between ALARM_SEGMENT and ALARM_DIRTY_ZONE + a little.
157 frac + ALARM_SEGMENT
158 } else {
159 frac
160 };
161
162 ((self.time.load(Ordering::Acquire) as u32) * ALARM_SEGMENT)
163 .wrapping_add(frac)
164 .wrapping_sub(ALARM_SEGMENT) // To compensate for our start with a dirty MSB=0 segment.
165 };
166
167 loop {
168 // We get the time twice, to ensure that we get a consistent answer.
169 let x = get_time_unstable();
170 let y = get_time_unstable();
171
172 if x == y {
173 return x;
174 }
175 }
176 }
177
178 /// Set the alarm for some time on or after a specific deadline.
179 pub fn set_alarm(&mut self, deadline: u32) -> Result<(), DeadlinePassed> {
180 self.deadline = NonZeroU32::new(deadline);
181 if self.update_alarm(false) {
182 // Alarm was triggered directly after setting the alarm.
183 Err(DeadlinePassed)
184 } else {
185 Ok(())
186 }
187 }
188
189 /// Clear the alarm, regardless of being set.
190 pub fn clear_alarm(&mut self) {
191 self.deadline = None;
192 self.reset_cmp();
193 }
194
195 /// Handle LPTIM1 interrupt, both to update time and a signal a possible alarm.
196 ///
197 /// Returns whether an alarm was triggered.
198 /// Needs to be run in interrupt-free context.
199 pub fn handle_interrupt(&mut self) -> bool {
200 let isr = self.inner.isr.read();
201 let arrm = isr.arrm().bit();
202 let cmpm = isr.cmpm().bit();
203 let cmpok = isr.cmpok().bit();
204
205 if cmpok {
206 self.uploading.store(false, Ordering::Release);
207 }
208
209 if arrm {
210 // Update the time as we've moved into the next segment.
211 self.dirty.store(true, Ordering::Release);
212 }
213
214 // This segment requires interrupt free context.
215 let fraction = self.get_fraction();
216 if cmpm
217 && fraction < ALARM_SEGMENT
218 && fraction >= ALARM_DIRTY_ZONE
219 && self
220 .dirty
221 .compare_exchange(true, false, Ordering::SeqCst, Ordering::Acquire)
222 .is_ok()
223 {
224 self.time.fetch_add(1, Ordering::SeqCst);
225 }
226
227 if arrm & cmpm {
228 panic!("Invariant ARRM and CMPM together violated");
229 }
230
231 // Always update alarm to most recent, as upload of CMP may intersect.
232 // Returns whether alarm was triggered.
233 let u = self.update_alarm(true);
234
235 // Clear pending interrupt flags.
236 self.inner
237 .icr
238 .write(|w| w.arrmcf().bit(arrm).cmpmcf().bit(cmpm).cmpokcf().bit(cmpok));
239
240 compiler_fence(Ordering::SeqCst);
241
242 u
243 }
244
245 /// When comparisons are not needed, align cmp with arr such that no extra unnecessary interrupts are triggered.
246 #[inline(always)]
247 fn reset_cmp(&mut self) {
248 self.set_cmp(ALARM_DIRTY_ZONE);
249 }
250
251 #[inline(always)]
252 fn set_cmp(&mut self, mut fractions: u32) {
253 if fractions == ALARM_SEGMENT {
254 // Never allow cmp to be set on ALARM_SEGMENT, because the peripheral would glitch out.
255 fractions = 0;
256 } else if fractions > ALARM_DIRTY_ZONE && self.dirty.load(Ordering::Acquire) {
257 // If deadline is in the future, but we're dirty, schedule a CMP first thing after the dirty zone.
258 // TODO consider whether this is necessary.
259 fractions = ALARM_DIRTY_ZONE;
260 }
261
262 // The currently configured deadline is no longer correct.
263 if fractions != self.inner.cmp.read().bits() {
264 // Claim the upload pipeline if available to update the deadline.
265 if self
266 .uploading
267 .compare_exchange(false, true, Ordering::SeqCst, Ordering::Acquire)
268 .is_ok()
269 {
270 self.inner.cmp.write(|w| unsafe { w.bits(fractions) });
271 }
272 }
273 }
274
275 /// Internally set registers to properly reflect the currently set alarm.
276 ///
277 /// Returns true if alarm has triggered (i.e. deadline passed).
278 /// When triggered will unset alarm, thus consecutive calls will not yield true until a new alarm is set.
279 fn update_alarm(&mut self, consume_alarm: bool) -> bool {
280 if let Some(deadline) = self.deadline {
281 // Note: if deadline && dirty, `set_cmp` will set the CMP alarm to
282 // ALARM_DIRTY_ZONE iff fractions >= ALARM_DIRTY_ZONE.
283 let deadline = deadline.get();
284 let now = self.get_time();
285 if deadline <= now {
286 // Deadline has passed and throw the alarm.
287 if consume_alarm {
288 // Remove the deadline and align the CMP interrupt with ARR, such that we throw one interrupt less for each segment.
289 self.deadline.take();
290 self.reset_cmp();
291 } else {
292 // Schedule the interrupt ASAP.
293 self.set_cmp((now + 2) % ALARM_SEGMENT);
294 }
295 return true;
296 } else if deadline < ((now / ALARM_SEGMENT) + 1) * ALARM_SEGMENT {
297 // The deadline is within our current segment. Schedule the interrupt.
298 self.set_cmp(deadline % ALARM_SEGMENT);
299 } else {
300 // Deadline is too far into the future, not in this segment.
301 self.reset_cmp();
302 }
303 } else if self.dirty.load(Ordering::Acquire) {
304 // If no deadline && dirty always set alarm to end of DIRTY_ZONE.
305 self.set_cmp(ALARM_DIRTY_ZONE);
306 return false;
307 }
308
309 false
310 }
311
312 /// Disable the LPTIM1 peripheral.
313 fn teardown(&mut self) {
314 self.inner.cr.modify(|_, w| w.enable().clear_bit());
315 self.inner
316 .icr
317 .write(|w| w.arrmcf().set_bit().cmpmcf().set_bit());
318 }
319
320 /// Get a fraction from 0x0000 to 0xFFFF relative to time.
321 pub fn get_fraction(&self) -> u32 {
322 // We must get two consecutive consistent reads.
323 loop {
324 let x = self.inner.cnt.read().bits();
325 let y = self.inner.cnt.read().bits();
326
327 if x == y {
328 return x & 0xFFFF;
329 }
330 }
331 }
332}
333
334impl Drop for Lptim1Rtc {
335 fn drop(&mut self) {
336 self.teardown();
337 }
338}
339
340/// The instant as directly used by the `Lptim1Rtc` timer.
341#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy, Debug)]
342pub struct InstantU32(pub u32);
343
344/// The duration as efficiently usable with instants of the `Lptim1Rtc` timer.
345#[derive(Clone)]
346pub struct DurationU32(pub u32);
347
348impl core::convert::From<core::time::Duration> for DurationU32 {
349 fn from(duration: core::time::Duration) -> Self {
350 Self(
351 ((duration.as_secs() as u64) * ALARM_TICKS_PER_SECOND
352 + (duration.subsec_nanos() as u64) * ALARM_TICKS_PER_SECOND / 1_000_000_000)
353 as u32,
354 )
355 }
356}
357
358impl core::ops::AddAssign<DurationU32> for InstantU32 {
359 fn add_assign(&mut self, duration: DurationU32) {
360 self.0 += duration.0;
361 }
362}
363
364impl core::ops::Add<DurationU32> for InstantU32 {
365 type Output = Self;
366
367 fn add(mut self, rhs: DurationU32) -> Self::Output {
368 self += rhs;
369 self
370 }
371}
372
373impl Timer for Lptim1Rtc {
374 type Instant = InstantU32;
375 type Duration = DurationU32;
376
377 const DELTA: Self::Duration = DurationU32(2);
378
379 #[inline(always)]
380 fn reset(&mut self) {
381 self.start_timer()
382 }
383
384 #[inline(always)]
385 fn interrupt_free<F: FnOnce(&CriticalSection) -> R, R>(f: F) -> R {
386 cortex_m::interrupt::free(f)
387 }
388
389 #[inline(always)]
390 fn now(&self) -> Self::Instant {
391 InstantU32(self.get_time())
392 }
393
394 #[inline(always)]
395 fn disarm(&mut self) {
396 self.clear_alarm();
397 }
398
399 #[inline(always)]
400 fn arm(&mut self, deadline: &Self::Instant) {
401 // Note we ignore that the deadline has already passed.
402 let _ = self.set_alarm(deadline.0);
403 }
404}
405
406/// Handle the LPTIM1 interrupt.
407///
408/// Will yield whether the AsyncTimer has been awoken.
409///
410/// You can call it as such:
411/// ```rust
412/// static mut TIMER: Option<AsyncTimer<Lptim1Rtc>> = None;
413///
414/// #[interrupt]
415/// #[allow(non_snake_case)]
416/// #[no_mangle]
417/// fn LPTIM1() {
418/// cortex_m::interrupt::free(|_| {
419/// if let Some(timer) = unsafe { TIMER.as_ref() } {
420/// handle_interrupt(move || timer);
421/// }
422/// })
423/// }
424/// ```
425
426#[inline(always)]
427pub fn handle_interrupt<'a>(get_timer: impl FnOnce() -> &'a AsyncTimer<Lptim1Rtc>) -> bool {
428 let timer = get_timer();
429 let triggered = unsafe { timer.get_inner(|t| t.handle_interrupt()) };
430 if triggered {
431 timer.awaken();
432 }
433 triggered
434}