#clock #timer #embedded-devices #delay #system-clock

no-std embedded-timers

Softwaretimers and -delays (ms/us) based on a Clock implementation

2 unstable releases

0.3.0 Jul 11, 2024
0.2.0 Feb 16, 2024

#1574 in Embedded development


Used in 2 crates

OLFL-1.3

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, 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.
  • The 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.

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