Add HAL for SubGhz peripheral for STM32 WL series

Based on the HAL from stm32wl, the peripheral driver has been
modified to fit into embassy, using the embassy APIs, providing
operation of the radio peripheral.

The initial version does not offer any async APIs, but the example
shows how the radio IRQ can be used to perform async TX of the radio.
This commit is contained in:
Ulf Lilleengen 2021-08-31 14:32:48 +02:00
parent db3cb02032
commit 7ad6280e65
42 changed files with 7110 additions and 9 deletions

View file

@ -18,3 +18,4 @@ defmt = { version = "0.2.0", optional = true }
log = { version = "0.4.11", optional = true } log = { version = "0.4.11", optional = true }
cortex-m = "0.7.1" cortex-m = "0.7.1"
usb-device = "0.2.7" usb-device = "0.2.7"
num-traits = { version = "0.2.14", default-features = false }

View file

@ -8,6 +8,7 @@ mod macros;
pub mod peripheral; pub mod peripheral;
pub mod ring_buffer; pub mod ring_buffer;
pub mod usb; pub mod usb;
pub mod ratio;
/// Low power blocking wait loop using WFE/SEV. /// Low power blocking wait loop using WFE/SEV.
pub fn low_power_wait_until(mut condition: impl FnMut() -> bool) { pub fn low_power_wait_until(mut condition: impl FnMut() -> bool) {

View 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);
}
}

View file

@ -44,6 +44,7 @@ defmt-error = [ ]
sdmmc-rs = ["embedded-sdmmc"] sdmmc-rs = ["embedded-sdmmc"]
net = ["embassy-net", "vcell"] net = ["embassy-net", "vcell"]
memory-x = ["stm32-metapac/memory-x"] memory-x = ["stm32-metapac/memory-x"]
subghz = []
# Features starting with `_` are for internal use only. They're not intended # 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. # to be enabled by other crates, and are not covered by semver guarantees.

View file

@ -51,6 +51,9 @@ pub mod spi;
#[cfg(usart)] #[cfg(usart)]
pub mod 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. // This must go last, so that it sees all the impl_foo! macros defined earlier.
mod generated { mod generated {

View file

@ -1,5 +1,6 @@
#[cfg_attr(any(pwr_h7, pwr_h7smps), path = "h7.rs")] #[cfg_attr(any(pwr_h7, pwr_h7smps), path = "h7.rs")]
#[cfg_attr(pwr_f4, path = "f4.rs")] #[cfg_attr(pwr_f4, path = "f4.rs")]
#[cfg_attr(pwr_wl5, path = "wl5.rs")]
mod _version; mod _version;
pub use _version::*; pub use _version::*;

View file

@ -2,7 +2,6 @@ pub use super::types::*;
use crate::pac; use crate::pac;
use crate::peripherals::{self, RCC}; use crate::peripherals::{self, RCC};
use crate::rcc::{get_freqs, set_freqs, Clocks}; use crate::rcc::{get_freqs, set_freqs, Clocks};
use crate::time::Hertz;
use crate::time::U32Ext; use crate::time::U32Ext;
use core::marker::PhantomData; use core::marker::PhantomData;
use embassy::util::Unborrow; use embassy::util::Unborrow;
@ -16,10 +15,12 @@ use embassy_hal_common::unborrow;
/// HSI speed /// HSI speed
pub const HSI_FREQ: u32 = 16_000_000; pub const HSI_FREQ: u32 = 16_000_000;
pub const HSE32_FREQ: u32 = 32_000_000;
/// System clock mux source /// System clock mux source
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum ClockSrc { pub enum ClockSrc {
HSE(Hertz), HSE32,
HSI16, HSI16,
} }
@ -137,14 +138,17 @@ impl RccExt for RCC {
(HSI_FREQ, 0x01) (HSI_FREQ, 0x01)
} }
ClockSrc::HSE(freq) => { ClockSrc::HSE32 => {
// Enable HSE // Enable HSE32
unsafe { 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() {} while !rcc.cr().read().hserdy() {}
} }
(freq.0, 0x02) (HSE32_FREQ, 0x02)
} }
}; };

View file

@ -9,6 +9,7 @@ pub use _version::*;
use crate::gpio::Pin; use crate::gpio::Pin;
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error { pub enum Error {
Framing, Framing,

View file

@ -71,7 +71,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
let miso = miso.degrade(); let miso = miso.degrade();
let pclk = T::frequency(); 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 { unsafe {
T::enable(); T::enable();

View 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
}
}

View 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()
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View 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()
}
}

View 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],
}
}
}

File diff suppressed because it is too large Load diff

View 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);
}
}

View 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,
}

View 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)
}
}

View 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);
}
}

View 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()
}
}

View 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()
}
}

View 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),
}
}
}

View 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
}
}

View 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,
}

View 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
}
}

View 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
}
}

View 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);
}
}

View 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
}
}

View 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()
}
}

View 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
}
}

View 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
}
}

View 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);
}
}

View 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()
)
}
}

View 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()
}
}

View 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()))
);
}
}

View 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()
}
}

View 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)
}
}
}

View file

@ -19,7 +19,7 @@ defmt-error = []
[dependencies] [dependencies]
embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-trace"] } embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-trace"] }
embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] } 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" } embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" }
defmt = "0.2.0" defmt = "0.2.0"

View 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