init project

This commit is contained in:
2025-04-24 12:07:40 +02:00
commit 788d9bd6ea
305 changed files with 61443 additions and 0 deletions

81
embassy-time/src/delay.rs Normal file
View File

@@ -0,0 +1,81 @@
use super::{Duration, Instant};
use crate::Timer;
/// Blocks for at least `duration`.
pub fn block_for(duration: Duration) {
let expires_at = Instant::now() + duration;
while Instant::now() < expires_at {}
}
/// Type implementing async delays and blocking `embedded-hal` delays.
///
/// 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 is defined by the currently
/// active driver.
#[derive(Clone)]
pub struct Delay;
impl embedded_hal_1::delay::DelayNs for Delay {
fn delay_ns(&mut self, ns: u32) {
block_for(Duration::from_nanos(ns as u64))
}
fn delay_us(&mut self, us: u32) {
block_for(Duration::from_micros(us as u64))
}
fn delay_ms(&mut self, ms: u32) {
block_for(Duration::from_millis(ms as u64))
}
}
impl embedded_hal_async::delay::DelayNs for Delay {
async fn delay_ns(&mut self, ns: u32) {
Timer::after_nanos(ns as _).await
}
async fn delay_us(&mut self, us: u32) {
Timer::after_micros(us as _).await
}
async fn delay_ms(&mut self, ms: u32) {
Timer::after_millis(ms as _).await
}
}
impl embedded_hal_02::blocking::delay::DelayMs<u8> for Delay {
fn delay_ms(&mut self, ms: u8) {
block_for(Duration::from_millis(ms as u64))
}
}
impl embedded_hal_02::blocking::delay::DelayMs<u16> for Delay {
fn delay_ms(&mut self, ms: u16) {
block_for(Duration::from_millis(ms as u64))
}
}
impl embedded_hal_02::blocking::delay::DelayMs<u32> for Delay {
fn delay_ms(&mut self, ms: u32) {
block_for(Duration::from_millis(ms as u64))
}
}
impl embedded_hal_02::blocking::delay::DelayUs<u8> for Delay {
fn delay_us(&mut self, us: u8) {
block_for(Duration::from_micros(us as u64))
}
}
impl embedded_hal_02::blocking::delay::DelayUs<u16> for Delay {
fn delay_us(&mut self, us: u16) {
block_for(Duration::from_micros(us as u64))
}
}
impl embedded_hal_02::blocking::delay::DelayUs<u32> for Delay {
fn delay_us(&mut self, us: u32) {
block_for(Duration::from_micros(us as u64))
}
}

View File

@@ -0,0 +1,145 @@
use core::cell::RefCell;
use core::task::Waker;
use critical_section::Mutex as CsMutex;
use embassy_time_driver::Driver;
use embassy_time_queue_utils::Queue;
use crate::{Duration, Instant};
/// A mock driver that can be manually advanced.
/// This is useful for testing code that works with [`Instant`] and [`Duration`].
///
/// This driver can also be used to test runtime functionality, such as
/// timers, delays, etc.
///
/// # Example
///
/// ```ignore
/// fn has_a_second_passed(reference: Instant) -> bool {
/// Instant::now().duration_since(reference) >= Duration::from_secs(1)
/// }
///
/// fn test_second_passed() {
/// let driver = embassy_time::MockDriver::get();
/// let reference = Instant::now();
/// assert_eq!(false, has_a_second_passed(reference));
/// driver.advance(Duration::from_secs(1));
/// assert_eq!(true, has_a_second_passed(reference));
/// }
/// ```
pub struct MockDriver(CsMutex<RefCell<InnerMockDriver>>);
embassy_time_driver::time_driver_impl!(static DRIVER: MockDriver = MockDriver::new());
impl MockDriver {
/// Creates a new mock driver.
pub const fn new() -> Self {
Self(CsMutex::new(RefCell::new(InnerMockDriver::new())))
}
/// Gets a reference to the global mock driver.
pub fn get() -> &'static MockDriver {
&DRIVER
}
/// Resets the internal state of the mock driver
/// This will clear and deallocate all alarms, and reset the current time to 0.
pub fn reset(&self) {
critical_section::with(|cs| {
self.0.borrow(cs).replace(InnerMockDriver::new());
});
}
/// Advances the time by the specified [`Duration`].
/// Calling any alarm callbacks that are due.
pub fn advance(&self, duration: Duration) {
critical_section::with(|cs| {
let inner = &mut *self.0.borrow_ref_mut(cs);
inner.now += duration;
// wake expired tasks.
inner.queue.next_expiration(inner.now.as_ticks());
})
}
}
impl Driver for MockDriver {
fn now(&self) -> u64 {
critical_section::with(|cs| self.0.borrow_ref(cs).now).as_ticks()
}
fn schedule_wake(&self, at: u64, waker: &Waker) {
critical_section::with(|cs| {
let inner = &mut *self.0.borrow_ref_mut(cs);
// enqueue it
inner.queue.schedule_wake(at, waker);
// wake it if it's in the past.
inner.queue.next_expiration(inner.now.as_ticks());
})
}
}
struct InnerMockDriver {
now: Instant,
queue: Queue,
}
impl InnerMockDriver {
const fn new() -> Self {
Self {
now: Instant::from_ticks(0),
queue: Queue::new(),
}
}
}
#[cfg(test)]
mod tests {
use core::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::task::Wake;
use serial_test::serial;
use super::*;
fn setup() {
DRIVER.reset();
}
#[test]
#[serial]
fn test_advance() {
setup();
let driver = MockDriver::get();
let reference = driver.now();
driver.advance(Duration::from_secs(1));
assert_eq!(Duration::from_secs(1).as_ticks(), driver.now() - reference);
}
#[test]
#[serial]
fn test_schedule_wake() {
setup();
static CALLBACK_CALLED: AtomicBool = AtomicBool::new(false);
struct MockWaker;
impl Wake for MockWaker {
fn wake(self: Arc<Self>) {
CALLBACK_CALLED.store(true, Ordering::Relaxed);
}
}
let waker = Arc::new(MockWaker).into();
let driver = MockDriver::get();
driver.schedule_wake(driver.now() + 1, &waker);
assert_eq!(false, CALLBACK_CALLED.load(Ordering::Relaxed));
driver.advance(Duration::from_secs(1));
assert_eq!(true, CALLBACK_CALLED.load(Ordering::Relaxed));
}
}

