Merge pull request #381 from lulf/stm32wl55-subghz
Add HAL for SubGhz peripheral for STM32 WL series
This commit is contained in:
commit
eff8ae9c4d
@ -18,3 +18,4 @@ defmt = { version = "0.2.0", optional = true }
|
||||
log = { version = "0.4.11", optional = true }
|
||||
cortex-m = "0.7.1"
|
||||
usb-device = "0.2.7"
|
||||
num-traits = { version = "0.2.14", default-features = false }
|
||||
|
@ -6,6 +6,7 @@ pub(crate) mod fmt;
|
||||
pub mod interrupt;
|
||||
mod macros;
|
||||
pub mod peripheral;
|
||||
pub mod ratio;
|
||||
pub mod ring_buffer;
|
||||
pub mod usb;
|
||||
|
||||
|
128
embassy-hal-common/src/ratio.rs
Normal file
128
embassy-hal-common/src/ratio.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use core::ops::{Add, Div, Mul};
|
||||
use num_traits::{CheckedAdd, CheckedDiv, CheckedMul};
|
||||
|
||||
/// Represents the ratio between two numbers.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Ratio<T> {
|
||||
/// Numerator.
|
||||
numer: T,
|
||||
/// Denominator.
|
||||
denom: T,
|
||||
}
|
||||
|
||||
impl<T> Ratio<T> {
|
||||
/// Creates a new `Ratio`.
|
||||
#[inline(always)]
|
||||
pub const fn new_raw(numer: T, denom: T) -> Ratio<T> {
|
||||
Ratio { numer, denom }
|
||||
}
|
||||
|
||||
/// Gets an immutable reference to the numerator.
|
||||
#[inline(always)]
|
||||
pub const fn numer(&self) -> &T {
|
||||
&self.numer
|
||||
}
|
||||
|
||||
/// Gets an immutable reference to the denominator.
|
||||
#[inline(always)]
|
||||
pub const fn denom(&self) -> &T {
|
||||
&self.denom
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CheckedDiv> Ratio<T> {
|
||||
/// Converts to an integer, rounding towards zero.
|
||||
#[inline(always)]
|
||||
pub fn to_integer(&self) -> T {
|
||||
unwrap!(self.numer().checked_div(self.denom()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CheckedMul> Div<T> for Ratio<T> {
|
||||
type Output = Self;
|
||||
|
||||
#[inline(always)]
|
||||
fn div(mut self, rhs: T) -> Self::Output {
|
||||
self.denom = unwrap!(self.denom().checked_mul(&rhs));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CheckedMul> Mul<T> for Ratio<T> {
|
||||
type Output = Self;
|
||||
|
||||
#[inline(always)]
|
||||
fn mul(mut self, rhs: T) -> Self::Output {
|
||||
self.numer = unwrap!(self.numer().checked_mul(&rhs));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CheckedMul + CheckedAdd> Add<T> for Ratio<T> {
|
||||
type Output = Self;
|
||||
|
||||
#[inline(always)]
|
||||
fn add(mut self, rhs: T) -> Self::Output {
|
||||
self.numer = unwrap!(unwrap!(self.denom().checked_mul(&rhs)).checked_add(self.numer()));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from_for_float {
|
||||
($from:ident) => {
|
||||
impl From<Ratio<$from>> for f32 {
|
||||
#[inline(always)]
|
||||
fn from(r: Ratio<$from>) -> Self {
|
||||
(r.numer as f32) / (r.denom as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Ratio<$from>> for f64 {
|
||||
#[inline(always)]
|
||||
fn from(r: Ratio<$from>) -> Self {
|
||||
(r.numer as f64) / (r.denom as f64)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_from_for_float!(u8);
|
||||
impl_from_for_float!(u16);
|
||||
impl_from_for_float!(u32);
|
||||
impl_from_for_float!(u64);
|
||||
impl_from_for_float!(u128);
|
||||
impl_from_for_float!(i8);
|
||||
impl_from_for_float!(i16);
|
||||
impl_from_for_float!(i32);
|
||||
impl_from_for_float!(i64);
|
||||
impl_from_for_float!(i128);
|
||||
|
||||
impl<T: core::fmt::Display> core::fmt::Display for Ratio<T> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
core::write!(f, "{} / {}", self.numer(), self.denom())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ratio;
|
||||
|
||||
#[test]
|
||||
fn basics() {
|
||||
let mut r = Ratio::new_raw(1, 2) + 2;
|
||||
assert_eq!(*r.numer(), 5);
|
||||
assert_eq!(*r.denom(), 2);
|
||||
assert_eq!(r.to_integer(), 2);
|
||||
|
||||
r = r * 2;
|
||||
assert_eq!(*r.numer(), 10);
|
||||
assert_eq!(*r.denom(), 2);
|
||||
assert_eq!(r.to_integer(), 5);
|
||||
|
||||
r = r / 2;
|
||||
assert_eq!(*r.numer(), 10);
|
||||
assert_eq!(*r.denom(), 4);
|
||||
assert_eq!(r.to_integer(), 2);
|
||||
}
|
||||
}
|
@ -44,6 +44,7 @@ defmt-error = [ ]
|
||||
sdmmc-rs = ["embedded-sdmmc"]
|
||||
net = ["embassy-net", "vcell"]
|
||||
memory-x = ["stm32-metapac/memory-x"]
|
||||
subghz = []
|
||||
|
||||
# Features starting with `_` are for internal use only. They're not intended
|
||||
# to be enabled by other crates, and are not covered by semver guarantees.
|
||||
|
1
embassy-stm32/src/adc/g0.rs
Normal file
1
embassy-stm32/src/adc/g0.rs
Normal file
@ -0,0 +1 @@
|
||||
|
@ -1,6 +1,7 @@
|
||||
#![macro_use]
|
||||
|
||||
#[cfg_attr(adc_v3, path = "v3.rs")]
|
||||
#[cfg_attr(adc_g0, path = "g0.rs")]
|
||||
mod _version;
|
||||
|
||||
#[allow(unused)]
|
||||
|
@ -50,6 +50,9 @@ pub mod spi;
|
||||
#[cfg(usart)]
|
||||
pub mod usart;
|
||||
|
||||
#[cfg(feature = "subghz")]
|
||||
pub mod subghz;
|
||||
|
||||
// This must go last, so that it sees all the impl_foo! macros defined earlier.
|
||||
mod generated {
|
||||
|
||||
|
1
embassy-stm32/src/pwr/g0.rs
Normal file
1
embassy-stm32/src/pwr/g0.rs
Normal file
@ -0,0 +1 @@
|
||||
|
@ -1,5 +1,7 @@
|
||||
#[cfg_attr(any(pwr_h7, pwr_h7smps), path = "h7.rs")]
|
||||
#[cfg_attr(pwr_f4, path = "f4.rs")]
|
||||
#[cfg_attr(pwr_wl5, path = "wl5.rs")]
|
||||
#[cfg_attr(pwr_g0, path = "g0.rs")]
|
||||
mod _version;
|
||||
|
||||
pub use _version::*;
|
||||
|
1
embassy-stm32/src/pwr/wl5.rs
Normal file
1
embassy-stm32/src/pwr/wl5.rs
Normal file
@ -0,0 +1 @@
|
||||
|
@ -2,7 +2,6 @@ pub use super::types::*;
|
||||
use crate::pac;
|
||||
use crate::peripherals::{self, RCC};
|
||||
use crate::rcc::{get_freqs, set_freqs, Clocks};
|
||||
use crate::time::Hertz;
|
||||
use crate::time::U32Ext;
|
||||
use core::marker::PhantomData;
|
||||
use embassy::util::Unborrow;
|
||||
@ -16,10 +15,12 @@ use embassy_hal_common::unborrow;
|
||||
/// HSI speed
|
||||
pub const HSI_FREQ: u32 = 16_000_000;
|
||||
|
||||
pub const HSE32_FREQ: u32 = 32_000_000;
|
||||
|
||||
/// System clock mux source
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ClockSrc {
|
||||
HSE(Hertz),
|
||||
HSE32,
|
||||
HSI16,
|
||||
}
|
||||
|
||||
@ -137,14 +138,17 @@ impl RccExt for RCC {
|
||||
|
||||
(HSI_FREQ, 0x01)
|
||||
}
|
||||
ClockSrc::HSE(freq) => {
|
||||
// Enable HSE
|
||||
ClockSrc::HSE32 => {
|
||||
// Enable HSE32
|
||||
unsafe {
|
||||
rcc.cr().write(|w| w.set_hseon(true));
|
||||
rcc.cr().write(|w| {
|
||||
w.set_hsebyppwr(true);
|
||||
w.set_hseon(true);
|
||||
});
|
||||
while !rcc.cr().read().hserdy() {}
|
||||
}
|
||||
|
||||
(freq.0, 0x02)
|
||||
(HSE32_FREQ, 0x02)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@ pub use _version::*;
|
||||
|
||||
use crate::gpio::Pin;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Error {
|
||||
Framing,
|
||||
|
@ -71,7 +71,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
||||
let miso = miso.degrade();
|
||||
|
||||
let pclk = T::frequency();
|
||||
let br = Self::compute_baud_rate(pclk, freq.into());
|
||||
let freq = freq.into();
|
||||
let br = Self::compute_baud_rate(pclk, freq);
|
||||
|
||||
unsafe {
|
||||
T::enable();
|
||||
|
160
embassy-stm32/src/subghz/bit_sync.rs
Normal file
160
embassy-stm32/src/subghz/bit_sync.rs
Normal file
@ -0,0 +1,160 @@
|
||||
/// Bit synchronization.
|
||||
///
|
||||
/// This must be cleared to `0x00` (the reset value) when using packet types
|
||||
/// other than LoRa.
|
||||
///
|
||||
/// Argument of [`set_bit_sync`](crate::subghz::SubGhz::set_bit_sync).
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct BitSync {
|
||||
val: u8,
|
||||
}
|
||||
|
||||
impl BitSync {
|
||||
/// Bit synchronization register reset value.
|
||||
pub const RESET: BitSync = BitSync { val: 0x00 };
|
||||
|
||||
/// Create a new [`BitSync`] structure from a raw value.
|
||||
///
|
||||
/// Reserved bits will be masked.
|
||||
pub const fn from_raw(raw: u8) -> Self {
|
||||
Self { val: raw & 0x70 }
|
||||
}
|
||||
|
||||
/// Get the raw value of the [`BitSync`] register.
|
||||
pub const fn as_bits(&self) -> u8 {
|
||||
self.val
|
||||
}
|
||||
|
||||
/// LoRa simple bit synchronization enable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Enable simple bit synchronization.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::BitSync;
|
||||
///
|
||||
/// const BIT_SYNC: BitSync = BitSync::RESET.set_simple_bit_sync_en(true);
|
||||
/// # assert_eq!(u8::from(BIT_SYNC), 0x40u8);
|
||||
/// ```
|
||||
#[must_use = "set_simple_bit_sync_en returns a modified BitSync"]
|
||||
pub const fn set_simple_bit_sync_en(mut self, en: bool) -> BitSync {
|
||||
if en {
|
||||
self.val |= 1 << 6;
|
||||
} else {
|
||||
self.val &= !(1 << 6);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` if simple bit synchronization is enabled.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::BitSync;
|
||||
///
|
||||
/// let bs: BitSync = BitSync::RESET;
|
||||
/// assert_eq!(bs.simple_bit_sync_en(), false);
|
||||
/// let bs: BitSync = bs.set_simple_bit_sync_en(true);
|
||||
/// assert_eq!(bs.simple_bit_sync_en(), true);
|
||||
/// let bs: BitSync = bs.set_simple_bit_sync_en(false);
|
||||
/// assert_eq!(bs.simple_bit_sync_en(), false);
|
||||
/// ```
|
||||
pub const fn simple_bit_sync_en(&self) -> bool {
|
||||
self.val & (1 << 6) != 0
|
||||
}
|
||||
|
||||
/// LoRa RX data inversion.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Invert receive data.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::BitSync;
|
||||
///
|
||||
/// const BIT_SYNC: BitSync = BitSync::RESET.set_rx_data_inv(true);
|
||||
/// # assert_eq!(u8::from(BIT_SYNC), 0x20u8);
|
||||
/// ```
|
||||
#[must_use = "set_rx_data_inv returns a modified BitSync"]
|
||||
pub const fn set_rx_data_inv(mut self, inv: bool) -> BitSync {
|
||||
if inv {
|
||||
self.val |= 1 << 5;
|
||||
} else {
|
||||
self.val &= !(1 << 5);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` if LoRa RX data is inverted.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::BitSync;
|
||||
///
|
||||
/// let bs: BitSync = BitSync::RESET;
|
||||
/// assert_eq!(bs.rx_data_inv(), false);
|
||||
/// let bs: BitSync = bs.set_rx_data_inv(true);
|
||||
/// assert_eq!(bs.rx_data_inv(), true);
|
||||
/// let bs: BitSync = bs.set_rx_data_inv(false);
|
||||
/// assert_eq!(bs.rx_data_inv(), false);
|
||||
/// ```
|
||||
pub const fn rx_data_inv(&self) -> bool {
|
||||
self.val & (1 << 5) != 0
|
||||
}
|
||||
|
||||
/// LoRa normal bit synchronization enable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Enable normal bit synchronization.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::BitSync;
|
||||
///
|
||||
/// const BIT_SYNC: BitSync = BitSync::RESET.set_norm_bit_sync_en(true);
|
||||
/// # assert_eq!(u8::from(BIT_SYNC), 0x10u8);
|
||||
/// ```
|
||||
#[must_use = "set_norm_bit_sync_en returns a modified BitSync"]
|
||||
pub const fn set_norm_bit_sync_en(mut self, en: bool) -> BitSync {
|
||||
if en {
|
||||
self.val |= 1 << 4;
|
||||
} else {
|
||||
self.val &= !(1 << 4);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` if normal bit synchronization is enabled.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::BitSync;
|
||||
///
|
||||
/// let bs: BitSync = BitSync::RESET;
|
||||
/// assert_eq!(bs.norm_bit_sync_en(), false);
|
||||
/// let bs: BitSync = bs.set_norm_bit_sync_en(true);
|
||||
/// assert_eq!(bs.norm_bit_sync_en(), true);
|
||||
/// let bs: BitSync = bs.set_norm_bit_sync_en(false);
|
||||
/// assert_eq!(bs.norm_bit_sync_en(), false);
|
||||
/// ```
|
||||
pub const fn norm_bit_sync_en(&self) -> bool {
|
||||
self.val & (1 << 4) != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BitSync> for u8 {
|
||||
fn from(bs: BitSync) -> Self {
|
||||
bs.val
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BitSync {
|
||||
fn default() -> Self {
|
||||
Self::RESET
|
||||
}
|
||||
}
|
230
embassy-stm32/src/subghz/cad_params.rs
Normal file
230
embassy-stm32/src/subghz/cad_params.rs
Normal file
@ -0,0 +1,230 @@
|
||||
use crate::subghz::timeout::Timeout;
|
||||
|
||||
/// Number of symbols used for channel activity detection scans.
|
||||
///
|
||||
/// Argument of [`CadParams::set_num_symbol`].
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum NbCadSymbol {
|
||||
/// 1 symbol.
|
||||
S1 = 0x0,
|
||||
/// 2 symbols.
|
||||
S2 = 0x1,
|
||||
/// 4 symbols.
|
||||
S4 = 0x2,
|
||||
/// 8 symbols.
|
||||
S8 = 0x3,
|
||||
/// 16 symbols.
|
||||
S16 = 0x4,
|
||||
}
|
||||
|
||||
/// Mode to enter after a channel activity detection scan is finished.
|
||||
///
|
||||
/// Argument of [`CadParams::set_exit_mode`].
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum ExitMode {
|
||||
/// Standby with RC 13 MHz mode entry after CAD.
|
||||
Standby = 0,
|
||||
/// Standby with RC 13 MHz mode after CAD if no LoRa symbol is detected
|
||||
/// during the CAD scan.
|
||||
/// If a LoRa symbol is detected, the sub-GHz radio stays in RX mode
|
||||
/// until a packet is received or until the CAD timeout is reached.
|
||||
StandbyLoRa = 1,
|
||||
}
|
||||
|
||||
/// Channel activity detection (CAD) parameters.
|
||||
///
|
||||
/// Argument of [`set_cad_params`].
|
||||
///
|
||||
/// # Recommended CAD settings
|
||||
///
|
||||
/// This is taken directly from the datasheet.
|
||||
///
|
||||
/// "The correct values selected in the table below must be carefully tested to
|
||||
/// ensure a good detection at sensitivity level and to limit the number of
|
||||
/// false detections"
|
||||
///
|
||||
/// | SF (Spreading Factor) | [`set_det_peak`] | [`set_det_min`] |
|
||||
/// |-----------------------|------------------|-----------------|
|
||||
/// | 5 | 0x18 | 0x10 |
|
||||
/// | 6 | 0x19 | 0x10 |
|
||||
/// | 7 | 0x20 | 0x10 |
|
||||
/// | 8 | 0x21 | 0x10 |
|
||||
/// | 9 | 0x22 | 0x10 |
|
||||
/// | 10 | 0x23 | 0x10 |
|
||||
/// | 11 | 0x24 | 0x10 |
|
||||
/// | 12 | 0x25 | 0x10 |
|
||||
///
|
||||
/// [`set_cad_params`]: crate::subghz::SubGhz::set_cad_params
|
||||
/// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak
|
||||
/// [`set_det_min`]: crate::subghz::CadParams::set_det_min
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CadParams {
|
||||
buf: [u8; 8],
|
||||
}
|
||||
|
||||
impl CadParams {
|
||||
/// Create a new `CadParams`.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::CadParams;
|
||||
///
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new();
|
||||
/// assert_eq!(CAD_PARAMS, CadParams::default());
|
||||
/// ```
|
||||
pub const fn new() -> CadParams {
|
||||
CadParams {
|
||||
buf: [super::OpCode::SetCadParams as u8, 0, 0, 0, 0, 0, 0, 0],
|
||||
}
|
||||
.set_num_symbol(NbCadSymbol::S1)
|
||||
.set_det_peak(0x18)
|
||||
.set_det_min(0x10)
|
||||
.set_exit_mode(ExitMode::Standby)
|
||||
}
|
||||
|
||||
/// Number of symbols used for a CAD scan.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Set the number of symbols to 4.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CadParams, NbCadSymbol};
|
||||
///
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new().set_num_symbol(NbCadSymbol::S4);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[1], 0x2);
|
||||
/// ```
|
||||
#[must_use = "set_num_symbol returns a modified CadParams"]
|
||||
pub const fn set_num_symbol(mut self, nb: NbCadSymbol) -> CadParams {
|
||||
self.buf[1] = nb as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Used with [`set_det_min`] to correlate the LoRa symbol.
|
||||
///
|
||||
/// See the table in [`CadParams`] docs for recommended values.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Setting the recommended value for a spreading factor of 7.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::CadParams;
|
||||
///
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x20).set_det_min(0x10);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x20);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10);
|
||||
/// ```
|
||||
///
|
||||
/// [`set_det_min`]: crate::subghz::CadParams::set_det_min
|
||||
#[must_use = "set_det_peak returns a modified CadParams"]
|
||||
pub const fn set_det_peak(mut self, peak: u8) -> CadParams {
|
||||
self.buf[2] = peak;
|
||||
self
|
||||
}
|
||||
|
||||
/// Used with [`set_det_peak`] to correlate the LoRa symbol.
|
||||
///
|
||||
/// See the table in [`CadParams`] docs for recommended values.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Setting the recommended value for a spreading factor of 6.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::CadParams;
|
||||
///
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x18).set_det_min(0x10);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x18);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10);
|
||||
/// ```
|
||||
///
|
||||
/// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak
|
||||
#[must_use = "set_det_min returns a modified CadParams"]
|
||||
pub const fn set_det_min(mut self, min: u8) -> CadParams {
|
||||
self.buf[3] = min;
|
||||
self
|
||||
}
|
||||
|
||||
/// Mode to enter after a channel activity detection scan is finished.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CadParams, ExitMode};
|
||||
///
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new().set_exit_mode(ExitMode::Standby);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x00);
|
||||
/// # assert_eq!(CAD_PARAMS.set_exit_mode(ExitMode::StandbyLoRa).as_slice()[4], 0x01);
|
||||
/// ```
|
||||
#[must_use = "set_exit_mode returns a modified CadParams"]
|
||||
pub const fn set_exit_mode(mut self, mode: ExitMode) -> CadParams {
|
||||
self.buf[4] = mode as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the timeout.
|
||||
///
|
||||
/// This is only used with [`ExitMode::StandbyLoRa`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CadParams, ExitMode, Timeout};
|
||||
///
|
||||
/// const TIMEOUT: Timeout = Timeout::from_raw(0x123456);
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new()
|
||||
/// .set_exit_mode(ExitMode::StandbyLoRa)
|
||||
/// .set_timeout(TIMEOUT);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x01);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[5], 0x12);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[6], 0x34);
|
||||
/// # assert_eq!(CAD_PARAMS.as_slice()[7], 0x56);
|
||||
/// ```
|
||||
#[must_use = "set_timeout returns a modified CadParams"]
|
||||
pub const fn set_timeout(mut self, to: Timeout) -> CadParams {
|
||||
let to_bytes: [u8; 3] = to.as_bytes();
|
||||
self.buf[5] = to_bytes[0];
|
||||
self.buf[6] = to_bytes[1];
|
||||
self.buf[7] = to_bytes[2];
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CadParams, ExitMode, NbCadSymbol, Timeout};
|
||||
///
|
||||
/// const TIMEOUT: Timeout = Timeout::from_raw(0x123456);
|
||||
/// const CAD_PARAMS: CadParams = CadParams::new()
|
||||
/// .set_num_symbol(NbCadSymbol::S4)
|
||||
/// .set_det_peak(0x18)
|
||||
/// .set_det_min(0x10)
|
||||
/// .set_exit_mode(ExitMode::StandbyLoRa)
|
||||
/// .set_timeout(TIMEOUT);
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// CAD_PARAMS.as_slice(),
|
||||
/// &[0x88, 0x02, 0x18, 0x10, 0x01, 0x12, 0x34, 0x56]
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CadParams {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
122
embassy-stm32/src/subghz/calibrate.rs
Normal file
122
embassy-stm32/src/subghz/calibrate.rs
Normal file
@ -0,0 +1,122 @@
|
||||
/// Image calibration.
|
||||
///
|
||||
/// Argument of [`calibrate_image`].
|
||||
///
|
||||
/// [`calibrate_image`]: crate::subghz::SubGhz::calibrate_image
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CalibrateImage(pub(crate) u8, pub(crate) u8);
|
||||
|
||||
impl CalibrateImage {
|
||||
/// Image calibration for the 430 - 440 MHz ISM band.
|
||||
pub const ISM_430_440: CalibrateImage = CalibrateImage(0x6B, 0x6F);
|
||||
|
||||
/// Image calibration for the 470 - 510 MHz ISM band.
|
||||
pub const ISM_470_510: CalibrateImage = CalibrateImage(0x75, 0x81);
|
||||
|
||||
/// Image calibration for the 779 - 787 MHz ISM band.
|
||||
pub const ISM_779_787: CalibrateImage = CalibrateImage(0xC1, 0xC5);
|
||||
|
||||
/// Image calibration for the 863 - 870 MHz ISM band.
|
||||
pub const ISM_863_870: CalibrateImage = CalibrateImage(0xD7, 0xDB);
|
||||
|
||||
/// Image calibration for the 902 - 928 MHz ISM band.
|
||||
pub const ISM_902_928: CalibrateImage = CalibrateImage(0xE1, 0xE9);
|
||||
|
||||
/// Create a new `CalibrateImage` structure from raw values.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::CalibrateImage;
|
||||
///
|
||||
/// const CAL: CalibrateImage = CalibrateImage::new(0xE1, 0xE9);
|
||||
/// assert_eq!(CAL, CalibrateImage::ISM_902_928);
|
||||
/// ```
|
||||
pub const fn new(f1: u8, f2: u8) -> CalibrateImage {
|
||||
CalibrateImage(f1, f2)
|
||||
}
|
||||
|
||||
/// Create a new `CalibrateImage` structure from two frequencies.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// The units for `freq1` and `freq2` are in MHz.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// * Panics if `freq1` is less than `freq2`.
|
||||
/// * Panics if `freq1` or `freq2` is not a multiple of 4MHz.
|
||||
/// * Panics if `freq1` or `freq2` is greater than `1020`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Create an image calibration for the 430 - 440 MHz ISM band.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::CalibrateImage;
|
||||
///
|
||||
/// let cal: CalibrateImage = CalibrateImage::from_freq(428, 444);
|
||||
/// assert_eq!(cal, CalibrateImage::ISM_430_440);
|
||||
/// ```
|
||||
pub fn from_freq(freq1: u16, freq2: u16) -> CalibrateImage {
|
||||
assert!(freq2 >= freq1);
|
||||
assert_eq!(freq1 % 4, 0);
|
||||
assert_eq!(freq2 % 4, 0);
|
||||
assert!(freq1 <= 1020);
|
||||
assert!(freq2 <= 1020);
|
||||
CalibrateImage((freq1 / 4) as u8, (freq2 / 4) as u8)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CalibrateImage {
|
||||
fn default() -> Self {
|
||||
CalibrateImage::new(0xE1, 0xE9)
|
||||
}
|
||||
}
|
||||
|
||||
/// Block calibration.
|
||||
///
|
||||
/// Argument of [`calibrate`].
|
||||
///
|
||||
/// [`calibrate`]: crate::subghz::SubGhz::calibrate
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum Calibrate {
|
||||
/// Image calibration
|
||||
Image = 1 << 6,
|
||||
/// RF-ADC bulk P calibration
|
||||
AdcBulkP = 1 << 5,
|
||||
/// RF-ADC bulk N calibration
|
||||
AdcBulkN = 1 << 4,
|
||||
/// RF-ADC pulse calibration
|
||||
AdcPulse = 1 << 3,
|
||||
/// RF-PLL calibration
|
||||
Pll = 1 << 2,
|
||||
/// Sub-GHz radio RC 13 MHz calibration
|
||||
Rc13M = 1 << 1,
|
||||
/// Sub-GHz radio RC 64 kHz calibration
|
||||
Rc64K = 1,
|
||||
}
|
||||
|
||||
impl Calibrate {
|
||||
/// Get the bitmask for the block calibration.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::Calibrate;
|
||||
///
|
||||
/// assert_eq!(Calibrate::Image.mask(), 0b0100_0000);
|
||||
/// assert_eq!(Calibrate::AdcBulkP.mask(), 0b0010_0000);
|
||||
/// assert_eq!(Calibrate::AdcBulkN.mask(), 0b0001_0000);
|
||||
/// assert_eq!(Calibrate::AdcPulse.mask(), 0b0000_1000);
|
||||
/// assert_eq!(Calibrate::Pll.mask(), 0b0000_0100);
|
||||
/// assert_eq!(Calibrate::Rc13M.mask(), 0b0000_0010);
|
||||
/// assert_eq!(Calibrate::Rc64K.mask(), 0b0000_0001);
|
||||
/// ```
|
||||
pub const fn mask(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
}
|
37
embassy-stm32/src/subghz/fallback_mode.rs
Normal file
37
embassy-stm32/src/subghz/fallback_mode.rs
Normal file
@ -0,0 +1,37 @@
|
||||
/// Fallback mode after successful packet transmission or packet reception.
|
||||
///
|
||||
/// Argument of [`set_tx_rx_fallback_mode`].
|
||||
///
|
||||
/// [`set_tx_rx_fallback_mode`]: crate::subghz::SubGhz::set_tx_rx_fallback_mode.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum FallbackMode {
|
||||
/// Standby mode entry.
|
||||
Standby = 0x20,
|
||||
/// Standby with HSE32 enabled.
|
||||
StandbyHse = 0x30,
|
||||
/// Frequency synthesizer entry.
|
||||
Fs = 0x40,
|
||||
}
|
||||
|
||||
impl From<FallbackMode> for u8 {
|
||||
fn from(fm: FallbackMode) -> Self {
|
||||
fm as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FallbackMode {
|
||||
/// Default fallback mode after power-on reset.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::FallbackMode;
|
||||
///
|
||||
/// assert_eq!(FallbackMode::default(), FallbackMode::Standby);
|
||||
/// ```
|
||||
fn default() -> Self {
|
||||
FallbackMode::Standby
|
||||
}
|
||||
}
|
107
embassy-stm32/src/subghz/hse_trim.rs
Normal file
107
embassy-stm32/src/subghz/hse_trim.rs
Normal file
@ -0,0 +1,107 @@
|
||||
use crate::subghz::value_error::ValueError;
|
||||
|
||||
/// HSE32 load capacitor trimming.
|
||||
///
|
||||
/// Argument of [`set_hse_in_trim`] and [`set_hse_out_trim`].
|
||||
///
|
||||
/// [`set_hse_in_trim`]: crate::subghz::SubGhz::set_hse_in_trim
|
||||
/// [`set_hse_out_trim`]: crate::subghz::SubGhz::set_hse_out_trim
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct HseTrim {
|
||||
val: u8,
|
||||
}
|
||||
|
||||
impl HseTrim {
|
||||
/// Maximum capacitor value, ~33.4 pF
|
||||
pub const MAX: HseTrim = HseTrim::from_raw(0x2F);
|
||||
|
||||
/// Minimum capacitor value, ~11.3 pF
|
||||
pub const MIN: HseTrim = HseTrim::from_raw(0x00);
|
||||
|
||||
/// Power-on-reset capacitor value, ~20.3 pF
|
||||
///
|
||||
/// This is the same as `default`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::HseTrim;
|
||||
///
|
||||
/// assert_eq!(HseTrim::POR, HseTrim::default());
|
||||
/// ```
|
||||
pub const POR: HseTrim = HseTrim::from_raw(0x12);
|
||||
|
||||
/// Create a new [`HseTrim`] structure from a raw value.
|
||||
///
|
||||
/// Values greater than the maximum of `0x2F` will be set to the maximum.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::HseTrim;
|
||||
///
|
||||
/// assert_eq!(HseTrim::from_raw(0xFF), HseTrim::MAX);
|
||||
/// assert_eq!(HseTrim::from_raw(0x2F), HseTrim::MAX);
|
||||
/// assert_eq!(HseTrim::from_raw(0x00), HseTrim::MIN);
|
||||
/// ```
|
||||
pub const fn from_raw(raw: u8) -> HseTrim {
|
||||
if raw > 0x2F {
|
||||
HseTrim { val: 0x2F }
|
||||
} else {
|
||||
HseTrim { val: raw }
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a HSE trim value from farads.
|
||||
///
|
||||
/// Values greater than the maximum of 33.4 pF will be set to the maximum.
|
||||
/// Values less than the minimum of 11.3 pF will be set to the minimum.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::HseTrim;
|
||||
///
|
||||
/// assert!(HseTrim::from_farads(1.0).is_err());
|
||||
/// assert!(HseTrim::from_farads(1e-12).is_err());
|
||||
/// assert_eq!(HseTrim::from_farads(20.2e-12), Ok(HseTrim::default()));
|
||||
/// ```
|
||||
pub fn from_farads(farads: f32) -> Result<HseTrim, ValueError<f32>> {
|
||||
const MAX: f32 = 33.4E-12;
|
||||
const MIN: f32 = 11.3E-12;
|
||||
if farads > MAX {
|
||||
Err(ValueError::too_high(farads, MAX))
|
||||
} else if farads < MIN {
|
||||
Err(ValueError::too_low(farads, MIN))
|
||||
} else {
|
||||
Ok(HseTrim::from_raw(((farads - 11.3e-12) / 0.47e-12) as u8))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the capacitance as farads.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::HseTrim;
|
||||
///
|
||||
/// assert_eq!((HseTrim::MAX.as_farads() * 10e11) as u8, 33);
|
||||
/// assert_eq!((HseTrim::MIN.as_farads() * 10e11) as u8, 11);
|
||||
/// ```
|
||||
pub fn as_farads(&self) -> f32 {
|
||||
(self.val as f32) * 0.47E-12 + 11.3E-12
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HseTrim> for u8 {
|
||||
fn from(ht: HseTrim) -> Self {
|
||||
ht.val
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HseTrim {
|
||||
fn default() -> Self {
|
||||
Self::POR
|
||||
}
|
||||
}
|
292
embassy-stm32/src/subghz/irq.rs
Normal file
292
embassy-stm32/src/subghz/irq.rs
Normal file
@ -0,0 +1,292 @@
|
||||
/// Interrupt lines.
|
||||
///
|
||||
/// Argument of [`CfgIrq::irq_enable`] and [`CfgIrq::irq_disable`].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum IrqLine {
|
||||
/// Global interrupt.
|
||||
Global,
|
||||
/// Interrupt line 1.
|
||||
///
|
||||
/// This will output to the [`RfIrq0`](crate::gpio::RfIrq0) pin.
|
||||
Line1,
|
||||
/// Interrupt line 2.
|
||||
///
|
||||
/// This will output to the [`RfIrq1`](crate::gpio::RfIrq1) pin.
|
||||
Line2,
|
||||
/// Interrupt line 3.
|
||||
///
|
||||
/// This will output to the [`RfIrq2`](crate::gpio::RfIrq2) pin.
|
||||
Line3,
|
||||
}
|
||||
|
||||
impl IrqLine {
|
||||
pub(super) const fn offset(&self) -> usize {
|
||||
match self {
|
||||
IrqLine::Global => 1,
|
||||
IrqLine::Line1 => 3,
|
||||
IrqLine::Line2 => 5,
|
||||
IrqLine::Line3 => 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// IRQ bit mapping
|
||||
///
|
||||
/// See table 37 "IRQ bit mapping and definition" in the reference manual for
|
||||
/// more information.
|
||||
#[repr(u16)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Irq {
|
||||
/// Packet transmission finished.
|
||||
///
|
||||
/// * Packet type: LoRa and GFSK
|
||||
/// * Operation: TX
|
||||
TxDone = (1 << 0),
|
||||
/// Packet reception finished.
|
||||
///
|
||||
/// * Packet type: LoRa and GFSK
|
||||
/// * Operation: RX
|
||||
RxDone = (1 << 1),
|
||||
/// Preamble detected.
|
||||
///
|
||||
/// * Packet type: LoRa and GFSK
|
||||
/// * Operation: RX
|
||||
PreambleDetected = (1 << 2),
|
||||
/// Synchronization word valid.
|
||||
///
|
||||
/// * Packet type: GFSK
|
||||
/// * Operation: RX
|
||||
SyncDetected = (1 << 3),
|
||||
/// Header valid.
|
||||
///
|
||||
/// * Packet type: LoRa
|
||||
/// * Operation: RX
|
||||
HeaderValid = (1 << 4),
|
||||
/// Header CRC error.
|
||||
///
|
||||
/// * Packet type: LoRa
|
||||
/// * Operation: RX
|
||||
HeaderErr = (1 << 5),
|
||||
/// Dual meaning error.
|
||||
///
|
||||
/// For GFSK RX this indicates a preamble, syncword, address, CRC, or length
|
||||
/// error.
|
||||
///
|
||||
/// For LoRa RX this indicates a CRC error.
|
||||
Err = (1 << 6),
|
||||
/// Channel activity detection finished.
|
||||
///
|
||||
/// * Packet type: LoRa
|
||||
/// * Operation: CAD
|
||||
CadDone = (1 << 7),
|
||||
/// Channel activity detected.
|
||||
///
|
||||
/// * Packet type: LoRa
|
||||
/// * Operation: CAD
|
||||
CadDetected = (1 << 8),
|
||||
/// RX or TX timeout.
|
||||
///
|
||||
/// * Packet type: LoRa and GFSK
|
||||
/// * Operation: RX and TX
|
||||
Timeout = (1 << 9),
|
||||
}
|
||||
|
||||
impl Irq {
|
||||
/// Get the bitmask for an IRQ.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::Irq;
|
||||
///
|
||||
/// assert_eq!(Irq::TxDone.mask(), 0x0001);
|
||||
/// assert_eq!(Irq::Timeout.mask(), 0x0200);
|
||||
/// ```
|
||||
pub const fn mask(self) -> u16 {
|
||||
self as u16
|
||||
}
|
||||
}
|
||||
|
||||
/// Argument for [`set_irq_cfg`].
|
||||
///
|
||||
/// [`set_irq_cfg`]: crate::subghz::SubGhz::set_irq_cfg
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct CfgIrq {
|
||||
buf: [u8; 9],
|
||||
}
|
||||
|
||||
impl CfgIrq {
|
||||
/// Create a new `CfgIrq`.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// The default value has all interrupts disabled on all lines.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::CfgIrq;
|
||||
///
|
||||
/// const IRQ_CFG: CfgIrq = CfgIrq::new();
|
||||
/// ```
|
||||
pub const fn new() -> CfgIrq {
|
||||
CfgIrq {
|
||||
buf: [
|
||||
super::OpCode::CfgDioIrq as u8,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable an interrupt.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CfgIrq, Irq, IrqLine};
|
||||
///
|
||||
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
|
||||
/// .irq_enable(IrqLine::Global, Irq::TxDone)
|
||||
/// .irq_enable(IrqLine::Global, Irq::Timeout);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[1], 0x02);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[2], 0x01);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[3], 0x00);
|
||||
/// ```
|
||||
#[must_use = "irq_enable returns a modified CfgIrq"]
|
||||
pub const fn irq_enable(mut self, line: IrqLine, irq: Irq) -> CfgIrq {
|
||||
let mask: u16 = irq as u16;
|
||||
let offset: usize = line.offset();
|
||||
self.buf[offset] |= ((mask >> 8) & 0xFF) as u8;
|
||||
self.buf[offset + 1] |= (mask & 0xFF) as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable an interrupt on all lines.
|
||||
///
|
||||
/// As far as I can tell with empirical testing all IRQ lines need to be
|
||||
/// enabled for the internal interrupt to be pending in the NVIC.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CfgIrq, Irq};
|
||||
///
|
||||
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
|
||||
/// .irq_enable_all(Irq::TxDone)
|
||||
/// .irq_enable_all(Irq::Timeout);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[1], 0x02);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[2], 0x01);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[3], 0x02);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[4], 0x01);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[5], 0x02);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[6], 0x01);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[7], 0x02);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[8], 0x01);
|
||||
/// ```
|
||||
#[must_use = "irq_enable_all returns a modified CfgIrq"]
|
||||
pub const fn irq_enable_all(mut self, irq: Irq) -> CfgIrq {
|
||||
let mask: [u8; 2] = irq.mask().to_be_bytes();
|
||||
|
||||
self.buf[1] |= mask[0];
|
||||
self.buf[2] |= mask[1];
|
||||
self.buf[3] |= mask[0];
|
||||
self.buf[4] |= mask[1];
|
||||
self.buf[5] |= mask[0];
|
||||
self.buf[6] |= mask[1];
|
||||
self.buf[7] |= mask[0];
|
||||
self.buf[8] |= mask[1];
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable an interrupt.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CfgIrq, Irq, IrqLine};
|
||||
///
|
||||
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
|
||||
/// .irq_enable(IrqLine::Global, Irq::TxDone)
|
||||
/// .irq_enable(IrqLine::Global, Irq::Timeout)
|
||||
/// .irq_disable(IrqLine::Global, Irq::TxDone)
|
||||
/// .irq_disable(IrqLine::Global, Irq::Timeout);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[1], 0x00);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[2], 0x00);
|
||||
/// # assert_eq!(IRQ_CFG.as_slice()[3], 0x00);
|
||||
/// ```
|
||||
#[must_use = "irq_disable returns a modified CfgIrq"]
|
||||
pub const fn irq_disable(mut self, line: IrqLine, irq: Irq) -> CfgIrq {
|
||||
let mask: u16 = !(irq as u16);
|
||||
let offset: usize = line.offset();
|
||||
self.buf[offset] &= ((mask >> 8) & 0xFF) as u8;
|
||||
self.buf[offset + 1] &= (mask & 0xFF) as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable an interrupt on all lines.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CfgIrq, Irq};
|
||||
///
|
||||
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
|
||||
/// .irq_enable_all(Irq::TxDone)
|
||||
/// .irq_enable_all(Irq::Timeout)
|
||||
/// .irq_disable_all(Irq::TxDone)
|
||||
/// .irq_disable_all(Irq::Timeout);
|
||||
/// # assert_eq!(IRQ_CFG, CfgIrq::new());
|
||||
/// ```
|
||||
#[must_use = "irq_disable_all returns a modified CfgIrq"]
|
||||
pub const fn irq_disable_all(mut self, irq: Irq) -> CfgIrq {
|
||||
let mask: [u8; 2] = (!irq.mask()).to_be_bytes();
|
||||
|
||||
self.buf[1] &= mask[0];
|
||||
self.buf[2] &= mask[1];
|
||||
self.buf[3] &= mask[0];
|
||||
self.buf[4] &= mask[1];
|
||||
self.buf[5] &= mask[0];
|
||||
self.buf[6] &= mask[1];
|
||||
self.buf[7] &= mask[0];
|
||||
self.buf[8] &= mask[1];
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CfgIrq, Irq};
|
||||
///
|
||||
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
|
||||
/// .irq_enable_all(Irq::TxDone)
|
||||
/// .irq_enable_all(Irq::Timeout);
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// IRQ_CFG.as_slice(),
|
||||
/// &[0x08, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01]
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CfgIrq {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
20
embassy-stm32/src/subghz/lora_sync_word.rs
Normal file
20
embassy-stm32/src/subghz/lora_sync_word.rs
Normal file
@ -0,0 +1,20 @@
|
||||
/// LoRa synchronization word.
|
||||
///
|
||||
/// Argument of [`set_lora_sync_word`][crate::subghz::SubGhz::set_lora_sync_word].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum LoRaSyncWord {
|
||||
/// LoRa private network.
|
||||
Private,
|
||||
/// LoRa public network.
|
||||
Public,
|
||||
}
|
||||
|
||||
impl LoRaSyncWord {
|
||||
pub(crate) const fn bytes(self) -> [u8; 2] {
|
||||
match self {
|
||||
LoRaSyncWord::Private => [0x14, 0x24],
|
||||
LoRaSyncWord::Public => [0x34, 0x44],
|
||||
}
|
||||
}
|
||||
}
|
1681
embassy-stm32/src/subghz/mod.rs
Normal file
1681
embassy-stm32/src/subghz/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
996
embassy-stm32/src/subghz/mod_params.rs
Normal file
996
embassy-stm32/src/subghz/mod_params.rs
Normal file
@ -0,0 +1,996 @@
|
||||
/// Bandwidth options for [`FskModParams`].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum FskBandwidth {
|
||||
/// 4.8 kHz double-sideband
|
||||
Bw4 = 0x1F,
|
||||
/// 5.8 kHz double-sideband
|
||||
Bw5 = 0x17,
|
||||
/// 7.3 kHz double-sideband
|
||||
Bw7 = 0x0F,
|
||||
/// 9.7 kHz double-sideband
|
||||
Bw9 = 0x1E,
|
||||
/// 11.7 kHz double-sideband
|
||||
Bw11 = 0x16,
|
||||
/// 14.6 kHz double-sideband
|
||||
Bw14 = 0x0E,
|
||||
/// 19.5 kHz double-sideband
|
||||
Bw19 = 0x1D,
|
||||
/// 23.4 kHz double-sideband
|
||||
Bw23 = 0x15,
|
||||
/// 29.3 kHz double-sideband
|
||||
Bw29 = 0x0D,
|
||||
/// 39.0 kHz double-sideband
|
||||
Bw39 = 0x1C,
|
||||
/// 46.9 kHz double-sideband
|
||||
Bw46 = 0x14,
|
||||
/// 58.6 kHz double-sideband
|
||||
Bw58 = 0x0C,
|
||||
/// 78.2 kHz double-sideband
|
||||
Bw78 = 0x1B,
|
||||
/// 93.8 kHz double-sideband
|
||||
Bw93 = 0x13,
|
||||
/// 117.3 kHz double-sideband
|
||||
Bw117 = 0x0B,
|
||||
/// 156.2 kHz double-sideband
|
||||
Bw156 = 0x1A,
|
||||
/// 187.2 kHz double-sideband
|
||||
Bw187 = 0x12,
|
||||
/// 234.3 kHz double-sideband
|
||||
Bw234 = 0x0A,
|
||||
/// 312.0 kHz double-sideband
|
||||
Bw312 = 0x19,
|
||||
/// 373.6 kHz double-sideband
|
||||
Bw373 = 0x11,
|
||||
/// 467.0 kHz double-sideband
|
||||
Bw467 = 0x09,
|
||||
}
|
||||
|
||||
impl FskBandwidth {
|
||||
/// Get the bandwidth in hertz.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::FskBandwidth;
|
||||
///
|
||||
/// assert_eq!(FskBandwidth::Bw4.hertz(), 4_800);
|
||||
/// assert_eq!(FskBandwidth::Bw5.hertz(), 5_800);
|
||||
/// assert_eq!(FskBandwidth::Bw7.hertz(), 7_300);
|
||||
/// assert_eq!(FskBandwidth::Bw9.hertz(), 9_700);
|
||||
/// assert_eq!(FskBandwidth::Bw11.hertz(), 11_700);
|
||||
/// assert_eq!(FskBandwidth::Bw14.hertz(), 14_600);
|
||||
/// assert_eq!(FskBandwidth::Bw19.hertz(), 19_500);
|
||||
/// assert_eq!(FskBandwidth::Bw23.hertz(), 23_400);
|
||||
/// assert_eq!(FskBandwidth::Bw29.hertz(), 29_300);
|
||||
/// assert_eq!(FskBandwidth::Bw39.hertz(), 39_000);
|
||||
/// assert_eq!(FskBandwidth::Bw46.hertz(), 46_900);
|
||||
/// assert_eq!(FskBandwidth::Bw58.hertz(), 58_600);
|
||||
/// assert_eq!(FskBandwidth::Bw78.hertz(), 78_200);
|
||||
/// assert_eq!(FskBandwidth::Bw93.hertz(), 93_800);
|
||||
/// assert_eq!(FskBandwidth::Bw117.hertz(), 117_300);
|
||||
/// assert_eq!(FskBandwidth::Bw156.hertz(), 156_200);
|
||||
/// assert_eq!(FskBandwidth::Bw187.hertz(), 187_200);
|
||||
/// assert_eq!(FskBandwidth::Bw234.hertz(), 234_300);
|
||||
/// assert_eq!(FskBandwidth::Bw312.hertz(), 312_000);
|
||||
/// assert_eq!(FskBandwidth::Bw373.hertz(), 373_600);
|
||||
/// assert_eq!(FskBandwidth::Bw467.hertz(), 467_000);
|
||||
/// ```
|
||||
pub const fn hertz(&self) -> u32 {
|
||||
match self {
|
||||
FskBandwidth::Bw4 => 4_800,
|
||||
FskBandwidth::Bw5 => 5_800,
|
||||
FskBandwidth::Bw7 => 7_300,
|
||||
FskBandwidth::Bw9 => 9_700,
|
||||
FskBandwidth::Bw11 => 11_700,
|
||||
FskBandwidth::Bw14 => 14_600,
|
||||
FskBandwidth::Bw19 => 19_500,
|
||||
FskBandwidth::Bw23 => 23_400,
|
||||
FskBandwidth::Bw29 => 29_300,
|
||||
FskBandwidth::Bw39 => 39_000,
|
||||
FskBandwidth::Bw46 => 46_900,
|
||||
FskBandwidth::Bw58 => 58_600,
|
||||
FskBandwidth::Bw78 => 78_200,
|
||||
FskBandwidth::Bw93 => 93_800,
|
||||
FskBandwidth::Bw117 => 117_300,
|
||||
FskBandwidth::Bw156 => 156_200,
|
||||
FskBandwidth::Bw187 => 187_200,
|
||||
FskBandwidth::Bw234 => 234_300,
|
||||
FskBandwidth::Bw312 => 312_000,
|
||||
FskBandwidth::Bw373 => 373_600,
|
||||
FskBandwidth::Bw467 => 467_000,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert from a raw bit value.
|
||||
///
|
||||
/// Invalid values will be returned in the `Err` variant of the result.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::FskBandwidth;
|
||||
///
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x1F), Ok(FskBandwidth::Bw4));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x17), Ok(FskBandwidth::Bw5));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x0F), Ok(FskBandwidth::Bw7));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x1E), Ok(FskBandwidth::Bw9));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x16), Ok(FskBandwidth::Bw11));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x0E), Ok(FskBandwidth::Bw14));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x1D), Ok(FskBandwidth::Bw19));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x15), Ok(FskBandwidth::Bw23));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x0D), Ok(FskBandwidth::Bw29));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x1C), Ok(FskBandwidth::Bw39));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x14), Ok(FskBandwidth::Bw46));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x0C), Ok(FskBandwidth::Bw58));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x1B), Ok(FskBandwidth::Bw78));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x13), Ok(FskBandwidth::Bw93));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x0B), Ok(FskBandwidth::Bw117));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x1A), Ok(FskBandwidth::Bw156));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x12), Ok(FskBandwidth::Bw187));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x0A), Ok(FskBandwidth::Bw234));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x19), Ok(FskBandwidth::Bw312));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x11), Ok(FskBandwidth::Bw373));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x09), Ok(FskBandwidth::Bw467));
|
||||
/// assert_eq!(FskBandwidth::from_bits(0x00), Err(0x00));
|
||||
/// ```
|
||||
pub const fn from_bits(bits: u8) -> Result<Self, u8> {
|
||||
match bits {
|
||||
0x1F => Ok(Self::Bw4),
|
||||
0x17 => Ok(Self::Bw5),
|
||||
0x0F => Ok(Self::Bw7),
|
||||
0x1E => Ok(Self::Bw9),
|
||||
0x16 => Ok(Self::Bw11),
|
||||
0x0E => Ok(Self::Bw14),
|
||||
0x1D => Ok(Self::Bw19),
|
||||
0x15 => Ok(Self::Bw23),
|
||||
0x0D => Ok(Self::Bw29),
|
||||
0x1C => Ok(Self::Bw39),
|
||||
0x14 => Ok(Self::Bw46),
|
||||
0x0C => Ok(Self::Bw58),
|
||||
0x1B => Ok(Self::Bw78),
|
||||
0x13 => Ok(Self::Bw93),
|
||||
0x0B => Ok(Self::Bw117),
|
||||
0x1A => Ok(Self::Bw156),
|
||||
0x12 => Ok(Self::Bw187),
|
||||
0x0A => Ok(Self::Bw234),
|
||||
0x19 => Ok(Self::Bw312),
|
||||
0x11 => Ok(Self::Bw373),
|
||||
0x09 => Ok(Self::Bw467),
|
||||
x => Err(x),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FskBandwidth {
|
||||
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
||||
self.hertz().cmp(&other.hertz())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for FskBandwidth {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
|
||||
Some(self.hertz().cmp(&other.hertz()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Pulse shaping options for [`FskModParams`].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum FskPulseShape {
|
||||
/// No filtering applied.
|
||||
None = 0b00,
|
||||
/// Gaussian BT 0.3
|
||||
Bt03 = 0x08,
|
||||
/// Gaussian BT 0.5
|
||||
Bt05 = 0x09,
|
||||
/// Gaussian BT 0.7
|
||||
Bt07 = 0x0A,
|
||||
/// Gaussian BT 1.0
|
||||
Bt10 = 0x0B,
|
||||
}
|
||||
|
||||
/// Bitrate argument for [`FskModParams::set_bitrate`] and
|
||||
/// [`BpskModParams::set_bitrate`].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct FskBitrate {
|
||||
bits: u32,
|
||||
}
|
||||
|
||||
impl FskBitrate {
|
||||
/// Create a new `FskBitrate` from a bitrate in bits per second.
|
||||
///
|
||||
/// This the resulting value will be rounded down, and will saturate if
|
||||
/// `bps` is outside of the theoretical limits.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::FskBitrate;
|
||||
///
|
||||
/// const BITRATE: FskBitrate = FskBitrate::from_bps(9600);
|
||||
/// assert_eq!(BITRATE.as_bps(), 9600);
|
||||
/// ```
|
||||
pub const fn from_bps(bps: u32) -> Self {
|
||||
const MAX: u32 = 0x00FF_FFFF;
|
||||
if bps == 0 {
|
||||
Self { bits: MAX }
|
||||
} else {
|
||||
let bits: u32 = 32 * 32_000_000 / bps;
|
||||
if bits > MAX {
|
||||
Self { bits: MAX }
|
||||
} else {
|
||||
Self { bits }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `FskBitrate` from a raw bit value.
|
||||
///
|
||||
/// bits = 32 × 32 MHz / bitrate
|
||||
///
|
||||
/// **Note:** Only the first 24 bits of the `u32` are used, the `bits`
|
||||
/// argument will be masked.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::FskBitrate;
|
||||
///
|
||||
/// const BITRATE: FskBitrate = FskBitrate::from_raw(0x7D00);
|
||||
/// assert_eq!(BITRATE.as_bps(), 32_000);
|
||||
/// ```
|
||||
pub const fn from_raw(bits: u32) -> Self {
|
||||
Self {
|
||||
bits: bits & 0x00FF_FFFF,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the bitrate in bits per second, rounded down.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::FskBitrate;
|
||||
///
|
||||
/// const BITS_PER_SEC: u32 = 9600;
|
||||
/// const BITRATE: FskBitrate = FskBitrate::from_bps(BITS_PER_SEC);
|
||||
/// assert_eq!(BITRATE.as_bps(), BITS_PER_SEC);
|
||||
/// ```
|
||||
pub const fn as_bps(&self) -> u32 {
|
||||
if self.bits == 0 {
|
||||
0
|
||||
} else {
|
||||
32 * 32_000_000 / self.bits
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn into_bits(self) -> u32 {
|
||||
self.bits
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for FskBitrate {
|
||||
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
||||
self.as_bps().cmp(&other.as_bps())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for FskBitrate {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
|
||||
Some(self.as_bps().cmp(&other.as_bps()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Frequency deviation argument for [`FskModParams::set_fdev`]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct FskFdev {
|
||||
bits: u32,
|
||||
}
|
||||
|
||||
impl FskFdev {
|
||||
/// Create a new `FskFdev` from a frequency deviation in hertz, rounded
|
||||
/// down.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::FskFdev;
|
||||
///
|
||||
/// const FDEV: FskFdev = FskFdev::from_hertz(31_250);
|
||||
/// assert_eq!(FDEV.as_hertz(), 31_250);
|
||||
/// ```
|
||||
pub const fn from_hertz(hz: u32) -> Self {
|
||||
Self {
|
||||
bits: ((hz as u64) * (1 << 25) / 32_000_000) as u32 & 0x00FF_FFFF,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `FskFdev` from a raw bit value.
|
||||
///
|
||||
/// bits = fdev × 2<sup>25</sup> / 32 MHz
|
||||
///
|
||||
/// **Note:** Only the first 24 bits of the `u32` are used, the `bits`
|
||||
/// argument will be masked.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::FskFdev;
|
||||
///
|
||||
/// const FDEV: FskFdev = FskFdev::from_raw(0x8000);
|
||||
/// assert_eq!(FDEV.as_hertz(), 31_250);
|
||||
/// ```
|
||||
pub const fn from_raw(bits: u32) -> Self {
|
||||
Self {
|
||||
bits: bits & 0x00FF_FFFF,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the frequency deviation in hertz, rounded down.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::FskFdev;
|
||||
///
|
||||
/// const HERTZ: u32 = 31_250;
|
||||
/// const FDEV: FskFdev = FskFdev::from_hertz(HERTZ);
|
||||
/// assert_eq!(FDEV.as_hertz(), HERTZ);
|
||||
/// ```
|
||||
pub const fn as_hertz(&self) -> u32 {
|
||||
((self.bits as u64) * 32_000_000 / (1 << 25)) as u32
|
||||
}
|
||||
|
||||
pub(crate) const fn into_bits(self) -> u32 {
|
||||
self.bits
|
||||
}
|
||||
}
|
||||
|
||||
/// (G)FSK modulation paramters.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct FskModParams {
|
||||
buf: [u8; 9],
|
||||
}
|
||||
|
||||
impl FskModParams {
|
||||
/// Create a new `FskModParams` struct.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::FskModParams;
|
||||
///
|
||||
/// const MOD_PARAMS: FskModParams = FskModParams::new();
|
||||
/// ```
|
||||
pub const fn new() -> FskModParams {
|
||||
FskModParams {
|
||||
buf: [
|
||||
super::OpCode::SetModulationParams as u8,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
],
|
||||
}
|
||||
.set_bitrate(FskBitrate::from_bps(50_000))
|
||||
.set_pulse_shape(FskPulseShape::None)
|
||||
.set_bandwidth(FskBandwidth::Bw58)
|
||||
.set_fdev(FskFdev::from_hertz(25_000))
|
||||
}
|
||||
|
||||
/// Get the bitrate.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Setting the bitrate to 32,000 bits per second.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{FskBitrate, FskModParams};
|
||||
///
|
||||
/// const BITRATE: FskBitrate = FskBitrate::from_bps(32_000);
|
||||
/// const MOD_PARAMS: FskModParams = FskModParams::new().set_bitrate(BITRATE);
|
||||
/// assert_eq!(MOD_PARAMS.bitrate(), BITRATE);
|
||||
/// ```
|
||||
pub const fn bitrate(&self) -> FskBitrate {
|
||||
let raw: u32 = u32::from_be_bytes([0, self.buf[1], self.buf[2], self.buf[3]]);
|
||||
FskBitrate::from_raw(raw)
|
||||
}
|
||||
|
||||
/// Set the bitrate.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Setting the bitrate to 32,000 bits per second.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{FskBitrate, FskModParams};
|
||||
///
|
||||
/// const BITRATE: FskBitrate = FskBitrate::from_bps(32_000);
|
||||
/// const MOD_PARAMS: FskModParams = FskModParams::new().set_bitrate(BITRATE);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice()[1], 0x00);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice()[2], 0x7D);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice()[3], 0x00);
|
||||
/// ```
|
||||
#[must_use = "set_bitrate returns a modified FskModParams"]
|
||||
pub const fn set_bitrate(mut self, bitrate: FskBitrate) -> FskModParams {
|
||||
let bits: u32 = bitrate.into_bits();
|
||||
self.buf[1] = ((bits >> 16) & 0xFF) as u8;
|
||||
self.buf[2] = ((bits >> 8) & 0xFF) as u8;
|
||||
self.buf[3] = (bits & 0xFF) as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the pulse shaping.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{FskModParams, FskPulseShape};
|
||||
///
|
||||
/// const MOD_PARAMS: FskModParams = FskModParams::new().set_pulse_shape(FskPulseShape::Bt03);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice()[4], 0x08);
|
||||
/// ```
|
||||
#[must_use = "set_pulse_shape returns a modified FskModParams"]
|
||||
pub const fn set_pulse_shape(mut self, shape: FskPulseShape) -> FskModParams {
|
||||
self.buf[4] = shape as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the bandwidth.
|
||||
///
|
||||
/// Values that do not correspond to a valid [`FskBandwidth`] will be
|
||||
/// returned in the `Err` variant of the result.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{FskBandwidth, FskModParams};
|
||||
///
|
||||
/// const MOD_PARAMS: FskModParams = FskModParams::new().set_bandwidth(FskBandwidth::Bw9);
|
||||
/// assert_eq!(MOD_PARAMS.bandwidth(), Ok(FskBandwidth::Bw9));
|
||||
/// ```
|
||||
pub const fn bandwidth(&self) -> Result<FskBandwidth, u8> {
|
||||
FskBandwidth::from_bits(self.buf[5])
|
||||
}
|
||||
|
||||
/// Set the bandwidth.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{FskBandwidth, FskModParams};
|
||||
///
|
||||
/// const MOD_PARAMS: FskModParams = FskModParams::new().set_bandwidth(FskBandwidth::Bw9);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice()[5], 0x1E);
|
||||
/// ```
|
||||
#[must_use = "set_pulse_shape returns a modified FskModParams"]
|
||||
pub const fn set_bandwidth(mut self, bw: FskBandwidth) -> FskModParams {
|
||||
self.buf[5] = bw as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the frequency deviation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{FskFdev, FskModParams};
|
||||
///
|
||||
/// const FDEV: FskFdev = FskFdev::from_hertz(31_250);
|
||||
/// const MOD_PARAMS: FskModParams = FskModParams::new().set_fdev(FDEV);
|
||||
/// assert_eq!(MOD_PARAMS.fdev(), FDEV);
|
||||
/// ```
|
||||
pub const fn fdev(&self) -> FskFdev {
|
||||
let raw: u32 = u32::from_be_bytes([0, self.buf[6], self.buf[7], self.buf[8]]);
|
||||
FskFdev::from_raw(raw)
|
||||
}
|
||||
|
||||
/// Set the frequency deviation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{FskFdev, FskModParams};
|
||||
///
|
||||
/// const FDEV: FskFdev = FskFdev::from_hertz(31_250);
|
||||
/// const MOD_PARAMS: FskModParams = FskModParams::new().set_fdev(FDEV);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice()[6], 0x00);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice()[7], 0x80);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice()[8], 0x00);
|
||||
/// ```
|
||||
#[must_use = "set_fdev returns a modified FskModParams"]
|
||||
pub const fn set_fdev(mut self, fdev: FskFdev) -> FskModParams {
|
||||
let bits: u32 = fdev.into_bits();
|
||||
self.buf[6] = ((bits >> 16) & 0xFF) as u8;
|
||||
self.buf[7] = ((bits >> 8) & 0xFF) as u8;
|
||||
self.buf[8] = (bits & 0xFF) as u8;
|
||||
self
|
||||
}
|
||||
/// Returns `true` if the modulation parameters are valid.
|
||||
///
|
||||
/// The bandwidth must be chosen so that:
|
||||
///
|
||||
/// [`FskBandwidth`] > [`FskBitrate`] + 2 × [`FskFdev`] + frequency error
|
||||
///
|
||||
/// Where frequency error = 2 × HSE32<sub>FREQ</sub> error.
|
||||
///
|
||||
/// The datasheet (DS13293 Rev 1) gives these requirements for the HSE32
|
||||
/// frequency tolerance:
|
||||
///
|
||||
/// * Initial: ±10 ppm
|
||||
/// * Over temperature (-20 to 70 °C): ±10 ppm
|
||||
/// * Aging over 10 years: ±10 ppm
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Checking valid parameters at compile-time
|
||||
///
|
||||
/// ```
|
||||
/// extern crate static_assertions as sa;
|
||||
/// use stm32wl_hal::subghz::{FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape};
|
||||
///
|
||||
/// const MOD_PARAMS: FskModParams = FskModParams::new()
|
||||
/// .set_bitrate(FskBitrate::from_bps(20_000))
|
||||
/// .set_pulse_shape(FskPulseShape::Bt03)
|
||||
/// .set_bandwidth(FskBandwidth::Bw58)
|
||||
/// .set_fdev(FskFdev::from_hertz(10_000));
|
||||
///
|
||||
/// // 30 PPM is wost case (if the HSE32 crystal meets requirements)
|
||||
/// sa::const_assert!(MOD_PARAMS.is_valid(30));
|
||||
/// ```
|
||||
#[must_use = "the return value indicates if the modulation parameters are valid"]
|
||||
pub const fn is_valid(&self, ppm: u8) -> bool {
|
||||
let bw: u32 = match self.bandwidth() {
|
||||
Ok(bw) => bw.hertz(),
|
||||
Err(_) => return false,
|
||||
};
|
||||
let br: u32 = self.bitrate().as_bps();
|
||||
let fdev: u32 = self.fdev().as_hertz();
|
||||
let hse_err: u32 = 32 * (ppm as u32);
|
||||
let freq_err: u32 = 2 * hse_err;
|
||||
|
||||
bw > br + 2 * fdev + freq_err
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape};
|
||||
///
|
||||
/// const BITRATE: FskBitrate = FskBitrate::from_bps(20_000);
|
||||
/// const PULSE_SHAPE: FskPulseShape = FskPulseShape::Bt03;
|
||||
/// const BW: FskBandwidth = FskBandwidth::Bw58;
|
||||
/// const FDEV: FskFdev = FskFdev::from_hertz(10_000);
|
||||
///
|
||||
/// const MOD_PARAMS: FskModParams = FskModParams::new()
|
||||
/// .set_bitrate(BITRATE)
|
||||
/// .set_pulse_shape(PULSE_SHAPE)
|
||||
/// .set_bandwidth(BW)
|
||||
/// .set_fdev(FDEV);
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// MOD_PARAMS.as_slice(),
|
||||
/// &[0x8B, 0x00, 0xC8, 0x00, 0x08, 0x0C, 0x00, 0x28, 0xF5]
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FskModParams {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// LoRa spreading factor.
|
||||
///
|
||||
/// Argument of [`LoRaModParams::set_sf`].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum SpreadingFactor {
|
||||
/// Spreading factor 5.
|
||||
Sf5 = 0x05,
|
||||
/// Spreading factor 6.
|
||||
Sf6 = 0x06,
|
||||
/// Spreading factor 7.
|
||||
Sf7 = 0x07,
|
||||
/// Spreading factor 8.
|
||||
Sf8 = 0x08,
|
||||
/// Spreading factor 9.
|
||||
Sf9 = 0x09,
|
||||
/// Spreading factor 10.
|
||||
Sf10 = 0xA0,
|
||||
/// Spreading factor 11.
|
||||
Sf11 = 0xB0,
|
||||
/// Spreading factor 12.
|
||||
Sf12 = 0xC0,
|
||||
}
|
||||
|
||||
impl From<SpreadingFactor> for u8 {
|
||||
fn from(sf: SpreadingFactor) -> Self {
|
||||
sf as u8
|
||||
}
|
||||
}
|
||||
|
||||
/// LoRa bandwidth.
|
||||
///
|
||||
/// Argument of [`LoRaModParams::set_bw`].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum LoRaBandwidth {
|
||||
/// 7.81 kHz
|
||||
Bw7 = 0x00,
|
||||
/// 10.42 kHz
|
||||
Bw10 = 0x08,
|
||||
/// 15.63 kHz
|
||||
Bw15 = 0x01,
|
||||
/// 20.83 kHz
|
||||
Bw20 = 0x09,
|
||||
/// 31.25 kHz
|
||||
Bw31 = 0x02,
|
||||
/// 41.67 kHz
|
||||
Bw41 = 0x0A,
|
||||
/// 62.50 kHz
|
||||
Bw62 = 0x03,
|
||||
/// 125 kHz
|
||||
Bw125 = 0x04,
|
||||
/// 250 kHz
|
||||
Bw250 = 0x05,
|
||||
/// 500 kHz
|
||||
Bw500 = 0x06,
|
||||
}
|
||||
|
||||
impl LoRaBandwidth {
|
||||
/// Get the bandwidth in hertz.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::LoRaBandwidth;
|
||||
///
|
||||
/// assert_eq!(LoRaBandwidth::Bw7.hertz(), 7_810);
|
||||
/// assert_eq!(LoRaBandwidth::Bw10.hertz(), 10_420);
|
||||
/// assert_eq!(LoRaBandwidth::Bw15.hertz(), 15_630);
|
||||
/// assert_eq!(LoRaBandwidth::Bw20.hertz(), 20_830);
|
||||
/// assert_eq!(LoRaBandwidth::Bw31.hertz(), 31_250);
|
||||
/// assert_eq!(LoRaBandwidth::Bw41.hertz(), 41_670);
|
||||
/// assert_eq!(LoRaBandwidth::Bw62.hertz(), 62_500);
|
||||
/// assert_eq!(LoRaBandwidth::Bw125.hertz(), 125_000);
|
||||
/// assert_eq!(LoRaBandwidth::Bw250.hertz(), 250_000);
|
||||
/// assert_eq!(LoRaBandwidth::Bw500.hertz(), 500_000);
|
||||
/// ```
|
||||
pub const fn hertz(&self) -> u32 {
|
||||
match self {
|
||||
LoRaBandwidth::Bw7 => 7_810,
|
||||
LoRaBandwidth::Bw10 => 10_420,
|
||||
LoRaBandwidth::Bw15 => 15_630,
|
||||
LoRaBandwidth::Bw20 => 20_830,
|
||||
LoRaBandwidth::Bw31 => 31_250,
|
||||
LoRaBandwidth::Bw41 => 41_670,
|
||||
LoRaBandwidth::Bw62 => 62_500,
|
||||
LoRaBandwidth::Bw125 => 125_000,
|
||||
LoRaBandwidth::Bw250 => 250_000,
|
||||
LoRaBandwidth::Bw500 => 500_000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for LoRaBandwidth {
|
||||
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
||||
self.hertz().cmp(&other.hertz())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for LoRaBandwidth {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
|
||||
Some(self.hertz().cmp(&other.hertz()))
|
||||
}
|
||||
}
|
||||
|
||||
/// LoRa forward error correction coding rate.
|
||||
///
|
||||
/// Argument of [`LoRaModParams::set_cr`].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum CodingRate {
|
||||
/// No forward error correction coding rate 4/4
|
||||
Cr44 = 0x00,
|
||||
/// Forward error correction coding rate 4/5
|
||||
Cr45 = 0x1,
|
||||
/// Forward error correction coding rate 4/6
|
||||
Cr46 = 0x2,
|
||||
/// Forward error correction coding rate 4/7
|
||||
Cr47 = 0x3,
|
||||
/// Forward error correction coding rate 4/8
|
||||
Cr48 = 0x4,
|
||||
}
|
||||
|
||||
/// LoRa modulation paramters.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
|
||||
pub struct LoRaModParams {
|
||||
buf: [u8; 5],
|
||||
}
|
||||
|
||||
impl LoRaModParams {
|
||||
/// Create a new `LoRaModParams` struct.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::LoRaModParams;
|
||||
///
|
||||
/// const MOD_PARAMS: LoRaModParams = LoRaModParams::new();
|
||||
/// assert_eq!(MOD_PARAMS, LoRaModParams::default());
|
||||
/// ```
|
||||
pub const fn new() -> LoRaModParams {
|
||||
LoRaModParams {
|
||||
buf: [
|
||||
super::OpCode::SetModulationParams as u8,
|
||||
0x05, // valid spreading factor
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the spreading factor.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{LoRaModParams, SpreadingFactor};
|
||||
///
|
||||
/// const MOD_PARAMS: LoRaModParams = LoRaModParams::new().set_sf(SpreadingFactor::Sf7);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x07, 0x00, 0x00, 0x00]);
|
||||
/// ```
|
||||
#[must_use = "set_sf returns a modified LoRaModParams"]
|
||||
pub const fn set_sf(mut self, sf: SpreadingFactor) -> Self {
|
||||
self.buf[1] = sf as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the bandwidth.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{LoRaBandwidth, LoRaModParams};
|
||||
///
|
||||
/// const MOD_PARAMS: LoRaModParams = LoRaModParams::new().set_bw(LoRaBandwidth::Bw125);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x05, 0x04, 0x00, 0x00]);
|
||||
/// ```
|
||||
#[must_use = "set_bw returns a modified LoRaModParams"]
|
||||
pub const fn set_bw(mut self, bw: LoRaBandwidth) -> Self {
|
||||
self.buf[2] = bw as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the forward error correction coding rate.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CodingRate, LoRaModParams};
|
||||
///
|
||||
/// const MOD_PARAMS: LoRaModParams = LoRaModParams::new().set_cr(CodingRate::Cr45);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x05, 0x00, 0x01, 0x00]);
|
||||
/// ```
|
||||
#[must_use = "set_cr returns a modified LoRaModParams"]
|
||||
pub const fn set_cr(mut self, cr: CodingRate) -> Self {
|
||||
self.buf[3] = cr as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set low data rate optimization enable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::LoRaModParams;
|
||||
///
|
||||
/// const MOD_PARAMS: LoRaModParams = LoRaModParams::new().set_ldro_en(true);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x05, 0x00, 0x00, 0x01]);
|
||||
/// ```
|
||||
#[must_use = "set_ldro_en returns a modified LoRaModParams"]
|
||||
pub const fn set_ldro_en(mut self, en: bool) -> Self {
|
||||
self.buf[4] = en as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CodingRate, LoRaBandwidth, LoRaModParams, SpreadingFactor};
|
||||
///
|
||||
/// const MOD_PARAMS: LoRaModParams = LoRaModParams::new()
|
||||
/// .set_sf(SpreadingFactor::Sf7)
|
||||
/// .set_bw(LoRaBandwidth::Bw125)
|
||||
/// .set_cr(CodingRate::Cr45)
|
||||
/// .set_ldro_en(false);
|
||||
///
|
||||
/// assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x07, 0x04, 0x01, 0x00]);
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoRaModParams {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// BPSK modulation paramters.
|
||||
///
|
||||
/// **Note:** There is no method to set the pulse shape because there is only
|
||||
/// one valid pulse shape (Gaussian BT 0.5).
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct BpskModParams {
|
||||
buf: [u8; 5],
|
||||
}
|
||||
|
||||
impl BpskModParams {
|
||||
/// Create a new `BpskModParams` struct.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::BpskModParams;
|
||||
///
|
||||
/// const MOD_PARAMS: BpskModParams = BpskModParams::new();
|
||||
/// assert_eq!(MOD_PARAMS, BpskModParams::default());
|
||||
/// ```
|
||||
pub const fn new() -> BpskModParams {
|
||||
const OPCODE: u8 = super::OpCode::SetModulationParams as u8;
|
||||
BpskModParams {
|
||||
buf: [OPCODE, 0x1A, 0x0A, 0xAA, 0x16],
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the bitrate.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Setting the bitrate to 600 bits per second.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{BpskModParams, FskBitrate};
|
||||
///
|
||||
/// const BITRATE: FskBitrate = FskBitrate::from_bps(600);
|
||||
/// const MOD_PARAMS: BpskModParams = BpskModParams::new().set_bitrate(BITRATE);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice()[1], 0x1A);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice()[2], 0x0A);
|
||||
/// # assert_eq!(MOD_PARAMS.as_slice()[3], 0xAA);
|
||||
/// ```
|
||||
#[must_use = "set_bitrate returns a modified BpskModParams"]
|
||||
pub const fn set_bitrate(mut self, bitrate: FskBitrate) -> BpskModParams {
|
||||
let bits: u32 = bitrate.into_bits();
|
||||
self.buf[1] = ((bits >> 16) & 0xFF) as u8;
|
||||
self.buf[2] = ((bits >> 8) & 0xFF) as u8;
|
||||
self.buf[3] = (bits & 0xFF) as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{BpskModParams, FskBitrate};
|
||||
///
|
||||
/// const BITRATE: FskBitrate = FskBitrate::from_bps(100);
|
||||
/// const MOD_PARAMS: BpskModParams = BpskModParams::new().set_bitrate(BITRATE);
|
||||
/// assert_eq!(MOD_PARAMS.as_slice(), [0x8B, 0x9C, 0x40, 0x00, 0x16]);
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BpskModParams {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{FskBandwidth, FskBitrate, FskFdev, LoRaBandwidth};
|
||||
|
||||
#[test]
|
||||
fn fsk_bw_ord() {
|
||||
assert!((FskBandwidth::Bw4 as u8) > (FskBandwidth::Bw5 as u8));
|
||||
assert!(FskBandwidth::Bw4 < FskBandwidth::Bw5);
|
||||
assert!(FskBandwidth::Bw5 > FskBandwidth::Bw4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lora_bw_ord() {
|
||||
assert!((LoRaBandwidth::Bw10 as u8) > (LoRaBandwidth::Bw15 as u8));
|
||||
assert!(LoRaBandwidth::Bw10 < LoRaBandwidth::Bw15);
|
||||
assert!(LoRaBandwidth::Bw15 > LoRaBandwidth::Bw10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fsk_bitrate_ord() {
|
||||
assert!(FskBitrate::from_bps(9600) > FskBitrate::from_bps(4800));
|
||||
assert!(FskBitrate::from_bps(4800) < FskBitrate::from_bps(9600));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fsk_bitrate_as_bps_limits() {
|
||||
const ZERO: FskBitrate = FskBitrate::from_raw(0);
|
||||
const ONE: FskBitrate = FskBitrate::from_raw(1);
|
||||
const MAX: FskBitrate = FskBitrate::from_raw(u32::MAX);
|
||||
|
||||
assert_eq!(ZERO.as_bps(), 0);
|
||||
assert_eq!(ONE.as_bps(), 1_024_000_000);
|
||||
assert_eq!(MAX.as_bps(), 61);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fsk_bitrate_from_bps_limits() {
|
||||
const ZERO: FskBitrate = FskBitrate::from_bps(0);
|
||||
const ONE: FskBitrate = FskBitrate::from_bps(1);
|
||||
const MAX: FskBitrate = FskBitrate::from_bps(u32::MAX);
|
||||
|
||||
assert_eq!(ZERO.as_bps(), 61);
|
||||
assert_eq!(ONE.as_bps(), 61);
|
||||
assert_eq!(MAX.as_bps(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fsk_fdev_ord() {
|
||||
assert!(FskFdev::from_hertz(30_000) > FskFdev::from_hertz(20_000));
|
||||
assert!(FskFdev::from_hertz(20_000) < FskFdev::from_hertz(30_000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fsk_fdev_as_hertz_limits() {
|
||||
const ZERO: FskFdev = FskFdev::from_raw(0);
|
||||
const ONE: FskFdev = FskFdev::from_raw(1);
|
||||
const MAX: FskFdev = FskFdev::from_raw(u32::MAX);
|
||||
|
||||
assert_eq!(ZERO.as_hertz(), 0);
|
||||
assert_eq!(ONE.as_hertz(), 0);
|
||||
assert_eq!(MAX.as_hertz(), 15_999_999);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fsk_fdev_from_hertz_limits() {
|
||||
const ZERO: FskFdev = FskFdev::from_hertz(0);
|
||||
const ONE: FskFdev = FskFdev::from_hertz(1);
|
||||
const MAX: FskFdev = FskFdev::from_hertz(u32::MAX);
|
||||
|
||||
assert_eq!(ZERO.as_hertz(), 0);
|
||||
assert_eq!(ONE.as_hertz(), 0);
|
||||
assert_eq!(MAX.as_hertz(), 6_967_294);
|
||||
}
|
||||
}
|
14
embassy-stm32/src/subghz/ocp.rs
Normal file
14
embassy-stm32/src/subghz/ocp.rs
Normal file
@ -0,0 +1,14 @@
|
||||
/// Power amplifier over current protection.
|
||||
///
|
||||
/// Used by [`set_pa_ocp`].
|
||||
///
|
||||
/// [`set_pa_ocp`]: crate::subghz::SubGhz::set_pa_ocp
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum Ocp {
|
||||
/// Maximum 60mA current for LP PA mode.
|
||||
Max60m = 0x18,
|
||||
/// Maximum 140mA for HP PA mode.
|
||||
Max140m = 0x38,
|
||||
}
|
48
embassy-stm32/src/subghz/op_error.rs
Normal file
48
embassy-stm32/src/subghz/op_error.rs
Normal file
@ -0,0 +1,48 @@
|
||||
/// Operation Errors.
|
||||
///
|
||||
/// Returned by [`op_error`].
|
||||
///
|
||||
/// [`op_error`]: crate::subghz::SubGhz::op_error
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum OpError {
|
||||
/// PA ramping failed
|
||||
PaRampError = 8,
|
||||
/// RF-PLL locking failed
|
||||
PllLockError = 6,
|
||||
/// HSE32 clock startup failed
|
||||
XoscStartError = 5,
|
||||
/// Image calibration failed
|
||||
ImageCalibrationError = 4,
|
||||
/// RF-ADC calibration failed
|
||||
AdcCalibrationError = 3,
|
||||
/// RF-PLL calibration failed
|
||||
PllCalibrationError = 2,
|
||||
/// Sub-GHz radio RC 13 MHz oscillator
|
||||
RC13MCalibrationError = 1,
|
||||
/// Sub-GHz radio RC 64 kHz oscillator
|
||||
RC64KCalibrationError = 0,
|
||||
}
|
||||
|
||||
impl OpError {
|
||||
/// Get the bitmask for the error.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::OpError;
|
||||
///
|
||||
/// assert_eq!(OpError::PaRampError.mask(), 0b1_0000_0000);
|
||||
/// assert_eq!(OpError::PllLockError.mask(), 0b0_0100_0000);
|
||||
/// assert_eq!(OpError::XoscStartError.mask(), 0b0_0010_0000);
|
||||
/// assert_eq!(OpError::ImageCalibrationError.mask(), 0b0_0001_0000);
|
||||
/// assert_eq!(OpError::AdcCalibrationError.mask(), 0b0_0000_1000);
|
||||
/// assert_eq!(OpError::PllCalibrationError.mask(), 0b0_0000_0100);
|
||||
/// assert_eq!(OpError::RC13MCalibrationError.mask(), 0b0_0000_0010);
|
||||
/// assert_eq!(OpError::RC64KCalibrationError.mask(), 0b0_0000_0001);
|
||||
/// ```
|
||||
pub const fn mask(self) -> u16 {
|
||||
1 << (self as u8)
|
||||
}
|
||||
}
|
161
embassy-stm32/src/subghz/pa_config.rs
Normal file
161
embassy-stm32/src/subghz/pa_config.rs
Normal file
@ -0,0 +1,161 @@
|
||||
/// Power amplifier configuration paramters.
|
||||
///
|
||||
/// Argument of [`set_pa_config`].
|
||||
///
|
||||
/// [`set_pa_config`]: crate::subghz::SubGhz::set_pa_config
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct PaConfig {
|
||||
buf: [u8; 5],
|
||||
}
|
||||
|
||||
impl PaConfig {
|
||||
/// Create a new `PaConfig` struct.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::PaConfig;
|
||||
///
|
||||
/// const PA_CONFIG: PaConfig = PaConfig::new();
|
||||
/// ```
|
||||
pub const fn new() -> PaConfig {
|
||||
PaConfig {
|
||||
buf: [super::OpCode::SetPaConfig as u8, 0x01, 0x00, 0x01, 0x01],
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the power amplifier duty cycle (conduit angle) control.
|
||||
///
|
||||
/// **Note:** Only the first 3 bits of the `pa_duty_cycle` argument are used.
|
||||
///
|
||||
/// Duty cycle = 0.2 + 0.04 × bits
|
||||
///
|
||||
/// # Caution
|
||||
///
|
||||
/// The following restrictions must be observed to avoid over-stress on the PA:
|
||||
/// * LP PA mode with synthesis frequency > 400 MHz, PaDutyCycle must be < 0x7.
|
||||
/// * LP PA mode with synthesis frequency < 400 MHz, PaDutyCycle must be < 0x4.
|
||||
/// * HP PA mode, PaDutyCycle must be < 0x4
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{PaConfig, PaSel};
|
||||
///
|
||||
/// const PA_CONFIG: PaConfig = PaConfig::new().set_pa(PaSel::Lp).set_pa_duty_cycle(0x4);
|
||||
/// # assert_eq!(PA_CONFIG.as_slice()[1], 0x04);
|
||||
/// ```
|
||||
#[must_use = "set_pa_duty_cycle returns a modified PaConfig"]
|
||||
pub const fn set_pa_duty_cycle(mut self, pa_duty_cycle: u8) -> PaConfig {
|
||||
self.buf[1] = pa_duty_cycle & 0b111;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the high power amplifier output power.
|
||||
///
|
||||
/// **Note:** Only the first 3 bits of the `hp_max` argument are used.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{PaConfig, PaSel};
|
||||
///
|
||||
/// const PA_CONFIG: PaConfig = PaConfig::new().set_pa(PaSel::Hp).set_hp_max(0x2);
|
||||
/// # assert_eq!(PA_CONFIG.as_slice()[2], 0x02);
|
||||
/// ```
|
||||
#[must_use = "set_hp_max returns a modified PaConfig"]
|
||||
pub const fn set_hp_max(mut self, hp_max: u8) -> PaConfig {
|
||||
self.buf[2] = hp_max & 0b111;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the power amplifier to use, low or high power.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{PaConfig, PaSel};
|
||||
///
|
||||
/// const PA_CONFIG_HP: PaConfig = PaConfig::new().set_pa(PaSel::Hp);
|
||||
/// const PA_CONFIG_LP: PaConfig = PaConfig::new().set_pa(PaSel::Lp);
|
||||
/// # assert_eq!(PA_CONFIG_HP.as_slice()[3], 0x00);
|
||||
/// # assert_eq!(PA_CONFIG_LP.as_slice()[3], 0x01);
|
||||
/// ```
|
||||
#[must_use = "set_pa returns a modified PaConfig"]
|
||||
pub const fn set_pa(mut self, pa: PaSel) -> PaConfig {
|
||||
self.buf[3] = pa as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{PaConfig, PaSel};
|
||||
///
|
||||
/// const PA_CONFIG: PaConfig = PaConfig::new()
|
||||
/// .set_pa(PaSel::Hp)
|
||||
/// .set_pa_duty_cycle(0x2)
|
||||
/// .set_hp_max(0x3);
|
||||
///
|
||||
/// assert_eq!(PA_CONFIG.as_slice(), &[0x95, 0x2, 0x03, 0x00, 0x01]);
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PaConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Power amplifier selection.
|
||||
///
|
||||
/// Argument of [`PaConfig::set_pa`].
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum PaSel {
|
||||
/// High power amplifier.
|
||||
Hp = 0b0,
|
||||
/// Low power amplifier.
|
||||
Lp = 0b1,
|
||||
}
|
||||
|
||||
impl PartialOrd for PaSel {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for PaSel {
|
||||
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
||||
match (self, other) {
|
||||
(PaSel::Hp, PaSel::Hp) | (PaSel::Lp, PaSel::Lp) => core::cmp::Ordering::Equal,
|
||||
(PaSel::Hp, PaSel::Lp) => core::cmp::Ordering::Greater,
|
||||
(PaSel::Lp, PaSel::Hp) => core::cmp::Ordering::Less,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PaSel {
|
||||
fn default() -> Self {
|
||||
PaSel::Lp
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::PaSel;
|
||||
|
||||
#[test]
|
||||
fn pa_sel_ord() {
|
||||
assert!(PaSel::Lp < PaSel::Hp);
|
||||
assert!(PaSel::Hp > PaSel::Lp);
|
||||
}
|
||||
}
|
537
embassy-stm32/src/subghz/packet_params.rs
Normal file
537
embassy-stm32/src/subghz/packet_params.rs
Normal file
@ -0,0 +1,537 @@
|
||||
/// Preamble detection length for [`GenericPacketParams`].
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PreambleDetection {
|
||||
/// Preamble detection disabled.
|
||||
Disabled = 0x0,
|
||||
/// 8-bit preamble detection.
|
||||
Bit8 = 0x4,
|
||||
/// 16-bit preamble detection.
|
||||
Bit16 = 0x5,
|
||||
/// 24-bit preamble detection.
|
||||
Bit24 = 0x6,
|
||||
/// 32-bit preamble detection.
|
||||
Bit32 = 0x7,
|
||||
}
|
||||
|
||||
/// Address comparison/filtering for [`GenericPacketParams`].
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum AddrComp {
|
||||
/// Address comparison/filtering disabled.
|
||||
Disabled = 0x0,
|
||||
/// Address comparison/filtering on node address.
|
||||
Node = 0x1,
|
||||
/// Address comparison/filtering on node and broadcast addresses.
|
||||
Broadcast = 0x2,
|
||||
}
|
||||
|
||||
/// Packet header type.
|
||||
///
|
||||
/// Argument of [`GenericPacketParams::set_header_type`] and
|
||||
/// [`LoRaPacketParams::set_header_type`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum HeaderType {
|
||||
/// Fixed; payload length and header field not added to packet.
|
||||
Fixed,
|
||||
/// Variable; payload length and header field added to packet.
|
||||
Variable,
|
||||
}
|
||||
|
||||
impl HeaderType {
|
||||
pub(crate) const fn to_bits_generic(self) -> u8 {
|
||||
match self {
|
||||
HeaderType::Fixed => 0,
|
||||
HeaderType::Variable => 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn to_bits_lora(self) -> u8 {
|
||||
match self {
|
||||
HeaderType::Fixed => 1,
|
||||
HeaderType::Variable => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// CRC type definition for [`GenericPacketParams`].
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum CrcType {
|
||||
/// 1-byte CRC.
|
||||
Byte1 = 0x0,
|
||||
/// CRC disabled.
|
||||
Disabled = 0x1,
|
||||
/// 2-byte CRC.
|
||||
Byte2 = 0x2,
|
||||
/// 1-byte inverted CRC.
|
||||
Byte1Inverted = 0x4,
|
||||
/// 2-byte inverted CRC.
|
||||
Byte2Inverted = 0x6,
|
||||
}
|
||||
|
||||
/// Packet parameters for [`set_packet_params`].
|
||||
///
|
||||
/// [`set_packet_params`]: crate::subghz::SubGhz::set_packet_params
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct GenericPacketParams {
|
||||
buf: [u8; 10],
|
||||
}
|
||||
|
||||
impl GenericPacketParams {
|
||||
/// Create a new `GenericPacketParams`.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::GenericPacketParams;
|
||||
///
|
||||
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new();
|
||||
/// assert_eq!(PKT_PARAMS, GenericPacketParams::default());
|
||||
/// ```
|
||||
pub const fn new() -> GenericPacketParams {
|
||||
const OPCODE: u8 = super::OpCode::SetPacketParams as u8;
|
||||
// const variable ensure the compile always optimizes the methods
|
||||
const NEW: GenericPacketParams = GenericPacketParams {
|
||||
buf: [OPCODE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||||
}
|
||||
.set_preamble_len(1)
|
||||
.set_preamble_detection(PreambleDetection::Disabled)
|
||||
.set_sync_word_len(0)
|
||||
.set_addr_comp(AddrComp::Disabled)
|
||||
.set_header_type(HeaderType::Fixed)
|
||||
.set_payload_len(1);
|
||||
|
||||
NEW
|
||||
}
|
||||
|
||||
/// Preamble length in number of symbols.
|
||||
///
|
||||
/// Values of zero are invalid, and will automatically be set to 1.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::GenericPacketParams;
|
||||
///
|
||||
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_preamble_len(0x1234);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[1], 0x12);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[2], 0x34);
|
||||
/// ```
|
||||
#[must_use = "preamble_length returns a modified GenericPacketParams"]
|
||||
pub const fn set_preamble_len(mut self, mut len: u16) -> GenericPacketParams {
|
||||
if len == 0 {
|
||||
len = 1
|
||||
}
|
||||
self.buf[1] = ((len >> 8) & 0xFF) as u8;
|
||||
self.buf[2] = (len & 0xFF) as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Preabmle detection length in number of bit symbols.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{GenericPacketParams, PreambleDetection};
|
||||
///
|
||||
/// const PKT_PARAMS: GenericPacketParams =
|
||||
/// GenericPacketParams::new().set_preamble_detection(PreambleDetection::Bit8);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[3], 0x4);
|
||||
/// ```
|
||||
#[must_use = "set_preamble_detection returns a modified GenericPacketParams"]
|
||||
pub const fn set_preamble_detection(
|
||||
mut self,
|
||||
pb_det: PreambleDetection,
|
||||
) -> GenericPacketParams {
|
||||
self.buf[3] = pb_det as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sync word length in number of bit symbols.
|
||||
///
|
||||
/// Valid values are `0x00` - `0x40` for 0 to 64-bits respectively.
|
||||
/// Values that exceed the maximum will saturate at `0x40`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Set the sync word length to 4 bytes (16 bits).
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::GenericPacketParams;
|
||||
///
|
||||
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_sync_word_len(16);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[4], 0x10);
|
||||
/// ```
|
||||
#[must_use = "set_sync_word_len returns a modified GenericPacketParams"]
|
||||
pub const fn set_sync_word_len(mut self, len: u8) -> GenericPacketParams {
|
||||
const MAX: u8 = 0x40;
|
||||
if len > MAX {
|
||||
self.buf[4] = MAX;
|
||||
} else {
|
||||
self.buf[4] = len;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Address comparison/filtering.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Enable address on the node address.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{AddrComp, GenericPacketParams};
|
||||
///
|
||||
/// const PKT_PARAMS: GenericPacketParams =
|
||||
/// GenericPacketParams::new().set_addr_comp(AddrComp::Node);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[5], 0x01);
|
||||
/// ```
|
||||
#[must_use = "set_addr_comp returns a modified GenericPacketParams"]
|
||||
pub const fn set_addr_comp(mut self, addr_comp: AddrComp) -> GenericPacketParams {
|
||||
self.buf[5] = addr_comp as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Header type definition.
|
||||
///
|
||||
/// **Note:** The reference manual calls this packet type, but that results
|
||||
/// in a conflicting variable name for the modulation scheme, which the
|
||||
/// reference manual also calls packet type.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Set the header type to a variable length.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{GenericPacketParams, HeaderType};
|
||||
///
|
||||
/// const PKT_PARAMS: GenericPacketParams =
|
||||
/// GenericPacketParams::new().set_header_type(HeaderType::Variable);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[6], 0x01);
|
||||
/// ```
|
||||
#[must_use = "set_header_type returns a modified GenericPacketParams"]
|
||||
pub const fn set_header_type(mut self, header_type: HeaderType) -> GenericPacketParams {
|
||||
self.buf[6] = header_type.to_bits_generic();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the payload length in bytes.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::GenericPacketParams;
|
||||
///
|
||||
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_payload_len(12);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[7], 12);
|
||||
/// ```
|
||||
#[must_use = "set_payload_len returns a modified GenericPacketParams"]
|
||||
pub const fn set_payload_len(mut self, len: u8) -> GenericPacketParams {
|
||||
self.buf[7] = len;
|
||||
self
|
||||
}
|
||||
|
||||
/// CRC type definition.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CrcType, GenericPacketParams};
|
||||
///
|
||||
/// const PKT_PARAMS: GenericPacketParams =
|
||||
/// GenericPacketParams::new().set_crc_type(CrcType::Byte2Inverted);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[8], 0x6);
|
||||
/// ```
|
||||
#[must_use = "set_payload_len returns a modified GenericPacketParams"]
|
||||
pub const fn set_crc_type(mut self, crc_type: CrcType) -> GenericPacketParams {
|
||||
self.buf[8] = crc_type as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Whitening enable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Enable whitening.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::GenericPacketParams;
|
||||
///
|
||||
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_whitening_enable(true);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[9], 1);
|
||||
/// ```
|
||||
#[must_use = "set_whitening_enable returns a modified GenericPacketParams"]
|
||||
pub const fn set_whitening_enable(mut self, en: bool) -> GenericPacketParams {
|
||||
self.buf[9] = en as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{
|
||||
/// AddrComp, CrcType, GenericPacketParams, HeaderType, PreambleDetection,
|
||||
/// };
|
||||
///
|
||||
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new()
|
||||
/// .set_preamble_len(8)
|
||||
/// .set_preamble_detection(PreambleDetection::Disabled)
|
||||
/// .set_sync_word_len(2)
|
||||
/// .set_addr_comp(AddrComp::Disabled)
|
||||
/// .set_header_type(HeaderType::Fixed)
|
||||
/// .set_payload_len(128)
|
||||
/// .set_crc_type(CrcType::Byte2)
|
||||
/// .set_whitening_enable(true);
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// PKT_PARAMS.as_slice(),
|
||||
/// &[0x8C, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x80, 0x02, 0x01]
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GenericPacketParams {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Packet parameters for [`set_lora_packet_params`].
|
||||
///
|
||||
/// [`set_lora_packet_params`]: crate::subghz::SubGhz::set_lora_packet_params
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct LoRaPacketParams {
|
||||
buf: [u8; 7],
|
||||
}
|
||||
|
||||
impl LoRaPacketParams {
|
||||
/// Create a new `GenericPacketParams`.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::LoRaPacketParams;
|
||||
///
|
||||
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new();
|
||||
/// assert_eq!(PKT_PARAMS, LoRaPacketParams::default());
|
||||
/// ```
|
||||
pub const fn new() -> LoRaPacketParams {
|
||||
const OPCODE: u8 = super::OpCode::SetPacketParams as u8;
|
||||
// const variable ensure the compile always optimizes the methods
|
||||
const NEW: LoRaPacketParams = LoRaPacketParams {
|
||||
buf: [OPCODE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||||
}
|
||||
.set_preamble_len(1)
|
||||
.set_header_type(HeaderType::Fixed)
|
||||
.set_payload_len(1)
|
||||
.set_crc_en(true)
|
||||
.set_invert_iq(false);
|
||||
|
||||
NEW
|
||||
}
|
||||
|
||||
/// Preamble length in number of symbols.
|
||||
///
|
||||
/// Values of zero are invalid, and will automatically be set to 1.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::LoRaPacketParams;
|
||||
///
|
||||
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_preamble_len(0x1234);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[1], 0x12);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[2], 0x34);
|
||||
/// ```
|
||||
#[must_use = "preamble_length returns a modified LoRaPacketParams"]
|
||||
pub const fn set_preamble_len(mut self, mut len: u16) -> LoRaPacketParams {
|
||||
if len == 0 {
|
||||
len = 1
|
||||
}
|
||||
self.buf[1] = ((len >> 8) & 0xFF) as u8;
|
||||
self.buf[2] = (len & 0xFF) as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Header type (fixed or variable).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Set the payload type to a fixed length.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{HeaderType, LoRaPacketParams};
|
||||
///
|
||||
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_header_type(HeaderType::Fixed);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[3], 0x01);
|
||||
/// ```
|
||||
#[must_use = "set_header_type returns a modified LoRaPacketParams"]
|
||||
pub const fn set_header_type(mut self, header_type: HeaderType) -> LoRaPacketParams {
|
||||
self.buf[3] = header_type.to_bits_lora();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the payload length in bytes.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::LoRaPacketParams;
|
||||
///
|
||||
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_payload_len(12);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[4], 12);
|
||||
/// ```
|
||||
#[must_use = "set_payload_len returns a modified LoRaPacketParams"]
|
||||
pub const fn set_payload_len(mut self, len: u8) -> LoRaPacketParams {
|
||||
self.buf[4] = len;
|
||||
self
|
||||
}
|
||||
|
||||
/// CRC enable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Enable CRC.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::LoRaPacketParams;
|
||||
///
|
||||
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_crc_en(true);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[5], 0x1);
|
||||
/// ```
|
||||
#[must_use = "set_crc_en returns a modified LoRaPacketParams"]
|
||||
pub const fn set_crc_en(mut self, en: bool) -> LoRaPacketParams {
|
||||
self.buf[5] = en as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// IQ setup.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Use an inverted IQ setup.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::LoRaPacketParams;
|
||||
///
|
||||
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_invert_iq(true);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[6], 0x1);
|
||||
/// ```
|
||||
#[must_use = "set_invert_iq returns a modified LoRaPacketParams"]
|
||||
pub const fn set_invert_iq(mut self, invert: bool) -> LoRaPacketParams {
|
||||
self.buf[6] = invert as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{HeaderType, LoRaPacketParams};
|
||||
///
|
||||
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new()
|
||||
/// .set_preamble_len(5 * 8)
|
||||
/// .set_header_type(HeaderType::Fixed)
|
||||
/// .set_payload_len(64)
|
||||
/// .set_crc_en(true)
|
||||
/// .set_invert_iq(true);
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// PKT_PARAMS.as_slice(),
|
||||
/// &[0x8C, 0x00, 0x28, 0x01, 0x40, 0x01, 0x01]
|
||||
/// );
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LoRaPacketParams {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Packet parameters for [`set_lora_packet_params`].
|
||||
///
|
||||
/// [`set_lora_packet_params`]: crate::subghz::SubGhz::set_lora_packet_params
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct BpskPacketParams {
|
||||
buf: [u8; 2],
|
||||
}
|
||||
|
||||
impl BpskPacketParams {
|
||||
/// Create a new `BpskPacketParams`.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::BpskPacketParams;
|
||||
///
|
||||
/// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new();
|
||||
/// assert_eq!(PKT_PARAMS, BpskPacketParams::default());
|
||||
/// ```
|
||||
pub const fn new() -> BpskPacketParams {
|
||||
BpskPacketParams {
|
||||
buf: [super::OpCode::SetPacketParams as u8, 0x00],
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the payload length in bytes.
|
||||
///
|
||||
/// The length includes preamble, sync word, device ID, and CRC.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::BpskPacketParams;
|
||||
///
|
||||
/// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new().set_payload_len(12);
|
||||
/// # assert_eq!(PKT_PARAMS.as_slice()[1], 12);
|
||||
/// ```
|
||||
#[must_use = "set_payload_len returns a modified BpskPacketParams"]
|
||||
pub const fn set_payload_len(mut self, len: u8) -> BpskPacketParams {
|
||||
self.buf[1] = len;
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{BpskPacketParams, HeaderType};
|
||||
///
|
||||
/// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new().set_payload_len(24);
|
||||
///
|
||||
/// assert_eq!(PKT_PARAMS.as_slice(), &[0x8C, 24]);
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BpskPacketParams {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
279
embassy-stm32/src/subghz/packet_status.rs
Normal file
279
embassy-stm32/src/subghz/packet_status.rs
Normal file
@ -0,0 +1,279 @@
|
||||
use embassy_hal_common::ratio::Ratio;
|
||||
|
||||
use crate::subghz::status::Status;
|
||||
|
||||
/// (G)FSK packet status.
|
||||
///
|
||||
/// Returned by [`fsk_packet_status`].
|
||||
///
|
||||
/// [`fsk_packet_status`]: crate::subghz::SubGhz::fsk_packet_status
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct FskPacketStatus {
|
||||
buf: [u8; 4],
|
||||
}
|
||||
|
||||
impl From<[u8; 4]> for FskPacketStatus {
|
||||
fn from(buf: [u8; 4]) -> Self {
|
||||
FskPacketStatus { buf }
|
||||
}
|
||||
}
|
||||
|
||||
impl FskPacketStatus {
|
||||
/// Get the status.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CmdStatus, FskPacketStatus, Status, StatusMode};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 4] = [0x54, 0, 0, 0];
|
||||
/// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio);
|
||||
/// let status: Status = pkt_status.status();
|
||||
/// assert_eq!(status.mode(), Ok(StatusMode::Rx));
|
||||
/// assert_eq!(status.cmd(), Ok(CmdStatus::Avaliable));
|
||||
/// ```
|
||||
pub const fn status(&self) -> Status {
|
||||
Status::from_raw(self.buf[0])
|
||||
}
|
||||
|
||||
/// Returns `true` if a preabmle error occured.
|
||||
pub const fn preamble_error(&self) -> bool {
|
||||
(self.buf[1] & (1 << 7)) != 0
|
||||
}
|
||||
|
||||
/// Returns `true` if a synchronization error occured.
|
||||
pub const fn sync_err(&self) -> bool {
|
||||
(self.buf[1] & (1 << 6)) != 0
|
||||
}
|
||||
|
||||
/// Returns `true` if an address error occured.
|
||||
pub const fn adrs_err(&self) -> bool {
|
||||
(self.buf[1] & (1 << 5)) != 0
|
||||
}
|
||||
|
||||
/// Returns `true` if an crc error occured.
|
||||
pub const fn crc_err(&self) -> bool {
|
||||
(self.buf[1] & (1 << 4)) != 0
|
||||
}
|
||||
|
||||
/// Returns `true` if a length error occured.
|
||||
pub const fn length_err(&self) -> bool {
|
||||
(self.buf[1] & (1 << 3)) != 0
|
||||
}
|
||||
|
||||
/// Returns `true` if an abort error occured.
|
||||
pub const fn abort_err(&self) -> bool {
|
||||
(self.buf[1] & (1 << 2)) != 0
|
||||
}
|
||||
|
||||
/// Returns `true` if a packet is received.
|
||||
pub const fn pkt_received(&self) -> bool {
|
||||
(self.buf[1] & (1 << 1)) != 0
|
||||
}
|
||||
|
||||
/// Returns `true` when a packet has been sent.
|
||||
pub const fn pkt_sent(&self) -> bool {
|
||||
(self.buf[1] & 1) != 0
|
||||
}
|
||||
|
||||
/// RSSI level when the synchronization address is detected.
|
||||
///
|
||||
/// Units are in dBm.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::{subghz::FskPacketStatus, Ratio};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 4] = [0, 0, 80, 0];
|
||||
/// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio);
|
||||
/// assert_eq!(pkt_status.rssi_sync().to_integer(), -40);
|
||||
/// ```
|
||||
pub fn rssi_sync(&self) -> Ratio<i16> {
|
||||
Ratio::new_raw(i16::from(self.buf[2]), -2)
|
||||
}
|
||||
|
||||
/// Return the RSSI level over the received packet.
|
||||
///
|
||||
/// Units are in dBm.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::{subghz::FskPacketStatus, Ratio};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 4] = [0, 0, 0, 100];
|
||||
/// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio);
|
||||
/// assert_eq!(pkt_status.rssi_avg().to_integer(), -50);
|
||||
/// ```
|
||||
pub fn rssi_avg(&self) -> Ratio<i16> {
|
||||
Ratio::new_raw(i16::from(self.buf[3]), -2)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for FskPacketStatus {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
r#"FskPacketStatus {{
|
||||
status: {},
|
||||
preamble_error: {},
|
||||
sync_err: {},
|
||||
adrs_err: {},
|
||||
crc_err: {},
|
||||
length_err: {},
|
||||
abort_err: {},
|
||||
pkt_received: {},
|
||||
pkt_sent: {},
|
||||
rssi_sync: {},
|
||||
rssi_avg: {},
|
||||
}}"#,
|
||||
self.status(),
|
||||
self.preamble_error(),
|
||||
self.sync_err(),
|
||||
self.adrs_err(),
|
||||
self.crc_err(),
|
||||
self.length_err(),
|
||||
self.abort_err(),
|
||||
self.pkt_received(),
|
||||
self.pkt_sent(),
|
||||
self.rssi_sync().to_integer(),
|
||||
self.rssi_avg().to_integer()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for FskPacketStatus {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("FskPacketStatus")
|
||||
.field("status", &self.status())
|
||||
.field("preamble_error", &self.preamble_error())
|
||||
.field("sync_err", &self.sync_err())
|
||||
.field("adrs_err", &self.adrs_err())
|
||||
.field("crc_err", &self.crc_err())
|
||||
.field("length_err", &self.length_err())
|
||||
.field("abort_err", &self.abort_err())
|
||||
.field("pkt_received", &self.pkt_received())
|
||||
.field("pkt_sent", &self.pkt_sent())
|
||||
.field("rssi_sync", &self.rssi_sync().to_integer())
|
||||
.field("rssi_avg", &self.rssi_avg().to_integer())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// (G)FSK packet status.
|
||||
///
|
||||
/// Returned by [`lora_packet_status`].
|
||||
///
|
||||
/// [`lora_packet_status`]: crate::subghz::SubGhz::lora_packet_status
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct LoRaPacketStatus {
|
||||
buf: [u8; 4],
|
||||
}
|
||||
|
||||
impl From<[u8; 4]> for LoRaPacketStatus {
|
||||
fn from(buf: [u8; 4]) -> Self {
|
||||
LoRaPacketStatus { buf }
|
||||
}
|
||||
}
|
||||
|
||||
impl LoRaPacketStatus {
|
||||
/// Get the status.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CmdStatus, LoRaPacketStatus, Status, StatusMode};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 4] = [0x54, 0, 0, 0];
|
||||
/// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio);
|
||||
/// let status: Status = pkt_status.status();
|
||||
/// assert_eq!(status.mode(), Ok(StatusMode::Rx));
|
||||
/// assert_eq!(status.cmd(), Ok(CmdStatus::Avaliable));
|
||||
/// ```
|
||||
pub const fn status(&self) -> Status {
|
||||
Status::from_raw(self.buf[0])
|
||||
}
|
||||
|
||||
/// Average RSSI level over the received packet.
|
||||
///
|
||||
/// Units are in dBm.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::{subghz::LoRaPacketStatus, Ratio};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 4] = [0, 80, 0, 0];
|
||||
/// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio);
|
||||
/// assert_eq!(pkt_status.rssi_pkt().to_integer(), -40);
|
||||
/// ```
|
||||
pub fn rssi_pkt(&self) -> Ratio<i16> {
|
||||
Ratio::new_raw(i16::from(self.buf[1]), -2)
|
||||
}
|
||||
|
||||
/// Estimation of SNR over the received packet.
|
||||
///
|
||||
/// Units are in dB.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::{subghz::LoRaPacketStatus, Ratio};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 4] = [0, 0, 40, 0];
|
||||
/// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio);
|
||||
/// assert_eq!(pkt_status.snr_pkt().to_integer(), 10);
|
||||
/// ```
|
||||
pub fn snr_pkt(&self) -> Ratio<i16> {
|
||||
Ratio::new_raw(i16::from(self.buf[2]), 4)
|
||||
}
|
||||
|
||||
/// Estimation of RSSI level of the LoRa signal after despreading.
|
||||
///
|
||||
/// Units are in dBm.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::{subghz::LoRaPacketStatus, Ratio};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 4] = [0, 0, 0, 80];
|
||||
/// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio);
|
||||
/// assert_eq!(pkt_status.signal_rssi_pkt().to_integer(), -40);
|
||||
/// ```
|
||||
pub fn signal_rssi_pkt(&self) -> Ratio<i16> {
|
||||
Ratio::new_raw(i16::from(self.buf[3]), -2)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for LoRaPacketStatus {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
r#"LoRaPacketStatus {{
|
||||
status: {},
|
||||
rssi_pkt: {},
|
||||
snr_pkt: {},
|
||||
signal_rssi_pkt: {},
|
||||
}}"#,
|
||||
self.status(),
|
||||
self.rssi_pkt().to_integer(),
|
||||
self.snr_pkt().to_integer(),
|
||||
self.signal_rssi_pkt().to_integer(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for LoRaPacketStatus {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("LoRaPacketStatus")
|
||||
.field("status", &self.status())
|
||||
.field("rssi_pkt", &self.rssi_pkt().to_integer())
|
||||
.field("snr_pkt", &self.snr_pkt().to_integer())
|
||||
.field("signal_rssi_pkt", &self.signal_rssi_pkt().to_integer())
|
||||
.finish()
|
||||
}
|
||||
}
|
44
embassy-stm32/src/subghz/packet_type.rs
Normal file
44
embassy-stm32/src/subghz/packet_type.rs
Normal file
@ -0,0 +1,44 @@
|
||||
/// Packet type definition.
|
||||
///
|
||||
/// Argument of [`set_packet_type`]
|
||||
///
|
||||
/// [`set_packet_type`]: crate::subghz::SubGhz::set_packet_type
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PacketType {
|
||||
/// FSK (frequency shift keying) generic packet type.
|
||||
Fsk = 0,
|
||||
/// LoRa (long range) packet type.
|
||||
LoRa = 1,
|
||||
/// BPSK (binary phase shift keying) packet type.
|
||||
Bpsk = 2,
|
||||
/// MSK (minimum shift keying) generic packet type.
|
||||
Msk = 3,
|
||||
}
|
||||
|
||||
impl PacketType {
|
||||
/// Create a new `PacketType` from bits.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::PacketType;
|
||||
///
|
||||
/// assert_eq!(PacketType::from_raw(0), Ok(PacketType::Fsk));
|
||||
/// assert_eq!(PacketType::from_raw(1), Ok(PacketType::LoRa));
|
||||
/// assert_eq!(PacketType::from_raw(2), Ok(PacketType::Bpsk));
|
||||
/// assert_eq!(PacketType::from_raw(3), Ok(PacketType::Msk));
|
||||
/// // Other values are reserved
|
||||
/// assert_eq!(PacketType::from_raw(4), Err(4));
|
||||
/// ```
|
||||
pub const fn from_raw(bits: u8) -> Result<PacketType, u8> {
|
||||
match bits {
|
||||
0 => Ok(PacketType::Fsk),
|
||||
1 => Ok(PacketType::LoRa),
|
||||
2 => Ok(PacketType::Bpsk),
|
||||
3 => Ok(PacketType::Msk),
|
||||
_ => Err(bits),
|
||||
}
|
||||
}
|
||||
}
|
247
embassy-stm32/src/subghz/pkt_ctrl.rs
Normal file
247
embassy-stm32/src/subghz/pkt_ctrl.rs
Normal file
@ -0,0 +1,247 @@
|
||||
/// Generic packet infinite sequence selection.
|
||||
///
|
||||
/// Argument of [`PktCtrl::set_inf_seq_sel`].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum InfSeqSel {
|
||||
/// Preamble `0x5555`.
|
||||
Five = 0b00,
|
||||
/// Preamble `0x0000`.
|
||||
Zero = 0b01,
|
||||
/// Preamble `0xFFFF`.
|
||||
One = 0b10,
|
||||
/// PRBS9.
|
||||
Prbs9 = 0b11,
|
||||
}
|
||||
|
||||
impl Default for InfSeqSel {
|
||||
fn default() -> Self {
|
||||
InfSeqSel::Five
|
||||
}
|
||||
}
|
||||
|
||||
/// Generic packet control.
|
||||
///
|
||||
/// Argument of [`set_pkt_ctrl`](crate::subghz::SubGhz::set_pkt_ctrl).
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct PktCtrl {
|
||||
val: u8,
|
||||
}
|
||||
|
||||
impl PktCtrl {
|
||||
/// Reset value of the packet control register.
|
||||
pub const RESET: PktCtrl = PktCtrl { val: 0x21 };
|
||||
|
||||
/// Create a new [`PktCtrl`] structure from a raw value.
|
||||
///
|
||||
/// Reserved bits will be masked.
|
||||
pub const fn from_raw(raw: u8) -> Self {
|
||||
Self { val: raw & 0x3F }
|
||||
}
|
||||
|
||||
/// Get the raw value of the [`PktCtrl`] register.
|
||||
pub const fn as_bits(&self) -> u8 {
|
||||
self.val
|
||||
}
|
||||
|
||||
/// Generic packet synchronization word detection enable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::PktCtrl;
|
||||
///
|
||||
/// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_sync_det_en(true);
|
||||
/// ```
|
||||
#[must_use = "set_sync_det_en returns a modified PktCtrl"]
|
||||
pub const fn set_sync_det_en(mut self, en: bool) -> PktCtrl {
|
||||
if en {
|
||||
self.val |= 1 << 5;
|
||||
} else {
|
||||
self.val &= !(1 << 5);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` if generic packet synchronization word detection is
|
||||
/// enabled.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::PktCtrl;
|
||||
///
|
||||
/// let pc: PktCtrl = PktCtrl::RESET;
|
||||
/// assert_eq!(pc.sync_det_en(), true);
|
||||
/// let pc: PktCtrl = pc.set_sync_det_en(false);
|
||||
/// assert_eq!(pc.sync_det_en(), false);
|
||||
/// let pc: PktCtrl = pc.set_sync_det_en(true);
|
||||
/// assert_eq!(pc.sync_det_en(), true);
|
||||
/// ```
|
||||
pub const fn sync_det_en(&self) -> bool {
|
||||
self.val & (1 << 5) != 0
|
||||
}
|
||||
|
||||
/// Generic packet continuous transmit enable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::PktCtrl;
|
||||
///
|
||||
/// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_cont_tx_en(true);
|
||||
/// ```
|
||||
#[must_use = "set_cont_tx_en returns a modified PktCtrl"]
|
||||
pub const fn set_cont_tx_en(mut self, en: bool) -> PktCtrl {
|
||||
if en {
|
||||
self.val |= 1 << 4;
|
||||
} else {
|
||||
self.val &= !(1 << 4);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` if generic packet continuous transmit is enabled.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::PktCtrl;
|
||||
///
|
||||
/// let pc: PktCtrl = PktCtrl::RESET;
|
||||
/// assert_eq!(pc.cont_tx_en(), false);
|
||||
/// let pc: PktCtrl = pc.set_cont_tx_en(true);
|
||||
/// assert_eq!(pc.cont_tx_en(), true);
|
||||
/// let pc: PktCtrl = pc.set_cont_tx_en(false);
|
||||
/// assert_eq!(pc.cont_tx_en(), false);
|
||||
/// ```
|
||||
pub const fn cont_tx_en(&self) -> bool {
|
||||
self.val & (1 << 4) != 0
|
||||
}
|
||||
|
||||
/// Set the continuous sequence type.
|
||||
#[must_use = "set_inf_seq_sel returns a modified PktCtrl"]
|
||||
pub const fn set_inf_seq_sel(mut self, sel: InfSeqSel) -> PktCtrl {
|
||||
self.val &= !(0b11 << 2);
|
||||
self.val |= (sel as u8) << 2;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the continuous sequence type.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{InfSeqSel, PktCtrl};
|
||||
///
|
||||
/// let pc: PktCtrl = PktCtrl::RESET;
|
||||
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Five);
|
||||
///
|
||||
/// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Zero);
|
||||
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Zero);
|
||||
///
|
||||
/// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::One);
|
||||
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::One);
|
||||
///
|
||||
/// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Prbs9);
|
||||
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Prbs9);
|
||||
///
|
||||
/// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Five);
|
||||
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Five);
|
||||
/// ```
|
||||
pub const fn inf_seq_sel(&self) -> InfSeqSel {
|
||||
match (self.val >> 2) & 0b11 {
|
||||
0b00 => InfSeqSel::Five,
|
||||
0b01 => InfSeqSel::Zero,
|
||||
0b10 => InfSeqSel::One,
|
||||
_ => InfSeqSel::Prbs9,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable infinute sequence generation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::PktCtrl;
|
||||
///
|
||||
/// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_inf_seq_en(true);
|
||||
/// ```
|
||||
#[must_use = "set_inf_seq_en returns a modified PktCtrl"]
|
||||
pub const fn set_inf_seq_en(mut self, en: bool) -> PktCtrl {
|
||||
if en {
|
||||
self.val |= 1 << 1;
|
||||
} else {
|
||||
self.val &= !(1 << 1);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` if infinute sequence generation is enabled.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::PktCtrl;
|
||||
///
|
||||
/// let pc: PktCtrl = PktCtrl::RESET;
|
||||
/// assert_eq!(pc.inf_seq_en(), false);
|
||||
/// let pc: PktCtrl = pc.set_inf_seq_en(true);
|
||||
/// assert_eq!(pc.inf_seq_en(), true);
|
||||
/// let pc: PktCtrl = pc.set_inf_seq_en(false);
|
||||
/// assert_eq!(pc.inf_seq_en(), false);
|
||||
/// ```
|
||||
pub const fn inf_seq_en(&self) -> bool {
|
||||
self.val & (1 << 1) != 0
|
||||
}
|
||||
|
||||
/// Set the value of bit-8 (9th bit) for generic packet whitening.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::PktCtrl;
|
||||
///
|
||||
/// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_whitening_init(true);
|
||||
/// ```
|
||||
#[must_use = "set_whitening_init returns a modified PktCtrl"]
|
||||
pub const fn set_whitening_init(mut self, val: bool) -> PktCtrl {
|
||||
if val {
|
||||
self.val |= 1;
|
||||
} else {
|
||||
self.val &= !1;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` if bit-8 of the generic packet whitening is set.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::PktCtrl;
|
||||
///
|
||||
/// let pc: PktCtrl = PktCtrl::RESET;
|
||||
/// assert_eq!(pc.whitening_init(), true);
|
||||
/// let pc: PktCtrl = pc.set_whitening_init(false);
|
||||
/// assert_eq!(pc.whitening_init(), false);
|
||||
/// let pc: PktCtrl = pc.set_whitening_init(true);
|
||||
/// assert_eq!(pc.whitening_init(), true);
|
||||
/// ```
|
||||
pub const fn whitening_init(&self) -> bool {
|
||||
self.val & 0b1 != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PktCtrl> for u8 {
|
||||
fn from(pc: PktCtrl) -> Self {
|
||||
pc.val
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PktCtrl {
|
||||
fn default() -> Self {
|
||||
Self::RESET
|
||||
}
|
||||
}
|
27
embassy-stm32/src/subghz/pmode.rs
Normal file
27
embassy-stm32/src/subghz/pmode.rs
Normal file
@ -0,0 +1,27 @@
|
||||
/// RX gain power modes.
|
||||
///
|
||||
/// Argument of [`set_rx_gain`].
|
||||
///
|
||||
/// [`set_rx_gain`]: crate::subghz::SubGhz::set_rx_gain
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum PMode {
|
||||
/// Power saving mode.
|
||||
///
|
||||
/// Reduces sensitivity.
|
||||
#[allow(clippy::identity_op)]
|
||||
PowerSaving = (0x25 << 2) | 0b00,
|
||||
/// Boost mode level 1.
|
||||
///
|
||||
/// Improves sensitivity at detriment of power consumption.
|
||||
Boost1 = (0x25 << 2) | 0b01,
|
||||
/// Boost mode level 2.
|
||||
///
|
||||
/// Improves a set further sensitivity at detriment of power consumption.
|
||||
Boost2 = (0x25 << 2) | 0b10,
|
||||
/// Boost mode.
|
||||
///
|
||||
/// Best receiver sensitivity.
|
||||
Boost = (0x25 << 2) | 0b11,
|
||||
}
|
160
embassy-stm32/src/subghz/pwr_ctrl.rs
Normal file
160
embassy-stm32/src/subghz/pwr_ctrl.rs
Normal file
@ -0,0 +1,160 @@
|
||||
/// Power-supply current limit.
|
||||
///
|
||||
/// Argument of [`PwrCtrl::set_current_lim`].
|
||||
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum CurrentLim {
|
||||
/// 25 mA
|
||||
Milli25 = 0x0,
|
||||
/// 50 mA (default)
|
||||
Milli50 = 0x1,
|
||||
/// 100 mA
|
||||
Milli100 = 0x2,
|
||||
/// 200 mA
|
||||
Milli200 = 0x3,
|
||||
}
|
||||
|
||||
impl CurrentLim {
|
||||
/// Get the SMPS drive value as milliamps.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::CurrentLim;
|
||||
///
|
||||
/// assert_eq!(CurrentLim::Milli25.as_milliamps(), 25);
|
||||
/// assert_eq!(CurrentLim::Milli50.as_milliamps(), 50);
|
||||
/// assert_eq!(CurrentLim::Milli100.as_milliamps(), 100);
|
||||
/// assert_eq!(CurrentLim::Milli200.as_milliamps(), 200);
|
||||
/// ```
|
||||
pub const fn as_milliamps(&self) -> u8 {
|
||||
match self {
|
||||
CurrentLim::Milli25 => 25,
|
||||
CurrentLim::Milli50 => 50,
|
||||
CurrentLim::Milli100 => 100,
|
||||
CurrentLim::Milli200 => 200,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CurrentLim {
|
||||
fn default() -> Self {
|
||||
CurrentLim::Milli50
|
||||
}
|
||||
}
|
||||
|
||||
/// Power control.
|
||||
///
|
||||
/// Argument of [`set_bit_sync`](crate::subghz::SubGhz::set_bit_sync).
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct PwrCtrl {
|
||||
val: u8,
|
||||
}
|
||||
|
||||
impl PwrCtrl {
|
||||
/// Power control register reset value.
|
||||
pub const RESET: PwrCtrl = PwrCtrl { val: 0x50 };
|
||||
|
||||
/// Create a new [`PwrCtrl`] structure from a raw value.
|
||||
///
|
||||
/// Reserved bits will be masked.
|
||||
pub const fn from_raw(raw: u8) -> Self {
|
||||
Self { val: raw & 0x70 }
|
||||
}
|
||||
|
||||
/// Get the raw value of the [`PwrCtrl`] register.
|
||||
pub const fn as_bits(&self) -> u8 {
|
||||
self.val
|
||||
}
|
||||
|
||||
/// Set the current limiter enable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::PwrCtrl;
|
||||
///
|
||||
/// const PWR_CTRL: PwrCtrl = PwrCtrl::RESET.set_current_lim_en(true);
|
||||
/// # assert_eq!(u8::from(PWR_CTRL), 0x50u8);
|
||||
/// ```
|
||||
#[must_use = "set_current_lim_en returns a modified PwrCtrl"]
|
||||
pub const fn set_current_lim_en(mut self, en: bool) -> PwrCtrl {
|
||||
if en {
|
||||
self.val |= 1 << 6;
|
||||
} else {
|
||||
self.val &= !(1 << 6);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns `true` if current limiting is enabled
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::PwrCtrl;
|
||||
///
|
||||
/// let pc: PwrCtrl = PwrCtrl::RESET;
|
||||
/// assert_eq!(pc.current_limit_en(), true);
|
||||
/// let pc: PwrCtrl = pc.set_current_lim_en(false);
|
||||
/// assert_eq!(pc.current_limit_en(), false);
|
||||
/// let pc: PwrCtrl = pc.set_current_lim_en(true);
|
||||
/// assert_eq!(pc.current_limit_en(), true);
|
||||
/// ```
|
||||
pub const fn current_limit_en(&self) -> bool {
|
||||
self.val & (1 << 6) != 0
|
||||
}
|
||||
|
||||
/// Set the current limit.
|
||||
#[must_use = "set_current_lim returns a modified PwrCtrl"]
|
||||
pub const fn set_current_lim(mut self, lim: CurrentLim) -> PwrCtrl {
|
||||
self.val &= !(0x30);
|
||||
self.val |= (lim as u8) << 4;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the current limit.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CurrentLim, PwrCtrl};
|
||||
///
|
||||
/// let pc: PwrCtrl = PwrCtrl::RESET;
|
||||
/// assert_eq!(pc.current_lim(), CurrentLim::Milli50);
|
||||
///
|
||||
/// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli25);
|
||||
/// assert_eq!(pc.current_lim(), CurrentLim::Milli25);
|
||||
///
|
||||
/// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli50);
|
||||
/// assert_eq!(pc.current_lim(), CurrentLim::Milli50);
|
||||
///
|
||||
/// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli100);
|
||||
/// assert_eq!(pc.current_lim(), CurrentLim::Milli100);
|
||||
///
|
||||
/// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli200);
|
||||
/// assert_eq!(pc.current_lim(), CurrentLim::Milli200);
|
||||
/// ```
|
||||
pub const fn current_lim(&self) -> CurrentLim {
|
||||
match (self.val >> 4) & 0b11 {
|
||||
0x0 => CurrentLim::Milli25,
|
||||
0x1 => CurrentLim::Milli50,
|
||||
0x2 => CurrentLim::Milli100,
|
||||
_ => CurrentLim::Milli200,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PwrCtrl> for u8 {
|
||||
fn from(bs: PwrCtrl) -> Self {
|
||||
bs.val
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PwrCtrl {
|
||||
fn default() -> Self {
|
||||
Self::RESET
|
||||
}
|
||||
}
|
18
embassy-stm32/src/subghz/reg_mode.rs
Normal file
18
embassy-stm32/src/subghz/reg_mode.rs
Normal file
@ -0,0 +1,18 @@
|
||||
/// Radio power supply selection.
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum RegMode {
|
||||
/// Linear dropout regulator
|
||||
Ldo = 0b0,
|
||||
/// Switch mode power supply.
|
||||
///
|
||||
/// Used in standby with HSE32, FS, RX, and TX modes.
|
||||
Smps = 0b1,
|
||||
}
|
||||
|
||||
impl Default for RegMode {
|
||||
fn default() -> Self {
|
||||
RegMode::Ldo
|
||||
}
|
||||
}
|
138
embassy-stm32/src/subghz/rf_frequency.rs
Normal file
138
embassy-stm32/src/subghz/rf_frequency.rs
Normal file
@ -0,0 +1,138 @@
|
||||
/// RF frequency structure.
|
||||
///
|
||||
/// Argument of [`set_rf_frequency`].
|
||||
///
|
||||
/// [`set_rf_frequency`]: crate::subghz::SubGhz::set_rf_frequency
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct RfFreq {
|
||||
buf: [u8; 5],
|
||||
}
|
||||
|
||||
impl RfFreq {
|
||||
/// 915MHz, often used in Australia and North America.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::RfFreq;
|
||||
///
|
||||
/// assert_eq!(RfFreq::F915.freq(), 915_000_000);
|
||||
/// ```
|
||||
pub const F915: RfFreq = RfFreq::from_raw(0x39_30_00_00);
|
||||
|
||||
/// 868MHz, often used in Europe.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::RfFreq;
|
||||
///
|
||||
/// assert_eq!(RfFreq::F868.freq(), 868_000_000);
|
||||
/// ```
|
||||
pub const F868: RfFreq = RfFreq::from_raw(0x36_40_00_00);
|
||||
|
||||
/// 433MHz, often used in Europe.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::RfFreq;
|
||||
///
|
||||
/// assert_eq!(RfFreq::F433.freq(), 433_000_000);
|
||||
/// ```
|
||||
pub const F433: RfFreq = RfFreq::from_raw(0x1B_10_00_00);
|
||||
|
||||
/// Create a new `RfFreq` from a raw bit value.
|
||||
///
|
||||
/// The equation used to get the PLL frequency from the raw bits is:
|
||||
///
|
||||
/// RF<sub>PLL</sub> = 32e6 × bits / 2<sup>25</sup>
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::RfFreq;
|
||||
///
|
||||
/// const FREQ: RfFreq = RfFreq::from_raw(0x39300000);
|
||||
/// assert_eq!(FREQ, RfFreq::F915);
|
||||
/// ```
|
||||
pub const fn from_raw(bits: u32) -> RfFreq {
|
||||
RfFreq {
|
||||
buf: [
|
||||
super::OpCode::SetRfFrequency as u8,
|
||||
((bits >> 24) & 0xFF) as u8,
|
||||
((bits >> 16) & 0xFF) as u8,
|
||||
((bits >> 8) & 0xFF) as u8,
|
||||
(bits & 0xFF) as u8,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `RfFreq` from a PLL frequency.
|
||||
///
|
||||
/// The equation used to get the raw bits from the PLL frequency is:
|
||||
///
|
||||
/// bits = RF<sub>PLL</sub> * 2<sup>25</sup> / 32e6
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::RfFreq;
|
||||
///
|
||||
/// const FREQ: RfFreq = RfFreq::from_frequency(915_000_000);
|
||||
/// assert_eq!(FREQ, RfFreq::F915);
|
||||
/// ```
|
||||
pub const fn from_frequency(freq: u32) -> RfFreq {
|
||||
Self::from_raw((((freq as u64) * (1 << 25)) / 32_000_000) as u32)
|
||||
}
|
||||
|
||||
// Get the frequency bit value.
|
||||
const fn as_bits(&self) -> u32 {
|
||||
((self.buf[1] as u32) << 24)
|
||||
| ((self.buf[2] as u32) << 16)
|
||||
| ((self.buf[3] as u32) << 8)
|
||||
| (self.buf[4] as u32)
|
||||
}
|
||||
|
||||
/// Get the actual frequency.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::RfFreq;
|
||||
///
|
||||
/// assert_eq!(RfFreq::from_raw(0x39300000).freq(), 915_000_000);
|
||||
/// ```
|
||||
pub fn freq(&self) -> u32 {
|
||||
(32_000_000 * (self.as_bits() as u64) / (1 << 25)) as u32
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::RfFreq;
|
||||
///
|
||||
/// assert_eq!(RfFreq::F915.as_slice(), &[0x86, 0x39, 0x30, 0x00, 0x00]);
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::RfFreq;
|
||||
|
||||
#[test]
|
||||
fn max() {
|
||||
assert_eq!(RfFreq::from_raw(u32::MAX).freq(), 4_095_999_999);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn min() {
|
||||
assert_eq!(RfFreq::from_raw(u32::MIN).freq(), 0);
|
||||
}
|
||||
}
|
21
embassy-stm32/src/subghz/rx_timeout_stop.rs
Normal file
21
embassy-stm32/src/subghz/rx_timeout_stop.rs
Normal file
@ -0,0 +1,21 @@
|
||||
/// Receiver event which stops the RX timeout timer.
|
||||
///
|
||||
/// Used by [`set_rx_timeout_stop`].
|
||||
///
|
||||
/// [`set_rx_timeout_stop`]: crate::subghz::SubGhz::set_rx_timeout_stop
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum RxTimeoutStop {
|
||||
/// Receive timeout stopped on synchronization word detection in generic
|
||||
/// packet mode or header detection in LoRa packet mode.
|
||||
Sync = 0b0,
|
||||
/// Receive timeout stopped on preamble detection.
|
||||
Preamble = 0b1,
|
||||
}
|
||||
|
||||
impl From<RxTimeoutStop> for u8 {
|
||||
fn from(rx_ts: RxTimeoutStop) -> Self {
|
||||
rx_ts as u8
|
||||
}
|
||||
}
|
109
embassy-stm32/src/subghz/sleep_cfg.rs
Normal file
109
embassy-stm32/src/subghz/sleep_cfg.rs
Normal file
@ -0,0 +1,109 @@
|
||||
/// Startup configurations when exiting sleep mode.
|
||||
///
|
||||
/// Argument of [`SleepCfg::set_startup`].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum Startup {
|
||||
/// Cold startup when exiting Sleep mode, configuration registers reset.
|
||||
Cold = 0,
|
||||
/// Warm startup when exiting Sleep mode,
|
||||
/// configuration registers kept in retention.
|
||||
///
|
||||
/// **Note:** Only the configuration of the activated modem,
|
||||
/// before going to sleep mode, is retained.
|
||||
/// The configuration of the other modes is lost and must be re-configured
|
||||
/// when exiting sleep mode.
|
||||
Warm = 1,
|
||||
}
|
||||
|
||||
impl Default for Startup {
|
||||
fn default() -> Self {
|
||||
Startup::Warm
|
||||
}
|
||||
}
|
||||
|
||||
/// Sleep configuration.
|
||||
///
|
||||
/// Argument of [`set_sleep`].
|
||||
///
|
||||
/// [`set_sleep`]: crate::subghz::SubGhz::set_sleep
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct SleepCfg(u8);
|
||||
|
||||
impl SleepCfg {
|
||||
/// Create a new `SleepCfg` structure.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// The defaults are a warm startup, with RTC wakeup enabled.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::SleepCfg;
|
||||
///
|
||||
/// const SLEEP_CFG: SleepCfg = SleepCfg::new();
|
||||
/// assert_eq!(SLEEP_CFG, SleepCfg::default());
|
||||
/// # assert_eq!(u8::from(SLEEP_CFG), 0b101);
|
||||
/// ```
|
||||
pub const fn new() -> SleepCfg {
|
||||
SleepCfg(0)
|
||||
.set_startup(Startup::Warm)
|
||||
.set_rtc_wakeup_en(true)
|
||||
}
|
||||
|
||||
/// Set the startup mode.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{SleepCfg, Startup};
|
||||
///
|
||||
/// const SLEEP_CFG: SleepCfg = SleepCfg::new().set_startup(Startup::Cold);
|
||||
/// # assert_eq!(u8::from(SLEEP_CFG), 0b001);
|
||||
/// # assert_eq!(u8::from(SLEEP_CFG.set_startup(Startup::Warm)), 0b101);
|
||||
/// ```
|
||||
pub const fn set_startup(mut self, startup: Startup) -> SleepCfg {
|
||||
if startup as u8 == 1 {
|
||||
self.0 |= 1 << 2
|
||||
} else {
|
||||
self.0 &= !(1 << 2)
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the RTC wakeup enable.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::SleepCfg;
|
||||
///
|
||||
/// const SLEEP_CFG: SleepCfg = SleepCfg::new().set_rtc_wakeup_en(false);
|
||||
/// # assert_eq!(u8::from(SLEEP_CFG), 0b100);
|
||||
/// # assert_eq!(u8::from(SLEEP_CFG.set_rtc_wakeup_en(true)), 0b101);
|
||||
/// ```
|
||||
#[must_use = "set_rtc_wakeup_en returns a modified SleepCfg"]
|
||||
pub const fn set_rtc_wakeup_en(mut self, en: bool) -> SleepCfg {
|
||||
if en {
|
||||
self.0 |= 0b1
|
||||
} else {
|
||||
self.0 &= !0b1
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SleepCfg> for u8 {
|
||||
fn from(sc: SleepCfg) -> Self {
|
||||
sc.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SleepCfg {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
45
embassy-stm32/src/subghz/smps.rs
Normal file
45
embassy-stm32/src/subghz/smps.rs
Normal file
@ -0,0 +1,45 @@
|
||||
/// SMPS maximum drive capability.
|
||||
///
|
||||
/// Argument of [`set_smps_drv`](crate::subghz::SubGhz::set_smps_drv).
|
||||
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum SmpsDrv {
|
||||
/// 20 mA
|
||||
Milli20 = 0x0,
|
||||
/// 40 mA
|
||||
Milli40 = 0x1,
|
||||
/// 60 mA
|
||||
Milli60 = 0x2,
|
||||
/// 100 mA (default)
|
||||
Milli100 = 0x3,
|
||||
}
|
||||
|
||||
impl SmpsDrv {
|
||||
/// Get the SMPS drive value as milliamps.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::SmpsDrv;
|
||||
///
|
||||
/// assert_eq!(SmpsDrv::Milli20.as_milliamps(), 20);
|
||||
/// assert_eq!(SmpsDrv::Milli40.as_milliamps(), 40);
|
||||
/// assert_eq!(SmpsDrv::Milli60.as_milliamps(), 60);
|
||||
/// assert_eq!(SmpsDrv::Milli100.as_milliamps(), 100);
|
||||
/// ```
|
||||
pub const fn as_milliamps(&self) -> u8 {
|
||||
match self {
|
||||
SmpsDrv::Milli20 => 20,
|
||||
SmpsDrv::Milli40 => 40,
|
||||
SmpsDrv::Milli60 => 60,
|
||||
SmpsDrv::Milli100 => 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SmpsDrv {
|
||||
fn default() -> Self {
|
||||
SmpsDrv::Milli100
|
||||
}
|
||||
}
|
20
embassy-stm32/src/subghz/standby_clk.rs
Normal file
20
embassy-stm32/src/subghz/standby_clk.rs
Normal file
@ -0,0 +1,20 @@
|
||||
/// Clock in standby mode.
|
||||
///
|
||||
/// Used by [`set_standby`].
|
||||
///
|
||||
/// [`set_standby`]: crate::subghz::SubGhz::set_standby
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum StandbyClk {
|
||||
/// RC 13 MHz used in standby mode.
|
||||
Rc = 0b0,
|
||||
/// HSE32 used in standby mode.
|
||||
Hse = 0b1,
|
||||
}
|
||||
|
||||
impl From<StandbyClk> for u8 {
|
||||
fn from(sc: StandbyClk) -> Self {
|
||||
sc as u8
|
||||
}
|
||||
}
|
184
embassy-stm32/src/subghz/stats.rs
Normal file
184
embassy-stm32/src/subghz/stats.rs
Normal file
@ -0,0 +1,184 @@
|
||||
use crate::subghz::status::Status;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct LoRaStats;
|
||||
|
||||
impl LoRaStats {
|
||||
pub const fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct FskStats;
|
||||
|
||||
impl FskStats {
|
||||
pub const fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Packet statistics.
|
||||
///
|
||||
/// Returned by [`fsk_stats`] and [`lora_stats`].
|
||||
///
|
||||
/// [`fsk_stats`]: crate::subghz::SubGhz::fsk_stats
|
||||
/// [`lora_stats`]: crate::subghz::SubGhz::lora_stats
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Stats<ModType> {
|
||||
status: Status,
|
||||
pkt_rx: u16,
|
||||
pkt_crc: u16,
|
||||
pkt_len_or_hdr_err: u16,
|
||||
ty: ModType,
|
||||
}
|
||||
|
||||
impl<ModType> Stats<ModType> {
|
||||
const fn from_buf(buf: [u8; 7], ty: ModType) -> Stats<ModType> {
|
||||
Stats {
|
||||
status: Status::from_raw(buf[0]),
|
||||
pkt_rx: u16::from_be_bytes([buf[1], buf[2]]),
|
||||
pkt_crc: u16::from_be_bytes([buf[3], buf[4]]),
|
||||
pkt_len_or_hdr_err: u16::from_be_bytes([buf[5], buf[6]]),
|
||||
ty,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the radio status returned with the packet statistics.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CmdStatus, FskStats, Stats, StatusMode};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0];
|
||||
/// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio);
|
||||
/// assert_eq!(stats.status().mode(), Ok(StatusMode::Rx));
|
||||
/// assert_eq!(stats.status().cmd(), Ok(CmdStatus::Avaliable));
|
||||
/// ```
|
||||
pub const fn status(&self) -> Status {
|
||||
self.status
|
||||
}
|
||||
|
||||
/// Number of packets received.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{FskStats, Stats};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 3, 0, 0, 0, 0];
|
||||
/// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio);
|
||||
/// assert_eq!(stats.pkt_rx(), 3);
|
||||
/// ```
|
||||
pub const fn pkt_rx(&self) -> u16 {
|
||||
self.pkt_rx
|
||||
}
|
||||
|
||||
/// Number of packets received with a payload CRC error
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{LoRaStats, Stats};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 1, 0, 0];
|
||||
/// let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio);
|
||||
/// assert_eq!(stats.pkt_crc(), 1);
|
||||
/// ```
|
||||
pub const fn pkt_crc(&self) -> u16 {
|
||||
self.pkt_crc
|
||||
}
|
||||
}
|
||||
|
||||
impl Stats<FskStats> {
|
||||
/// Create a new FSK packet statistics structure from a raw buffer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{FskStats, Stats};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0];
|
||||
/// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio);
|
||||
/// ```
|
||||
pub const fn from_raw_fsk(buf: [u8; 7]) -> Stats<FskStats> {
|
||||
Self::from_buf(buf, FskStats::new())
|
||||
}
|
||||
|
||||
/// Number of packets received with a payload length error.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{FskStats, Stats};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 1];
|
||||
/// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio);
|
||||
/// assert_eq!(stats.pkt_len_err(), 1);
|
||||
/// ```
|
||||
pub const fn pkt_len_err(&self) -> u16 {
|
||||
self.pkt_len_or_hdr_err
|
||||
}
|
||||
}
|
||||
|
||||
impl Stats<LoRaStats> {
|
||||
/// Create a new LoRa packet statistics structure from a raw buffer.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{LoRaStats, Stats};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0];
|
||||
/// let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio);
|
||||
/// ```
|
||||
pub const fn from_raw_lora(buf: [u8; 7]) -> Stats<LoRaStats> {
|
||||
Self::from_buf(buf, LoRaStats::new())
|
||||
}
|
||||
|
||||
/// Number of packets received with a header CRC error.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{LoRaStats, Stats};
|
||||
///
|
||||
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 1];
|
||||
/// let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio);
|
||||
/// assert_eq!(stats.pkt_hdr_err(), 1);
|
||||
/// ```
|
||||
pub const fn pkt_hdr_err(&self) -> u16 {
|
||||
self.pkt_len_or_hdr_err
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Stats<FskStats> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Stats")
|
||||
.field("status", &self.status())
|
||||
.field("pkt_rx", &self.pkt_rx())
|
||||
.field("pkt_crc", &self.pkt_crc())
|
||||
.field("pkt_len_err", &self.pkt_len_err())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::subghz::{CmdStatus, LoRaStats, Stats, StatusMode};
|
||||
|
||||
#[test]
|
||||
fn mixed() {
|
||||
let example_data_from_radio: [u8; 7] = [0x54, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
|
||||
let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio);
|
||||
assert_eq!(stats.status().mode(), Ok(StatusMode::Rx));
|
||||
assert_eq!(stats.status().cmd(), Ok(CmdStatus::Avaliable));
|
||||
assert_eq!(stats.pkt_rx(), 0x0102);
|
||||
assert_eq!(stats.pkt_crc(), 0x0304);
|
||||
assert_eq!(stats.pkt_hdr_err(), 0x0506);
|
||||
}
|
||||
}
|
202
embassy-stm32/src/subghz/status.rs
Normal file
202
embassy-stm32/src/subghz/status.rs
Normal file
@ -0,0 +1,202 @@
|
||||
/// sub-GHz radio operating mode.
|
||||
///
|
||||
/// See `Get_Status` under section 5.8.5 "Communcation status information commands"
|
||||
/// in the reference manual.
|
||||
///
|
||||
/// This is returned by [`Status::mode`].
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum StatusMode {
|
||||
/// Standby mode with RC 13MHz.
|
||||
StandbyRc = 0x2,
|
||||
/// Standby mode with HSE32.
|
||||
StandbyHse = 0x3,
|
||||
/// Frequency Synthesis mode.
|
||||
Fs = 0x4,
|
||||
/// Receive mode.
|
||||
Rx = 0x5,
|
||||
/// Transmit mode.
|
||||
Tx = 0x6,
|
||||
}
|
||||
|
||||
impl StatusMode {
|
||||
/// Create a new `StatusMode` from bits.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::StatusMode;
|
||||
///
|
||||
/// assert_eq!(StatusMode::from_raw(0x2), Ok(StatusMode::StandbyRc));
|
||||
/// assert_eq!(StatusMode::from_raw(0x3), Ok(StatusMode::StandbyHse));
|
||||
/// assert_eq!(StatusMode::from_raw(0x4), Ok(StatusMode::Fs));
|
||||
/// assert_eq!(StatusMode::from_raw(0x5), Ok(StatusMode::Rx));
|
||||
/// assert_eq!(StatusMode::from_raw(0x6), Ok(StatusMode::Tx));
|
||||
/// // Other values are reserved
|
||||
/// assert_eq!(StatusMode::from_raw(0), Err(0));
|
||||
/// ```
|
||||
pub const fn from_raw(bits: u8) -> Result<Self, u8> {
|
||||
match bits {
|
||||
0x2 => Ok(StatusMode::StandbyRc),
|
||||
0x3 => Ok(StatusMode::StandbyHse),
|
||||
0x4 => Ok(StatusMode::Fs),
|
||||
0x5 => Ok(StatusMode::Rx),
|
||||
0x6 => Ok(StatusMode::Tx),
|
||||
_ => Err(bits),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Command status.
|
||||
///
|
||||
/// See `Get_Status` under section 5.8.5 "Communcation status information commands"
|
||||
/// in the reference manual.
|
||||
///
|
||||
/// This is returned by [`Status::cmd`].
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum CmdStatus {
|
||||
/// Data available to host.
|
||||
///
|
||||
/// Packet received successfully and data can be retrieved.
|
||||
Avaliable = 0x2,
|
||||
/// Command time out.
|
||||
///
|
||||
/// Command took too long to complete triggering a sub-GHz radio watchdog
|
||||
/// timeout.
|
||||
Timeout = 0x3,
|
||||
/// Command processing error.
|
||||
///
|
||||
/// Invalid opcode or incorrect number of parameters.
|
||||
ProcessingError = 0x4,
|
||||
/// Command execution failure.
|
||||
///
|
||||
/// Command successfully received but cannot be executed at this time,
|
||||
/// requested operating mode cannot be entered or requested data cannot be
|
||||
/// sent.
|
||||
ExecutionFailure = 0x5,
|
||||
/// Transmit command completed.
|
||||
///
|
||||
/// Current packet transmission completed.
|
||||
Complete = 0x6,
|
||||
}
|
||||
|
||||
impl CmdStatus {
|
||||
/// Create a new `CmdStatus` from bits.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::CmdStatus;
|
||||
///
|
||||
/// assert_eq!(CmdStatus::from_raw(0x2), Ok(CmdStatus::Avaliable));
|
||||
/// assert_eq!(CmdStatus::from_raw(0x3), Ok(CmdStatus::Timeout));
|
||||
/// assert_eq!(CmdStatus::from_raw(0x4), Ok(CmdStatus::ProcessingError));
|
||||
/// assert_eq!(CmdStatus::from_raw(0x5), Ok(CmdStatus::ExecutionFailure));
|
||||
/// assert_eq!(CmdStatus::from_raw(0x6), Ok(CmdStatus::Complete));
|
||||
/// // Other values are reserved
|
||||
/// assert_eq!(CmdStatus::from_raw(0), Err(0));
|
||||
/// ```
|
||||
pub const fn from_raw(bits: u8) -> Result<Self, u8> {
|
||||
match bits {
|
||||
0x2 => Ok(CmdStatus::Avaliable),
|
||||
0x3 => Ok(CmdStatus::Timeout),
|
||||
0x4 => Ok(CmdStatus::ProcessingError),
|
||||
0x5 => Ok(CmdStatus::ExecutionFailure),
|
||||
0x6 => Ok(CmdStatus::Complete),
|
||||
_ => Err(bits),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Radio status.
|
||||
///
|
||||
/// This is returned by [`status`].
|
||||
///
|
||||
/// [`status`]: crate::subghz::SubGhz::status
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct Status(u8);
|
||||
|
||||
impl From<u8> for Status {
|
||||
fn from(x: u8) -> Self {
|
||||
Status(x)
|
||||
}
|
||||
}
|
||||
impl From<Status> for u8 {
|
||||
fn from(x: Status) -> Self {
|
||||
x.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Status {
|
||||
/// Create a new `Status` from a raw `u8` value.
|
||||
///
|
||||
/// This is the same as `Status::from(u8)`, but in a `const` function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CmdStatus, Status, StatusMode};
|
||||
///
|
||||
/// const STATUS: Status = Status::from_raw(0x54_u8);
|
||||
/// assert_eq!(STATUS.mode(), Ok(StatusMode::Rx));
|
||||
/// assert_eq!(STATUS.cmd(), Ok(CmdStatus::Avaliable));
|
||||
/// ```
|
||||
pub const fn from_raw(value: u8) -> Status {
|
||||
Status(value)
|
||||
}
|
||||
|
||||
/// sub-GHz radio operating mode.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{Status, StatusMode};
|
||||
///
|
||||
/// let status: Status = 0xACu8.into();
|
||||
/// assert_eq!(status.mode(), Ok(StatusMode::StandbyRc));
|
||||
/// ```
|
||||
pub const fn mode(&self) -> Result<StatusMode, u8> {
|
||||
StatusMode::from_raw((self.0 >> 4) & 0b111)
|
||||
}
|
||||
|
||||
/// Command status.
|
||||
///
|
||||
/// For some reason `Err(1)` is a pretty common return value for this,
|
||||
/// despite being a reserved value.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{CmdStatus, Status};
|
||||
///
|
||||
/// let status: Status = 0xACu8.into();
|
||||
/// assert_eq!(status.cmd(), Ok(CmdStatus::Complete));
|
||||
/// ```
|
||||
pub const fn cmd(&self) -> Result<CmdStatus, u8> {
|
||||
CmdStatus::from_raw((self.0 >> 1) & 0b111)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Display for Status {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("Status")
|
||||
.field("mode", &self.mode())
|
||||
.field("cmd", &self.cmd())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for Status {
|
||||
fn format(&self, fmt: defmt::Formatter) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"Status {{ mode: {}, cmd: {} }}",
|
||||
self.mode(),
|
||||
self.cmd()
|
||||
)
|
||||
}
|
||||
}
|
170
embassy-stm32/src/subghz/tcxo_mode.rs
Normal file
170
embassy-stm32/src/subghz/tcxo_mode.rs
Normal file
@ -0,0 +1,170 @@
|
||||
use crate::subghz::timeout::Timeout;
|
||||
|
||||
/// TCXO trim.
|
||||
///
|
||||
/// **Note:** To use V<sub>DDTCXO</sub>, the V<sub>DDRF</sub> supply must be at
|
||||
/// least + 200 mV higher than the selected `TcxoTrim` voltage level.
|
||||
///
|
||||
/// Used by [`TcxoMode`].
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum TcxoTrim {
|
||||
/// 1.6V
|
||||
Volts1pt6 = 0x0,
|
||||
/// 1.7V
|
||||
Volts1pt7 = 0x1,
|
||||
/// 1.8V
|
||||
Volts1pt8 = 0x2,
|
||||
/// 2.2V
|
||||
Volts2pt2 = 0x3,
|
||||
/// 2.4V
|
||||
Volts2pt4 = 0x4,
|
||||
/// 2.7V
|
||||
Volts2pt7 = 0x5,
|
||||
/// 3.0V
|
||||
Volts3pt0 = 0x6,
|
||||
/// 3.3V
|
||||
Volts3pt3 = 0x7,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for TcxoTrim {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
TcxoTrim::Volts1pt6 => write!(f, "1.6V"),
|
||||
TcxoTrim::Volts1pt7 => write!(f, "1.7V"),
|
||||
TcxoTrim::Volts1pt8 => write!(f, "1.8V"),
|
||||
TcxoTrim::Volts2pt2 => write!(f, "2.2V"),
|
||||
TcxoTrim::Volts2pt4 => write!(f, "2.4V"),
|
||||
TcxoTrim::Volts2pt7 => write!(f, "2.7V"),
|
||||
TcxoTrim::Volts3pt0 => write!(f, "3.0V"),
|
||||
TcxoTrim::Volts3pt3 => write!(f, "3.3V"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TcxoTrim {
|
||||
/// Get the value of the TXCO trim in millivolts.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::TcxoTrim;
|
||||
///
|
||||
/// assert_eq!(TcxoTrim::Volts1pt6.as_millivolts(), 1600);
|
||||
/// assert_eq!(TcxoTrim::Volts1pt7.as_millivolts(), 1700);
|
||||
/// assert_eq!(TcxoTrim::Volts1pt8.as_millivolts(), 1800);
|
||||
/// assert_eq!(TcxoTrim::Volts2pt2.as_millivolts(), 2200);
|
||||
/// assert_eq!(TcxoTrim::Volts2pt4.as_millivolts(), 2400);
|
||||
/// assert_eq!(TcxoTrim::Volts2pt7.as_millivolts(), 2700);
|
||||
/// assert_eq!(TcxoTrim::Volts3pt0.as_millivolts(), 3000);
|
||||
/// assert_eq!(TcxoTrim::Volts3pt3.as_millivolts(), 3300);
|
||||
/// ```
|
||||
pub const fn as_millivolts(&self) -> u16 {
|
||||
match self {
|
||||
TcxoTrim::Volts1pt6 => 1600,
|
||||
TcxoTrim::Volts1pt7 => 1700,
|
||||
TcxoTrim::Volts1pt8 => 1800,
|
||||
TcxoTrim::Volts2pt2 => 2200,
|
||||
TcxoTrim::Volts2pt4 => 2400,
|
||||
TcxoTrim::Volts2pt7 => 2700,
|
||||
TcxoTrim::Volts3pt0 => 3000,
|
||||
TcxoTrim::Volts3pt3 => 3300,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TCXO trim and HSE32 ready timeout.
|
||||
///
|
||||
/// Argument of [`set_tcxo_mode`].
|
||||
///
|
||||
/// [`set_tcxo_mode`]: crate::subghz::SubGhz::set_tcxo_mode
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TcxoMode {
|
||||
buf: [u8; 5],
|
||||
}
|
||||
|
||||
impl TcxoMode {
|
||||
/// Create a new `TcxoMode` struct.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::TcxoMode;
|
||||
///
|
||||
/// const TCXO_MODE: TcxoMode = TcxoMode::new();
|
||||
/// ```
|
||||
pub const fn new() -> TcxoMode {
|
||||
TcxoMode {
|
||||
buf: [super::OpCode::SetTcxoMode as u8, 0x00, 0x00, 0x00, 0x00],
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the TCXO trim.
|
||||
///
|
||||
/// **Note:** To use V<sub>DDTCXO</sub>, the V<sub>DDRF</sub> supply must be
|
||||
/// at least + 200 mV higher than the selected `TcxoTrim` voltage level.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{TcxoMode, TcxoTrim};
|
||||
///
|
||||
/// const TCXO_MODE: TcxoMode = TcxoMode::new().set_txco_trim(TcxoTrim::Volts1pt6);
|
||||
/// # assert_eq!(TCXO_MODE.as_slice()[1], 0x00);
|
||||
/// ```
|
||||
#[must_use = "set_txco_trim returns a modified TcxoMode"]
|
||||
pub const fn set_txco_trim(mut self, tcxo_trim: TcxoTrim) -> TcxoMode {
|
||||
self.buf[1] = tcxo_trim as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the ready timeout duration.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use core::time::Duration;
|
||||
/// use stm32wl_hal::subghz::{TcxoMode, Timeout};
|
||||
///
|
||||
/// // 15.625 ms timeout
|
||||
/// const TIMEOUT: Timeout = Timeout::from_duration_sat(Duration::from_millis(15_625));
|
||||
/// const TCXO_MODE: TcxoMode = TcxoMode::new().set_timeout(TIMEOUT);
|
||||
/// # assert_eq!(TCXO_MODE.as_slice()[2], 0x0F);
|
||||
/// # assert_eq!(TCXO_MODE.as_slice()[3], 0x42);
|
||||
/// # assert_eq!(TCXO_MODE.as_slice()[4], 0x40);
|
||||
/// ```
|
||||
#[must_use = "set_timeout returns a modified TcxoMode"]
|
||||
pub const fn set_timeout(mut self, timeout: Timeout) -> TcxoMode {
|
||||
let timeout_bits: u32 = timeout.into_bits();
|
||||
self.buf[2] = ((timeout_bits >> 16) & 0xFF) as u8;
|
||||
self.buf[3] = ((timeout_bits >> 8) & 0xFF) as u8;
|
||||
self.buf[4] = (timeout_bits & 0xFF) as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{TcxoMode, TcxoTrim, Timeout};
|
||||
///
|
||||
/// const TCXO_MODE: TcxoMode = TcxoMode::new()
|
||||
/// .set_txco_trim(TcxoTrim::Volts1pt7)
|
||||
/// .set_timeout(Timeout::from_raw(0x123456));
|
||||
/// assert_eq!(TCXO_MODE.as_slice(), &[0x97, 0x1, 0x12, 0x34, 0x56]);
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TcxoMode {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
469
embassy-stm32/src/subghz/timeout.rs
Normal file
469
embassy-stm32/src/subghz/timeout.rs
Normal file
@ -0,0 +1,469 @@
|
||||
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()))
|
||||
);
|
||||
}
|
||||
}
|
166
embassy-stm32/src/subghz/tx_params.rs
Normal file
166
embassy-stm32/src/subghz/tx_params.rs
Normal file
@ -0,0 +1,166 @@
|
||||
/// Power amplifier ramp time for FSK, MSK, and LoRa modulation.
|
||||
///
|
||||
/// Argument of [`set_ramp_time`][`crate::subghz::TxParams::set_ramp_time`].
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
#[repr(u8)]
|
||||
pub enum RampTime {
|
||||
/// 10µs
|
||||
Micros10 = 0x00,
|
||||
/// 20µs
|
||||
Micros20 = 0x01,
|
||||
/// 40µs
|
||||
Micros40 = 0x02,
|
||||
/// 80µs
|
||||
Micros80 = 0x03,
|
||||
/// 200µs
|
||||
Micros200 = 0x04,
|
||||
/// 800µs
|
||||
Micros800 = 0x05,
|
||||
/// 1.7ms
|
||||
Micros1700 = 0x06,
|
||||
/// 3.4ms
|
||||
Micros3400 = 0x07,
|
||||
}
|
||||
|
||||
impl From<RampTime> for u8 {
|
||||
fn from(rt: RampTime) -> Self {
|
||||
rt as u8
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RampTime> for core::time::Duration {
|
||||
fn from(rt: RampTime) -> Self {
|
||||
match rt {
|
||||
RampTime::Micros10 => core::time::Duration::from_micros(10),
|
||||
RampTime::Micros20 => core::time::Duration::from_micros(20),
|
||||
RampTime::Micros40 => core::time::Duration::from_micros(40),
|
||||
RampTime::Micros80 => core::time::Duration::from_micros(80),
|
||||
RampTime::Micros200 => core::time::Duration::from_micros(200),
|
||||
RampTime::Micros800 => core::time::Duration::from_micros(800),
|
||||
RampTime::Micros1700 => core::time::Duration::from_micros(1700),
|
||||
RampTime::Micros3400 => core::time::Duration::from_micros(3400),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RampTime> for embassy::time::Duration {
|
||||
fn from(rt: RampTime) -> Self {
|
||||
match rt {
|
||||
RampTime::Micros10 => embassy::time::Duration::from_micros(10),
|
||||
RampTime::Micros20 => embassy::time::Duration::from_micros(20),
|
||||
RampTime::Micros40 => embassy::time::Duration::from_micros(40),
|
||||
RampTime::Micros80 => embassy::time::Duration::from_micros(80),
|
||||
RampTime::Micros200 => embassy::time::Duration::from_micros(200),
|
||||
RampTime::Micros800 => embassy::time::Duration::from_micros(800),
|
||||
RampTime::Micros1700 => embassy::time::Duration::from_micros(1700),
|
||||
RampTime::Micros3400 => embassy::time::Duration::from_micros(3400),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Transmit parameters, output power and power amplifier ramp up time.
|
||||
///
|
||||
/// Argument of [`set_tx_params`][`crate::subghz::SubGhz::set_tx_params`].
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct TxParams {
|
||||
buf: [u8; 3],
|
||||
}
|
||||
|
||||
impl TxParams {
|
||||
/// Create a new `TxParams` struct.
|
||||
///
|
||||
/// This is the same as `default`, but in a `const` function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::TxParams;
|
||||
///
|
||||
/// const TX_PARAMS: TxParams = TxParams::new();
|
||||
/// assert_eq!(TX_PARAMS, TxParams::default());
|
||||
/// ```
|
||||
pub const fn new() -> TxParams {
|
||||
TxParams {
|
||||
buf: [super::OpCode::SetTxParams as u8, 0x00, 0x00],
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the output power.
|
||||
///
|
||||
/// For low power selected in [`set_pa_config`]:
|
||||
///
|
||||
/// * 0x0E: +14 dB
|
||||
/// * ...
|
||||
/// * 0x00: 0 dB
|
||||
/// * ...
|
||||
/// * 0xEF: -17 dB
|
||||
/// * Others: reserved
|
||||
///
|
||||
/// For high power selected in [`set_pa_config`]:
|
||||
///
|
||||
/// * 0x16: +22 dB
|
||||
/// * ...
|
||||
/// * 0x00: 0 dB
|
||||
/// * ...
|
||||
/// * 0xF7: -9 dB
|
||||
/// * Others: reserved
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Set the output power to 0 dB.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{RampTime, TxParams};
|
||||
///
|
||||
/// const TX_PARAMS: TxParams = TxParams::new().set_power(0x00);
|
||||
/// # assert_eq!(TX_PARAMS.as_slice()[1], 0x00);
|
||||
/// ```
|
||||
///
|
||||
/// [`set_pa_config`]: crate::subghz::SubGhz::set_pa_config
|
||||
#[must_use = "set_power returns a modified TxParams"]
|
||||
pub const fn set_power(mut self, power: u8) -> TxParams {
|
||||
self.buf[1] = power;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the Power amplifier ramp time for FSK, MSK, and LoRa modulation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Set the ramp time to 200 microseconds.
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{RampTime, TxParams};
|
||||
///
|
||||
/// const TX_PARAMS: TxParams = TxParams::new().set_ramp_time(RampTime::Micros200);
|
||||
/// # assert_eq!(TX_PARAMS.as_slice()[2], 0x04);
|
||||
/// ```
|
||||
#[must_use = "set_ramp_time returns a modified TxParams"]
|
||||
pub const fn set_ramp_time(mut self, rt: RampTime) -> TxParams {
|
||||
self.buf[2] = rt as u8;
|
||||
self
|
||||
}
|
||||
|
||||
/// Extracts a slice containing the packet.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::{RampTime, TxParams};
|
||||
///
|
||||
/// const TX_PARAMS: TxParams = TxParams::new()
|
||||
/// .set_ramp_time(RampTime::Micros80)
|
||||
/// .set_power(0x0E);
|
||||
/// assert_eq!(TX_PARAMS.as_slice(), &[0x8E, 0x0E, 0x03]);
|
||||
/// ```
|
||||
pub const fn as_slice(&self) -> &[u8] {
|
||||
&self.buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TxParams {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
129
embassy-stm32/src/subghz/value_error.rs
Normal file
129
embassy-stm32/src/subghz/value_error.rs
Normal file
@ -0,0 +1,129 @@
|
||||
/// Error for a value that is out-of-bounds.
|
||||
///
|
||||
/// Used by [`Timeout::from_duration`].
|
||||
///
|
||||
/// [`Timeout::from_duration`]: crate::subghz::Timeout::from_duration
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct ValueError<T> {
|
||||
value: T,
|
||||
limit: T,
|
||||
over: bool,
|
||||
}
|
||||
|
||||
impl<T> ValueError<T> {
|
||||
/// Create a new `ValueError` for a value that exceeded an upper bound.
|
||||
///
|
||||
/// Unfortunately panic is not avaliable in `const fn`, so there are no
|
||||
/// guarantees on the value being greater than the limit.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::ValueError;
|
||||
///
|
||||
/// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8);
|
||||
/// assert!(ERROR.over());
|
||||
/// assert!(!ERROR.under());
|
||||
/// ```
|
||||
pub const fn too_high(value: T, limit: T) -> ValueError<T> {
|
||||
ValueError {
|
||||
value,
|
||||
limit,
|
||||
over: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `ValueError` for a value that exceeded a lower bound.
|
||||
///
|
||||
/// Unfortunately panic is not avaliable in `const fn`, so there are no
|
||||
/// guarantees on the value being less than the limit.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::ValueError;
|
||||
///
|
||||
/// const ERROR: ValueError<u8> = ValueError::too_low(200u8, 201u8);
|
||||
/// assert!(ERROR.under());
|
||||
/// assert!(!ERROR.over());
|
||||
/// ```
|
||||
pub const fn too_low(value: T, limit: T) -> ValueError<T> {
|
||||
ValueError {
|
||||
value,
|
||||
limit,
|
||||
over: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value that caused the error.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::ValueError;
|
||||
///
|
||||
/// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8);
|
||||
/// assert_eq!(ERROR.value(), &101u8);
|
||||
/// ```
|
||||
pub const fn value(&self) -> &T {
|
||||
&self.value
|
||||
}
|
||||
|
||||
/// Get the limit for the value.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::ValueError;
|
||||
///
|
||||
/// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8);
|
||||
/// assert_eq!(ERROR.limit(), &100u8);
|
||||
/// ```
|
||||
pub const fn limit(&self) -> &T {
|
||||
&self.limit
|
||||
}
|
||||
|
||||
/// Returns `true` if the value was over the limit.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::ValueError;
|
||||
///
|
||||
/// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8);
|
||||
/// assert!(ERROR.over());
|
||||
/// assert!(!ERROR.under());
|
||||
/// ```
|
||||
pub const fn over(&self) -> bool {
|
||||
self.over
|
||||
}
|
||||
|
||||
/// Returns `true` if the value was under the limit.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use stm32wl_hal::subghz::ValueError;
|
||||
///
|
||||
/// const ERROR: ValueError<u8> = ValueError::too_low(200u8, 201u8);
|
||||
/// assert!(ERROR.under());
|
||||
/// assert!(!ERROR.over());
|
||||
/// ```
|
||||
pub const fn under(&self) -> bool {
|
||||
!self.over
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> core::fmt::Display for ValueError<T>
|
||||
where
|
||||
T: core::fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
if self.over {
|
||||
write!(f, "Value is too high {} > {}", self.value, self.limit)
|
||||
} else {
|
||||
write!(f, "Value is too low {} < {}", self.value, self.limit)
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ defmt-error = []
|
||||
[dependencies]
|
||||
embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-trace"] }
|
||||
embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] }
|
||||
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "defmt-trace", "stm32wl55jc_cm4", "time-driver-tim2", "memory-x"] }
|
||||
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "defmt-trace", "stm32wl55jc_cm4", "time-driver-tim2", "memory-x", "subghz"] }
|
||||
embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" }
|
||||
|
||||
defmt = "0.2.0"
|
||||
|
129
examples/stm32wl55/src/bin/subghz.rs
Normal file
129
examples/stm32wl55/src/bin/subghz.rs
Normal file
@ -0,0 +1,129 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![macro_use]
|
||||
#![allow(dead_code)]
|
||||
#![feature(generic_associated_types)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
#[path = "../example_common.rs"]
|
||||
mod example_common;
|
||||
|
||||
use embassy::{traits::gpio::WaitForRisingEdge, util::InterruptFuture};
|
||||
use embassy_stm32::{
|
||||
dbgmcu::Dbgmcu,
|
||||
dma::NoDma,
|
||||
exti::ExtiInput,
|
||||
gpio::{Input, Level, Output, Pull, Speed},
|
||||
interrupt,
|
||||
subghz::*,
|
||||
Peripherals,
|
||||
};
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use example_common::unwrap;
|
||||
|
||||
const PING_DATA: &str = "PING";
|
||||
const DATA_LEN: u8 = PING_DATA.len() as u8;
|
||||
const PING_DATA_BYTES: &[u8] = PING_DATA.as_bytes();
|
||||
const PREAMBLE_LEN: u16 = 5 * 8;
|
||||
|
||||
const RF_FREQ: RfFreq = RfFreq::from_frequency(867_500_000);
|
||||
|
||||
const SYNC_WORD: [u8; 8] = [0x79, 0x80, 0x0C, 0xC0, 0x29, 0x95, 0xF8, 0x4A];
|
||||
const SYNC_WORD_LEN: u8 = SYNC_WORD.len() as u8;
|
||||
const SYNC_WORD_LEN_BITS: u8 = SYNC_WORD_LEN * 8;
|
||||
|
||||
const TX_BUF_OFFSET: u8 = 128;
|
||||
const RX_BUF_OFFSET: u8 = 0;
|
||||
const LORA_PACKET_PARAMS: LoRaPacketParams = LoRaPacketParams::new()
|
||||
.set_crc_en(true)
|
||||
.set_preamble_len(PREAMBLE_LEN)
|
||||
.set_payload_len(DATA_LEN)
|
||||
.set_invert_iq(false)
|
||||
.set_header_type(HeaderType::Fixed);
|
||||
|
||||
const LORA_MOD_PARAMS: LoRaModParams = LoRaModParams::new()
|
||||
.set_bw(LoRaBandwidth::Bw125)
|
||||
.set_cr(CodingRate::Cr45)
|
||||
.set_ldro_en(true)
|
||||
.set_sf(SpreadingFactor::Sf7);
|
||||
|
||||
// configuration for +10 dBm output power
|
||||
// see table 35 "PA optimal setting and operating modes"
|
||||
const PA_CONFIG: PaConfig = PaConfig::new()
|
||||
.set_pa_duty_cycle(0x1)
|
||||
.set_hp_max(0x0)
|
||||
.set_pa(PaSel::Lp);
|
||||
|
||||
const TCXO_MODE: TcxoMode = TcxoMode::new()
|
||||
.set_txco_trim(TcxoTrim::Volts1pt7)
|
||||
.set_timeout(Timeout::from_duration_sat(
|
||||
core::time::Duration::from_millis(10),
|
||||
));
|
||||
|
||||
const TX_PARAMS: TxParams = TxParams::new()
|
||||
.set_power(0x0D)
|
||||
.set_ramp_time(RampTime::Micros40);
|
||||
|
||||
fn config() -> embassy_stm32::Config {
|
||||
let mut config = embassy_stm32::Config::default();
|
||||
config.rcc = config.rcc.clock_src(embassy_stm32::rcc::ClockSrc::HSE32);
|
||||
config
|
||||
}
|
||||
|
||||
#[embassy::main(config = "config()")]
|
||||
async fn main(_spawner: embassy::executor::Spawner, p: Peripherals) {
|
||||
unsafe {
|
||||
Dbgmcu::enable_all();
|
||||
}
|
||||
|
||||
let mut led1 = Output::new(p.PB15, Level::High, Speed::Low);
|
||||
let mut led2 = Output::new(p.PB9, Level::Low, Speed::Low);
|
||||
let mut led3 = Output::new(p.PB11, Level::Low, Speed::Low);
|
||||
|
||||
let button = Input::new(p.PA0, Pull::Up);
|
||||
let mut pin = ExtiInput::new(button, p.EXTI0);
|
||||
|
||||
let mut radio_irq = interrupt::take!(SUBGHZ_RADIO);
|
||||
let mut radio = SubGhz::new(p.SUBGHZSPI, p.PA5, p.PA7, p.PA6, NoDma, NoDma);
|
||||
|
||||
defmt::info!("Radio ready for use");
|
||||
|
||||
unwrap!(led1.set_low());
|
||||
|
||||
unwrap!(led2.set_high());
|
||||
|
||||
unwrap!(radio.set_standby(StandbyClk::Rc));
|
||||
unwrap!(radio.set_tcxo_mode(&TCXO_MODE));
|
||||
unwrap!(radio.set_standby(StandbyClk::Hse));
|
||||
unwrap!(radio.set_regulator_mode(RegMode::Ldo));
|
||||
unwrap!(radio.set_buffer_base_address(TX_BUF_OFFSET, RX_BUF_OFFSET));
|
||||
unwrap!(radio.set_pa_config(&PA_CONFIG));
|
||||
unwrap!(radio.set_pa_ocp(Ocp::Max60m));
|
||||
unwrap!(radio.set_tx_params(&TX_PARAMS));
|
||||
unwrap!(radio.set_packet_type(PacketType::LoRa));
|
||||
unwrap!(radio.set_lora_sync_word(LoRaSyncWord::Public));
|
||||
unwrap!(radio.set_lora_mod_params(&LORA_MOD_PARAMS));
|
||||
unwrap!(radio.set_lora_packet_params(&LORA_PACKET_PARAMS));
|
||||
unwrap!(radio.calibrate_image(CalibrateImage::ISM_863_870));
|
||||
unwrap!(radio.set_rf_frequency(&RF_FREQ));
|
||||
|
||||
defmt::info!("Status: {:?}", unwrap!(radio.status()));
|
||||
|
||||
unwrap!(led2.set_low());
|
||||
|
||||
loop {
|
||||
pin.wait_for_rising_edge().await;
|
||||
unwrap!(led3.set_high());
|
||||
unwrap!(radio.set_irq_cfg(&CfgIrq::new().irq_enable_all(Irq::TxDone)));
|
||||
unwrap!(radio.write_buffer(TX_BUF_OFFSET, PING_DATA_BYTES));
|
||||
unwrap!(radio.set_tx(Timeout::DISABLED));
|
||||
|
||||
InterruptFuture::new(&mut radio_irq).await;
|
||||
let (_, irq_status) = unwrap!(radio.irq_status());
|
||||
if irq_status & Irq::TxDone.mask() != 0 {
|
||||
defmt::info!("TX done");
|
||||
}
|
||||
unwrap!(radio.clear_irq_status(irq_status));
|
||||
unwrap!(led3.set_low());
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit bf50912000cd6c24ef5cb8cc7a0372a116457124
|
||||
Subproject commit 3fb217ad3eebe2d8808b8af4d04ce051c69ecb72
|
Loading…
Reference in New Issue
Block a user