dwt_systick_monotonic/
lib.rs

1//! # `Monotonic` implementation based on DWT cycle counter and SysTick
2
3#![no_std]
4
5use cortex_m::peripheral::{syst::SystClkSource, DCB, DWT, SYST};
6pub use fugit;
7#[cfg(not(feature = "extend"))]
8pub use fugit::ExtU32;
9#[cfg(feature = "extend")]
10pub use fugit::ExtU64;
11use rtic_monotonic::Monotonic;
12
13/// DWT and Systick combination implementing `rtic_monotonic::Monotonic`.
14///
15/// This implementation is tickless. It does not use periodic interrupts to count
16/// "ticks" (like `systick-monotonic`) but only to obtain actual desired compare
17/// events and to manage overflows.
18///
19/// The frequency of the DWT and SysTick is encoded using the parameter `TIMER_HZ`.
20/// They must be equal.
21///
22/// Note that the SysTick interrupt must not be disabled longer than half the
23/// cycle counter overflow period (typically a couple seconds).
24///
25/// When the `extend` feature is enabled, the cycle counter width is extended to
26/// `u64` by detecting and counting overflows.
27pub struct DwtSystick<const TIMER_HZ: u32> {
28    dwt: DWT,
29    systick: SYST,
30    #[cfg(feature = "extend")]
31    last: u64,
32}
33
34impl<const TIMER_HZ: u32> DwtSystick<TIMER_HZ> {
35    /// Enable the DWT and provide a new `Monotonic` based on DWT and SysTick.
36    ///
37    /// Note that the `sysclk` parameter should come from e.g. the HAL's clock generation function
38    /// so the speed calculated at runtime and the declared speed (generic parameter
39    /// `TIMER_HZ`) can be compared.
40    #[inline(always)]
41    pub fn new(dcb: &mut DCB, mut dwt: DWT, mut systick: SYST, sysclk: u32) -> Self {
42        assert!(TIMER_HZ == sysclk);
43
44        dcb.enable_trace();
45        DWT::unlock();
46        assert!(DWT::has_cycle_counter());
47
48        // Clear the cycle counter here so scheduling (`set_compare()`) before `reset()`
49        // works correctly.
50        dwt.set_cycle_count(0);
51
52        systick.set_clock_source(SystClkSource::Core);
53
54        // We do not start the counters here but in `reset()`.
55
56        DwtSystick {
57            dwt,
58            systick,
59            #[cfg(feature = "extend")]
60            last: 0,
61        }
62    }
63}
64
65impl<const TIMER_HZ: u32> Monotonic for DwtSystick<TIMER_HZ> {
66    cfg_if::cfg_if! {
67        if #[cfg(not(feature = "extend"))] {
68            const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = true;
69
70            type Instant = fugit::TimerInstantU32<TIMER_HZ>;
71            type Duration = fugit::TimerDurationU32<TIMER_HZ>;
72
73            #[inline(always)]
74            fn now(&mut self) -> Self::Instant {
75                Self::Instant::from_ticks(DWT::cycle_count())
76            }
77        } else {
78            // Need to detect and track overflows.
79            const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = false;
80
81            type Instant = fugit::TimerInstantU64<TIMER_HZ>;
82            type Duration = fugit::TimerDurationU64<TIMER_HZ>;
83
84            #[inline(always)]
85            fn now(&mut self) -> Self::Instant {
86                let mut high = (self.last >> 32) as u32;
87                let low = self.last as u32;
88                let now = DWT::cycle_count();
89
90                // Detect CYCCNT overflow
91                if now < low {
92                    high = high.wrapping_add(1);
93                }
94                self.last = ((high as u64) << 32) | (now as u64);
95
96                Self::Instant::from_ticks(self.last)
97            }
98        }
99    }
100
101    unsafe fn reset(&mut self) {
102        self.systick.enable_counter();
103
104        // Enable and reset the cycle counter to locate the epoch.
105        self.dwt.enable_cycle_counter();
106        self.dwt.set_cycle_count(0);
107    }
108
109    fn set_compare(&mut self, val: Self::Instant) {
110        // The input `val` refers to the cycle counter value (up-counter)
111        // but the SysTick is a down-counter with interrupt on zero.
112        let reload = val
113            .checked_duration_since(self.now())
114            // Minimum reload value if `val` is in the past
115            .map_or(0, |duration| duration.ticks())
116            // CYCCNT and SysTick have the same clock, no
117            // ticks conversion necessary, only clamping:
118            //
119            // ARM Architecture Reference Manual says:
120            // "Setting SYST_RVR to zero has the effect of
121            // disabling the SysTick counter independently
122            // of the counter enable bit.", so the min is 1
123            .max(1)
124            // SysTick is a 24 bit counter.
125            .min(0xff_ffff) as u32;
126
127        self.systick.set_reload(reload);
128        // Also clear the current counter. That doesn't cause a SysTick
129        // interrupt and loads the reload value on the next cycle.
130        self.systick.clear_current();
131    }
132
133    #[inline(always)]
134    fn zero() -> Self::Instant {
135        Self::Instant::from_ticks(0)
136    }
137
138    #[inline(always)]
139    fn clear_compare_flag(&mut self) {
140        // SysTick exceptions don't need flag clearing.
141        //
142        // But when extending the cycle counter range, we need to keep
143        // the interrupts enabled to detect overflow.
144        // This function is always called in the interrupt handler early.
145        // Reset a maximum reload value in case `set_compare()` is not called.
146        // Otherwise the interrupt would keep firing at the previous set
147        // interval.
148        #[cfg(feature = "extend")]
149        {
150            self.systick.set_reload(0xff_ffff);
151            self.systick.clear_current();
152        }
153    }
154
155    #[cfg(feature = "extend")]
156    fn on_interrupt(&mut self) {
157        // Ensure `now()` is called regularly to track overflows.
158        // Since SysTick is narrower than CYCCNT, this is sufficient.
159        self.now();
160    }
161}