Document embassy::time
This commit is contained in:
parent
09ffdf63f1
commit
503be49417
@ -9,7 +9,15 @@ resolver = "2"
|
|||||||
default = []
|
default = []
|
||||||
std = ["futures/std", "embassy-traits/std"]
|
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 = []
|
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-32768hz = ["time"]
|
||||||
time-tick-1000hz = ["time"]
|
time-tick-1000hz = ["time"]
|
||||||
time-tick-1mhz = ["time"]
|
time-tick-1mhz = ["time"]
|
||||||
|
@ -4,11 +4,10 @@ use super::{Duration, Instant, Timer};
|
|||||||
|
|
||||||
/// Type implementing async delays and blocking `embedded-hal` delays.
|
/// 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 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.
|
/// 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
|
/// Make sure to use a suitable tick rate for your use case. The tick rate is defined by the currently
|
||||||
/// features flags of this crate.
|
/// active driver.
|
||||||
pub struct Delay;
|
pub struct Delay;
|
||||||
|
|
||||||
impl crate::traits::delay::Delay for Delay {
|
impl crate::traits::delay::Delay for Delay {
|
||||||
@ -58,9 +57,7 @@ impl embedded_hal::blocking::delay::DelayUs<u32> for Delay {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Blocks the cpu for at least `duration`.
|
/// Blocks for at least `duration`.
|
||||||
///
|
|
||||||
/// For this interface to work, the Executor's clock must be correctly initialized before using it.
|
|
||||||
pub fn block_for(duration: Duration) {
|
pub fn block_for(duration: Duration) {
|
||||||
let expires_at = Instant::now() + duration;
|
let expires_at = Instant::now() + duration;
|
||||||
while Instant::now() < expires_at {}
|
while Instant::now() < expires_at {}
|
||||||
|
@ -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<AlarmHandle> {
|
||||||
|
/// 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.
|
/// Alarm handle, assigned by the driver.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct AlarmHandle {
|
pub struct AlarmHandle {
|
||||||
@ -73,7 +128,7 @@ pub(crate) fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// struct MyDriver;
|
/// struct MyDriver; // not public!
|
||||||
/// embassy::time_driver_impl!(MyDriver);
|
/// embassy::time_driver_impl!(MyDriver);
|
||||||
///
|
///
|
||||||
/// unsafe impl embassy::time::driver::Driver for MyDriver {
|
/// unsafe impl embassy::time::driver::Driver for MyDriver {
|
||||||
@ -90,7 +145,7 @@ pub(crate) fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
|
|||||||
/// todo!()
|
/// todo!()
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! time_driver_impl {
|
macro_rules! time_driver_impl {
|
||||||
($t: ty) => {
|
($t: ty) => {
|
||||||
|
@ -11,18 +11,27 @@ pub struct Duration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
pub const fn as_ticks(&self) -> u64 {
|
||||||
self.ticks
|
self.ticks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert the `Duration` to seconds, rounding down.
|
||||||
pub const fn as_secs(&self) -> u64 {
|
pub const fn as_secs(&self) -> u64 {
|
||||||
self.ticks / TICKS_PER_SECOND
|
self.ticks / TICKS_PER_SECOND
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert the `Duration` to milliseconds, rounding down.
|
||||||
pub const fn as_millis(&self) -> u64 {
|
pub const fn as_millis(&self) -> u64 {
|
||||||
self.ticks * 1000 / TICKS_PER_SECOND
|
self.ticks * 1000 / TICKS_PER_SECOND
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert the `Duration` to microseconds, rounding down.
|
||||||
pub const fn as_micros(&self) -> u64 {
|
pub const fn as_micros(&self) -> u64 {
|
||||||
self.ticks * 1_000_000 / TICKS_PER_SECOND
|
self.ticks * 1_000_000 / TICKS_PER_SECOND
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,9 @@ pub struct Instant {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Instant {
|
impl Instant {
|
||||||
|
/// The smallest (earliest) value that can be represented by the `Instant` type.
|
||||||
pub const MIN: Instant = Instant { ticks: u64::MIN };
|
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 };
|
pub const MAX: Instant = Instant { ticks: u64::MAX };
|
||||||
|
|
||||||
/// Returns an Instant representing the current time.
|
/// 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 {
|
pub const fn from_ticks(ticks: u64) -> Self {
|
||||||
Self { ticks }
|
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 {
|
pub const fn from_millis(millis: u64) -> Self {
|
||||||
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 {
|
pub const fn from_secs(seconds: u64) -> Self {
|
||||||
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 {
|
pub const fn as_ticks(&self) -> u64 {
|
||||||
self.ticks
|
self.ticks
|
||||||
}
|
}
|
||||||
/// Instant as seconds since MCU start.
|
|
||||||
|
|
||||||
|
/// Seconds since system boot.
|
||||||
pub const fn as_secs(&self) -> u64 {
|
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 {
|
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
|
/// Duration between this Instant and another Instant
|
||||||
@ -92,11 +93,14 @@ impl Instant {
|
|||||||
Instant::now() - *self
|
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<Instant> {
|
pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
|
||||||
self.ticks
|
self.ticks
|
||||||
.checked_add(duration.ticks)
|
.checked_add(duration.ticks)
|
||||||
.map(|ticks| Instant { 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<Instant> {
|
pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
|
||||||
self.ticks
|
self.ticks
|
||||||
.checked_sub(duration.ticks)
|
.checked_sub(duration.ticks)
|
||||||
|
@ -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;
|
mod delay;
|
||||||
pub mod driver;
|
pub mod driver;
|
||||||
@ -12,10 +52,18 @@ pub use instant::Instant;
|
|||||||
pub use timer::{with_timeout, Ticker, TimeoutError, Timer};
|
pub use timer::{with_timeout, Ticker, TimeoutError, Timer};
|
||||||
|
|
||||||
#[cfg(feature = "time-tick-1000hz")]
|
#[cfg(feature = "time-tick-1000hz")]
|
||||||
pub const TICKS_PER_SECOND: u64 = 1_000;
|
const TPS: u64 = 1_000;
|
||||||
|
|
||||||
#[cfg(feature = "time-tick-32768hz")]
|
#[cfg(feature = "time-tick-32768hz")]
|
||||||
pub const TICKS_PER_SECOND: u64 = 32_768;
|
const TPS: u64 = 32_768;
|
||||||
|
|
||||||
#[cfg(feature = "time-tick-1mhz")]
|
#[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;
|
||||||
|
@ -6,7 +6,13 @@ use futures::{future::select, future::Either, pin_mut, Stream};
|
|||||||
use crate::executor::raw;
|
use crate::executor::raw;
|
||||||
use crate::time::{Duration, Instant};
|
use crate::time::{Duration, Instant};
|
||||||
|
|
||||||
|
/// Error returned by [`with_timeout`] on timeout.
|
||||||
pub struct TimeoutError;
|
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<F: Future>(timeout: Duration, fut: F) -> Result<F::Output, TimeoutError> {
|
pub async fn with_timeout<F: Future>(timeout: Duration, fut: F) -> Result<F::Output, TimeoutError> {
|
||||||
let timeout_fut = Timer::after(timeout);
|
let timeout_fut = Timer::after(timeout);
|
||||||
pin_mut!(fut);
|
pin_mut!(fut);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user