From d7028a9bc1dec813f8ef30f07ae2353401e8a8f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onur=20=C3=96zkan?= Date: Sun, 19 Jul 2026 22:40:04 +0300 Subject: [PATCH] libs/kaptan-stm32f7: add DWT microsecond clock --- libs/kaptan-stm32f7/src/stm32f722/time.rs | 94 +++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 libs/kaptan-stm32f7/src/stm32f722/time.rs diff --git a/libs/kaptan-stm32f7/src/stm32f722/time.rs b/libs/kaptan-stm32f7/src/stm32f722/time.rs new file mode 100644 index 0000000..d48e74e --- /dev/null +++ b/libs/kaptan-stm32f7/src/stm32f722/time.rs @@ -0,0 +1,94 @@ +//! DWT-backed monotonic microsecond clock for STM32F722. + +use cortex_m::peripheral::{DCB, DWT}; +use kaptan_time::{MonotonicClock, Timestamp}; + +/// A DWT cycle counter extended into monotonic microseconds. +/// +/// `now` must be called at least once during every 32-bit cycle-counter wrap +/// interval. At the Kakute reset clock this is approximately 268 seconds; the +/// board periodic service calls it much more frequently. +pub struct DwtMicrosecondClock { + cycles_per_microsecond: u32, + previous_cycles: u32, + elapsed_cycles: u64, +} + +/// Invalid fixed clock configuration for the DWT timestamp source. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DwtClockStartError { + /// A zero-megahertz core clock cannot produce microsecond timestamps. + ZeroFrequency, +} + +impl DwtMicrosecondClock { + /// Enables the DWT cycle counter for a fixed integer-MHz core clock. + /// + /// `MEGAHERTZ` must match the board's core clock. This function is called + /// once during board initialization before interrupts are enabled. + pub fn start( + dcb: &mut DCB, + dwt: &mut DWT, + ) -> Result { + if MEGAHERTZ == 0 { + return Err(DwtClockStartError::ZeroFrequency); + } + + dcb.enable_trace(); + DWT::unlock(); + dwt.set_cycle_count(0); + dwt.enable_cycle_counter(); + + Ok(Self { + cycles_per_microsecond: MEGAHERTZ, + previous_cycles: 0, + elapsed_cycles: 0, + }) + } + + /// Captures the current monotonic microsecond timestamp. + pub fn now(&mut self) -> Timestamp { + self.advance_to(DWT::cycle_count()) + } + + fn advance_to(&mut self, current_cycles: u32) -> Timestamp { + let elapsed = current_cycles.wrapping_sub(self.previous_cycles); + self.previous_cycles = current_cycles; + self.elapsed_cycles = self.elapsed_cycles.saturating_add(u64::from(elapsed)); + + Timestamp::from_microseconds(self.elapsed_cycles / u64::from(self.cycles_per_microsecond)) + } +} + +impl MonotonicClock for DwtMicrosecondClock { + fn now(&mut self) -> Timestamp { + Self::now(self) + } +} + +#[cfg(test)] +mod tests { + use super::DwtMicrosecondClock; + + fn clock(previous_cycles: u32) -> DwtMicrosecondClock { + DwtMicrosecondClock { + cycles_per_microsecond: 16, + previous_cycles, + elapsed_cycles: 0, + } + } + + #[test] + fn converts_cycles_to_microseconds() { + let mut clock = clock(0); + + assert_eq!(clock.advance_to(160).as_microseconds(), 10); + } + + #[test] + fn extends_a_single_cycle_counter_wrap() { + let mut clock = clock(u32::MAX - 15); + + assert_eq!(clock.advance_to(16).as_microseconds(), 2); + } +}