2 unstable releases
0.3.0 | Jul 11, 2024 |
---|---|
0.2.0 | Feb 16, 2024 |
#1574 in Embedded development
Used in 2 crates
53KB
520 lines
embedded-timers
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.
Design Decisions
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:
- Use whatever is already defined in
core
. Specifically, thecore::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. - The
Clock
andInstant
traits are inspired bystd::time::Instant
which should be familiar for Rust developers. But different fromstd::time::Instant
, no assumption of a globally available clock is made. Therefore, the functionality is split in two different traits.
Usage
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)
}
}
Delay
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);
}
Timer
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));
}
}
}
License
Open Logistics Foundation License
Version 1.3, January 2023
See the LICENSE file in the top-level directory.
Contact
Fraunhofer IML Embedded Rust Group - embedded-rust@iml.fraunhofer.de
Dependencies
~71KB