Crates.io | embedded-timers |
lib.rs | embedded-timers |
version | 0.3.0 |
source | src |
created_at | 2024-02-16 13:43:40.536025 |
updated_at | 2024-07-11 13:21:58.596003 |
description | Softwaretimers and -delays (ms/us) based on a Clock implementation |
homepage | |
repository | https://git.openlogisticsfoundation.org/silicon-economy/libraries/serum/embedded-timers |
max_upload_size | |
id | 1142455 |
size | 69,580 |
This crate provides a common base for monotonically nondecreasing clocks in no_std
environments. It defines the Clock
and Instant
traits
as interface and implements timers and delays based on these traits.
By using the Clock
trait as dependency, device drivers and applications can be developed in
a platform-independent way. Therefore, this crate serves the same purpose for clocks as the
embedded-hal
or
embedded-nal
crates for peripheral or
network abstraction, respectively.
This crate is only concerned with monotonically nondecreasing clocks, i.e. clocks which serve
the same purpose as the
std::time::Instant
type.
These can be used for measuring durations between instants, creating timers/timeouts or just
wait/delay program execution. Anything else which might be part of a time library is a
non-goal of this crate: This crate is not concerned with system time or wall clock time
which might jump when the clock is synchronized. Furthermore, this crate does not cover time
zone handling or calendars.
To create a common interface for handling clocks in no_std
environments, this crate aims to
make decisions which are acceptable for many use cases and many users. In this regard, it tries
to be as straightforward (boring) as possible and to avoid too opinionated or debatable
decisions. For example:
core
. Specifically, the
core::time::Duration
type
is used for durations although its 12 byte memory layout seems like overkill for many embedded
applications. But it covers all use cases from high-precision to multi-year timing and it is
already agreed upon in the community.Clock
and Instant
traits are inspired by std::time::Instant
which should be
familiar for Rust developers. But different from std::time::Instant
, no assumption of a
globally available clock is made. Therefore, the functionality is split in two different
traits.Most users (application, library or device driver developers) will depend on the Clock
trait.
Then, the clock can be used for timers or delays without being concerned with the underlying
Instant
type:
fn application(clock: &impl embedded_timers::clock::Clock) {
// Get the current instant, the instant type is inferred from the generic Clock
let earlier = clock.now();
let later = clock.now();
// The instant type is guaranteed to support calculations due to the Instant trait
let time_passed: core::time::Duration = later - earlier;
// Timers and delays can easily be written manually
let deadline = clock.now() + core::time::Duration::from_secs(1); // 1 second timer/delay
loop {
// By comparing clock.now() with the deadline, we determine if the timer has expired.
// When doing nothing in the loop, this is a simple busy wait delay.
if clock.now() > deadline {
break;
}
}
// Alternatively, the provided helper types for timer and delay can be used
let mut timer = embedded_timers::timer::Timer::new(clock);
timer.try_start(core::time::Duration::from_secs(1)).unwrap();
let is_expired = timer.is_expired().unwrap();
}
On the platform level, the Clock
trait has to be implemented for a clock type. The associated
Instant
type needs to implement the Instant
trait. If the std
feature is enabled,
std::time::Instant
implements the Instant
trait and can be used for a very simple clock:
struct StdClock;
impl embedded_timers::clock::Clock for StdClock {
type Instant = std::time::Instant;
fn now(&self) -> Self::Instant {
std::time::Instant::now()
}
}
In an actual no_std
environment, a clock needs to be implemented manually. For the associated
Instant
type, it may use one of the types defined in the [instant
] module. It needs to take
care of setting up an appropriate tick interrupt handler and to synchronize accesses to the
tick variable. This may look somewhat like:
// Global tick counter variable which is incremented in the tick interrupt handler. By using an
// atomic variable, we can do this without unsafe code. Note that using a 32 bit counter for
// milliseconds will wrap around after around 50 days so this might not be feasible in a real
// scenario.
static TICKS: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
// This tick interrupt handler is assumed to be called once per millisecond
fn tick_handler() {
TICKS.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
struct MilliSecondClock32;
impl embedded_timers::clock::Clock for MilliSecondClock32 {
type Instant = embedded_timers::instant::Instant32<1000>;
fn now(&self) -> Self::Instant {
let ticks = TICKS.load(core::sync::atomic::Ordering::Relaxed);
embedded_timers::instant::Instant32::<1000>::new(ticks)
}
}
From the clock, a delay can be created. This will perform a busy waiting delay.
use embedded_timers::clock::Clock;
use embedded_hal::blocking::delay::DelayMs;
#[derive(Debug)]
pub struct MilliSecondClock;
let clock = MilliSecondClock;
let mut delay = embedded_timers::delay::Delay::new(&clock);
loop {
println!("This shows every second");
delay.delay_ms(1000_u32);
}
The crate provides a convenient timer interface with functionality to check if the timer
is_running
or is_expired
and how much duration_left
.
use embedded_timers::clock::Clock;
use embedded_timers::timer::Timer;
use embedded_hal::timer::CountDown;
#[derive(Debug)]
pub struct MilliSecondClock;
let clock = MilliSecondClock;
let mut timer = embedded_timers::timer::Timer::new(&clock);
timer.start(core::time::Duration::from_secs(1));
loop {
if let Ok(expired) = timer.is_expired() {
if expired {
println!("This shows every second");
timer.start(core::time::Duration::from_secs(1));
}
}
}
The embedded_timers::Timer
also implements embedded_hal::timer::CountDown
as this is a
common interface for embedded timers.
use embedded_timers::clock::Clock;
use embedded_timers::timer::Timer;
use embedded_hal::timer::CountDown;
use embedded_hal::blocking::delay::DelayMs;
#[derive(Debug)]
pub struct MilliSecondClock;
let clock = MilliSecondClock;
let mut timer = embedded_timers::timer::Timer::new(&clock);
let mut delay = embedded_timers::delay::Delay::new(&clock);
timer.start(core::time::Duration::from_secs(1));
loop {
match timer.wait() {
Err(nb::Error::WouldBlock) => {
println!("Timer still running");
delay.delay_ms(50_u32);
}
Err(_) => panic!("TIMER ERROR"),
Ok(_) => {
println!("This shows every second");
timer.start(core::time::Duration::from_secs(1));
}
}
}
Open Logistics Foundation License
Version 1.3, January 2023
See the LICENSE file in the top-level directory.
Fraunhofer IML Embedded Rust Group - embedded-rust@iml.fraunhofer.de