diff --git a/embassy/Cargo.toml b/embassy/Cargo.toml index ca7d38a5..ec76299e 100644 --- a/embassy/Cargo.toml +++ b/embassy/Cargo.toml @@ -9,7 +9,15 @@ resolver = "2" default = [] std = ["futures/std", "embassy-traits/std"] +# Enable `embassy::time` module. +# NOTE: This feature is only intended to be enabled by crates providing the time driver implementation. +# Enabling it directly without supplying a time driver will fail to link. time = [] + +# Set the `embassy::time` tick rate. +# NOTE: This feature is only intended to be enabled by crates providing the time driver implementation. +# If you're not writing your own driver, check the driver documentation to customize the tick rate. +# If you're writing a driver and your tick rate is not listed here, please add it and send a PR! time-tick-32768hz = ["time"] time-tick-1000hz = ["time"] time-tick-1mhz = ["time"] diff --git a/embassy/src/time/delay.rs b/embassy/src/time/delay.rs index c97e8b5c..a46ee3a4 100644 --- a/embassy/src/time/delay.rs +++ b/embassy/src/time/delay.rs @@ -4,11 +4,10 @@ use super::{Duration, Instant, Timer}; /// Type implementing async delays and blocking `embedded-hal` delays. /// -/// For this interface to work, the Executor's clock must be correctly initialized before using it. /// The delays are implemented in a "best-effort" way, meaning that the cpu will block for at least /// the amount provided, but accuracy can be affected by many factors, including interrupt usage. -/// Make sure to use a suitable tick rate for your use case. The tick rate can be chosen through -/// features flags of this crate. +/// Make sure to use a suitable tick rate for your use case. The tick rate is defined by the currently +/// active driver. pub struct Delay; impl crate::traits::delay::Delay for Delay { @@ -58,9 +57,7 @@ impl embedded_hal::blocking::delay::DelayUs for Delay { } } -/// Blocks the cpu for at least `duration`. -/// -/// For this interface to work, the Executor's clock must be correctly initialized before using it. +/// Blocks for at least `duration`. pub fn block_for(duration: Duration) { let expires_at = Instant::now() + duration; while Instant::now() < expires_at {} diff --git a/embassy/src/time/driver.rs b/embassy/src/time/driver.rs index b09cffd8..21817b92 100644 --- a/embassy/src/time/driver.rs +++ b/embassy/src/time/driver.rs @@ -1,3 +1,58 @@ +//! Time driver interface +//! +//! This module defines the interface a driver needs to implement to power the `embassy::time` module. +//! +//! # Implementing a driver +//! +//! - Define a struct `MyDriver` +//! - Implement [`Driver`] for it +//! - Register it as the global driver with [`time_driver_impl`]. +//! - Enable the Cargo features `embassy/time` and one of `embassy/time-tick-*` corresponding to the +//! tick rate of your driver. +//! +//! If you wish to make the tick rate configurable by the end user, you should do so by exposing your own +//! Cargo features and having each enable the corresponding `embassy/time-tick-*`. +//! +//! # 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 `Instant`s 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. +//! +/// # Example +/// +/// ``` +/// struct MyDriver; // not public! +/// embassy::time_driver_impl!(MyDriver); +/// +/// unsafe impl embassy::time::driver::Driver for MyDriver { +/// fn now() -> u64 { +/// todo!() +/// } +/// unsafe fn allocate_alarm() -> Option { +/// todo!() +/// } +/// fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { +/// todo!() +/// } +/// fn set_alarm(alarm: AlarmHandle, timestamp: u64) { +/// todo!() +/// } +/// } +/// ``` + /// Alarm handle, assigned by the driver. #[derive(Clone, Copy)] pub struct AlarmHandle { @@ -73,7 +128,7 @@ pub(crate) fn set_alarm(alarm: AlarmHandle, timestamp: u64) { /// # Example /// /// ``` -/// struct MyDriver; +/// struct MyDriver; // not public! /// embassy::time_driver_impl!(MyDriver); /// /// unsafe impl embassy::time::driver::Driver for MyDriver { @@ -90,7 +145,7 @@ pub(crate) fn set_alarm(alarm: AlarmHandle, timestamp: u64) { /// todo!() /// } /// } -/// +/// ``` #[macro_export] macro_rules! time_driver_impl { ($t: ty) => { diff --git a/embassy/src/time/duration.rs b/embassy/src/time/duration.rs index 5157450a..e196a00c 100644 --- a/embassy/src/time/duration.rs +++ b/embassy/src/time/duration.rs @@ -11,18 +11,27 @@ pub struct Duration { } impl Duration { + /// The smallest value that can be represented by the `Duration` type. + pub const MIN: Duration = Duration { ticks: u64::MIN }; + /// The largest value that can be represented by the `Duration` type. + pub const MAX: Duration = Duration { ticks: u64::MAX }; + + /// Tick count of the `Duration`. pub const fn as_ticks(&self) -> u64 { self.ticks } + /// Convert the `Duration` to seconds, rounding down. pub const fn as_secs(&self) -> u64 { self.ticks / TICKS_PER_SECOND } + /// Convert the `Duration` to milliseconds, rounding down. pub const fn as_millis(&self) -> u64 { self.ticks * 1000 / TICKS_PER_SECOND } + /// Convert the `Duration` to microseconds, rounding down. pub const fn as_micros(&self) -> u64 { self.ticks * 1_000_000 / TICKS_PER_SECOND } diff --git a/embassy/src/time/instant.rs b/embassy/src/time/instant.rs index 3d430d88..36c2b2dc 100644 --- a/embassy/src/time/instant.rs +++ b/embassy/src/time/instant.rs @@ -11,7 +11,9 @@ pub struct Instant { } impl Instant { + /// The smallest (earliest) value that can be represented by the `Instant` type. pub const MIN: Instant = Instant { ticks: u64::MIN }; + /// The largest (latest) value that can be represented by the `Instant` type. pub const MAX: Instant = Instant { ticks: u64::MAX }; /// Returns an Instant representing the current time. @@ -21,39 +23,38 @@ impl Instant { } } - /// Instant as clock ticks since MCU start. + /// Create an Instant from a tick count since system boot. pub const fn from_ticks(ticks: u64) -> Self { Self { ticks } } - /// Instant as milliseconds since MCU start. + /// Create an Instant from a millisecond count since system boot. pub const fn from_millis(millis: u64) -> Self { Self { - ticks: millis * TICKS_PER_SECOND as u64 / 1000, + ticks: millis * TICKS_PER_SECOND / 1000, } } - /// Instant representing seconds since MCU start. + /// Create an Instant from a second count since system boot. pub const fn from_secs(seconds: u64) -> Self { Self { - ticks: seconds * TICKS_PER_SECOND as u64, + ticks: seconds * TICKS_PER_SECOND, } } - /// Instant as ticks since MCU start. - + /// Tick count since system boot. pub const fn as_ticks(&self) -> u64 { self.ticks } - /// Instant as seconds since MCU start. + /// Seconds since system boot. pub const fn as_secs(&self) -> u64 { - self.ticks / TICKS_PER_SECOND as u64 + self.ticks / TICKS_PER_SECOND } - /// Instant as miliseconds since MCU start. + /// Milliseconds since system boot. pub const fn as_millis(&self) -> u64 { - self.ticks * 1000 / TICKS_PER_SECOND as u64 + self.ticks * 1000 / TICKS_PER_SECOND } /// Duration between this Instant and another Instant @@ -92,11 +93,14 @@ impl Instant { Instant::now() - *self } + /// Adds one Duration to self, returning a new `Instant` or None in the event of an overflow. pub fn checked_add(&self, duration: Duration) -> Option { self.ticks .checked_add(duration.ticks) .map(|ticks| Instant { ticks }) } + + /// Subtracts one Duration to self, returning a new `Instant` or None in the event of an overflow. pub fn checked_sub(&self, duration: Duration) -> Option { self.ticks .checked_sub(duration.ticks) diff --git a/embassy/src/time/mod.rs b/embassy/src/time/mod.rs index f5ad1b3f..db48fb3e 100644 --- a/embassy/src/time/mod.rs +++ b/embassy/src/time/mod.rs @@ -1,4 +1,44 @@ -//! Time abstractions +//! Timekeeping, delays and timeouts. +//! +//! Timekeeping is done with elapsed time since system boot. Time is represented in +//! ticks, where the tick rate is defined by the current driver, usually to match +//! the tick rate of the hardware. +//! +//! Tick counts are 64 bits. At the highest supported tick rate of 1Mhz this supports +//! representing time spans of up to ~584558 years, which is big enough for all practical +//! purposes and allows not having to worry about overflows. +//! +//! [`Instant`] represents a given instant of time (relative to system boot), and [`Duration`] +//! represents the duration of a span of time. They implement the math operations you'd expect, +//! like addition and substraction. +//! +//! # Delays and timeouts +//! +//! [`Timer`] allows performing async delays. [`Ticker`] allows periodic delays without drifting over time. +//! +//! An implementation of the `embedded-hal` delay traits is provided by [`Delay`], for compatibility +//! with libraries from the ecosystem. +//! +//! # Wall-clock time +//! +//! The `time` module deals exclusively with a monotonically increasing tick count. +//! Therefore it has no direct support for wall-clock time ("real life" datetimes +//! like `2021-08-24 13:33:21`). +//! +//! If persistence across reboots is not needed, support can be built on top of +//! `embassy::time` by storing the offset between "seconds elapsed since boot" +//! and "seconds since unix epoch". +//! +//! # Time driver +//! +//! The `time` module 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. +//! +//! For more details, check the [`driver`] module. mod delay; pub mod driver; @@ -12,10 +52,18 @@ pub use instant::Instant; pub use timer::{with_timeout, Ticker, TimeoutError, Timer}; #[cfg(feature = "time-tick-1000hz")] -pub const TICKS_PER_SECOND: u64 = 1_000; +const TPS: u64 = 1_000; #[cfg(feature = "time-tick-32768hz")] -pub const TICKS_PER_SECOND: u64 = 32_768; +const TPS: u64 = 32_768; #[cfg(feature = "time-tick-1mhz")] -pub const TICKS_PER_SECOND: u64 = 1_000_000; +const TPS: u64 = 1_000_000; + +/// Ticks per second of the global timebase. +/// +/// This value is specified by the `time-tick-*` Cargo features, which +/// should be set by the time driver. Some drivers support a fixed tick rate, others +/// allow you to choose a tick rate with Cargo features of their own. You should not +/// set the `time-tick-*` features for embassy yourself as an end user. +pub const TICKS_PER_SECOND: u64 = TPS; diff --git a/embassy/src/time/timer.rs b/embassy/src/time/timer.rs index 22f1ffe3..d1ed6a51 100644 --- a/embassy/src/time/timer.rs +++ b/embassy/src/time/timer.rs @@ -6,7 +6,13 @@ use futures::{future::select, future::Either, pin_mut, Stream}; use crate::executor::raw; use crate::time::{Duration, Instant}; +/// Error returned by [`with_timeout`] on timeout. pub struct TimeoutError; + +/// Runs a given future with a timeout. +/// +/// If the future completes before the timeout, its output is returned. Otherwise, on timeout, +/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned. pub async fn with_timeout(timeout: Duration, fut: F) -> Result { let timeout_fut = Timer::after(timeout); pin_mut!(fut);