2 unstable releases

0.2.0 Jan 2, 2025
0.1.0 Jan 11, 2024

#1456 in Embedded development

Download history 11433/week @ 2024-09-20 6387/week @ 2024-09-27 6920/week @ 2024-10-04 6418/week @ 2024-10-11 7469/week @ 2024-10-18 7155/week @ 2024-10-25 8464/week @ 2024-11-01 8014/week @ 2024-11-08 8210/week @ 2024-11-15 10165/week @ 2024-11-22 7168/week @ 2024-11-29 6276/week @ 2024-12-06 9578/week @ 2024-12-13 7981/week @ 2024-12-20 6648/week @ 2024-12-27 12573/week @ 2025-01-03

37,813 downloads per month
Used in 110 crates (16 directly)

MIT/Apache

42KB
581 lines

embassy-time-driver

This crate contains the driver trait necessary for adding embassy-time support for a new hardware platform.

If you want to use embassy-time with already made drivers, you should depend on the main embassy-time crate, not on this crate.

If you are writing a driver, you should depend only on this crate, not on the main embassy-time crate. This will allow your driver to continue working for newer embassy-time major versions, without needing an update, if the driver trait has not had breaking changes.

How it works

embassy-time is backed by a global "time driver" specified at build time. Only one driver can be active in a program.

All methods and structs transparently call into the active driver. This makes it possible for libraries to use embassy-time in a driver-agnostic way without requiring generic parameters.


lib.rs:

Implementing a driver

If your driver has a single set tick rate, enable the corresponding tick-hz-* feature, which will prevent users from needing to configure it themselves (or selecting an incorrect configuration).

If your driver supports a small number of set tick rates, expose your own cargo features and have each one enable the corresponding embassy-time-driver/tick-*.

Otherwise, don’t enable any tick-hz-* feature to let the user configure the tick rate themselves by enabling a feature on embassy-time.

Example

use core::task::Waker;

use embassy_time_driver::Driver;

struct MyDriver{} // not public!

impl Driver for MyDriver {
    fn now(&self) -> u64 {
        todo!()
    }

    fn schedule_wake(&self, at: u64, waker: &Waker) {
        todo!()
    }
}

embassy_time_driver::time_driver_impl!(static DRIVER: MyDriver = MyDriver{});

Implementing the timer queue

The simplest (but suboptimal) way to implement a timer queue is to define a single queue in the time driver. Declare a field protected by an appropriate mutex (e.g. critical_section::Mutex).

Then, you'll need to adapt the schedule_wake method to use this queue.

Note that if you are using multiple queues, you will need to ensure that a single timer queue item is only ever enqueued into a single queue at a time.

use core::cell::RefCell;
use core::task::Waker;

use critical_section::{CriticalSection, Mutex};
use embassy_time_queue_utils::Queue;
use embassy_time_driver::Driver;

struct MyDriver {
    queue: Mutex<RefCell<Queue>>,
}

impl MyDriver {
   fn set_alarm(&self, cs: &CriticalSection, at: u64) -> bool {
       todo!()
   }
}

impl Driver for MyDriver {
    fn now(&self) -> u64 { todo!() }

    fn schedule_wake(&self, at: u64, waker: &Waker) {
        critical_section::with(|cs| {
            let mut queue = self.queue.borrow(cs).borrow_mut();
            if queue.schedule_wake(at, waker) {
                let mut next = queue.next_expiration(self.now());
                while !self.set_alarm(&cs, next) {
                    next = queue.next_expiration(self.now());
                }
            }
        });
    }
}

Linkage details

Instead of the usual "trait + generic params" approach, calls from embassy to the driver are done via extern functions.

embassy internally defines the driver function as extern "Rust" { fn _embassy_time_now() -> u64; } and calls it. The driver crate defines the function as #[no_mangle] fn _embassy_time_now() -> u64. The linker will resolve the calls from the embassy crate to call into the driver crate.

If there is none or multiple drivers in the crate tree, linking will fail.

This method has a few key advantages for something as foundational as timekeeping:

  • The time driver is available everywhere easily, without having to thread the implementation through generic parameters. This is especially helpful for libraries.
  • It means comparing Instants will always make sense: if there were multiple drivers active, one could compare an Instant from driver A to an Instant from driver B, which would yield incorrect results.

Feature flags

Dependencies

~175KB