ep_systick/lib.rs
1//! [`EmbeddedProfiler`] implementation based on [`systick`](cortex_m::peripheral::SYST).
2//!
3//! This profiler depends on the [`SYST`] hardware common to most cortex-M devices.
4//! The profiler's configured 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 24 bit to [`u32`] or [`u64`] using the [`SysTick`] exception. It is set
8//! to expire just before overflow, so you can expect an exception to fire every 2**24
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_systick::SysTickProfiler::<CORE_FREQ> =
23//! ep_systick::SysTickProfiler::<CORE_FREQ>::new(core.SYST, 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//! as discussed above, extend the native resolution of 24 bits to either 32 or 64 bits
37//! using the [`SysTick`] exception. The exception fires ever 2**24 clock cycles.
38//!
39//! ### `container-u64`
40//!
41//! enables the `container-u64` feature in [`embedded-profiling`](embedded_profiling). Use
42//! a [`u64`] as the time storage type instead of [`u32`] for longer running profiling.
43//!
44//! ### `proc-macros`
45//!
46//! enables the `proc-macros` feature in [`embedded-profiling`](embedded_profiling). Enables
47//! the [`embedded_profiling::profile_function`] procedural macro.
48//!
49//! [`SYST`]: cortex_m::peripheral::SYST
50//! [`SysTick`]: `cortex_m::peripheral::scb::Exception::SysTick`
51//! [`embedded_profiling::profile_function`]: https://docs.rs/embedded-profiling/latest/embedded_profiling/attr.profile_function.html
52#![cfg_attr(not(test), no_std)]
53
54use cortex_m::peripheral::{syst::SystClkSource, SYST};
55use embedded_profiling::{EPContainer, EPInstant, EPInstantGeneric, EPSnapshot, EmbeddedProfiler};
56
57#[cfg(feature = "extended")]
58use core::sync::atomic::{AtomicU32, Ordering};
59
60#[cfg(feature = "extended")]
61/// Tracker of `systick` cycle count overflows to extend systick's 24 bit timer
62static ROLLOVER_COUNT: AtomicU32 = AtomicU32::new(0);
63
64/// The reload value of the [`systick`](cortex_m::peripheral::SYST) peripheral. Also is the max it can go (2**24).
65const SYSTICK_RELOAD: u32 = 0x00FF_FFFF;
66/// the resolution of [`systick`](cortex_m::peripheral::SYST), 2**24
67#[cfg(feature = "extended")]
68const SYSTICK_RESOLUTION: EPContainer = 0x0100_0000;
69
70/// [`systick`](cortex_m::peripheral::SYST) implementation of [`EmbeddedProfiler`].
71///
72/// The frequency of the [`systick`](cortex_m::peripheral::SYST) is encoded using the parameter `FREQ`.
73pub struct SysTickProfiler<const FREQ: u32> {
74 #[allow(unused)]
75 // we currently take SYST by value only to ensure ownership
76 systick: SYST,
77}
78
79impl<const FREQ: u32> SysTickProfiler<FREQ> {
80 /// Enable the [`systick`](cortex_m::peripheral::SYST) and provide a new [`EmbeddedProfiler`].
81 ///
82 /// Note that the `sysclk` parameter should come from e.g. the HAL's clock generation function
83 /// so the real speed and the declared speed can be compared.
84 ///
85 /// # Panics
86 /// asserts that the compile time constant `FREQ` matches the runtime provided `sysclk`
87 #[must_use]
88 pub fn new(mut systick: SYST, sysclk: u32) -> Self {
89 assert!(FREQ == sysclk);
90
91 systick.disable_counter();
92 systick.set_clock_source(SystClkSource::Core);
93 systick.clear_current();
94 systick.set_reload(SYSTICK_RELOAD);
95 systick.enable_counter();
96
97 #[cfg(feature = "extended")]
98 systick.enable_interrupt();
99
100 Self { systick }
101 }
102}
103
104impl<const FREQ: u32> EmbeddedProfiler for SysTickProfiler<FREQ> {
105 fn read_clock(&self) -> EPInstant {
106 // Read SYSTICK count and maybe account for rollovers
107 let count = {
108 #[cfg(feature = "extended")]
109 {
110 // read the clock & ROLLOVER_COUNT. We read `SYST` twice because we need to detect
111 // if we've rolled over, and if we have make sure we have the right value for ROLLOVER_COUNT.
112 let first = SYST::get_current();
113 let rollover_count: EPContainer = ROLLOVER_COUNT.load(Ordering::Acquire).into();
114 let second = SYST::get_current();
115
116 // Since the SYSTICK counter is a count down timer, check if first is larger than second
117 if first > second {
118 // The usual case. We did not roll over between the first and second reading,
119 // and because of that we also know we got a valid read on ROLLOVER_COUNT.
120 rollover_count * SYSTICK_RESOLUTION + EPContainer::from(SYSTICK_RELOAD - first)
121 } else {
122 // we rolled over sometime between the first and second read. We may or may not have
123 // caught the right ROLLOVER_COUNT, so grab that again and then use the second reading.
124 let rollover_count: EPContainer = ROLLOVER_COUNT.load(Ordering::Acquire).into();
125 rollover_count * SYSTICK_RESOLUTION + EPContainer::from(SYSTICK_RELOAD - second)
126 }
127 }
128
129 #[cfg(not(feature = "extended"))]
130 {
131 // We aren't trying to be fancy here, we don't care if this rolled over from the last read.
132 EPContainer::from(SYSTICK_RELOAD - SYST::get_current())
133 }
134 };
135
136 embedded_profiling::convert_instant(EPInstantGeneric::<1, FREQ>::from_ticks(count))
137 }
138
139 fn log_snapshot(&self, snapshot: &EPSnapshot) {
140 log::info!("{}", snapshot);
141 }
142}
143
144#[cfg(feature = "extended")]
145use cortex_m_rt::exception;
146
147#[cfg(feature = "extended")]
148#[exception]
149#[allow(non_snake_case)]
150fn SysTick() {
151 ROLLOVER_COUNT.fetch_add(1, Ordering::Release);
152}