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}