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}