1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
//! # `Monotonic` implementation based on DWT cycle counter and SysTick

#![no_std]

use cortex_m::peripheral::{syst::SystClkSource, DCB, DWT, SYST};
pub use fugit;
#[cfg(not(feature = "extend"))]
pub use fugit::ExtU32;
#[cfg(feature = "extend")]
pub use fugit::ExtU64;
use rtic_monotonic::Monotonic;

/// DWT and Systick combination implementing `rtic_monotonic::Monotonic`.
///
/// This implementation is tickless. It does not use periodic interrupts to count
/// "ticks" (like `systick-monotonic`) but only to obtain actual desired compare
/// events and to manage overflows.
///
/// The frequency of the DWT and SysTick is encoded using the parameter `TIMER_HZ`.
/// They must be equal.
///
/// Note that the SysTick interrupt must not be disabled longer than half the
/// cycle counter overflow period (typically a couple seconds).
///
/// When the `extend` feature is enabled, the cycle counter width is extended to
/// `u64` by detecting and counting overflows.
pub struct DwtSystick<const TIMER_HZ: u32> {
    dwt: DWT,
    systick: SYST,
    #[cfg(feature = "extend")]
    last: u64,
}

impl<const TIMER_HZ: u32> DwtSystick<TIMER_HZ> {
    /// Enable the DWT and provide a new `Monotonic` based on DWT and SysTick.
    ///
    /// Note that the `sysclk` parameter should come from e.g. the HAL's clock generation function
    /// so the speed calculated at runtime and the declared speed (generic parameter
    /// `TIMER_HZ`) can be compared.
    #[inline(always)]
    pub fn new(dcb: &mut DCB, mut dwt: DWT, mut systick: SYST, sysclk: u32) -> Self {
        assert!(TIMER_HZ == sysclk);

        dcb.enable_trace();
        DWT::unlock();
        assert!(DWT::has_cycle_counter());

        // Clear the cycle counter here so scheduling (`set_compare()`) before `reset()`
        // works correctly.
        dwt.set_cycle_count(0);

        systick.set_clock_source(SystClkSource::Core);

        // We do not start the counters here but in `reset()`.

        DwtSystick {
            dwt,
            systick,
            #[cfg(feature = "extend")]
            last: 0,
        }
    }
}

impl<const TIMER_HZ: u32> Monotonic for DwtSystick<TIMER_HZ> {
    cfg_if::cfg_if! {
        if #[cfg(not(feature = "extend"))] {
            const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = true;

            type Instant = fugit::TimerInstantU32<TIMER_HZ>;
            type Duration = fugit::TimerDurationU32<TIMER_HZ>;

            #[inline(always)]
            fn now(&mut self) -> Self::Instant {
                Self::Instant::from_ticks(DWT::cycle_count())
            }
        } else {
            // Need to detect and track overflows.
            const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = false;

            type Instant = fugit::TimerInstantU64<TIMER_HZ>;
            type Duration = fugit::TimerDurationU64<TIMER_HZ>;

            #[inline(always)]
            fn now(&mut self) -> Self::Instant {
                let mut high = (self.last >> 32) as u32;
                let low = self.last as u32;
                let now = DWT::cycle_count();

                // Detect CYCCNT overflow
                if now < low {
                    high = high.wrapping_add(1);
                }
                self.last = ((high as u64) << 32) | (now as u64);

                Self::Instant::from_ticks(self.last)
            }
        }
    }

    unsafe fn reset(&mut self) {
        self.systick.enable_counter();

        // Enable and reset the cycle counter to locate the epoch.
        self.dwt.enable_cycle_counter();
        self.dwt.set_cycle_count(0);
    }

    fn set_compare(&mut self, val: Self::Instant) {
        // The input `val` refers to the cycle counter value (up-counter)
        // but the SysTick is a down-counter with interrupt on zero.
        let reload = val
            .checked_duration_since(self.now())
            // Minimum reload value if `val` is in the past
            .map_or(0, |duration| duration.ticks())
            // CYCCNT and SysTick have the same clock, no
            // ticks conversion necessary, only clamping:
            //
            // ARM Architecture Reference Manual says:
            // "Setting SYST_RVR to zero has the effect of
            // disabling the SysTick counter independently
            // of the counter enable bit.", so the min is 1
            .max(1)
            // SysTick is a 24 bit counter.
            .min(0xff_ffff) as u32;

        self.systick.set_reload(reload);
        // Also clear the current counter. That doesn't cause a SysTick
        // interrupt and loads the reload value on the next cycle.
        self.systick.clear_current();
    }

    #[inline(always)]
    fn zero() -> Self::Instant {
        Self::Instant::from_ticks(0)
    }

    #[inline(always)]
    fn clear_compare_flag(&mut self) {
        // SysTick exceptions don't need flag clearing.
        //
        // But when extending the cycle counter range, we need to keep
        // the interrupts enabled to detect overflow.
        // This function is always called in the interrupt handler early.
        // Reset a maximum reload value in case `set_compare()` is not called.
        // Otherwise the interrupt would keep firing at the previous set
        // interval.
        #[cfg(feature = "extend")]
        {
            self.systick.set_reload(0xff_ffff);
            self.systick.clear_current();
        }
    }

    #[cfg(feature = "extend")]
    fn on_interrupt(&mut self) {
        // Ensure `now()` is called regularly to track overflows.
        // Since SysTick is narrower than CYCCNT, this is sufficient.
        self.now();
    }
}