Files
embassy/embassy-stm32/src/subghz/mod_params.rs
2022-06-14 16:27:42 +02:00

1046 lines
32 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// 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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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 parameters.
#[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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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
}
/// Returns `true` if the modulation parameters are valid for a worst-case
/// crystal tolerance.
///
/// This is equivalent to [`is_valid`](Self::is_valid) with a `ppm` argument
/// of 30.
#[must_use = "the return value indicates if the modulation parameters are valid"]
pub const fn is_valid_worst_case(&self) -> bool {
self.is_valid(30)
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_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`].
///
/// Higher spreading factors improve receiver sensitivity, but reduce bit rate
/// and increase power consumption.
#[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 = 0x0A,
/// Spreading factor 11.
Sf11 = 0x0B,
/// Spreading factor 12.
Sf12 = 0x0C,
}
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 stm32wlxx_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`].
///
/// A higher coding rate provides better immunity to interference at the expense
/// of longer transmission time.
/// In normal conditions [`CodingRate::Cr45`] provides the best trade off.
/// In case of strong interference, a higher coding rate may be used.
#[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
///
/// Overhead ratio of 1
Cr44 = 0x00,
/// Forward error correction coding rate 4/5
///
/// Overhead ratio of 1.25
Cr45 = 0x1,
/// Forward error correction coding rate 4/6
///
/// Overhead ratio of 1.5
Cr46 = 0x2,
/// Forward error correction coding rate 4/7
///
/// Overhead ratio of 1.75
Cr47 = 0x3,
/// Forward error correction coding rate 4/8
///
/// Overhead ratio of 2
Cr48 = 0x4,
}
/// LoRa modulation parameters.
#[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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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.
///
/// See [`CodingRate`] for more information.
///
/// # Example
///
/// ```
/// use stm32wlxx_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.
///
/// For low data rates (typically high SF or low BW) and very long payloads
/// (may last several seconds), the low data rate optimization (LDRO) can be
/// enabled.
/// This reduces the number of bits per symbol to the given SF minus 2,
/// to allow the receiver to have a better tracking of the LoRa receive
/// signal.
/// Depending on the payload length, the low data rate optimization is
/// usually recommended when the LoRa symbol time is equal or above
/// 16.38 ms.
/// When using LoRa modulation, the total frequency drift over the packet
/// time must be kept lower than Freq_drift_max:
///
/// Freq_drift_max = BW / (3 × 2SF)
///
/// When possible, enabling the low data rate optimization, relaxes the
/// total frequency drift over the packet time by 16:
///
/// Freq_drift_optimise_max = 16 × Freq_drift_max
///
/// # Example
///
/// ```
/// use stm32wlxx_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 stm32wlxx_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 parameters.
///
/// **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 stm32wlxx_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 stm32wlxx_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 stm32wlxx_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);
}
}