ep_dwt/
lib.rs

1//! [`EmbeddedProfiler`] implementation based on [`DWT`].
2//!
3//! This profiler depends on the [`DWT`] hardware which is not available on cortex-M0.
4//! The profiler's resolution is the same as the core clock. The cycle count clock is
5//! free-running, so overflows are likely if you have long running functions to profile.
6//! To mitigate this, one can use the `extended` feature, which extends the resolution of
7//! the counter from [`u32`] to [`u64`] using the [`DebugMonitor`] exception. It is set
8//! to expire just before overflow, so you can expect an exception to fire every 2**32
9//! clock cycles.
10//!
11//! Snapshots are logged using [`log::info!`], so having a logger installed is required
12//! if you want to use [`embedded_profiling::log_snapshot`] or functions that call it
13//! (like [`embedded_profiling::profile_function`]).
14//!
15//! ## Example Usage
16//!
17//!```no_run
18//! # use cortex_m::peripheral::Peripherals as CorePeripherals;
19//! # const CORE_FREQ: u32 = 120_000_000;
20//! let mut core = CorePeripherals::take().unwrap();
21//! // (...)
22//! let dwt_profiler = cortex_m::singleton!(: ep_dwt::DwtProfiler::<CORE_FREQ> =
23//!     ep_dwt::DwtProfiler::<CORE_FREQ>::new(&mut core.DCB, core.DWT, CORE_FREQ))
24//! .unwrap();
25//! unsafe {
26//!     embedded_profiling::set_profiler(dwt_profiler).unwrap();
27//! }
28//! // (...)
29//! embedded_profiling::profile("print_profile", || println!("Hello, world"));
30//! ```
31//!
32//! ## Features
33//!
34//! ### `extended`
35//!
36//! Extends the [`DWT`] cycle counter's native resolution from 32 bit to 64 bit using
37//! the cycle compare functionality and the [`DebugMonitor`] exception. The exception will
38//! fire every 2**32 clock cycles. Enables the [`embedded-profiling`](embedded_profiling)
39//! feature `container-u64`.
40//!
41//! ### `proc-macros`
42//!
43//! enables the `proc-macros` feature in [`embedded-profiling`](embedded_profiling). Enables
44//! the [`embedded_profiling::profile_function`] procedural macro.
45//!
46//! [`DWT`]: cortex_m::peripheral::DWT
47//! [`DebugMonitor`]: `cortex_m::peripheral::scb::Exception::DebugMonitor`
48//! [`embedded_profiling::profile_function`]: https://docs.rs/embedded-profiling/latest/embedded_profiling/attr.profile_function.html
49#![cfg_attr(not(test), no_std)]
50
51use embedded_profiling::{EPContainer, EPInstant, EPInstantGeneric, EPSnapshot, EmbeddedProfiler};
52
53use cortex_m::peripheral::{DCB, DWT};
54
55#[cfg(feature = "extended")]
56use core::sync::atomic::{AtomicU32, Ordering};
57#[cfg(feature = "extended")]
58use cortex_m_rt::exception;
59
60#[cfg(feature = "extended")]
61/// Tracker of `cyccnt` cycle count overflows to extend this timer to 64 bit
62static ROLLOVER_COUNT: AtomicU32 = AtomicU32::new(0);
63
64#[cfg(feature = "extended")]
65// For extended mode to work, we really need a u64 container. Double check this.
66static_assertions::assert_type_eq_all!(EPContainer, u64);
67
68/// DWT trace unit implementing [`EmbeddedProfiler`].
69///
70/// The frequency of the [`DWT`] is encoded using the parameter `FREQ`.
71pub struct DwtProfiler<const FREQ: u32> {
72    dwt: DWT,
73}
74
75impl<const FREQ: u32> DwtProfiler<FREQ> {
76    /// Enable the [`DWT`] and provide a new [`EmbeddedProfiler`].
77    ///
78    /// Note that the `sysclk` parameter should come from e.g. the HAL's clock generation function
79    /// so the real speed and the declared speed can be compared.
80    ///
81    /// # Panics
82    /// asserts that the compile time constant `FREQ` matches the runtime provided `sysclk`
83    pub fn new(dcb: &mut DCB, mut dwt: DWT, sysclk: u32) -> Self {
84        assert!(FREQ == sysclk);
85
86        // Enable the DWT block
87        dcb.enable_trace();
88        #[cfg(feature = "extended")]
89        // Enable DebugMonitor exceptions to fire to track overflows
90        unsafe {
91            dcb.demcr.modify(|f| f | 1 << 16);
92        }
93        DWT::unlock();
94
95        // reset cycle count and enable it to run
96        unsafe { dwt.cyccnt.write(0) };
97        dwt.enable_cycle_counter();
98
99        Self { dwt }
100    }
101}
102
103impl<const FREQ: u32> EmbeddedProfiler for DwtProfiler<FREQ> {
104    fn read_clock(&self) -> EPInstant {
105        // get the cycle count and add the rollover if we're extended
106        #[allow(unused_mut)]
107        let mut count = EPContainer::from(self.dwt.cyccnt.read());
108        #[cfg(feature = "extended")]
109        {
110            count += EPContainer::from(ROLLOVER_COUNT.load(Ordering::Relaxed))
111                * EPContainer::from(u32::MAX);
112        }
113
114        // convert count and return the instant
115        embedded_profiling::convert_instant(EPInstantGeneric::<1, FREQ>::from_ticks(count))
116    }
117
118    fn log_snapshot(&self, snapshot: &EPSnapshot) {
119        log::info!("{}", snapshot);
120    }
121}
122
123#[cfg(feature = "extended")]
124#[exception]
125#[allow(non_snake_case)]
126fn DebugMonitor() {
127    ROLLOVER_COUNT.fetch_add(1, Ordering::Relaxed);
128}