Timestamps
Applications that, directly or transitively, use any of
defmt
logging macros may use thetimestamp!
macro to define additional data to be included in every log frame.
The timestamp!
macro may only be used once throughout the crate graph. Its syntax is the same as for the other logging macros, except that timestamp!
is global and so cannot access any local variables.
By default, no timestamp is provided or transferred over the defmt sink.
Atomic timestamp
A simple timestamp
function that does not depend on device specific features and is good enough for development is shown below:
#![allow(unused)] fn main() { extern crate defmt; use std::sync::atomic::{AtomicUsize, Ordering}; // WARNING may overflow and wrap-around in long lived apps static COUNT: AtomicUsize = AtomicUsize::new(0); defmt::timestamp!("{=usize}", COUNT.fetch_add(1, Ordering::Relaxed)); }
Hardware timestamp
A timestamp
function that uses a device-specific monotonic timer can directly read a MMIO register.
It's OK if the function returns 0
while the timer is disabled.
The us
display hint can be used to format an integer value as a time in microseconds (eg. 1_000_000
may be displayed as 1.000000
).
extern crate defmt; fn monotonic_timer_counter_register() -> *mut u32 { static mut X: u32 = 0; unsafe { &mut X as *mut u32 } } // WARNING may overflow and wrap-around in long lived apps defmt::timestamp!("{=u32:us}", { // NOTE(interrupt-safe) single instruction volatile read operation unsafe { monotonic_timer_counter_register().read_volatile() } }); fn enable_monotonic_counter() {} fn main() { defmt::info!(".."); // timestamp = 0 defmt::debug!(".."); // timestamp = 0 enable_monotonic_counter(); defmt::info!(".."); // timestamp >= 0 // .. }
64-bit extension
Microcontrollers usually have only 32-bit counters / timers. Some of them may provide functionality to make one 32-bit counter increase the count of a second 32-bit counter when the first one wraps around. Where that functionality is not available, a 64-bit counter can be emulated using interrupts:
#![allow(unused)] fn main() { use std::sync::atomic::{AtomicU32, Ordering}; // the hardware counter is the "low (32-bit) counter" // this atomic variable is the "high (32-bit) counter" static OVERFLOW_COUNT: AtomicU32 = AtomicU32::new(0); // this is an interrupt handler running at highest priority fn on_first_counter_overflow() { let ord = Ordering::Relaxed; OVERFLOW_COUNT.store(OVERFLOW_COUNT.load(ord) + 1, ord); } }
To read the 64-bit value in a lock-free manner the following algorithm can be used (pseudo-code):
do {
high1: u32 <- read_high_count()
low : u32 <- read_low_count()
high2 : u32 <- read_high_count()
} while (high1 != high2)
count: u64 <- (high1 << 32) | low
The loop should be kept as tight as possible and the read operations must be single-instruction operations.