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}