cortex_m_microclock/lib.rs
1// Copyright © 2023 cortex-m-microclock. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! This crate provides a software clock which relies on the CYCCNT counter present in most Cortex-M chips
5//! to allow user to measure time and produce delays. The precision of the
6//! clock depends on your microcontroller core frequency. If you have a core running at at least
7//! 1MHZ, you will have a microsecond precision.
8//!
9//! ***Note 0***: Some Cortex-M cores like M0, M0+ and M23 cores does not have a CYCCNT counter.
10//! Therefore, the present crate cannot be used on these chips
11//! ***Note 1*** The present crate does not work on multicore systems.
12//!
13//! # Underlaying hardware
14//! The clock is based on the CYCCNT counter from the Cortex-M DWT peripheral, which increments
15//! with each processor clock cycle. However as the CYCCNT upcounter is only 32 bits wide, it may overflow
16//! quite rapidly depending on your SYSCLK frequency. The [`CYCCNTClock`] keeps track of
17//! multiple CYCCNT cycles using an internal counter so it can be used to evaluate very large durations of time.
18//!
19//! # Crate structure
20//! The [`CYCCNTClock`] is the structure representing the software clock. This structure is a
21//! singleton exposing all the methods of the crate available to user. All these methods are static and
22//! can be called from any thread without concurrency issues.
23//!
24//! # How to use this crate
25//! In order to use the clock you should first call the [`CYCCNTClock::init()`] method which takes ownership of
26//! the DWT peripheral. From this point you can use [`CYCCNTClock::now()`] and [`CYCCNTClock::delay()`] methods.
27//! The [`CYCCNTClock::update()`] method should be called periodically to avoid missing
28//! the CYCCNT wrapping around. Use the following equation to find the minimum update frequency:
29//!
30//! ```min_update_freq = SYS_CLK_FREQ/(2³²)```
31//!
32//! Note that the [`CYCCNTClock::now()`] and [`CYCCNTClock::delay()`] methods implicitely call the [`CYCCNTClock::update()`] method.
33//!
34//! # Example
35//! A complete example of the use of this crate on a STMF103 blue pill is featured in the `examples` directory of this
36//! project.
37//!
38//!# Credits
39//! Many thanks to the authors of `fugit` and `rtic::dwt_systick_monotonic` crates
40//!
41
42#![no_std]
43
44use cortex_m::peripheral::{DCB, DWT};
45use cortex_m::interrupt::{self, Mutex};
46use core::{cell::RefCell, ops::DerefMut};
47use cortex_m::asm;
48
49pub type Instant<const TIMER_HZ: u32> = fugit::TimerInstantU64<TIMER_HZ>;
50pub type Duration<const TIMER_HZ: u32> = fugit::TimerDurationU64<TIMER_HZ>;
51
52static DWT:Mutex<RefCell<Option<DWT>>> =
53 Mutex::new(RefCell::new(None));
54
55static PREVIOUS_CYCCNT_VAL : Mutex<RefCell<usize>> = Mutex::new(RefCell::new(0));
56static NB_CYCCNT_CYCLES : Mutex<RefCell<u32>> = Mutex::new(RefCell::new(0));
57
58
59/// Clock based on the Cortex-M CYCCNT counter allowing to measure time durations and produce
60/// delays.
61/// Precise at a microsecond scale if your SYSCLK clock is greater than 1MHz
62pub struct CYCCNTClock<const SYSCLK_HZ: u32>{
63}
64
65impl <const SYSCLK_HZ :u32 > CYCCNTClock<SYSCLK_HZ>{
66
67 const MAX_CYCCNT_VAL: u32 = core::u32::MAX;
68
69 /// Return an `Instant` object corresponding to a snapshot created at the time this method was called.
70 /// Panic if the counter has not been initialized with [`CYCCNTClock::init()`] before.
71 ///
72 /// ```
73 /// const SYSCLK_FREQ_HZ : u32 = 8_000_000;
74 /// let t1 = CYCCNTClock<SYSCLK_FREQ_HZ>::now();
75 ///
76 /// // Wait 100 us
77 /// let duration = Duration::micros(100);
78 /// CYCCNTClock<SYSCLK_FREQ_HZ>::delay(duration)
79 ///
80 /// let t2 = CYCCNTClock<SYSCLK_FREQ_HZ>::now();
81 /// let elpased_time = t2 - t1; // Very small elapsed time
82 /// println!("time_us {}", elapsed_time.to_micros());
83 /// ```
84 pub fn now() -> Instant<SYSCLK_HZ> {
85 interrupt::free( |cs|{
86 //Call `update()` because the CYCCNT counter could have wrapped around since the last time
87 //`update()` was called
88 Self::update();
89
90 let nb_cyccnt_cycles : u32 = *NB_CYCCNT_CYCLES.borrow(cs).borrow();
91
92 if let Some(ref mut dwt) = DWT.borrow(cs).borrow_mut().deref_mut().as_mut(){
93 let acc_cyccnt_val : u64 = (nb_cyccnt_cycles as u64) *(Self::MAX_CYCCNT_VAL as u64 +1) + (dwt.cyccnt.read() as u64);
94 Instant::from_ticks(acc_cyccnt_val)
95 }
96 else{
97 panic!("Counter not initialized");
98 }
99 })
100 }
101
102 /// Blocking wait for the duration specified as argument
103 /// Interrupts can still trigger during this call.
104 /// Panic if the counter has not been initialized with [`CYCCNTClock::init()`] before.
105 /// ```
106 /// const SYSCLK_FREQ_HZ : u32 = 8_000_000;
107 /// let t1 = CYCCNTClock<SYSCLK_FREQ_HZ>::now();
108 ///
109 /// // Wait 100 us
110 /// let duration = Duration::micros(100);
111 /// CYCCNTClock<SYSCLK_FREQ_HZ>::delay(duration)
112 ///
113 /// let t2 = CYCCNTClock<SYSCLK_FREQ_HZ>::now();
114 /// let elpased_time = t2 - t1; // Very small elapsed time
115 /// println!("time_us {}", elapsed_time.to_micros());
116 /// ```
117 pub fn delay(duration: Duration<SYSCLK_HZ>) {
118 let instant_init = Self::now();
119 while (Self::now() - instant_init) < duration{
120 asm::nop();
121 }
122 }
123
124 /// Synchronize the hardware counter with this clock.
125 /// Must be called at least one time for every CYCCNT counter cycle after init. Otherwise
126 /// time counting will be corrupted.
127 /// In general, calling this method in every SysTick IRQ call is the simpler option
128 /// This method will NOT panic if the [`CYCCNTClock::init()`] method has not be called first.
129 pub fn update(){
130
131 interrupt::free( |cs|{
132 if let Some(ref mut dwt) = DWT.borrow(cs).borrow_mut().deref_mut().as_mut(){
133 let cyccnt_val = dwt.cyccnt.read();
134
135 let mut previous_cyccnt_val = PREVIOUS_CYCCNT_VAL.borrow(cs).borrow_mut();
136
137 // increment the number of counted CYCCNT cycles in case of CYCCNT counter overflow
138 if *previous_cyccnt_val as u32 > cyccnt_val {
139 NB_CYCCNT_CYCLES.borrow(cs).replace_with(|&mut old| old +1);
140 }
141
142 *previous_cyccnt_val = cyccnt_val as usize;
143 }
144 });
145 }
146
147 /// Enable CYCCNT counting capability of the cortex core and
148 /// start the couting
149 ///```
150 /// let mut cp = cortex_m::Peripherals::take().unwrap();
151 /// let mut dcb = cp.DCB;
152 /// let dwt = cp.DWT;
153 /// CYCCNTClock<SYSCLK_FREQ_HZ>::init(&mut dcb, dwt);
154 ///```
155 pub fn init(dcb: &mut DCB, mut dwt : DWT){
156
157 interrupt::free( |cs|{
158 assert!(DWT::has_cycle_counter());
159
160 dcb.enable_trace();
161 DWT::unlock();
162 dwt.enable_cycle_counter();
163 dwt.set_cycle_count(0);
164
165 // Store the DWT peripheral into a shared object
166 DWT.borrow(cs).borrow_mut().replace(dwt);
167 });
168}
169
170}
171