Skip to main content

ws63_hal/
timer.rs

1//! Timer driver for WS63 (3 independent 32-bit timers).
2//!
3//! Each timer can operate in one-shot or periodic mode.
4//! The timer clock source is the system peripheral clock (PCLK = 240MHz).
5//!
6//! # Usage
7//!
8//! ```ignore
9//! let timer = TimerDriver::new(peripherals.TIMER);
10//! let mut oneshot = timer.oneshot(0);
11//! oneshot.start(240_000); // 1ms at 240MHz
12//! while !oneshot.expired() {}
13//! ```
14
15use crate::peripherals::Timer;
16use crate::soc::ws63::SYSTEM_CLOCK_HZ;
17
18/// Timer operating mode.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum TimerMode {
21    /// One-shot: timer counts once and stops.
22    OneShot = 0,
23    /// Periodic: timer reloads and repeats.
24    Periodic = 1,
25}
26
27/// Timer driver managing 3 independent timer channels.
28pub struct TimerDriver<'d> {
29    _timer: Timer<'d>,
30}
31
32impl<'d> TimerDriver<'d> {
33    /// Create a new timer driver.
34    pub fn new(timer: Timer<'d>) -> Self {
35        Self { _timer: timer }
36    }
37
38    fn regs(&self) -> &'static ws63_pac::timer::RegisterBlock {
39        // SAFETY: PAC peripheral pointer is a static physical MMIO address, always valid
40        unsafe { &*Timer::ptr() }
41    }
42
43    /// Configure a timer channel.
44    pub fn configure(&self, n: usize, mode: TimerMode, load_value: u32) {
45        let r = self.regs();
46        match n {
47            0 => r.timer0_load_count(0).write(|w| unsafe { w.bits(load_value) }),
48            1 => r.timer0_load_count(1).write(|w| unsafe { w.bits(load_value) }),
49            2 => r.timer0_load_count(2).write(|w| unsafe { w.bits(load_value) }),
50            _ => unreachable!(),
51        };
52        let ctl = ((mode as u32) & 0x3) << 1;
53        match n {
54            0 => r.timer0_control(0).write(|w| unsafe { w.bits(ctl) }),
55            1 => r.timer0_control(1).write(|w| unsafe { w.bits(ctl) }),
56            2 => r.timer0_control(2).write(|w| unsafe { w.bits(ctl) }),
57            _ => unreachable!(),
58        };
59    }
60
61    /// Enable a timer channel.
62    pub fn enable(&self, n: usize) {
63        let r = self.regs();
64        let prev = match n {
65            0 => r.timer0_control(0).read().bits(),
66            1 => r.timer0_control(1).read().bits(),
67            2 => r.timer0_control(2).read().bits(),
68            _ => unreachable!(),
69        };
70        match n {
71            0 => r.timer0_control(0).write(|w| unsafe { w.bits(prev | 1) }),
72            1 => r.timer0_control(1).write(|w| unsafe { w.bits(prev | 1) }),
73            2 => r.timer0_control(2).write(|w| unsafe { w.bits(prev | 1) }),
74            _ => unreachable!(),
75        };
76    }
77
78    /// Disable a timer channel.
79    pub fn disable(&self, n: usize) {
80        let r = self.regs();
81        let prev = match n {
82            0 => r.timer0_control(0).read().bits(),
83            1 => r.timer0_control(1).read().bits(),
84            2 => r.timer0_control(2).read().bits(),
85            _ => unreachable!(),
86        };
87        match n {
88            0 => r.timer0_control(0).write(|w| unsafe { w.bits(prev & !1) }),
89            1 => r.timer0_control(1).write(|w| unsafe { w.bits(prev & !1) }),
90            2 => r.timer0_control(2).write(|w| unsafe { w.bits(prev & !1) }),
91            _ => unreachable!(),
92        };
93    }
94
95    /// Read the current counter value.
96    pub fn current_value(&self, n: usize) -> u32 {
97        let r = self.regs();
98        match n {
99            0 => r.timer0_current_value(0).read().bits(),
100            1 => r.timer0_current_value(1).read().bits(),
101            2 => r.timer0_current_value(2).read().bits(),
102            _ => unreachable!(),
103        }
104    }
105
106    /// Check if a timer interrupt is pending.
107    pub fn interrupt_pending(&self, n: usize) -> bool {
108        let r = self.regs();
109        match n {
110            0 => r.timer0_raw_intr(0).read().bits() & 1 != 0,
111            1 => r.timer0_raw_intr(1).read().bits() & 1 != 0,
112            2 => r.timer0_raw_intr(2).read().bits() & 1 != 0,
113            _ => unreachable!(),
114        }
115    }
116
117    /// Clear a timer interrupt (per-channel EOI).
118    pub fn clear_interrupt(&self, n: usize) {
119        let r = self.regs();
120        match n {
121            0 => {
122                let _ = r.timer0_eoi(0).read().bits();
123            }
124            1 => {
125                let _ = r.timer0_eoi(1).read().bits();
126            }
127            2 => {
128                let _ = r.timer0_eoi(2).read().bits();
129            }
130            _ => unreachable!(),
131        }
132    }
133
134    /// Create a one-shot timer wrapper for the given channel.
135    pub fn oneshot(&self, channel: usize) -> OneShotTimer<'_> {
136        OneShotTimer { driver: self, channel }
137    }
138
139    /// Create a periodic timer wrapper for the given channel.
140    pub fn periodic(&self, channel: usize) -> PeriodicTimer<'_> {
141        PeriodicTimer { driver: self, channel }
142    }
143}
144
145// ── One-shot timer ────────────────────────────────────────────────
146
147/// One-shot timer wrapper.
148///
149/// Counts down from a loaded value and stops when it reaches zero.
150pub struct OneShotTimer<'a> {
151    driver: &'a TimerDriver<'a>,
152    channel: usize,
153}
154
155impl OneShotTimer<'_> {
156    /// Start the one-shot timer with a count value.
157    ///
158    /// `count` is in timer clock ticks (PCLK = 240MHz → 1 tick ≈ 4.17ns).
159    pub fn start(&mut self, count: u32) {
160        self.driver.configure(self.channel, TimerMode::OneShot, count);
161        self.driver.enable(self.channel);
162    }
163
164    /// Start the timer for the given duration in microseconds.
165    ///
166    /// Max duration: ~17.9 seconds at 240MHz (u32 ticks limit).
167    /// For longer durations, use `start_millis()` or loop `start_micros()`.
168    pub fn start_micros(&mut self, us: u32) {
169        let ticks64 = SYSTEM_CLOCK_HZ as u64 * us as u64 / 1_000_000;
170        // Clamp to u32 max — delays longer than ~17.9s at 240MHz
171        let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
172        self.start(ticks);
173    }
174
175    /// Start the timer for the given duration in milliseconds.
176    ///
177    /// Max duration: ~17,895ms (~17.9s) at 240MHz.
178    /// For longer durations, repeat this call in a loop.
179    pub fn start_millis(&mut self, ms: u32) {
180        let ticks64 = SYSTEM_CLOCK_HZ as u64 * ms as u64 / 1_000;
181        let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
182        self.start(ticks);
183    }
184
185    /// Check if the timer has expired.
186    pub fn expired(&self) -> bool {
187        self.driver.interrupt_pending(self.channel)
188    }
189
190    /// Wait for the timer to expire (busy-loop).
191    pub fn wait(&self) {
192        while !self.expired() {}
193        self.driver.clear_interrupt(self.channel);
194    }
195
196    /// Get the current counter value.
197    pub fn current(&self) -> u32 {
198        self.driver.current_value(self.channel)
199    }
200
201    /// Stop the timer.
202    pub fn stop(&self) {
203        self.driver.disable(self.channel);
204    }
205
206    /// Clear the interrupt flag.
207    pub fn clear(&self) {
208        self.driver.clear_interrupt(self.channel);
209    }
210}
211
212impl embedded_hal::delay::DelayNs for OneShotTimer<'_> {
213    fn delay_ns(&mut self, ns: u32) {
214        let ticks64 = (SYSTEM_CLOCK_HZ as u64 * ns as u64) / 1_000_000_000;
215        let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
216        if ticks > 0 {
217            self.start(ticks);
218            self.wait();
219        }
220    }
221
222    fn delay_us(&mut self, us: u32) {
223        self.start_micros(us);
224        self.wait();
225    }
226
227    fn delay_ms(&mut self, ms: u32) {
228        self.start_millis(ms);
229        self.wait();
230    }
231}
232
233// ── Periodic timer ────────────────────────────────────────────────
234
235/// Periodic timer wrapper.
236///
237/// Counts down and automatically reloads, generating an interrupt each cycle.
238pub struct PeriodicTimer<'a> {
239    driver: &'a TimerDriver<'a>,
240    channel: usize,
241}
242
243impl PeriodicTimer<'_> {
244    /// Start the periodic timer with the given period in ticks.
245    pub fn start(&mut self, period: u32) {
246        self.driver.configure(self.channel, TimerMode::Periodic, period);
247        self.driver.enable(self.channel);
248    }
249
250    /// Start the periodic timer with the period in microseconds.
251    pub fn start_micros(&mut self, us: u32) {
252        let ticks64 = SYSTEM_CLOCK_HZ as u64 * us as u64 / 1_000_000;
253        let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
254        self.start(ticks);
255    }
256
257    /// Check if a period has elapsed (interrupt pending).
258    pub fn tick_elapsed(&self) -> bool {
259        self.driver.interrupt_pending(self.channel)
260    }
261
262    /// Wait for the next timer tick.
263    pub fn wait_tick(&self) {
264        while !self.tick_elapsed() {}
265        self.driver.clear_interrupt(self.channel);
266    }
267
268    /// Stop the timer.
269    pub fn stop(&self) {
270        self.driver.disable(self.channel);
271    }
272
273    /// Get the current counter value.
274    pub fn current(&self) -> u32 {
275        self.driver.current_value(self.channel)
276    }
277
278    /// Clear the tick interrupt flag.
279    pub fn clear_tick(&self) {
280        self.driver.clear_interrupt(self.channel);
281    }
282}
283
284// ── Tests ──────────────────────────────────────────────────────
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289
290    #[test]
291    fn test_oneshot_overflows_u32_clamps() {
292        // 240MHz * 20_000_000us = 4.8e15 ticks > u32::MAX
293        // Should clamp to u32::MAX without panic or wrap
294        let ticks64: u64 = 240_000_000u64 * 20_000_000u64 / 1_000_000;
295        let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
296        assert_eq!(ticks, u32::MAX);
297    }
298
299    #[test]
300    fn test_oneshot_small_value_does_not_clamp() {
301        // 240MHz * 100us = 24,000 ticks — fits in u32
302        let ticks64: u64 = 240_000_000u64 * 100u64 / 1_000_000;
303        assert_eq!(ticks64, 24_000);
304        let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
305        assert_eq!(ticks, 24_000);
306    }
307
308    #[test]
309    fn test_oneshot_max_safe_value() {
310        // Maximum us that doesn't overflow: u32::MAX / 240 ≈ 17,895,697 us ≈ 17.9 seconds
311        let max_safe_us: u32 = u32::MAX / 240;
312        let ticks64: u64 = 240_000_000u64 * max_safe_us as u64 / 1_000_000;
313        assert!(ticks64 <= u32::MAX as u64);
314        let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
315        assert_eq!(ticks, ticks64 as u32);
316    }
317
318    #[test]
319    fn test_periodic_clamping_matches_oneshot() {
320        // Both start_micros implementations should clamp identically
321        let us: u32 = 20_000_000; // 20 seconds — guaranteed to overflow at 240MHz
322        let oneshot_ticks64: u64 = 240_000_000u64 * us as u64 / 1_000_000;
323        let periodic_ticks64: u64 = crate::soc::ws63::SYSTEM_CLOCK_HZ as u64 * us as u64 / 1_000_000;
324        assert_eq!(oneshot_ticks64, periodic_ticks64); // same formula
325        let oneshot = if oneshot_ticks64 > u32::MAX as u64 { u32::MAX } else { oneshot_ticks64 as u32 };
326        let periodic = if periodic_ticks64 > u32::MAX as u64 { u32::MAX } else { periodic_ticks64 as u32 };
327        assert_eq!(oneshot, periodic);
328        assert_eq!(oneshot, u32::MAX);
329    }
330}
331
332// ── Property-based fuzz tests ──────────────────────────────────
333
334#[cfg(test)]
335mod proptests {
336    use proptest::prelude::*;
337
338    proptest! {
339        /// Fuzz: Timer ticks calculation never panics for any u32 input.
340        #[test]
341        fn timer_ticks_never_panics(us in any::<u32>()) {
342            let ticks64: u64 = 240_000_000u64 * us as u64 / 1_000_000;
343            let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
344            let _ = ticks;
345        }
346
347        /// Fuzz: Clamping is idempotent — applying it twice gives the same result.
348        #[test]
349        fn timer_clamping_idempotent(us in any::<u32>()) {
350            let ticks64: u64 = 240_000_000u64 * us as u64 / 1_000_000;
351            let first = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
352            let second = if first as u64 > u32::MAX as u64 { u32::MAX } else { first };
353            prop_assert_eq!(first, second);
354        }
355
356        /// Fuzz: Safe range values (us <= 17_895_697) never get clamped.
357        fn timer_safe_range_not_clamped(us in 0u32..17_895_697u32) {
358            let ticks64: u64 = 240_000_000u64 * us as u64 / 1_000_000;
359            prop_assert!(ticks64 <= u32::MAX as u64, "safe us={} produced ticks64={} > u32::MAX", us, ticks64);
360        }
361
362        /// Fuzz: Overflow inputs always clamp to u32::MAX.
363        fn timer_overflow_always_clamps(us in 17_895_698u32..u32::MAX) {
364            let ticks64: u64 = 240_000_000u64 * us as u64 / 1_000_000;
365            prop_assert!(ticks64 > u32::MAX as u64);
366            let ticks = if ticks64 > u32::MAX as u64 { u32::MAX } else { ticks64 as u32 };
367            prop_assert_eq!(ticks, u32::MAX);
368        }
369
370        /// Fuzz: OneShot and Periodic timer formulas are identical.
371        #[test]
372        fn timer_both_formulas_equivalent(us in any::<u32>()) {
373            let oneshot: u64 = 240_000_000u64 * us as u64 / 1_000_000;
374            let periodic: u64 = crate::soc::ws63::SYSTEM_CLOCK_HZ as u64 * us as u64 / 1_000_000;
375            prop_assert_eq!(oneshot, periodic);
376        }
377    }
378}