1 unstable release

0.1.0 Jan 11, 2024

#1898 in Embedded development

Download history 2501/week @ 2024-01-22 2599/week @ 2024-01-29 2100/week @ 2024-02-05 2991/week @ 2024-02-12 4556/week @ 2024-02-19 3437/week @ 2024-02-26 4302/week @ 2024-03-04 5577/week @ 2024-03-11 5413/week @ 2024-03-18 4647/week @ 2024-03-25 5119/week @ 2024-04-01 5132/week @ 2024-04-08 6317/week @ 2024-04-15 5870/week @ 2024-04-22 4918/week @ 2024-04-29 3473/week @ 2024-05-06

20,948 downloads per month
Used in 57 crates (8 directly)


610 lines


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.


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.

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 functions as extern "Rust" { fn _embassy_time_now() -> u64; } and calls them. The driver crate defines the functions 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.


use embassy_time_driver::{Driver, AlarmHandle};

struct MyDriver{} // not public!

impl Driver for MyDriver {
    fn now(&self) -> u64 {
    unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> {
    fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
    fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool {

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

Feature flags