aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--embassy/Cargo.toml8
-rw-r--r--embassy/src/time/delay.rs9
-rw-r--r--embassy/src/time/driver.rs59
-rw-r--r--embassy/src/time/duration.rs9
-rw-r--r--embassy/src/time/instant.rs26
-rw-r--r--embassy/src/time/mod.rs56
-rw-r--r--embassy/src/time/timer.rs6
7 files changed, 150 insertions, 23 deletions
diff --git a/embassy/Cargo.toml b/embassy/Cargo.toml
index ca7d38a55..ec76299e1 100644
--- a/embassy/Cargo.toml
+++ b/embassy/Cargo.toml
@@ -9,7 +9,15 @@ resolver = "2"
9default = [] 9default = []
10std = ["futures/std", "embassy-traits/std"] 10std = ["futures/std", "embassy-traits/std"]
11 11
12# Enable `embassy::time` module.
13# NOTE: This feature is only intended to be enabled by crates providing the time driver implementation.
14# Enabling it directly without supplying a time driver will fail to link.
12time = [] 15time = []
16
17# Set the `embassy::time` tick rate.
18# NOTE: This feature is only intended to be enabled by crates providing the time driver implementation.
19# If you're not writing your own driver, check the driver documentation to customize the tick rate.
20# If you're writing a driver and your tick rate is not listed here, please add it and send a PR!
13time-tick-32768hz = ["time"] 21time-tick-32768hz = ["time"]
14time-tick-1000hz = ["time"] 22time-tick-1000hz = ["time"]
15time-tick-1mhz = ["time"] 23time-tick-1mhz = ["time"]
diff --git a/embassy/src/time/delay.rs b/embassy/src/time/delay.rs
index c97e8b5c2..a46ee3a4f 100644
--- a/embassy/src/time/delay.rs
+++ b/embassy/src/time/delay.rs
@@ -4,11 +4,10 @@ use super::{Duration, Instant, Timer};
4 4
5/// Type implementing async delays and blocking `embedded-hal` delays. 5/// Type implementing async delays and blocking `embedded-hal` delays.
6/// 6///
7/// For this interface to work, the Executor's clock must be correctly initialized before using it.
8/// The delays are implemented in a "best-effort" way, meaning that the cpu will block for at least 7/// The delays are implemented in a "best-effort" way, meaning that the cpu will block for at least
9/// the amount provided, but accuracy can be affected by many factors, including interrupt usage. 8/// the amount provided, but accuracy can be affected by many factors, including interrupt usage.
10/// Make sure to use a suitable tick rate for your use case. The tick rate can be chosen through 9/// Make sure to use a suitable tick rate for your use case. The tick rate is defined by the currently
11/// features flags of this crate. 10/// active driver.
12pub struct Delay; 11pub struct Delay;
13 12
14impl crate::traits::delay::Delay for Delay { 13impl crate::traits::delay::Delay for Delay {
@@ -58,9 +57,7 @@ impl embedded_hal::blocking::delay::DelayUs<u32> for Delay {
58 } 57 }
59} 58}
60 59
61/// Blocks the cpu for at least `duration`. 60/// Blocks for at least `duration`.
62///
63/// For this interface to work, the Executor's clock must be correctly initialized before using it.
64pub fn block_for(duration: Duration) { 61pub fn block_for(duration: Duration) {
65 let expires_at = Instant::now() + duration; 62 let expires_at = Instant::now() + duration;
66 while Instant::now() < expires_at {} 63 while Instant::now() < expires_at {}
diff --git a/embassy/src/time/driver.rs b/embassy/src/time/driver.rs
index b09cffd8c..21817b92f 100644
--- a/embassy/src/time/driver.rs
+++ b/embassy/src/time/driver.rs
@@ -1,3 +1,58 @@
1//! Time driver interface
2//!
3//! This module defines the interface a driver needs to implement to power the `embassy::time` module.
4//!
5//! # Implementing a driver
6//!
7//! - Define a struct `MyDriver`
8//! - Implement [`Driver`] for it
9//! - Register it as the global driver with [`time_driver_impl`].
10//! - Enable the Cargo features `embassy/time` and one of `embassy/time-tick-*` corresponding to the
11//! tick rate of your driver.
12//!
13//! If you wish to make the tick rate configurable by the end user, you should do so by exposing your own
14//! Cargo features and having each enable the corresponding `embassy/time-tick-*`.
15//!
16//! # Linkage details
17//!
18//! Instead of the usual "trait + generic params" approach, calls from embassy to the driver are done via `extern` functions.
19//!
20//! `embassy` internally defines the driver functions as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls them.
21//! The driver crate defines the functions as `#[no_mangle] fn _embassy_time_now() -> u64`. The linker will resolve the
22//! calls from the `embassy` crate to call into the driver crate.
23//!
24//! If there is none or multiple drivers in the crate tree, linking will fail.
25//!
26//! This method has a few key advantages for something as foundational as timekeeping:
27//!
28//! - The time driver is available everywhere easily, without having to thread the implementation
29//~ through generic parameters. This is especially helpful for libraries.
30//! - It means comparing `Instant`s will always make sense: if there were multiple drivers
31//! active, one could compare an `Instant` from driver A to an `Instant` from driver B, which
32//! would yield incorrect results.
33//!
34/// # Example
35///
36/// ```
37/// struct MyDriver; // not public!
38/// embassy::time_driver_impl!(MyDriver);
39///
40/// unsafe impl embassy::time::driver::Driver for MyDriver {
41/// fn now() -> u64 {
42/// todo!()
43/// }
44/// unsafe fn allocate_alarm() -> Option<AlarmHandle> {
45/// todo!()
46/// }
47/// fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) {
48/// todo!()
49/// }
50/// fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
51/// todo!()
52/// }
53/// }
54/// ```
55
1/// Alarm handle, assigned by the driver. 56/// Alarm handle, assigned by the driver.
2#[derive(Clone, Copy)] 57#[derive(Clone, Copy)]
3pub struct AlarmHandle { 58pub struct AlarmHandle {
@@ -73,7 +128,7 @@ pub(crate) fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
73/// # Example 128/// # Example
74/// 129///
75/// ``` 130/// ```
76/// struct MyDriver; 131/// struct MyDriver; // not public!
77/// embassy::time_driver_impl!(MyDriver); 132/// embassy::time_driver_impl!(MyDriver);
78/// 133///
79/// unsafe impl embassy::time::driver::Driver for MyDriver { 134/// unsafe impl embassy::time::driver::Driver for MyDriver {
@@ -90,7 +145,7 @@ pub(crate) fn set_alarm(alarm: AlarmHandle, timestamp: u64) {
90/// todo!() 145/// todo!()
91/// } 146/// }
92/// } 147/// }
93/// 148/// ```
94#[macro_export] 149#[macro_export]
95macro_rules! time_driver_impl { 150macro_rules! time_driver_impl {
96 ($t: ty) => { 151 ($t: ty) => {
diff --git a/embassy/src/time/duration.rs b/embassy/src/time/duration.rs
index 5157450ad..e196a00c7 100644
--- a/embassy/src/time/duration.rs
+++ b/embassy/src/time/duration.rs
@@ -11,18 +11,27 @@ pub struct Duration {
11} 11}
12 12
13impl Duration { 13impl Duration {
14 /// The smallest value that can be represented by the `Duration` type.
15 pub const MIN: Duration = Duration { ticks: u64::MIN };
16 /// The largest value that can be represented by the `Duration` type.
17 pub const MAX: Duration = Duration { ticks: u64::MAX };
18
19 /// Tick count of the `Duration`.
14 pub const fn as_ticks(&self) -> u64 { 20 pub const fn as_ticks(&self) -> u64 {
15 self.ticks 21 self.ticks
16 } 22 }
17 23
24 /// Convert the `Duration` to seconds, rounding down.
18 pub const fn as_secs(&self) -> u64 { 25 pub const fn as_secs(&self) -> u64 {
19 self.ticks / TICKS_PER_SECOND 26 self.ticks / TICKS_PER_SECOND
20 } 27 }
21 28
29 /// Convert the `Duration` to milliseconds, rounding down.
22 pub const fn as_millis(&self) -> u64 { 30 pub const fn as_millis(&self) -> u64 {
23 self.ticks * 1000 / TICKS_PER_SECOND 31 self.ticks * 1000 / TICKS_PER_SECOND
24 } 32 }
25 33
34 /// Convert the `Duration` to microseconds, rounding down.
26 pub const fn as_micros(&self) -> u64 { 35 pub const fn as_micros(&self) -> u64 {
27 self.ticks * 1_000_000 / TICKS_PER_SECOND 36 self.ticks * 1_000_000 / TICKS_PER_SECOND
28 } 37 }
diff --git a/embassy/src/time/instant.rs b/embassy/src/time/instant.rs
index 3d430d886..36c2b2dc2 100644
--- a/embassy/src/time/instant.rs
+++ b/embassy/src/time/instant.rs
@@ -11,7 +11,9 @@ pub struct Instant {
11} 11}
12 12
13impl Instant { 13impl Instant {
14 /// The smallest (earliest) value that can be represented by the `Instant` type.
14 pub const MIN: Instant = Instant { ticks: u64::MIN }; 15 pub const MIN: Instant = Instant { ticks: u64::MIN };
16 /// The largest (latest) value that can be represented by the `Instant` type.
15 pub const MAX: Instant = Instant { ticks: u64::MAX }; 17 pub const MAX: Instant = Instant { ticks: u64::MAX };
16 18
17 /// Returns an Instant representing the current time. 19 /// Returns an Instant representing the current time.
@@ -21,39 +23,38 @@ impl Instant {
21 } 23 }
22 } 24 }
23 25
24 /// Instant as clock ticks since MCU start. 26 /// Create an Instant from a tick count since system boot.
25 pub const fn from_ticks(ticks: u64) -> Self { 27 pub const fn from_ticks(ticks: u64) -> Self {
26 Self { ticks } 28 Self { ticks }
27 } 29 }
28 30
29 /// Instant as milliseconds since MCU start. 31 /// Create an Instant from a millisecond count since system boot.
30 pub const fn from_millis(millis: u64) -> Self { 32 pub const fn from_millis(millis: u64) -> Self {
31 Self { 33 Self {
32 ticks: millis * TICKS_PER_SECOND as u64 / 1000, 34 ticks: millis * TICKS_PER_SECOND / 1000,
33 } 35 }
34 } 36 }
35 37
36 /// Instant representing seconds since MCU start. 38 /// Create an Instant from a second count since system boot.
37 pub const fn from_secs(seconds: u64) -> Self { 39 pub const fn from_secs(seconds: u64) -> Self {
38 Self { 40 Self {
39 ticks: seconds * TICKS_PER_SECOND as u64, 41 ticks: seconds * TICKS_PER_SECOND,
40 } 42 }
41 } 43 }
42 44
43 /// Instant as ticks since MCU start. 45 /// Tick count since system boot.
44
45 pub const fn as_ticks(&self) -> u64 { 46 pub const fn as_ticks(&self) -> u64 {
46 self.ticks 47 self.ticks
47 } 48 }
48 /// Instant as seconds since MCU start.
49 49
50 /// Seconds since system boot.
50 pub const fn as_secs(&self) -> u64 { 51 pub const fn as_secs(&self) -> u64 {
51 self.ticks / TICKS_PER_SECOND as u64 52 self.ticks / TICKS_PER_SECOND
52 } 53 }
53 /// Instant as miliseconds since MCU start.
54 54
55 /// Milliseconds since system boot.
55 pub const fn as_millis(&self) -> u64 { 56 pub const fn as_millis(&self) -> u64 {
56 self.ticks * 1000 / TICKS_PER_SECOND as u64 57 self.ticks * 1000 / TICKS_PER_SECOND
57 } 58 }
58 59
59 /// Duration between this Instant and another Instant 60 /// Duration between this Instant and another Instant
@@ -92,11 +93,14 @@ impl Instant {
92 Instant::now() - *self 93 Instant::now() - *self
93 } 94 }
94 95
96 /// Adds one Duration to self, returning a new `Instant` or None in the event of an overflow.
95 pub fn checked_add(&self, duration: Duration) -> Option<Instant> { 97 pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
96 self.ticks 98 self.ticks
97 .checked_add(duration.ticks) 99 .checked_add(duration.ticks)
98 .map(|ticks| Instant { ticks }) 100 .map(|ticks| Instant { ticks })
99 } 101 }
102
103 /// Subtracts one Duration to self, returning a new `Instant` or None in the event of an overflow.
100 pub fn checked_sub(&self, duration: Duration) -> Option<Instant> { 104 pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
101 self.ticks 105 self.ticks
102 .checked_sub(duration.ticks) 106 .checked_sub(duration.ticks)
diff --git a/embassy/src/time/mod.rs b/embassy/src/time/mod.rs
index f5ad1b3ff..db48fb3ec 100644
--- a/embassy/src/time/mod.rs
+++ b/embassy/src/time/mod.rs
@@ -1,4 +1,44 @@
1//! Time abstractions 1//! Timekeeping, delays and timeouts.
2//!
3//! Timekeeping is done with elapsed time since system boot. Time is represented in
4//! ticks, where the tick rate is defined by the current driver, usually to match
5//! the tick rate of the hardware.
6//!
7//! Tick counts are 64 bits. At the highest supported tick rate of 1Mhz this supports
8//! representing time spans of up to ~584558 years, which is big enough for all practical
9//! purposes and allows not having to worry about overflows.
10//!
11//! [`Instant`] represents a given instant of time (relative to system boot), and [`Duration`]
12//! represents the duration of a span of time. They implement the math operations you'd expect,
13//! like addition and substraction.
14//!
15//! # Delays and timeouts
16//!
17//! [`Timer`] allows performing async delays. [`Ticker`] allows periodic delays without drifting over time.
18//!
19//! An implementation of the `embedded-hal` delay traits is provided by [`Delay`], for compatibility
20//! with libraries from the ecosystem.
21//!
22//! # Wall-clock time
23//!
24//! The `time` module deals exclusively with a monotonically increasing tick count.
25//! Therefore it has no direct support for wall-clock time ("real life" datetimes
26//! like `2021-08-24 13:33:21`).
27//!
28//! If persistence across reboots is not needed, support can be built on top of
29//! `embassy::time` by storing the offset between "seconds elapsed since boot"
30//! and "seconds since unix epoch".
31//!
32//! # Time driver
33//!
34//! The `time` module is backed by a global "time driver" specified at build time.
35//! Only one driver can be active in a program.
36//!
37//! All methods and structs transparently call into the active driver. This makes it
38//! possible for libraries to use `embassy::time` in a driver-agnostic way without
39//! requiring generic parameters.
40//!
41//! For more details, check the [`driver`] module.
2 42
3mod delay; 43mod delay;
4pub mod driver; 44pub mod driver;
@@ -12,10 +52,18 @@ pub use instant::Instant;
12pub use timer::{with_timeout, Ticker, TimeoutError, Timer}; 52pub use timer::{with_timeout, Ticker, TimeoutError, Timer};
13 53
14#[cfg(feature = "time-tick-1000hz")] 54#[cfg(feature = "time-tick-1000hz")]
15pub const TICKS_PER_SECOND: u64 = 1_000; 55const TPS: u64 = 1_000;
16 56
17#[cfg(feature = "time-tick-32768hz")] 57#[cfg(feature = "time-tick-32768hz")]
18pub const TICKS_PER_SECOND: u64 = 32_768; 58const TPS: u64 = 32_768;
19 59
20#[cfg(feature = "time-tick-1mhz")] 60#[cfg(feature = "time-tick-1mhz")]
21pub const TICKS_PER_SECOND: u64 = 1_000_000; 61const TPS: u64 = 1_000_000;
62
63/// Ticks per second of the global timebase.
64///
65/// This value is specified by the `time-tick-*` Cargo features, which
66/// should be set by the time driver. Some drivers support a fixed tick rate, others
67/// allow you to choose a tick rate with Cargo features of their own. You should not
68/// set the `time-tick-*` features for embassy yourself as an end user.
69pub const TICKS_PER_SECOND: u64 = TPS;
diff --git a/embassy/src/time/timer.rs b/embassy/src/time/timer.rs
index 22f1ffe30..d1ed6a512 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};
6use crate::executor::raw; 6use crate::executor::raw;
7use crate::time::{Duration, Instant}; 7use crate::time::{Duration, Instant};
8 8
9/// Error returned by [`with_timeout`] on timeout.
9pub struct TimeoutError; 10pub struct TimeoutError;
11
12/// Runs a given future with a timeout.
13///
14/// If the future completes before the timeout, its output is returned. Otherwise, on timeout,
15/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned.
10pub async fn with_timeout<F: Future>(timeout: Duration, fut: F) -> Result<F::Output, TimeoutError> { 16pub async fn with_timeout<F: Future>(timeout: Duration, fut: F) -> Result<F::Output, TimeoutError> {
11 let timeout_fut = Timer::after(timeout); 17 let timeout_fut = Timer::after(timeout);
12 pin_mut!(fut); 18 pin_mut!(fut);