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