View File

@@ -0,0 +1,104 @@
use std::sync::{Condvar, Mutex};
use std::thread;
use std::time::{Duration as StdDuration, Instant as StdInstant};
use embassy_time_driver::Driver;
use embassy_time_queue_utils::Queue;
struct TimeDriver {
signaler: Signaler,
inner: Mutex<Inner>,
}
struct Inner {
zero_instant: Option<StdInstant>,
queue: Queue,
}
embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
inner: Mutex::new(Inner{
zero_instant: None,
queue: Queue::new(),
}),
signaler: Signaler::new(),
});
impl Inner {
fn init(&mut self) -> StdInstant {
*self.zero_instant.get_or_insert_with(|| {
thread::spawn(alarm_thread);
StdInstant::now()
})
}
}
impl Driver for TimeDriver {
fn now(&self) -> u64 {
let mut inner = self.inner.lock().unwrap();
let zero = inner.init();
StdInstant::now().duration_since(zero).as_micros() as u64
}
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
let mut inner = self.inner.lock().unwrap();
inner.init();
if inner.queue.schedule_wake(at, waker) {
self.signaler.signal();
}
}
}
fn alarm_thread() {
let zero = DRIVER.inner.lock().unwrap().zero_instant.unwrap();
loop {
let now = DRIVER.now();
let next_alarm = DRIVER.inner.lock().unwrap().queue.next_expiration(now);
// Ensure we don't overflow
let until = zero
.checked_add(StdDuration::from_micros(next_alarm))
.unwrap_or_else(|| StdInstant::now() + StdDuration::from_secs(1));
DRIVER.signaler.wait_until(until);
}
}
struct Signaler {
mutex: Mutex<bool>,
condvar: Condvar,
}
impl Signaler {
const fn new() -> Self {
Self {
mutex: Mutex::new(false),
condvar: Condvar::new(),
}
}
fn wait_until(&self, until: StdInstant) {
let mut signaled = self.mutex.lock().unwrap();
while !*signaled {
let now = StdInstant::now();
if now >= until {
break;
}
let dur = until - now;
let (signaled2, timeout) = self.condvar.wait_timeout(signaled, dur).unwrap();
signaled = signaled2;
if timeout.timed_out() {
break;
}
}
*signaled = false;
}
fn signal(&self) {
let mut signaled = self.mutex.lock().unwrap();
*signaled = true;
self.condvar.notify_one();
}
}

View File

