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}