470 lines
14 KiB
Rust
470 lines
14 KiB
Rust
|
use core::time::Duration;
|
||
|
|
||
|
use crate::subghz::value_error::ValueError;
|
||
|
|
||
|
const fn abs_diff(a: u64, b: u64) -> u64 {
|
||
|
if a > b {
|
||
|
a - b
|
||
|
} else {
|
||
|
b - a
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Timeout argument.
|
||
|
///
|
||
|
/// This is used by:
|
||
|
/// * [`set_rx`]
|
||
|
/// * [`set_tx`]
|
||
|
/// * [`TcxoMode`]
|
||
|
///
|
||
|
/// Each timeout has 3 bytes, with a resolution of 15.625µs per bit, giving a
|
||
|
/// range of 0s to 262.143984375s.
|
||
|
///
|
||
|
/// [`set_rx`]: crate::subghz::SubGhz::set_rx
|
||
|
/// [`set_tx`]: crate::subghz::SubGhz::set_tx
|
||
|
/// [`TcxoMode`]: crate::subghz::TcxoMode
|
||
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||
|
pub struct Timeout {
|
||
|
bits: u32,
|
||
|
}
|
||
|
|
||
|
impl Timeout {
|
||
|
const BITS_PER_MILLI: u32 = 64; // 1e-3 / 15.625e-6
|
||
|
const BITS_PER_SEC: u32 = 64_000; // 1 / 15.625e-6
|
||
|
|
||
|
/// Disable the timeout (0s timeout).
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use core::time::Duration;
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// const TIMEOUT: Timeout = Timeout::DISABLED;
|
||
|
/// assert_eq!(TIMEOUT.as_duration(), Duration::from_secs(0));
|
||
|
/// ```
|
||
|
pub const DISABLED: Timeout = Timeout { bits: 0x0 };
|
||
|
|
||
|
/// Minimum timeout, 15.625µs.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use core::time::Duration;
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// const TIMEOUT: Timeout = Timeout::MIN;
|
||
|
/// assert_eq!(TIMEOUT.into_bits(), 1);
|
||
|
/// ```
|
||
|
pub const MIN: Timeout = Timeout { bits: 1 };
|
||
|
|
||
|
/// Maximum timeout, 262.143984375s.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use core::time::Duration;
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// const TIMEOUT: Timeout = Timeout::MAX;
|
||
|
/// assert_eq!(TIMEOUT.as_duration(), Duration::from_nanos(262_143_984_375));
|
||
|
/// ```
|
||
|
pub const MAX: Timeout = Timeout { bits: 0x00FF_FFFF };
|
||
|
|
||
|
/// Timeout resolution in nanoseconds, 15.625µs.
|
||
|
pub const RESOLUTION_NANOS: u16 = 15_625;
|
||
|
|
||
|
/// Timeout resolution, 15.625µs.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// assert_eq!(
|
||
|
/// Timeout::RESOLUTION.as_nanos(),
|
||
|
/// Timeout::RESOLUTION_NANOS as u128
|
||
|
/// );
|
||
|
/// ```
|
||
|
pub const RESOLUTION: Duration = Duration::from_nanos(Self::RESOLUTION_NANOS as u64);
|
||
|
|
||
|
/// Create a new timeout from a [`Duration`].
|
||
|
///
|
||
|
/// This will return the nearest timeout value possible, or a
|
||
|
/// [`ValueError`] if the value is out of bounds.
|
||
|
///
|
||
|
/// Use [`from_millis_sat`](Self::from_millis_sat) for runtime timeout
|
||
|
/// construction.
|
||
|
/// This is not _that_ useful right now, it is simply future proofing for a
|
||
|
/// time when `Result::unwrap` is avaliable for `const fn`.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// Value within bounds:
|
||
|
///
|
||
|
/// ```
|
||
|
/// use core::time::Duration;
|
||
|
/// use stm32wl_hal::subghz::{Timeout, ValueError};
|
||
|
///
|
||
|
/// const MIN: Duration = Timeout::RESOLUTION;
|
||
|
/// assert_eq!(Timeout::from_duration(MIN).unwrap(), Timeout::MIN);
|
||
|
/// ```
|
||
|
///
|
||
|
/// Value too low:
|
||
|
///
|
||
|
/// ```
|
||
|
/// use core::time::Duration;
|
||
|
/// use stm32wl_hal::subghz::{Timeout, ValueError};
|
||
|
///
|
||
|
/// const LOWER_LIMIT_NANOS: u128 = 7813;
|
||
|
/// const TOO_LOW_NANOS: u128 = LOWER_LIMIT_NANOS - 1;
|
||
|
/// const TOO_LOW_DURATION: Duration = Duration::from_nanos(TOO_LOW_NANOS as u64);
|
||
|
/// assert_eq!(
|
||
|
/// Timeout::from_duration(TOO_LOW_DURATION),
|
||
|
/// Err(ValueError::too_low(TOO_LOW_NANOS, LOWER_LIMIT_NANOS))
|
||
|
/// );
|
||
|
/// ```
|
||
|
///
|
||
|
/// Value too high:
|
||
|
///
|
||
|
/// ```
|
||
|
/// use core::time::Duration;
|
||
|
/// use stm32wl_hal::subghz::{Timeout, ValueError};
|
||
|
///
|
||
|
/// const UPPER_LIMIT_NANOS: u128 = Timeout::MAX.as_nanos() as u128 + 7812;
|
||
|
/// const TOO_HIGH_NANOS: u128 = UPPER_LIMIT_NANOS + 1;
|
||
|
/// const TOO_HIGH_DURATION: Duration = Duration::from_nanos(TOO_HIGH_NANOS as u64);
|
||
|
/// assert_eq!(
|
||
|
/// Timeout::from_duration(TOO_HIGH_DURATION),
|
||
|
/// Err(ValueError::too_high(TOO_HIGH_NANOS, UPPER_LIMIT_NANOS))
|
||
|
/// );
|
||
|
/// ```
|
||
|
pub const fn from_duration(duration: Duration) -> Result<Timeout, ValueError<u128>> {
|
||
|
// at the time of development many methods in
|
||
|
// `core::Duration` were not `const fn`, which leads to the hacks
|
||
|
// you see here.
|
||
|
let nanos: u128 = duration.as_nanos();
|
||
|
const UPPER_LIMIT: u128 =
|
||
|
Timeout::MAX.as_nanos() as u128 + (Timeout::RESOLUTION_NANOS as u128) / 2;
|
||
|
const LOWER_LIMIT: u128 = (((Timeout::RESOLUTION_NANOS as u128) + 1) / 2) as u128;
|
||
|
|
||
|
if nanos > UPPER_LIMIT {
|
||
|
Err(ValueError::too_high(nanos, UPPER_LIMIT))
|
||
|
} else if nanos < LOWER_LIMIT {
|
||
|
Err(ValueError::too_low(nanos, LOWER_LIMIT))
|
||
|
} else {
|
||
|
// safe to truncate here because of previous bounds check.
|
||
|
let duration_nanos: u64 = nanos as u64;
|
||
|
|
||
|
let div_floor: u64 = duration_nanos / (Self::RESOLUTION_NANOS as u64);
|
||
|
let div_ceil: u64 = 1 + (duration_nanos - 1) / (Self::RESOLUTION_NANOS as u64);
|
||
|
|
||
|
let timeout_ceil: Timeout = Timeout::from_raw(div_ceil as u32);
|
||
|
let timeout_floor: Timeout = Timeout::from_raw(div_floor as u32);
|
||
|
|
||
|
let error_ceil: u64 = abs_diff(timeout_ceil.as_nanos(), duration_nanos);
|
||
|
let error_floor: u64 = abs_diff(timeout_floor.as_nanos(), duration_nanos);
|
||
|
|
||
|
if error_ceil < error_floor {
|
||
|
Ok(timeout_ceil)
|
||
|
} else {
|
||
|
Ok(timeout_floor)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Create a new timeout from a [`Duration`].
|
||
|
///
|
||
|
/// This will return the nearest timeout value possible, saturating at the
|
||
|
/// limits.
|
||
|
///
|
||
|
/// This is an expensive function to call outside of `const` contexts.
|
||
|
/// Use [`from_millis_sat`](Self::from_millis_sat) for runtime timeout
|
||
|
/// construction.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use core::time::Duration;
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// const DURATION_MAX_NS: u64 = 262_143_984_376;
|
||
|
///
|
||
|
/// assert_eq!(
|
||
|
/// Timeout::from_duration_sat(Duration::from_millis(0)),
|
||
|
/// Timeout::MIN
|
||
|
/// );
|
||
|
/// assert_eq!(
|
||
|
/// Timeout::from_duration_sat(Duration::from_nanos(DURATION_MAX_NS)),
|
||
|
/// Timeout::MAX
|
||
|
/// );
|
||
|
/// assert_eq!(
|
||
|
/// Timeout::from_duration_sat(Timeout::RESOLUTION).into_bits(),
|
||
|
/// 1
|
||
|
/// );
|
||
|
/// ```
|
||
|
pub const fn from_duration_sat(duration: Duration) -> Timeout {
|
||
|
// at the time of development many methods in
|
||
|
// `core::Duration` were not `const fn`, which leads to the hacks
|
||
|
// you see here.
|
||
|
let nanos: u128 = duration.as_nanos();
|
||
|
const UPPER_LIMIT: u128 = Timeout::MAX.as_nanos() as u128;
|
||
|
|
||
|
if nanos > UPPER_LIMIT {
|
||
|
Timeout::MAX
|
||
|
} else if nanos < (Timeout::RESOLUTION_NANOS as u128) {
|
||
|
Timeout::from_raw(1)
|
||
|
} else {
|
||
|
// safe to truncate here because of previous bounds check.
|
||
|
let duration_nanos: u64 = duration.as_nanos() as u64;
|
||
|
|
||
|
let div_floor: u64 = duration_nanos / (Self::RESOLUTION_NANOS as u64);
|
||
|
let div_ceil: u64 = 1 + (duration_nanos - 1) / (Self::RESOLUTION_NANOS as u64);
|
||
|
|
||
|
let timeout_ceil: Timeout = Timeout::from_raw(div_ceil as u32);
|
||
|
let timeout_floor: Timeout = Timeout::from_raw(div_floor as u32);
|
||
|
|
||
|
let error_ceil: u64 = abs_diff(timeout_ceil.as_nanos(), duration_nanos);
|
||
|
let error_floor: u64 = abs_diff(timeout_floor.as_nanos(), duration_nanos);
|
||
|
|
||
|
if error_ceil < error_floor {
|
||
|
timeout_ceil
|
||
|
} else {
|
||
|
timeout_floor
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Create a new timeout from a milliseconds value.
|
||
|
///
|
||
|
/// This will round towards zero and saturate at the limits.
|
||
|
///
|
||
|
/// This is the preferred method to call when you need to generate a
|
||
|
/// timeout value at runtime.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// assert_eq!(Timeout::from_millis_sat(0), Timeout::MIN);
|
||
|
/// assert_eq!(Timeout::from_millis_sat(262_144), Timeout::MAX);
|
||
|
/// assert_eq!(Timeout::from_millis_sat(1).into_bits(), 64);
|
||
|
/// ```
|
||
|
pub const fn from_millis_sat(millis: u32) -> Timeout {
|
||
|
if millis == 0 {
|
||
|
Timeout::MIN
|
||
|
} else if millis >= 262_144 {
|
||
|
Timeout::MAX
|
||
|
} else {
|
||
|
Timeout::from_raw(millis * Self::BITS_PER_MILLI)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Create a timeout from raw bits, where each bit has the resolution of
|
||
|
/// [`Timeout::RESOLUTION`].
|
||
|
///
|
||
|
/// **Note:** Only the first 24 bits of the `u32` are used, the `bits`
|
||
|
/// argument will be masked.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// assert_eq!(Timeout::from_raw(u32::MAX), Timeout::MAX);
|
||
|
/// assert_eq!(Timeout::from_raw(0x00_FF_FF_FF), Timeout::MAX);
|
||
|
/// assert_eq!(Timeout::from_raw(1).as_duration(), Timeout::RESOLUTION);
|
||
|
/// assert_eq!(Timeout::from_raw(0), Timeout::DISABLED);
|
||
|
/// ```
|
||
|
pub const fn from_raw(bits: u32) -> Timeout {
|
||
|
Timeout {
|
||
|
bits: bits & 0x00FF_FFFF,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Get the timeout as nanoseconds.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// assert_eq!(Timeout::MAX.as_nanos(), 262_143_984_375);
|
||
|
/// assert_eq!(Timeout::DISABLED.as_nanos(), 0);
|
||
|
/// assert_eq!(Timeout::from_raw(1).as_nanos(), 15_625);
|
||
|
/// assert_eq!(Timeout::from_raw(64_000).as_nanos(), 1_000_000_000);
|
||
|
/// ```
|
||
|
pub const fn as_nanos(&self) -> u64 {
|
||
|
(self.bits as u64) * (Timeout::RESOLUTION_NANOS as u64)
|
||
|
}
|
||
|
|
||
|
/// Get the timeout as microseconds, rounding towards zero.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// assert_eq!(Timeout::MAX.as_micros(), 262_143_984);
|
||
|
/// assert_eq!(Timeout::DISABLED.as_micros(), 0);
|
||
|
/// assert_eq!(Timeout::from_raw(1).as_micros(), 15);
|
||
|
/// assert_eq!(Timeout::from_raw(64_000).as_micros(), 1_000_000);
|
||
|
/// ```
|
||
|
pub const fn as_micros(&self) -> u32 {
|
||
|
(self.as_nanos() / 1_000) as u32
|
||
|
}
|
||
|
|
||
|
/// Get the timeout as milliseconds, rounding towards zero.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// assert_eq!(Timeout::MAX.as_millis(), 262_143);
|
||
|
/// assert_eq!(Timeout::DISABLED.as_millis(), 0);
|
||
|
/// assert_eq!(Timeout::from_raw(1).as_millis(), 0);
|
||
|
/// assert_eq!(Timeout::from_raw(64_000).as_millis(), 1_000);
|
||
|
/// ```
|
||
|
pub const fn as_millis(&self) -> u32 {
|
||
|
self.into_bits() / Self::BITS_PER_MILLI
|
||
|
}
|
||
|
|
||
|
/// Get the timeout as seconds, rounding towards zero.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// assert_eq!(Timeout::MAX.as_secs(), 262);
|
||
|
/// assert_eq!(Timeout::DISABLED.as_secs(), 0);
|
||
|
/// assert_eq!(Timeout::from_raw(1).as_secs(), 0);
|
||
|
/// assert_eq!(Timeout::from_raw(64_000).as_secs(), 1);
|
||
|
/// ```
|
||
|
pub const fn as_secs(&self) -> u16 {
|
||
|
(self.into_bits() / Self::BITS_PER_SEC) as u16
|
||
|
}
|
||
|
|
||
|
/// Get the timeout as a [`Duration`].
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use core::time::Duration;
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// assert_eq!(
|
||
|
/// Timeout::MAX.as_duration(),
|
||
|
/// Duration::from_nanos(262_143_984_375)
|
||
|
/// );
|
||
|
/// assert_eq!(Timeout::DISABLED.as_duration(), Duration::from_nanos(0));
|
||
|
/// assert_eq!(Timeout::from_raw(1).as_duration(), Timeout::RESOLUTION);
|
||
|
/// ```
|
||
|
pub const fn as_duration(&self) -> Duration {
|
||
|
Duration::from_nanos((self.bits as u64) * (Timeout::RESOLUTION_NANOS as u64))
|
||
|
}
|
||
|
|
||
|
/// Get the bit value for the timeout.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// assert_eq!(Timeout::from_raw(u32::MAX).into_bits(), 0x00FF_FFFF);
|
||
|
/// assert_eq!(Timeout::from_raw(1).into_bits(), 1);
|
||
|
/// ```
|
||
|
pub const fn into_bits(self) -> u32 {
|
||
|
self.bits
|
||
|
}
|
||
|
|
||
|
/// Get the byte value for the timeout.
|
||
|
///
|
||
|
/// # Example
|
||
|
///
|
||
|
/// ```
|
||
|
/// use stm32wl_hal::subghz::Timeout;
|
||
|
///
|
||
|
/// assert_eq!(Timeout::from_raw(u32::MAX).as_bytes(), [0xFF, 0xFF, 0xFF]);
|
||
|
/// assert_eq!(Timeout::from_raw(1).as_bytes(), [0, 0, 1]);
|
||
|
/// ```
|
||
|
pub const fn as_bytes(self) -> [u8; 3] {
|
||
|
[
|
||
|
((self.bits >> 16) & 0xFF) as u8,
|
||
|
((self.bits >> 8) & 0xFF) as u8,
|
||
|
(self.bits & 0xFF) as u8,
|
||
|
]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl From<Timeout> for Duration {
|
||
|
fn from(to: Timeout) -> Self {
|
||
|
to.as_duration()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl From<Timeout> for [u8; 3] {
|
||
|
fn from(to: Timeout) -> Self {
|
||
|
to.as_bytes()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl From<Timeout> for embassy::time::Duration {
|
||
|
fn from(to: Timeout) -> Self {
|
||
|
embassy::time::Duration::from_micros(to.as_micros().into())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use super::{Timeout, ValueError};
|
||
|
use core::time::Duration;
|
||
|
|
||
|
#[test]
|
||
|
fn saturate() {
|
||
|
assert_eq!(
|
||
|
Timeout::from_duration_sat(Duration::from_secs(u64::MAX)),
|
||
|
Timeout::MAX
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn rounding() {
|
||
|
const NANO1: Duration = Duration::from_nanos(1);
|
||
|
let res_sub_1_ns: Duration = Timeout::RESOLUTION - NANO1;
|
||
|
let res_add_1_ns: Duration = Timeout::RESOLUTION + NANO1;
|
||
|
assert_eq!(Timeout::from_duration_sat(res_sub_1_ns).into_bits(), 1);
|
||
|
assert_eq!(Timeout::from_duration_sat(res_add_1_ns).into_bits(), 1);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn lower_limit() {
|
||
|
let low: Duration = (Timeout::RESOLUTION + Duration::from_nanos(1)) / 2;
|
||
|
assert_eq!(Timeout::from_duration(low), Ok(Timeout::from_raw(1)));
|
||
|
|
||
|
let too_low: Duration = low - Duration::from_nanos(1);
|
||
|
assert_eq!(
|
||
|
Timeout::from_duration(too_low),
|
||
|
Err(ValueError::too_low(too_low.as_nanos(), low.as_nanos()))
|
||
|
);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn upper_limit() {
|
||
|
let high: Duration = Timeout::MAX.as_duration() + Timeout::RESOLUTION / 2;
|
||
|
assert_eq!(
|
||
|
Timeout::from_duration(high),
|
||
|
Ok(Timeout::from_raw(0xFFFFFF))
|
||
|
);
|
||
|
|
||
|
let too_high: Duration = high + Duration::from_nanos(1);
|
||
|
assert_eq!(
|
||
|
Timeout::from_duration(too_high),
|
||
|
Err(ValueError::too_high(too_high.as_nanos(), high.as_nanos()))
|
||
|
);
|
||
|
}
|
||
|
}
|