@@ -0,0 +1,103 @@
use std::sync::Mutex;
use embassy_time_driver::Driver;
use embassy_time_queue_utils::Queue;
use wasm_bindgen::prelude::*;
use wasm_timer::Instant as StdInstant;
struct AlarmState {
token: Option<f64>,
}
impl AlarmState {
const fn new() -> Self {
Self { token: None }
}
}
#[wasm_bindgen]
extern "C" {
fn setTimeout(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
fn clearTimeout(token: f64);
}
struct TimeDriver {
inner: Mutex<Inner>,
}
struct Inner {
alarm: AlarmState,
zero_instant: Option<StdInstant>,
queue: Queue,
closure: Option<Closure<dyn FnMut()>>,
}
unsafe impl Send for Inner {}
embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver {
inner: Mutex::new(Inner{
zero_instant: None,
queue: Queue::new(),
alarm: AlarmState::new(),
closure: None,
}),
});
impl Inner {
fn init(&mut self) -> StdInstant {
*self.zero_instant.get_or_insert_with(StdInstant::now)
}
fn now(&mut self) -> u64 {
StdInstant::now().duration_since(self.zero_instant.unwrap()).as_micros() as u64
}
fn set_alarm(&mut self, timestamp: u64) -> bool {
if let Some(token) = self.alarm.token {
clearTimeout(token);
}
let now = self.now();
if timestamp <= now {
false
} else {
let timeout = (timestamp - now) as u32;
let closure = self.closure.get_or_insert_with(|| Closure::new(dispatch));
self.alarm.token = Some(setTimeout(closure, timeout / 1000));
true
}
}
}
impl Driver for TimeDriver {
fn now(&self) -> u64 {
let mut inner = self.inner.lock().unwrap();
let zero = inner.init();
StdInstant::now().duration_since(zero).as_micros() as u64
}
fn schedule_wake(&self, at: u64, waker: &core::task::Waker) {
let mut inner = self.inner.lock().unwrap();
inner.init();
if inner.queue.schedule_wake(at, waker) {
let now = inner.now();
let mut next = inner.queue.next_expiration(now);
while !inner.set_alarm(next) {
let now = inner.now();
next = inner.queue.next_expiration(now);
}
}
}
}
fn dispatch() {
let inner = &mut *DRIVER.inner.lock().unwrap();
let now = inner.now();
let mut next = inner.queue.next_expiration(now);
while !inner.set_alarm(next) {
let now = inner.now();
next = inner.queue.next_expiration(now);
}
}

View File

