7ad6280e65
Based on the HAL from stm32wl, the peripheral driver has been modified to fit into embassy, using the embassy APIs, providing operation of the radio peripheral. The initial version does not offer any async APIs, but the example shows how the radio IRQ can be used to perform async TX of the radio.
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()))
|
|
);
|
|
}
|
|
}
|