@@ -0,0 +1,295 @@
use core::fmt;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
use super::{GCD_1K, GCD_1M, TICK_HZ};
use crate::GCD_1G;
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Represents the difference between two [Instant](struct.Instant.html)s
pub struct Duration {
pub(crate) ticks: u64,
}
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 / TICK_HZ
}
/// Convert the `Duration` to milliseconds, rounding down.
pub const fn as_millis(&self) -> u64 {
self.ticks * (1000 / GCD_1K) / (TICK_HZ / GCD_1K)
}
/// Convert the `Duration` to microseconds, rounding down.
pub const fn as_micros(&self) -> u64 {
self.ticks * (1_000_000 / GCD_1M) / (TICK_HZ / GCD_1M)
}
/// Creates a duration from the specified number of clock ticks
pub const fn from_ticks(ticks: u64) -> Duration {
Duration { ticks }
}
/// Creates a duration from the specified number of seconds, rounding up.
pub const fn from_secs(secs: u64) -> Duration {
Duration { ticks: secs * TICK_HZ }
}
/// Creates a duration from the specified number of milliseconds, rounding up.
pub const fn from_millis(millis: u64) -> Duration {
Duration {
ticks: div_ceil(millis * (TICK_HZ / GCD_1K), 1000 / GCD_1K),
}
}
/// Creates a duration from the specified number of microseconds, rounding up.
/// NOTE: Delays this small may be inaccurate.
pub const fn from_micros(micros: u64) -> Duration {
Duration {
ticks: div_ceil(micros * (TICK_HZ / GCD_1M), 1_000_000 / GCD_1M),
}
}
/// Creates a duration from the specified number of nanoseconds, rounding up.
/// NOTE: Delays this small may be inaccurate.
pub const fn from_nanos(nanoseconds: u64) -> Duration {
Duration {
ticks: div_ceil(nanoseconds * (TICK_HZ / GCD_1G), 1_000_000_000 / GCD_1G),
}
}
/// Creates a duration from the specified number of seconds, rounding down.
pub const fn from_secs_floor(secs: u64) -> Duration {
Duration { ticks: secs * TICK_HZ }
}
/// Creates a duration from the specified number of milliseconds, rounding down.
pub const fn from_millis_floor(millis: u64) -> Duration {
Duration {
ticks: millis * (TICK_HZ / GCD_1K) / (1000 / GCD_1K),
}
}
/// Creates a duration from the specified number of microseconds, rounding down.
/// NOTE: Delays this small may be inaccurate.
pub const fn from_micros_floor(micros: u64) -> Duration {
Duration {
ticks: micros * (TICK_HZ / GCD_1M) / (1_000_000 / GCD_1M),
}
}
/// Try to create a duration from the specified number of seconds, rounding up.
/// Fails if the number of seconds is too large.
pub const fn try_from_secs(secs: u64) -> Option<Duration> {
let Some(ticks) = secs.checked_mul(TICK_HZ) else {
return None;
};
Some(Duration { ticks })
}
/// Try to create a duration from the specified number of milliseconds, rounding up.
/// Fails if the number of milliseconds is too large.
pub const fn try_from_millis(millis: u64) -> Option<Duration> {
let Some(value) = millis.checked_mul(TICK_HZ / GCD_1K) else {
return None;
};
Some(Duration {
ticks: div_ceil(value, 1000 / GCD_1K),
})
}
/// Try to create a duration from the specified number of microseconds, rounding up.
/// Fails if the number of microseconds is too large.
/// NOTE: Delays this small may be inaccurate.
pub const fn try_from_micros(micros: u64) -> Option<Duration> {
let Some(value) = micros.checked_mul(TICK_HZ / GCD_1M) else {
return None;
};
Some(Duration {
ticks: div_ceil(value, 1_000_000 / GCD_1M),
})
}
/// Try to create a duration from the specified number of nanoseconds, rounding up.
/// Fails if the number of nanoseconds is too large.
/// NOTE: Delays this small may be inaccurate.
pub const fn try_from_nanos(nanoseconds: u64) -> Option<Duration> {
let Some(value) = nanoseconds.checked_mul(TICK_HZ / GCD_1G) else {
return None;
};
Some(Duration {
ticks: div_ceil(value, 1_000_000_000 / GCD_1G),
})
}
/// Try to create a duration from the specified number of seconds, rounding down.
/// Fails if the number of seconds is too large.
pub const fn try_from_secs_floor(secs: u64) -> Option<Duration> {
let Some(ticks) = secs.checked_mul(TICK_HZ) else {
return None;
};
Some(Duration { ticks })
}
/// Try to create a duration from the specified number of milliseconds, rounding down.
/// Fails if the number of milliseconds is too large.
pub const fn try_from_millis_floor(millis: u64) -> Option<Duration> {
let Some(value) = millis.checked_mul(TICK_HZ / GCD_1K) else {
return None;
};
Some(Duration {
ticks: value / (1000 / GCD_1K),
})
}
/// Try to create a duration from the specified number of microseconds, rounding down.
/// Fails if the number of microseconds is too large.
/// NOTE: Delays this small may be inaccurate.
pub const fn try_from_micros_floor(micros: u64) -> Option<Duration> {
let Some(value) = micros.checked_mul(TICK_HZ / GCD_1M) else {
return None;
};
Some(Duration {
ticks: value / (1_000_000 / GCD_1M),
})
}
/// Creates a duration corresponding to the specified Hz.
/// NOTE: Giving this function a hz >= the TICK_HZ of your platform will clamp the Duration to 1
/// tick. Doing so will not deadlock, but will certainly not produce the desired output.
pub const fn from_hz(hz: u64) -> Duration {
let ticks = {
if hz >= TICK_HZ {
1
} else {
(TICK_HZ + hz / 2) / hz
}
};
Duration { ticks }
}
/// Adds one Duration to another, returning a new Duration or None in the event of an overflow.
pub fn checked_add(self, rhs: Duration) -> Option<Duration> {
self.ticks.checked_add(rhs.ticks).map(|ticks| Duration { ticks })
}
/// Subtracts one Duration to another, returning a new Duration or None in the event of an overflow.
pub fn checked_sub(self, rhs: Duration) -> Option<Duration> {
self.ticks.checked_sub(rhs.ticks).map(|ticks| Duration { ticks })
}
/// Multiplies one Duration by a scalar u32, returning a new Duration or None in the event of an overflow.
pub fn checked_mul(self, rhs: u32) -> Option<Duration> {
self.ticks.checked_mul(rhs as _).map(|ticks| Duration { ticks })
}
/// Divides one Duration a scalar u32, returning a new Duration or None in the event of an overflow.
pub fn checked_div(self, rhs: u32) -> Option<Duration> {
self.ticks.checked_div(rhs as _).map(|ticks| Duration { ticks })
}
}
impl Add for Duration {
type Output = Duration;
fn add(self, rhs: Duration) -> Duration {
self.checked_add(rhs).expect("overflow when adding durations")
}
}
impl AddAssign for Duration {
fn add_assign(&mut self, rhs: Duration) {
*self = *self + rhs;
}
}
impl Sub for Duration {
type Output = Duration;
fn sub(self, rhs: Duration) -> Duration {
self.checked_sub(rhs).expect("overflow when subtracting durations")
}
}
impl SubAssign for Duration {
fn sub_assign(&mut self, rhs: Duration) {
*self = *self - rhs;
}
}
impl Mul<u32> for Duration {
type Output = Duration;
fn mul(self, rhs: u32) -> Duration {
self.checked_mul(rhs)
.expect("overflow when multiplying duration by scalar")
}
}
impl Mul<Duration> for u32 {
type Output = Duration;
fn mul(self, rhs: Duration) -> Duration {
rhs * self
}
}
impl MulAssign<u32> for Duration {
fn mul_assign(&mut self, rhs: u32) {
*self = *self * rhs;
}
}
impl Div<u32> for Duration {
type Output = Duration;
fn div(self, rhs: u32) -> Duration {
self.checked_div(rhs)
.expect("divide by zero error when dividing duration by scalar")
}
}
impl DivAssign<u32> for Duration {
fn div_assign(&mut self, rhs: u32) {
*self = *self / rhs;
}
}
impl<'a> fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ticks", self.ticks)
}
}
#[inline]
const fn div_ceil(num: u64, den: u64) -> u64 {
(num + den - 1) / den
}
impl TryFrom<core::time::Duration> for Duration {
type Error = <u64 as TryFrom<u128>>::Error;
/// Converts using [`Duration::from_micros`]. Fails if value can not be represented as u64.
fn try_from(value: core::time::Duration) -> Result<Self, Self::Error> {
Ok(Self::from_micros(value.as_micros().try_into()?))
}
}
impl From<Duration> for core::time::Duration {
/// Converts using [`Duration::as_micros`].
fn from(value: Duration) -> Self {
core::time::Duration::from_micros(value.as_micros())
}
}

270
embassy-time/src/fmt.rs Normal file
View File

@@ -0,0 +1,270 @@
#![macro_use]
#![allow(unused)]
use core::fmt::{Debug, Display, LowerHex};
#[cfg(all(feature = "defmt", feature = "log"))]
compile_error!("You may not enable both `defmt` and `log` features.");
#[collapse_debuginfo(yes)]
macro_rules! assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_eq!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::assert_ne!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert_eq {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_eq!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_eq!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug_assert_ne {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::debug_assert_ne!($($x)*);
#[cfg(feature = "defmt")]
::defmt::debug_assert_ne!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! todo {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::todo!($($x)*);
#[cfg(feature = "defmt")]
::defmt::todo!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! unreachable {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::unreachable!($($x)*);
#[cfg(feature = "defmt")]
::defmt::unreachable!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! panic {
($($x:tt)*) => {
{
#[cfg(not(feature = "defmt"))]
::core::panic!($($x)*);
#[cfg(feature = "defmt")]
::defmt::panic!($($x)*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! trace {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::trace!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::trace!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! debug {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::debug!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::debug!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! info {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::info!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::info!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! warn {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::warn!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::warn!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[collapse_debuginfo(yes)]
macro_rules! error {
($s:literal $(, $x:expr)* $(,)?) => {
{
#[cfg(feature = "log")]
::log::error!($s $(, $x)*);
#[cfg(feature = "defmt")]
::defmt::error!($s $(, $x)*);
#[cfg(not(any(feature = "log", feature="defmt")))]
let _ = ($( & $x ),*);
}
};
}
#[cfg(feature = "defmt")]
#[collapse_debuginfo(yes)]
macro_rules! unwrap {
($($x:tt)*) => {
::defmt::unwrap!($($x)*)
};
}
#[cfg(not(feature = "defmt"))]
#[collapse_debuginfo(yes)]
macro_rules! unwrap {
($arg:expr) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
}
}
};
($arg:expr, $($msg:expr),+ $(,)? ) => {
match $crate::fmt::Try::into_result($arg) {
::core::result::Result::Ok(t) => t,
::core::result::Result::Err(e) => {
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct NoneError;
pub trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
type Ok = T;
type Error = E;
#[inline]
fn into_result(self) -> Self {
self
}
}
pub(crate) struct Bytes<'a>(pub &'a [u8]);
impl<'a> Debug for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
impl<'a> Display for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
impl<'a> LowerHex for Bytes<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:#02x?}", self.0)
}
}
#[cfg(feature = "defmt")]
impl<'a> defmt::Format for Bytes<'a> {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "{:02x}", self.0)
}
}

205
embassy-time/src/instant.rs Normal file
View File

@@ -0,0 +1,205 @@
use core::fmt;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use super::{Duration, GCD_1K, GCD_1M, TICK_HZ};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// An Instant in time, based on the MCU's clock ticks since startup.
pub struct Instant {
ticks: u64,
}
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.
#[inline]
pub fn now() -> Instant {
Instant {
ticks: embassy_time_driver::now(),
}
}
/// Create an Instant from a tick count since system boot.
pub const fn from_ticks(ticks: u64) -> Self {
Self { ticks }
}
/// Create an Instant from a microsecond count since system boot.
pub const fn from_micros(micros: u64) -> Self {
Self {
ticks: micros * (TICK_HZ / GCD_1M) / (1_000_000 / GCD_1M),
}
}
/// Create an Instant from a millisecond count since system boot.
pub const fn from_millis(millis: u64) -> Self {
Self {
ticks: millis * (TICK_HZ / GCD_1K) / (1000 / GCD_1K),
}
}
/// Create an Instant from a second count since system boot.
pub const fn from_secs(seconds: u64) -> Self {
Self {
ticks: seconds * TICK_HZ,
}
}
/// Try to create an Instant from a microsecond count since system boot.
/// Fails if the number of microseconds is too large.
pub const fn try_from_micros(micros: u64) -> Option<Self> {
let Some(value) = micros.checked_mul(TICK_HZ / GCD_1M) else {
return None;
};
Some(Self {
ticks: value / (1_000_000 / GCD_1M),
})
}
/// Try to create an Instant from a millisecond count since system boot.
/// Fails if the number of milliseconds is too large.
pub const fn try_from_millis(millis: u64) -> Option<Self> {
let Some(value) = millis.checked_mul(TICK_HZ / GCD_1K) else {
return None;
};
Some(Self {
ticks: value / (1000 / GCD_1K),
})
}
/// Try to create an Instant from a second count since system boot.
/// Fails if the number of seconds is too large.
pub const fn try_from_secs(seconds: u64) -> Option<Self> {
let Some(ticks) = seconds.checked_mul(TICK_HZ) else {
return None;
};
Some(Self { ticks })
}
/// Tick count since system boot.
pub const fn as_ticks(&self) -> u64 {
self.ticks
}
/// Seconds since system boot.
pub const fn as_secs(&self) -> u64 {
self.ticks / TICK_HZ
}
/// Milliseconds since system boot.
pub const fn as_millis(&self) -> u64 {
self.ticks * (1000 / GCD_1K) / (TICK_HZ / GCD_1K)
}
/// Microseconds since system boot.
pub const fn as_micros(&self) -> u64 {
self.ticks * (1_000_000 / GCD_1M) / (TICK_HZ / GCD_1M)
}
/// Duration between this Instant and another Instant
/// Panics on over/underflow.
pub fn duration_since(&self, earlier: Instant) -> Duration {
Duration {
ticks: unwrap!(self.ticks.checked_sub(earlier.ticks)),
}
}
/// Duration between this Instant and another Instant
pub fn checked_duration_since(&self, earlier: Instant) -> Option<Duration> {
if self.ticks < earlier.ticks {
None
} else {
Some(Duration {
ticks: self.ticks - earlier.ticks,
})
}
}
/// Returns the duration since the "earlier" Instant.
/// If the "earlier" instant is in the future, the duration is set to zero.
pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
Duration {
ticks: if self.ticks < earlier.ticks {
0
} else {
self.ticks - earlier.ticks
},
}
}
/// Duration elapsed since this Instant.
pub fn elapsed(&self) -> Duration {
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> {
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<Instant> {
self.ticks.checked_sub(duration.ticks).map(|ticks| Instant { ticks })
}
/// Adds a Duration to self. In case of overflow, the maximum value is returned.
pub fn saturating_add(mut self, duration: Duration) -> Self {
self.ticks = self.ticks.saturating_add(duration.ticks);
self
}
/// Subtracts a Duration from self. In case of overflow, the minimum value is returned.
pub fn saturating_sub(mut self, duration: Duration) -> Self {
self.ticks = self.ticks.saturating_sub(duration.ticks);
self
}
}
impl Add<Duration> for Instant {
type Output = Instant;
fn add(self, other: Duration) -> Instant {
self.checked_add(other)
.expect("overflow when adding duration to instant")
}
}
impl AddAssign<Duration> for Instant {
fn add_assign(&mut self, other: Duration) {
*self = *self + other;
}
}
impl Sub<Duration> for Instant {
type Output = Instant;
fn sub(self, other: Duration) -> Instant {
self.checked_sub(other)
.expect("overflow when subtracting duration from instant")
}
}
impl SubAssign<Duration> for Instant {
fn sub_assign(&mut self, other: Duration) {
*self = *self - other;
}
}
impl Sub<Instant> for Instant {
type Output = Duration;
fn sub(self, other: Instant) -> Duration {
self.duration_since(other)
}
}
impl<'a> fmt::Display for Instant {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ticks", self.ticks)
}
}

63
embassy-time/src/lib.rs Normal file
View File

@@ -0,0 +1,63 @@
#![cfg_attr(not(any(feature = "std", feature = "wasm", test)), no_std)]
#![allow(async_fn_in_trait)]
#![doc = include_str!("../README.md")]
#![allow(clippy::new_without_default)]
#![warn(missing_docs)]
//! ## Feature flags
#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
// This mod MUST go first, so that the others see its macros.
pub(crate) mod fmt;
mod delay;
mod duration;
mod instant;
mod timer;
#[cfg(feature = "mock-driver")]
mod driver_mock;
#[cfg(feature = "mock-driver")]
pub use driver_mock::MockDriver;
#[cfg(feature = "std")]
mod driver_std;
#[cfg(feature = "wasm")]
mod driver_wasm;
pub use delay::{block_for, Delay};
pub use duration::Duration;
pub use embassy_time_driver::TICK_HZ;
pub use instant::Instant;
pub use timer::{with_deadline, with_timeout, Ticker, TimeoutError, Timer, WithTimeout};
const fn gcd(a: u64, b: u64) -> u64 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
pub(crate) const GCD_1K: u64 = gcd(TICK_HZ, 1_000);
pub(crate) const GCD_1M: u64 = gcd(TICK_HZ, 1_000_000);
pub(crate) const GCD_1G: u64 = gcd(TICK_HZ, 1_000_000_000);
#[cfg(feature = "defmt-timestamp-uptime-s")]
defmt::timestamp! {"{=u64}", Instant::now().as_secs() }
#[cfg(feature = "defmt-timestamp-uptime-ms")]
defmt::timestamp! {"{=u64:ms}", Instant::now().as_millis() }
#[cfg(any(feature = "defmt-timestamp-uptime", feature = "defmt-timestamp-uptime-us"))]
defmt::timestamp! {"{=u64:us}", Instant::now().as_micros() }
#[cfg(feature = "defmt-timestamp-uptime-ts")]
defmt::timestamp! {"{=u64:ts}", Instant::now().as_secs() }
#[cfg(feature = "defmt-timestamp-uptime-tms")]
defmt::timestamp! {"{=u64:tms}", Instant::now().as_millis() }
#[cfg(feature = "defmt-timestamp-uptime-tus")]
defmt::timestamp! {"{=u64:tus}", Instant::now().as_micros() }

276
embassy-time/src/timer.rs Normal file
View File

@@ -0,0 +1,276 @@
use core::future::{poll_fn, Future};
use core::pin::{pin, Pin};
use core::task::{Context, Poll};
use futures_util::future::{select, Either};
use futures_util::stream::FusedStream;
use futures_util::Stream;
use crate::{Duration, Instant};
/// Error returned by [`with_timeout`] and [`with_deadline`] on timeout.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
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> {
let timeout_fut = Timer::after(timeout);
match select(pin!(fut), timeout_fut).await {
Either::Left((r, _)) => Ok(r),
Either::Right(_) => Err(TimeoutError),
}
}
/// Runs a given future with a deadline time.
///
/// If the future completes before the deadline, 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_deadline<F: Future>(at: Instant, fut: F) -> Result<F::Output, TimeoutError> {
let timeout_fut = Timer::at(at);
match select(pin!(fut), timeout_fut).await {
Either::Left((r, _)) => Ok(r),
Either::Right(_) => Err(TimeoutError),
}
}
/// Provides functions to run a given future with a timeout or a deadline.
pub trait WithTimeout {
/// Output type of the future.
type Output;
/// 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.
async fn with_timeout(self, timeout: Duration) -> Result<Self::Output, TimeoutError>;
/// Runs a given future with a deadline time.
///
/// If the future completes before the deadline, 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.
async fn with_deadline(self, at: Instant) -> Result<Self::Output, TimeoutError>;
}
impl<F: Future> WithTimeout for F {
type Output = F::Output;
async fn with_timeout(self, timeout: Duration) -> Result<Self::Output, TimeoutError> {
with_timeout(timeout, self).await
}
async fn with_deadline(self, at: Instant) -> Result<Self::Output, TimeoutError> {
with_deadline(at, self).await
}
}
/// A future that completes at a specified [Instant](struct.Instant.html).
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Timer {
expires_at: Instant,
yielded_once: bool,
}
impl Timer {
/// Expire at specified [Instant](struct.Instant.html)
pub fn at(expires_at: Instant) -> Self {
Self {
expires_at,
yielded_once: false,
}
}
/// Expire after specified [Duration](struct.Duration.html).
/// This can be used as a `sleep` abstraction.
///
/// Example:
/// ``` no_run
/// use embassy_time::{Duration, Timer};
///
/// #[embassy_executor::task]
/// async fn demo_sleep_seconds() {
/// // suspend this task for one second.
/// Timer::after(Duration::from_secs(1)).await;
/// }
/// ```
pub fn after(duration: Duration) -> Self {
Self {
expires_at: Instant::now() + duration,
yielded_once: false,
}
}
/// Expire after the specified number of ticks.
///
/// This method is a convenience wrapper for calling `Timer::after(Duration::from_ticks())`.
/// For more details, refer to [`Timer::after()`] and [`Duration::from_ticks()`].
#[inline]
pub fn after_ticks(ticks: u64) -> Self {
Self::after(Duration::from_ticks(ticks))
}
/// Expire after the specified number of nanoseconds.
///
/// This method is a convenience wrapper for calling `Timer::after(Duration::from_nanos())`.
/// For more details, refer to [`Timer::after()`] and [`Duration::from_nanos()`].
#[inline]
pub fn after_nanos(nanos: u64) -> Self {
Self::after(Duration::from_nanos(nanos))
}
/// Expire after the specified number of microseconds.
///
/// This method is a convenience wrapper for calling `Timer::after(Duration::from_micros())`.
/// For more details, refer to [`Timer::after()`] and [`Duration::from_micros()`].
#[inline]
pub fn after_micros(micros: u64) -> Self {
Self::after(Duration::from_micros(micros))
}
/// Expire after the specified number of milliseconds.
///
/// This method is a convenience wrapper for calling `Timer::after(Duration::from_millis())`.
/// For more details, refer to [`Timer::after`] and [`Duration::from_millis()`].
#[inline]
pub fn after_millis(millis: u64) -> Self {
Self::after(Duration::from_millis(millis))
}
/// Expire after the specified number of seconds.
///
/// This method is a convenience wrapper for calling `Timer::after(Duration::from_secs())`.
/// For more details, refer to [`Timer::after`] and [`Duration::from_secs()`].
#[inline]
pub fn after_secs(secs: u64) -> Self {
Self::after(Duration::from_secs(secs))
}
}
impl Unpin for Timer {}
impl Future for Timer {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.yielded_once && self.expires_at <= Instant::now() {
Poll::Ready(())
} else {
embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker());
self.yielded_once = true;
Poll::Pending
}
}
}
/// Asynchronous stream that yields every Duration, indefinitely.
///
/// This stream will tick at uniform intervals, even if blocking work is performed between ticks.
///
/// For instance, consider the following code fragment.
/// ``` no_run
/// use embassy_time::{Duration, Timer};
/// # fn foo() {}
///
/// #[embassy_executor::task]
/// async fn ticker_example_0() {
/// loop {
/// foo();
/// Timer::after(Duration::from_secs(1)).await;
/// }
/// }
/// ```
///
/// This fragment will not call `foo` every second.
/// Instead, it will call it every second + the time it took to previously call `foo`.
///
/// Example using ticker, which will consistently call `foo` once a second.
///
/// ``` no_run
/// use embassy_time::{Duration, Ticker};
/// # fn foo(){}
///
/// #[embassy_executor::task]
/// async fn ticker_example_1() {
/// let mut ticker = Ticker::every(Duration::from_secs(1));
/// loop {
/// foo();
/// ticker.next().await;
/// }
/// }
/// ```
///
/// ## Cancel safety
/// It is safe to cancel waiting for the next tick,
/// meaning no tick is lost if the Future is dropped.
pub struct Ticker {
expires_at: Instant,
duration: Duration,
}
impl Ticker {
/// Creates a new ticker that ticks at the specified duration interval.
pub fn every(duration: Duration) -> Self {
let expires_at = Instant::now() + duration;
Self { expires_at, duration }
}
/// Resets the ticker back to its original state.
/// This causes the ticker to go back to zero, even if the current tick isn't over yet.
pub fn reset(&mut self) {
self.expires_at = Instant::now() + self.duration;
}
/// Reset the ticker at the deadline.
/// If the deadline is in the past, the ticker will fire instantly.
pub fn reset_at(&mut self, deadline: Instant) {
self.expires_at = deadline + self.duration;
}
/// Resets the ticker, after the specified duration has passed.
/// If the specified duration is zero, the next tick will be after the duration of the ticker.
pub fn reset_after(&mut self, after: Duration) {
self.expires_at = Instant::now() + after + self.duration;
}
/// Waits for the next tick.
///
/// ## Cancel safety
/// The produced Future is cancel safe, meaning no tick is lost if the Future is dropped.
pub fn next(&mut self) -> impl Future<Output = ()> + Send + Sync + '_ {
poll_fn(|cx| {
if self.expires_at <= Instant::now() {
let dur = self.duration;
self.expires_at += dur;
Poll::Ready(())
} else {
embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker());
Poll::Pending
}
})
}
}
impl Unpin for Ticker {}
impl Stream for Ticker {
type Item = ();
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if self.expires_at <= Instant::now() {
let dur = self.duration;
self.expires_at += dur;
Poll::Ready(Some(()))
} else {
embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker());
Poll::Pending
}
}
}
impl FusedStream for Ticker {
fn is_terminated(&self) -> bool {
// `Ticker` keeps yielding values until dropped, it never terminates.
false
}
}