diff options
46 files changed, 7116 insertions, 8 deletions
diff --git a/embassy-hal-common/Cargo.toml b/embassy-hal-common/Cargo.toml index 4db536de4..0e28085ff 100644 --- a/embassy-hal-common/Cargo.toml +++ b/embassy-hal-common/Cargo.toml | |||
| @@ -18,3 +18,4 @@ defmt = { version = "0.2.0", optional = true } | |||
| 18 | log = { version = "0.4.11", optional = true } | 18 | log = { version = "0.4.11", optional = true } |
| 19 | cortex-m = "0.7.1" | 19 | cortex-m = "0.7.1" |
| 20 | usb-device = "0.2.7" | 20 | usb-device = "0.2.7" |
| 21 | num-traits = { version = "0.2.14", default-features = false } | ||
diff --git a/embassy-hal-common/src/lib.rs b/embassy-hal-common/src/lib.rs index b62ae8b98..ea20747eb 100644 --- a/embassy-hal-common/src/lib.rs +++ b/embassy-hal-common/src/lib.rs | |||
| @@ -6,6 +6,7 @@ pub(crate) mod fmt; | |||
| 6 | pub mod interrupt; | 6 | pub mod interrupt; |
| 7 | mod macros; | 7 | mod macros; |
| 8 | pub mod peripheral; | 8 | pub mod peripheral; |
| 9 | pub mod ratio; | ||
| 9 | pub mod ring_buffer; | 10 | pub mod ring_buffer; |
| 10 | pub mod usb; | 11 | pub mod usb; |
| 11 | 12 | ||
diff --git a/embassy-hal-common/src/ratio.rs b/embassy-hal-common/src/ratio.rs new file mode 100644 index 000000000..ce7e4b1b9 --- /dev/null +++ b/embassy-hal-common/src/ratio.rs | |||
| @@ -0,0 +1,128 @@ | |||
| 1 | use core::ops::{Add, Div, Mul}; | ||
| 2 | use num_traits::{CheckedAdd, CheckedDiv, CheckedMul}; | ||
| 3 | |||
| 4 | /// Represents the ratio between two numbers. | ||
| 5 | #[derive(Copy, Clone, Debug)] | ||
| 6 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 7 | pub struct Ratio<T> { | ||
| 8 | /// Numerator. | ||
| 9 | numer: T, | ||
| 10 | /// Denominator. | ||
| 11 | denom: T, | ||
| 12 | } | ||
| 13 | |||
| 14 | impl<T> Ratio<T> { | ||
| 15 | /// Creates a new `Ratio`. | ||
| 16 | #[inline(always)] | ||
| 17 | pub const fn new_raw(numer: T, denom: T) -> Ratio<T> { | ||
| 18 | Ratio { numer, denom } | ||
| 19 | } | ||
| 20 | |||
| 21 | /// Gets an immutable reference to the numerator. | ||
| 22 | #[inline(always)] | ||
| 23 | pub const fn numer(&self) -> &T { | ||
| 24 | &self.numer | ||
| 25 | } | ||
| 26 | |||
| 27 | /// Gets an immutable reference to the denominator. | ||
| 28 | #[inline(always)] | ||
| 29 | pub const fn denom(&self) -> &T { | ||
| 30 | &self.denom | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | impl<T: CheckedDiv> Ratio<T> { | ||
| 35 | /// Converts to an integer, rounding towards zero. | ||
| 36 | #[inline(always)] | ||
| 37 | pub fn to_integer(&self) -> T { | ||
| 38 | unwrap!(self.numer().checked_div(self.denom())) | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | impl<T: CheckedMul> Div<T> for Ratio<T> { | ||
| 43 | type Output = Self; | ||
| 44 | |||
| 45 | #[inline(always)] | ||
| 46 | fn div(mut self, rhs: T) -> Self::Output { | ||
| 47 | self.denom = unwrap!(self.denom().checked_mul(&rhs)); | ||
| 48 | self | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | impl<T: CheckedMul> Mul<T> for Ratio<T> { | ||
| 53 | type Output = Self; | ||
| 54 | |||
| 55 | #[inline(always)] | ||
| 56 | fn mul(mut self, rhs: T) -> Self::Output { | ||
| 57 | self.numer = unwrap!(self.numer().checked_mul(&rhs)); | ||
| 58 | self | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | impl<T: CheckedMul + CheckedAdd> Add<T> for Ratio<T> { | ||
| 63 | type Output = Self; | ||
| 64 | |||
| 65 | #[inline(always)] | ||
| 66 | fn add(mut self, rhs: T) -> Self::Output { | ||
| 67 | self.numer = unwrap!(unwrap!(self.denom().checked_mul(&rhs)).checked_add(self.numer())); | ||
| 68 | self | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | macro_rules! impl_from_for_float { | ||
| 73 | ($from:ident) => { | ||
| 74 | impl From<Ratio<$from>> for f32 { | ||
| 75 | #[inline(always)] | ||
| 76 | fn from(r: Ratio<$from>) -> Self { | ||
| 77 | (r.numer as f32) / (r.denom as f32) | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | impl From<Ratio<$from>> for f64 { | ||
| 82 | #[inline(always)] | ||
| 83 | fn from(r: Ratio<$from>) -> Self { | ||
| 84 | (r.numer as f64) / (r.denom as f64) | ||
| 85 | } | ||
| 86 | } | ||
| 87 | }; | ||
| 88 | } | ||
| 89 | |||
| 90 | impl_from_for_float!(u8); | ||
| 91 | impl_from_for_float!(u16); | ||
| 92 | impl_from_for_float!(u32); | ||
| 93 | impl_from_for_float!(u64); | ||
| 94 | impl_from_for_float!(u128); | ||
| 95 | impl_from_for_float!(i8); | ||
| 96 | impl_from_for_float!(i16); | ||
| 97 | impl_from_for_float!(i32); | ||
| 98 | impl_from_for_float!(i64); | ||
| 99 | impl_from_for_float!(i128); | ||
| 100 | |||
| 101 | impl<T: core::fmt::Display> core::fmt::Display for Ratio<T> { | ||
| 102 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 103 | core::write!(f, "{} / {}", self.numer(), self.denom()) | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | #[cfg(test)] | ||
| 108 | mod tests { | ||
| 109 | use super::Ratio; | ||
| 110 | |||
| 111 | #[test] | ||
| 112 | fn basics() { | ||
| 113 | let mut r = Ratio::new_raw(1, 2) + 2; | ||
| 114 | assert_eq!(*r.numer(), 5); | ||
| 115 | assert_eq!(*r.denom(), 2); | ||
| 116 | assert_eq!(r.to_integer(), 2); | ||
| 117 | |||
| 118 | r = r * 2; | ||
| 119 | assert_eq!(*r.numer(), 10); | ||
| 120 | assert_eq!(*r.denom(), 2); | ||
| 121 | assert_eq!(r.to_integer(), 5); | ||
| 122 | |||
| 123 | r = r / 2; | ||
| 124 | assert_eq!(*r.numer(), 10); | ||
| 125 | assert_eq!(*r.denom(), 4); | ||
| 126 | assert_eq!(r.to_integer(), 2); | ||
| 127 | } | ||
| 128 | } | ||
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index f3b2e0e44..325e128d2 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml | |||
| @@ -44,6 +44,7 @@ defmt-error = [ ] | |||
| 44 | sdmmc-rs = ["embedded-sdmmc"] | 44 | sdmmc-rs = ["embedded-sdmmc"] |
| 45 | net = ["embassy-net", "vcell"] | 45 | net = ["embassy-net", "vcell"] |
| 46 | memory-x = ["stm32-metapac/memory-x"] | 46 | memory-x = ["stm32-metapac/memory-x"] |
| 47 | subghz = [] | ||
| 47 | 48 | ||
| 48 | # Features starting with `_` are for internal use only. They're not intended | 49 | # Features starting with `_` are for internal use only. They're not intended |
| 49 | # to be enabled by other crates, and are not covered by semver guarantees. | 50 | # to be enabled by other crates, and are not covered by semver guarantees. |
diff --git a/embassy-stm32/src/adc/g0.rs b/embassy-stm32/src/adc/g0.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/embassy-stm32/src/adc/g0.rs | |||
| @@ -0,0 +1 @@ | |||
diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 1a32f0b9c..9955502a6 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | #![macro_use] | 1 | #![macro_use] |
| 2 | 2 | ||
| 3 | #[cfg_attr(adc_v3, path = "v3.rs")] | 3 | #[cfg_attr(adc_v3, path = "v3.rs")] |
| 4 | #[cfg_attr(adc_g0, path = "g0.rs")] | ||
| 4 | mod _version; | 5 | mod _version; |
| 5 | 6 | ||
| 6 | #[allow(unused)] | 7 | #[allow(unused)] |
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index a423e1554..073e79f2f 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs | |||
| @@ -50,6 +50,9 @@ pub mod spi; | |||
| 50 | #[cfg(usart)] | 50 | #[cfg(usart)] |
| 51 | pub mod usart; | 51 | pub mod usart; |
| 52 | 52 | ||
| 53 | #[cfg(feature = "subghz")] | ||
| 54 | pub mod subghz; | ||
| 55 | |||
| 53 | // This must go last, so that it sees all the impl_foo! macros defined earlier. | 56 | // This must go last, so that it sees all the impl_foo! macros defined earlier. |
| 54 | mod generated { | 57 | mod generated { |
| 55 | 58 | ||
diff --git a/embassy-stm32/src/pwr/g0.rs b/embassy-stm32/src/pwr/g0.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/embassy-stm32/src/pwr/g0.rs | |||
| @@ -0,0 +1 @@ | |||
diff --git a/embassy-stm32/src/pwr/mod.rs b/embassy-stm32/src/pwr/mod.rs index 1bb104bd3..b19ab3265 100644 --- a/embassy-stm32/src/pwr/mod.rs +++ b/embassy-stm32/src/pwr/mod.rs | |||
| @@ -1,5 +1,7 @@ | |||
| 1 | #[cfg_attr(any(pwr_h7, pwr_h7smps), path = "h7.rs")] | 1 | #[cfg_attr(any(pwr_h7, pwr_h7smps), path = "h7.rs")] |
| 2 | #[cfg_attr(pwr_f4, path = "f4.rs")] | 2 | #[cfg_attr(pwr_f4, path = "f4.rs")] |
| 3 | #[cfg_attr(pwr_wl5, path = "wl5.rs")] | ||
| 4 | #[cfg_attr(pwr_g0, path = "g0.rs")] | ||
| 3 | mod _version; | 5 | mod _version; |
| 4 | 6 | ||
| 5 | pub use _version::*; | 7 | pub use _version::*; |
diff --git a/embassy-stm32/src/pwr/wl5.rs b/embassy-stm32/src/pwr/wl5.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/embassy-stm32/src/pwr/wl5.rs | |||
| @@ -0,0 +1 @@ | |||
diff --git a/embassy-stm32/src/rcc/wl5x/mod.rs b/embassy-stm32/src/rcc/wl5x/mod.rs index e1e001c7b..8ed0cb957 100644 --- a/embassy-stm32/src/rcc/wl5x/mod.rs +++ b/embassy-stm32/src/rcc/wl5x/mod.rs | |||
| @@ -2,7 +2,6 @@ pub use super::types::*; | |||
| 2 | use crate::pac; | 2 | use crate::pac; |
| 3 | use crate::peripherals::{self, RCC}; | 3 | use crate::peripherals::{self, RCC}; |
| 4 | use crate::rcc::{get_freqs, set_freqs, Clocks}; | 4 | use crate::rcc::{get_freqs, set_freqs, Clocks}; |
| 5 | use crate::time::Hertz; | ||
| 6 | use crate::time::U32Ext; | 5 | use crate::time::U32Ext; |
| 7 | use core::marker::PhantomData; | 6 | use core::marker::PhantomData; |
| 8 | use embassy::util::Unborrow; | 7 | use embassy::util::Unborrow; |
| @@ -16,10 +15,12 @@ use embassy_hal_common::unborrow; | |||
| 16 | /// HSI speed | 15 | /// HSI speed |
| 17 | pub const HSI_FREQ: u32 = 16_000_000; | 16 | pub const HSI_FREQ: u32 = 16_000_000; |
| 18 | 17 | ||
| 18 | pub const HSE32_FREQ: u32 = 32_000_000; | ||
| 19 | |||
| 19 | /// System clock mux source | 20 | /// System clock mux source |
| 20 | #[derive(Clone, Copy)] | 21 | #[derive(Clone, Copy)] |
| 21 | pub enum ClockSrc { | 22 | pub enum ClockSrc { |
| 22 | HSE(Hertz), | 23 | HSE32, |
| 23 | HSI16, | 24 | HSI16, |
| 24 | } | 25 | } |
| 25 | 26 | ||
| @@ -137,14 +138,17 @@ impl RccExt for RCC { | |||
| 137 | 138 | ||
| 138 | (HSI_FREQ, 0x01) | 139 | (HSI_FREQ, 0x01) |
| 139 | } | 140 | } |
| 140 | ClockSrc::HSE(freq) => { | 141 | ClockSrc::HSE32 => { |
| 141 | // Enable HSE | 142 | // Enable HSE32 |
| 142 | unsafe { | 143 | unsafe { |
| 143 | rcc.cr().write(|w| w.set_hseon(true)); | 144 | rcc.cr().write(|w| { |
| 145 | w.set_hsebyppwr(true); | ||
| 146 | w.set_hseon(true); | ||
| 147 | }); | ||
| 144 | while !rcc.cr().read().hserdy() {} | 148 | while !rcc.cr().read().hserdy() {} |
| 145 | } | 149 | } |
| 146 | 150 | ||
| 147 | (freq.0, 0x02) | 151 | (HSE32_FREQ, 0x02) |
| 148 | } | 152 | } |
| 149 | }; | 153 | }; |
| 150 | 154 | ||
diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index 9bb5a729c..6249de84e 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs | |||
| @@ -9,6 +9,7 @@ pub use _version::*; | |||
| 9 | 9 | ||
| 10 | use crate::gpio::Pin; | 10 | use crate::gpio::Pin; |
| 11 | 11 | ||
| 12 | #[derive(Debug)] | ||
| 12 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 13 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 13 | pub enum Error { | 14 | pub enum Error { |
| 14 | Framing, | 15 | Framing, |
diff --git a/embassy-stm32/src/spi/v2.rs b/embassy-stm32/src/spi/v2.rs index 496d100f7..9df71ef25 100644 --- a/embassy-stm32/src/spi/v2.rs +++ b/embassy-stm32/src/spi/v2.rs | |||
| @@ -71,7 +71,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { | |||
| 71 | let miso = miso.degrade(); | 71 | let miso = miso.degrade(); |
| 72 | 72 | ||
| 73 | let pclk = T::frequency(); | 73 | let pclk = T::frequency(); |
| 74 | let br = Self::compute_baud_rate(pclk, freq.into()); | 74 | let freq = freq.into(); |
| 75 | let br = Self::compute_baud_rate(pclk, freq); | ||
| 75 | 76 | ||
| 76 | unsafe { | 77 | unsafe { |
| 77 | T::enable(); | 78 | T::enable(); |
diff --git a/embassy-stm32/src/subghz/bit_sync.rs b/embassy-stm32/src/subghz/bit_sync.rs new file mode 100644 index 000000000..86b6c48f3 --- /dev/null +++ b/embassy-stm32/src/subghz/bit_sync.rs | |||
| @@ -0,0 +1,160 @@ | |||
| 1 | /// Bit synchronization. | ||
| 2 | /// | ||
| 3 | /// This must be cleared to `0x00` (the reset value) when using packet types | ||
| 4 | /// other than LoRa. | ||
| 5 | /// | ||
| 6 | /// Argument of [`set_bit_sync`](crate::subghz::SubGhz::set_bit_sync). | ||
| 7 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 8 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 9 | pub struct BitSync { | ||
| 10 | val: u8, | ||
| 11 | } | ||
| 12 | |||
| 13 | impl BitSync { | ||
| 14 | /// Bit synchronization register reset value. | ||
| 15 | pub const RESET: BitSync = BitSync { val: 0x00 }; | ||
| 16 | |||
| 17 | /// Create a new [`BitSync`] structure from a raw value. | ||
| 18 | /// | ||
| 19 | /// Reserved bits will be masked. | ||
| 20 | pub const fn from_raw(raw: u8) -> Self { | ||
| 21 | Self { val: raw & 0x70 } | ||
| 22 | } | ||
| 23 | |||
| 24 | /// Get the raw value of the [`BitSync`] register. | ||
| 25 | pub const fn as_bits(&self) -> u8 { | ||
| 26 | self.val | ||
| 27 | } | ||
| 28 | |||
| 29 | /// LoRa simple bit synchronization enable. | ||
| 30 | /// | ||
| 31 | /// # Example | ||
| 32 | /// | ||
| 33 | /// Enable simple bit synchronization. | ||
| 34 | /// | ||
| 35 | /// ``` | ||
| 36 | /// use stm32wl_hal::subghz::BitSync; | ||
| 37 | /// | ||
| 38 | /// const BIT_SYNC: BitSync = BitSync::RESET.set_simple_bit_sync_en(true); | ||
| 39 | /// # assert_eq!(u8::from(BIT_SYNC), 0x40u8); | ||
| 40 | /// ``` | ||
| 41 | #[must_use = "set_simple_bit_sync_en returns a modified BitSync"] | ||
| 42 | pub const fn set_simple_bit_sync_en(mut self, en: bool) -> BitSync { | ||
| 43 | if en { | ||
| 44 | self.val |= 1 << 6; | ||
| 45 | } else { | ||
| 46 | self.val &= !(1 << 6); | ||
| 47 | } | ||
| 48 | self | ||
| 49 | } | ||
| 50 | |||
| 51 | /// Returns `true` if simple bit synchronization is enabled. | ||
| 52 | /// | ||
| 53 | /// # Example | ||
| 54 | /// | ||
| 55 | /// ``` | ||
| 56 | /// use stm32wl_hal::subghz::BitSync; | ||
| 57 | /// | ||
| 58 | /// let bs: BitSync = BitSync::RESET; | ||
| 59 | /// assert_eq!(bs.simple_bit_sync_en(), false); | ||
| 60 | /// let bs: BitSync = bs.set_simple_bit_sync_en(true); | ||
| 61 | /// assert_eq!(bs.simple_bit_sync_en(), true); | ||
| 62 | /// let bs: BitSync = bs.set_simple_bit_sync_en(false); | ||
| 63 | /// assert_eq!(bs.simple_bit_sync_en(), false); | ||
| 64 | /// ``` | ||
| 65 | pub const fn simple_bit_sync_en(&self) -> bool { | ||
| 66 | self.val & (1 << 6) != 0 | ||
| 67 | } | ||
| 68 | |||
| 69 | /// LoRa RX data inversion. | ||
| 70 | /// | ||
| 71 | /// # Example | ||
| 72 | /// | ||
| 73 | /// Invert receive data. | ||
| 74 | /// | ||
| 75 | /// ``` | ||
| 76 | /// use stm32wl_hal::subghz::BitSync; | ||
| 77 | /// | ||
| 78 | /// const BIT_SYNC: BitSync = BitSync::RESET.set_rx_data_inv(true); | ||
| 79 | /// # assert_eq!(u8::from(BIT_SYNC), 0x20u8); | ||
| 80 | /// ``` | ||
| 81 | #[must_use = "set_rx_data_inv returns a modified BitSync"] | ||
| 82 | pub const fn set_rx_data_inv(mut self, inv: bool) -> BitSync { | ||
| 83 | if inv { | ||
| 84 | self.val |= 1 << 5; | ||
| 85 | } else { | ||
| 86 | self.val &= !(1 << 5); | ||
| 87 | } | ||
| 88 | self | ||
| 89 | } | ||
| 90 | |||
| 91 | /// Returns `true` if LoRa RX data is inverted. | ||
| 92 | /// | ||
| 93 | /// # Example | ||
| 94 | /// | ||
| 95 | /// ``` | ||
| 96 | /// use stm32wl_hal::subghz::BitSync; | ||
| 97 | /// | ||
| 98 | /// let bs: BitSync = BitSync::RESET; | ||
| 99 | /// assert_eq!(bs.rx_data_inv(), false); | ||
| 100 | /// let bs: BitSync = bs.set_rx_data_inv(true); | ||
| 101 | /// assert_eq!(bs.rx_data_inv(), true); | ||
| 102 | /// let bs: BitSync = bs.set_rx_data_inv(false); | ||
| 103 | /// assert_eq!(bs.rx_data_inv(), false); | ||
| 104 | /// ``` | ||
| 105 | pub const fn rx_data_inv(&self) -> bool { | ||
| 106 | self.val & (1 << 5) != 0 | ||
| 107 | } | ||
| 108 | |||
| 109 | /// LoRa normal bit synchronization enable. | ||
| 110 | /// | ||
| 111 | /// # Example | ||
| 112 | /// | ||
| 113 | /// Enable normal bit synchronization. | ||
| 114 | /// | ||
| 115 | /// ``` | ||
| 116 | /// use stm32wl_hal::subghz::BitSync; | ||
| 117 | /// | ||
| 118 | /// const BIT_SYNC: BitSync = BitSync::RESET.set_norm_bit_sync_en(true); | ||
| 119 | /// # assert_eq!(u8::from(BIT_SYNC), 0x10u8); | ||
| 120 | /// ``` | ||
| 121 | #[must_use = "set_norm_bit_sync_en returns a modified BitSync"] | ||
| 122 | pub const fn set_norm_bit_sync_en(mut self, en: bool) -> BitSync { | ||
| 123 | if en { | ||
| 124 | self.val |= 1 << 4; | ||
| 125 | } else { | ||
| 126 | self.val &= !(1 << 4); | ||
| 127 | } | ||
| 128 | self | ||
| 129 | } | ||
| 130 | |||
| 131 | /// Returns `true` if normal bit synchronization is enabled. | ||
| 132 | /// | ||
| 133 | /// # Example | ||
| 134 | /// | ||
| 135 | /// ``` | ||
| 136 | /// use stm32wl_hal::subghz::BitSync; | ||
| 137 | /// | ||
| 138 | /// let bs: BitSync = BitSync::RESET; | ||
| 139 | /// assert_eq!(bs.norm_bit_sync_en(), false); | ||
| 140 | /// let bs: BitSync = bs.set_norm_bit_sync_en(true); | ||
| 141 | /// assert_eq!(bs.norm_bit_sync_en(), true); | ||
| 142 | /// let bs: BitSync = bs.set_norm_bit_sync_en(false); | ||
| 143 | /// assert_eq!(bs.norm_bit_sync_en(), false); | ||
| 144 | /// ``` | ||
| 145 | pub const fn norm_bit_sync_en(&self) -> bool { | ||
| 146 | self.val & (1 << 4) != 0 | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | impl From<BitSync> for u8 { | ||
| 151 | fn from(bs: BitSync) -> Self { | ||
| 152 | bs.val | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | impl Default for BitSync { | ||
| 157 | fn default() -> Self { | ||
| 158 | Self::RESET | ||
| 159 | } | ||
| 160 | } | ||
diff --git a/embassy-stm32/src/subghz/cad_params.rs b/embassy-stm32/src/subghz/cad_params.rs new file mode 100644 index 000000000..fc887a245 --- /dev/null +++ b/embassy-stm32/src/subghz/cad_params.rs | |||
| @@ -0,0 +1,230 @@ | |||
| 1 | use crate::subghz::timeout::Timeout; | ||
| 2 | |||
| 3 | /// Number of symbols used for channel activity detection scans. | ||
| 4 | /// | ||
| 5 | /// Argument of [`CadParams::set_num_symbol`]. | ||
| 6 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] | ||
| 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 8 | #[repr(u8)] | ||
| 9 | pub enum NbCadSymbol { | ||
| 10 | /// 1 symbol. | ||
| 11 | S1 = 0x0, | ||
| 12 | /// 2 symbols. | ||
| 13 | S2 = 0x1, | ||
| 14 | /// 4 symbols. | ||
| 15 | S4 = 0x2, | ||
| 16 | /// 8 symbols. | ||
| 17 | S8 = 0x3, | ||
| 18 | /// 16 symbols. | ||
| 19 | S16 = 0x4, | ||
| 20 | } | ||
| 21 | |||
| 22 | /// Mode to enter after a channel activity detection scan is finished. | ||
| 23 | /// | ||
| 24 | /// Argument of [`CadParams::set_exit_mode`]. | ||
| 25 | #[derive(Debug, PartialEq, Eq)] | ||
| 26 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 27 | #[repr(u8)] | ||
| 28 | pub enum ExitMode { | ||
| 29 | /// Standby with RC 13 MHz mode entry after CAD. | ||
| 30 | Standby = 0, | ||
| 31 | /// Standby with RC 13 MHz mode after CAD if no LoRa symbol is detected | ||
| 32 | /// during the CAD scan. | ||
| 33 | /// If a LoRa symbol is detected, the sub-GHz radio stays in RX mode | ||
| 34 | /// until a packet is received or until the CAD timeout is reached. | ||
| 35 | StandbyLoRa = 1, | ||
| 36 | } | ||
| 37 | |||
| 38 | /// Channel activity detection (CAD) parameters. | ||
| 39 | /// | ||
| 40 | /// Argument of [`set_cad_params`]. | ||
| 41 | /// | ||
| 42 | /// # Recommended CAD settings | ||
| 43 | /// | ||
| 44 | /// This is taken directly from the datasheet. | ||
| 45 | /// | ||
| 46 | /// "The correct values selected in the table below must be carefully tested to | ||
| 47 | /// ensure a good detection at sensitivity level and to limit the number of | ||
| 48 | /// false detections" | ||
| 49 | /// | ||
| 50 | /// | SF (Spreading Factor) | [`set_det_peak`] | [`set_det_min`] | | ||
| 51 | /// |-----------------------|------------------|-----------------| | ||
| 52 | /// | 5 | 0x18 | 0x10 | | ||
| 53 | /// | 6 | 0x19 | 0x10 | | ||
| 54 | /// | 7 | 0x20 | 0x10 | | ||
| 55 | /// | 8 | 0x21 | 0x10 | | ||
| 56 | /// | 9 | 0x22 | 0x10 | | ||
| 57 | /// | 10 | 0x23 | 0x10 | | ||
| 58 | /// | 11 | 0x24 | 0x10 | | ||
| 59 | /// | 12 | 0x25 | 0x10 | | ||
| 60 | /// | ||
| 61 | /// [`set_cad_params`]: crate::subghz::SubGhz::set_cad_params | ||
| 62 | /// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak | ||
| 63 | /// [`set_det_min`]: crate::subghz::CadParams::set_det_min | ||
| 64 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 65 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 66 | pub struct CadParams { | ||
| 67 | buf: [u8; 8], | ||
| 68 | } | ||
| 69 | |||
| 70 | impl CadParams { | ||
| 71 | /// Create a new `CadParams`. | ||
| 72 | /// | ||
| 73 | /// This is the same as `default`, but in a `const` function. | ||
| 74 | /// | ||
| 75 | /// # Example | ||
| 76 | /// | ||
| 77 | /// ``` | ||
| 78 | /// use stm32wl_hal::subghz::CadParams; | ||
| 79 | /// | ||
| 80 | /// const CAD_PARAMS: CadParams = CadParams::new(); | ||
| 81 | /// assert_eq!(CAD_PARAMS, CadParams::default()); | ||
| 82 | /// ``` | ||
| 83 | pub const fn new() -> CadParams { | ||
| 84 | CadParams { | ||
| 85 | buf: [super::OpCode::SetCadParams as u8, 0, 0, 0, 0, 0, 0, 0], | ||
| 86 | } | ||
| 87 | .set_num_symbol(NbCadSymbol::S1) | ||
| 88 | .set_det_peak(0x18) | ||
| 89 | .set_det_min(0x10) | ||
| 90 | .set_exit_mode(ExitMode::Standby) | ||
| 91 | } | ||
| 92 | |||
| 93 | /// Number of symbols used for a CAD scan. | ||
| 94 | /// | ||
| 95 | /// # Example | ||
| 96 | /// | ||
| 97 | /// Set the number of symbols to 4. | ||
| 98 | /// | ||
| 99 | /// ``` | ||
| 100 | /// use stm32wl_hal::subghz::{CadParams, NbCadSymbol}; | ||
| 101 | /// | ||
| 102 | /// const CAD_PARAMS: CadParams = CadParams::new().set_num_symbol(NbCadSymbol::S4); | ||
| 103 | /// # assert_eq!(CAD_PARAMS.as_slice()[1], 0x2); | ||
| 104 | /// ``` | ||
| 105 | #[must_use = "set_num_symbol returns a modified CadParams"] | ||
| 106 | pub const fn set_num_symbol(mut self, nb: NbCadSymbol) -> CadParams { | ||
| 107 | self.buf[1] = nb as u8; | ||
| 108 | self | ||
| 109 | } | ||
| 110 | |||
| 111 | /// Used with [`set_det_min`] to correlate the LoRa symbol. | ||
| 112 | /// | ||
| 113 | /// See the table in [`CadParams`] docs for recommended values. | ||
| 114 | /// | ||
| 115 | /// # Example | ||
| 116 | /// | ||
| 117 | /// Setting the recommended value for a spreading factor of 7. | ||
| 118 | /// | ||
| 119 | /// ``` | ||
| 120 | /// use stm32wl_hal::subghz::CadParams; | ||
| 121 | /// | ||
| 122 | /// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x20).set_det_min(0x10); | ||
| 123 | /// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x20); | ||
| 124 | /// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10); | ||
| 125 | /// ``` | ||
| 126 | /// | ||
| 127 | /// [`set_det_min`]: crate::subghz::CadParams::set_det_min | ||
| 128 | #[must_use = "set_det_peak returns a modified CadParams"] | ||
| 129 | pub const fn set_det_peak(mut self, peak: u8) -> CadParams { | ||
| 130 | self.buf[2] = peak; | ||
| 131 | self | ||
| 132 | } | ||
| 133 | |||
| 134 | /// Used with [`set_det_peak`] to correlate the LoRa symbol. | ||
| 135 | /// | ||
| 136 | /// See the table in [`CadParams`] docs for recommended values. | ||
| 137 | /// | ||
| 138 | /// # Example | ||
| 139 | /// | ||
| 140 | /// Setting the recommended value for a spreading factor of 6. | ||
| 141 | /// | ||
| 142 | /// ``` | ||
| 143 | /// use stm32wl_hal::subghz::CadParams; | ||
| 144 | /// | ||
| 145 | /// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x18).set_det_min(0x10); | ||
| 146 | /// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x18); | ||
| 147 | /// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10); | ||
| 148 | /// ``` | ||
| 149 | /// | ||
| 150 | /// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak | ||
| 151 | #[must_use = "set_det_min returns a modified CadParams"] | ||
| 152 | pub const fn set_det_min(mut self, min: u8) -> CadParams { | ||
| 153 | self.buf[3] = min; | ||
| 154 | self | ||
| 155 | } | ||
| 156 | |||
| 157 | /// Mode to enter after a channel activity detection scan is finished. | ||
| 158 | /// | ||
| 159 | /// # Example | ||
| 160 | /// | ||
| 161 | /// ``` | ||
| 162 | /// use stm32wl_hal::subghz::{CadParams, ExitMode}; | ||
| 163 | /// | ||
| 164 | /// const CAD_PARAMS: CadParams = CadParams::new().set_exit_mode(ExitMode::Standby); | ||
| 165 | /// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x00); | ||
| 166 | /// # assert_eq!(CAD_PARAMS.set_exit_mode(ExitMode::StandbyLoRa).as_slice()[4], 0x01); | ||
| 167 | /// ``` | ||
| 168 | #[must_use = "set_exit_mode returns a modified CadParams"] | ||
| 169 | pub const fn set_exit_mode(mut self, mode: ExitMode) -> CadParams { | ||
| 170 | self.buf[4] = mode as u8; | ||
| 171 | self | ||
| 172 | } | ||
| 173 | |||
| 174 | /// Set the timeout. | ||
| 175 | /// | ||
| 176 | /// This is only used with [`ExitMode::StandbyLoRa`]. | ||
| 177 | /// | ||
| 178 | /// # Example | ||
| 179 | /// | ||
| 180 | /// ``` | ||
| 181 | /// use stm32wl_hal::subghz::{CadParams, ExitMode, Timeout}; | ||
| 182 | /// | ||
| 183 | /// const TIMEOUT: Timeout = Timeout::from_raw(0x123456); | ||
| 184 | /// const CAD_PARAMS: CadParams = CadParams::new() | ||
| 185 | /// .set_exit_mode(ExitMode::StandbyLoRa) | ||
| 186 | /// .set_timeout(TIMEOUT); | ||
| 187 | /// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x01); | ||
| 188 | /// # assert_eq!(CAD_PARAMS.as_slice()[5], 0x12); | ||
| 189 | /// # assert_eq!(CAD_PARAMS.as_slice()[6], 0x34); | ||
| 190 | /// # assert_eq!(CAD_PARAMS.as_slice()[7], 0x56); | ||
| 191 | /// ``` | ||
| 192 | #[must_use = "set_timeout returns a modified CadParams"] | ||
| 193 | pub const fn set_timeout(mut self, to: Timeout) -> CadParams { | ||
| 194 | let to_bytes: [u8; 3] = to.as_bytes(); | ||
| 195 | self.buf[5] = to_bytes[0]; | ||
| 196 | self.buf[6] = to_bytes[1]; | ||
| 197 | self.buf[7] = to_bytes[2]; | ||
| 198 | self | ||
| 199 | } | ||
| 200 | |||
| 201 | /// Extracts a slice containing the packet. | ||
| 202 | /// | ||
| 203 | /// # Example | ||
| 204 | /// | ||
| 205 | /// ``` | ||
| 206 | /// use stm32wl_hal::subghz::{CadParams, ExitMode, NbCadSymbol, Timeout}; | ||
| 207 | /// | ||
| 208 | /// const TIMEOUT: Timeout = Timeout::from_raw(0x123456); | ||
| 209 | /// const CAD_PARAMS: CadParams = CadParams::new() | ||
| 210 | /// .set_num_symbol(NbCadSymbol::S4) | ||
| 211 | /// .set_det_peak(0x18) | ||
| 212 | /// .set_det_min(0x10) | ||
| 213 | /// .set_exit_mode(ExitMode::StandbyLoRa) | ||
| 214 | /// .set_timeout(TIMEOUT); | ||
| 215 | /// | ||
| 216 | /// assert_eq!( | ||
| 217 | /// CAD_PARAMS.as_slice(), | ||
| 218 | /// &[0x88, 0x02, 0x18, 0x10, 0x01, 0x12, 0x34, 0x56] | ||
| 219 | /// ); | ||
| 220 | /// ``` | ||
| 221 | pub const fn as_slice(&self) -> &[u8] { | ||
| 222 | &self.buf | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | impl Default for CadParams { | ||
| 227 | fn default() -> Self { | ||
| 228 | Self::new() | ||
| 229 | } | ||
| 230 | } | ||
diff --git a/embassy-stm32/src/subghz/calibrate.rs b/embassy-stm32/src/subghz/calibrate.rs new file mode 100644 index 000000000..dc8c8069d --- /dev/null +++ b/embassy-stm32/src/subghz/calibrate.rs | |||
| @@ -0,0 +1,122 @@ | |||
| 1 | /// Image calibration. | ||
| 2 | /// | ||
| 3 | /// Argument of [`calibrate_image`]. | ||
| 4 | /// | ||
| 5 | /// [`calibrate_image`]: crate::subghz::SubGhz::calibrate_image | ||
| 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 8 | pub struct CalibrateImage(pub(crate) u8, pub(crate) u8); | ||
| 9 | |||
| 10 | impl CalibrateImage { | ||
| 11 | /// Image calibration for the 430 - 440 MHz ISM band. | ||
| 12 | pub const ISM_430_440: CalibrateImage = CalibrateImage(0x6B, 0x6F); | ||
| 13 | |||
| 14 | /// Image calibration for the 470 - 510 MHz ISM band. | ||
| 15 | pub const ISM_470_510: CalibrateImage = CalibrateImage(0x75, 0x81); | ||
| 16 | |||
| 17 | /// Image calibration for the 779 - 787 MHz ISM band. | ||
| 18 | pub const ISM_779_787: CalibrateImage = CalibrateImage(0xC1, 0xC5); | ||
| 19 | |||
| 20 | /// Image calibration for the 863 - 870 MHz ISM band. | ||
| 21 | pub const ISM_863_870: CalibrateImage = CalibrateImage(0xD7, 0xDB); | ||
| 22 | |||
| 23 | /// Image calibration for the 902 - 928 MHz ISM band. | ||
| 24 | pub const ISM_902_928: CalibrateImage = CalibrateImage(0xE1, 0xE9); | ||
| 25 | |||
| 26 | /// Create a new `CalibrateImage` structure from raw values. | ||
| 27 | /// | ||
| 28 | /// # Example | ||
| 29 | /// | ||
| 30 | /// ``` | ||
| 31 | /// use stm32wl_hal::subghz::CalibrateImage; | ||
| 32 | /// | ||
| 33 | /// const CAL: CalibrateImage = CalibrateImage::new(0xE1, 0xE9); | ||
| 34 | /// assert_eq!(CAL, CalibrateImage::ISM_902_928); | ||
| 35 | /// ``` | ||
| 36 | pub const fn new(f1: u8, f2: u8) -> CalibrateImage { | ||
| 37 | CalibrateImage(f1, f2) | ||
| 38 | } | ||
| 39 | |||
| 40 | /// Create a new `CalibrateImage` structure from two frequencies. | ||
| 41 | /// | ||
| 42 | /// # Arguments | ||
| 43 | /// | ||
| 44 | /// The units for `freq1` and `freq2` are in MHz. | ||
| 45 | /// | ||
| 46 | /// # Panics | ||
| 47 | /// | ||
| 48 | /// * Panics if `freq1` is less than `freq2`. | ||
| 49 | /// * Panics if `freq1` or `freq2` is not a multiple of 4MHz. | ||
| 50 | /// * Panics if `freq1` or `freq2` is greater than `1020`. | ||
| 51 | /// | ||
| 52 | /// # Example | ||
| 53 | /// | ||
| 54 | /// Create an image calibration for the 430 - 440 MHz ISM band. | ||
| 55 | /// | ||
| 56 | /// ``` | ||
| 57 | /// use stm32wl_hal::subghz::CalibrateImage; | ||
| 58 | /// | ||
| 59 | /// let cal: CalibrateImage = CalibrateImage::from_freq(428, 444); | ||
| 60 | /// assert_eq!(cal, CalibrateImage::ISM_430_440); | ||
| 61 | /// ``` | ||
| 62 | pub fn from_freq(freq1: u16, freq2: u16) -> CalibrateImage { | ||
| 63 | assert!(freq2 >= freq1); | ||
| 64 | assert_eq!(freq1 % 4, 0); | ||
| 65 | assert_eq!(freq2 % 4, 0); | ||
| 66 | assert!(freq1 <= 1020); | ||
| 67 | assert!(freq2 <= 1020); | ||
| 68 | CalibrateImage((freq1 / 4) as u8, (freq2 / 4) as u8) | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | impl Default for CalibrateImage { | ||
| 73 | fn default() -> Self { | ||
| 74 | CalibrateImage::new(0xE1, 0xE9) | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | /// Block calibration. | ||
| 79 | /// | ||
| 80 | /// Argument of [`calibrate`]. | ||
| 81 | /// | ||
| 82 | /// [`calibrate`]: crate::subghz::SubGhz::calibrate | ||
| 83 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] | ||
| 84 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 85 | #[repr(u8)] | ||
| 86 | pub enum Calibrate { | ||
| 87 | /// Image calibration | ||
| 88 | Image = 1 << 6, | ||
| 89 | /// RF-ADC bulk P calibration | ||
| 90 | AdcBulkP = 1 << 5, | ||
| 91 | /// RF-ADC bulk N calibration | ||
| 92 | AdcBulkN = 1 << 4, | ||
| 93 | /// RF-ADC pulse calibration | ||
| 94 | AdcPulse = 1 << 3, | ||
| 95 | /// RF-PLL calibration | ||
| 96 | Pll = 1 << 2, | ||
| 97 | /// Sub-GHz radio RC 13 MHz calibration | ||
| 98 | Rc13M = 1 << 1, | ||
| 99 | /// Sub-GHz radio RC 64 kHz calibration | ||
| 100 | Rc64K = 1, | ||
| 101 | } | ||
| 102 | |||
| 103 | impl Calibrate { | ||
| 104 | /// Get the bitmask for the block calibration. | ||
| 105 | /// | ||
| 106 | /// # Example | ||
| 107 | /// | ||
| 108 | /// ``` | ||
| 109 | /// use stm32wl_hal::subghz::Calibrate; | ||
| 110 | /// | ||
| 111 | /// assert_eq!(Calibrate::Image.mask(), 0b0100_0000); | ||
| 112 | /// assert_eq!(Calibrate::AdcBulkP.mask(), 0b0010_0000); | ||
| 113 | /// assert_eq!(Calibrate::AdcBulkN.mask(), 0b0001_0000); | ||
| 114 | /// assert_eq!(Calibrate::AdcPulse.mask(), 0b0000_1000); | ||
| 115 | /// assert_eq!(Calibrate::Pll.mask(), 0b0000_0100); | ||
| 116 | /// assert_eq!(Calibrate::Rc13M.mask(), 0b0000_0010); | ||
| 117 | /// assert_eq!(Calibrate::Rc64K.mask(), 0b0000_0001); | ||
| 118 | /// ``` | ||
| 119 | pub const fn mask(self) -> u8 { | ||
| 120 | self as u8 | ||
| 121 | } | ||
| 122 | } | ||
diff --git a/embassy-stm32/src/subghz/fallback_mode.rs b/embassy-stm32/src/subghz/fallback_mode.rs new file mode 100644 index 000000000..bc7204da8 --- /dev/null +++ b/embassy-stm32/src/subghz/fallback_mode.rs | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | /// Fallback mode after successful packet transmission or packet reception. | ||
| 2 | /// | ||
| 3 | /// Argument of [`set_tx_rx_fallback_mode`]. | ||
| 4 | /// | ||
| 5 | /// [`set_tx_rx_fallback_mode`]: crate::subghz::SubGhz::set_tx_rx_fallback_mode. | ||
| 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 8 | #[repr(u8)] | ||
| 9 | pub enum FallbackMode { | ||
| 10 | /// Standby mode entry. | ||
| 11 | Standby = 0x20, | ||
| 12 | /// Standby with HSE32 enabled. | ||
| 13 | StandbyHse = 0x30, | ||
| 14 | /// Frequency synthesizer entry. | ||
| 15 | Fs = 0x40, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl From<FallbackMode> for u8 { | ||
| 19 | fn from(fm: FallbackMode) -> Self { | ||
| 20 | fm as u8 | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | impl Default for FallbackMode { | ||
| 25 | /// Default fallback mode after power-on reset. | ||
| 26 | /// | ||
| 27 | /// # Example | ||
| 28 | /// | ||
| 29 | /// ``` | ||
| 30 | /// use stm32wl_hal::subghz::FallbackMode; | ||
| 31 | /// | ||
| 32 | /// assert_eq!(FallbackMode::default(), FallbackMode::Standby); | ||
| 33 | /// ``` | ||
| 34 | fn default() -> Self { | ||
| 35 | FallbackMode::Standby | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/embassy-stm32/src/subghz/hse_trim.rs b/embassy-stm32/src/subghz/hse_trim.rs new file mode 100644 index 000000000..101baa860 --- /dev/null +++ b/embassy-stm32/src/subghz/hse_trim.rs | |||
| @@ -0,0 +1,107 @@ | |||
| 1 | use crate::subghz::value_error::ValueError; | ||
| 2 | |||
| 3 | /// HSE32 load capacitor trimming. | ||
| 4 | /// | ||
| 5 | /// Argument of [`set_hse_in_trim`] and [`set_hse_out_trim`]. | ||
| 6 | /// | ||
| 7 | /// [`set_hse_in_trim`]: crate::subghz::SubGhz::set_hse_in_trim | ||
| 8 | /// [`set_hse_out_trim`]: crate::subghz::SubGhz::set_hse_out_trim | ||
| 9 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] | ||
| 10 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 11 | pub struct HseTrim { | ||
| 12 | val: u8, | ||
| 13 | } | ||
| 14 | |||
| 15 | impl HseTrim { | ||
| 16 | /// Maximum capacitor value, ~33.4 pF | ||
| 17 | pub const MAX: HseTrim = HseTrim::from_raw(0x2F); | ||
| 18 | |||
| 19 | /// Minimum capacitor value, ~11.3 pF | ||
| 20 | pub const MIN: HseTrim = HseTrim::from_raw(0x00); | ||
| 21 | |||
| 22 | /// Power-on-reset capacitor value, ~20.3 pF | ||
| 23 | /// | ||
| 24 | /// This is the same as `default`. | ||
| 25 | /// | ||
| 26 | /// # Example | ||
| 27 | /// | ||
| 28 | /// ``` | ||
| 29 | /// use stm32wl_hal::subghz::HseTrim; | ||
| 30 | /// | ||
| 31 | /// assert_eq!(HseTrim::POR, HseTrim::default()); | ||
| 32 | /// ``` | ||
| 33 | pub const POR: HseTrim = HseTrim::from_raw(0x12); | ||
| 34 | |||
| 35 | /// Create a new [`HseTrim`] structure from a raw value. | ||
| 36 | /// | ||
| 37 | /// Values greater than the maximum of `0x2F` will be set to the maximum. | ||
| 38 | /// | ||
| 39 | /// # Example | ||
| 40 | /// | ||
| 41 | /// ``` | ||
| 42 | /// use stm32wl_hal::subghz::HseTrim; | ||
| 43 | /// | ||
| 44 | /// assert_eq!(HseTrim::from_raw(0xFF), HseTrim::MAX); | ||
| 45 | /// assert_eq!(HseTrim::from_raw(0x2F), HseTrim::MAX); | ||
| 46 | /// assert_eq!(HseTrim::from_raw(0x00), HseTrim::MIN); | ||
| 47 | /// ``` | ||
| 48 | pub const fn from_raw(raw: u8) -> HseTrim { | ||
| 49 | if raw > 0x2F { | ||
| 50 | HseTrim { val: 0x2F } | ||
| 51 | } else { | ||
| 52 | HseTrim { val: raw } | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | /// Create a HSE trim value from farads. | ||
| 57 | /// | ||
| 58 | /// Values greater than the maximum of 33.4 pF will be set to the maximum. | ||
| 59 | /// Values less than the minimum of 11.3 pF will be set to the minimum. | ||
| 60 | /// | ||
| 61 | /// # Example | ||
| 62 | /// | ||
| 63 | /// ``` | ||
| 64 | /// use stm32wl_hal::subghz::HseTrim; | ||
| 65 | /// | ||
| 66 | /// assert!(HseTrim::from_farads(1.0).is_err()); | ||
| 67 | /// assert!(HseTrim::from_farads(1e-12).is_err()); | ||
| 68 | /// assert_eq!(HseTrim::from_farads(20.2e-12), Ok(HseTrim::default())); | ||
| 69 | /// ``` | ||
| 70 | pub fn from_farads(farads: f32) -> Result<HseTrim, ValueError<f32>> { | ||
| 71 | const MAX: f32 = 33.4E-12; | ||
| 72 | const MIN: f32 = 11.3E-12; | ||
| 73 | if farads > MAX { | ||
| 74 | Err(ValueError::too_high(farads, MAX)) | ||
| 75 | } else if farads < MIN { | ||
| 76 | Err(ValueError::too_low(farads, MIN)) | ||
| 77 | } else { | ||
| 78 | Ok(HseTrim::from_raw(((farads - 11.3e-12) / 0.47e-12) as u8)) | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | /// Get the capacitance as farads. | ||
| 83 | /// | ||
| 84 | /// # Example | ||
| 85 | /// | ||
| 86 | /// ``` | ||
| 87 | /// use stm32wl_hal::subghz::HseTrim; | ||
| 88 | /// | ||
| 89 | /// assert_eq!((HseTrim::MAX.as_farads() * 10e11) as u8, 33); | ||
| 90 | /// assert_eq!((HseTrim::MIN.as_farads() * 10e11) as u8, 11); | ||
| 91 | /// ``` | ||
| 92 | pub fn as_farads(&self) -> f32 { | ||
| 93 | (self.val as f32) * 0.47E-12 + 11.3E-12 | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | impl From<HseTrim> for u8 { | ||
| 98 | fn from(ht: HseTrim) -> Self { | ||
| 99 | ht.val | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | impl Default for HseTrim { | ||
| 104 | fn default() -> Self { | ||
| 105 | Self::POR | ||
| 106 | } | ||
| 107 | } | ||
diff --git a/embassy-stm32/src/subghz/irq.rs b/embassy-stm32/src/subghz/irq.rs new file mode 100644 index 000000000..b113095a7 --- /dev/null +++ b/embassy-stm32/src/subghz/irq.rs | |||
| @@ -0,0 +1,292 @@ | |||
| 1 | /// Interrupt lines. | ||
| 2 | /// | ||
| 3 | /// Argument of [`CfgIrq::irq_enable`] and [`CfgIrq::irq_disable`]. | ||
| 4 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 6 | pub enum IrqLine { | ||
| 7 | /// Global interrupt. | ||
| 8 | Global, | ||
| 9 | /// Interrupt line 1. | ||
| 10 | /// | ||
| 11 | /// This will output to the [`RfIrq0`](crate::gpio::RfIrq0) pin. | ||
| 12 | Line1, | ||
| 13 | /// Interrupt line 2. | ||
| 14 | /// | ||
| 15 | /// This will output to the [`RfIrq1`](crate::gpio::RfIrq1) pin. | ||
| 16 | Line2, | ||
| 17 | /// Interrupt line 3. | ||
| 18 | /// | ||
| 19 | /// This will output to the [`RfIrq2`](crate::gpio::RfIrq2) pin. | ||
| 20 | Line3, | ||
| 21 | } | ||
| 22 | |||
| 23 | impl IrqLine { | ||
| 24 | pub(super) const fn offset(&self) -> usize { | ||
| 25 | match self { | ||
| 26 | IrqLine::Global => 1, | ||
| 27 | IrqLine::Line1 => 3, | ||
| 28 | IrqLine::Line2 => 5, | ||
| 29 | IrqLine::Line3 => 7, | ||
| 30 | } | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | /// IRQ bit mapping | ||
| 35 | /// | ||
| 36 | /// See table 37 "IRQ bit mapping and definition" in the reference manual for | ||
| 37 | /// more information. | ||
| 38 | #[repr(u16)] | ||
| 39 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 40 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 41 | pub enum Irq { | ||
| 42 | /// Packet transmission finished. | ||
| 43 | /// | ||
| 44 | /// * Packet type: LoRa and GFSK | ||
| 45 | /// * Operation: TX | ||
| 46 | TxDone = (1 << 0), | ||
| 47 | /// Packet reception finished. | ||
| 48 | /// | ||
| 49 | /// * Packet type: LoRa and GFSK | ||
| 50 | /// * Operation: RX | ||
| 51 | RxDone = (1 << 1), | ||
| 52 | /// Preamble detected. | ||
| 53 | /// | ||
| 54 | /// * Packet type: LoRa and GFSK | ||
| 55 | /// * Operation: RX | ||
| 56 | PreambleDetected = (1 << 2), | ||
| 57 | /// Synchronization word valid. | ||
| 58 | /// | ||
| 59 | /// * Packet type: GFSK | ||
| 60 | /// * Operation: RX | ||
| 61 | SyncDetected = (1 << 3), | ||
| 62 | /// Header valid. | ||
| 63 | /// | ||
| 64 | /// * Packet type: LoRa | ||
| 65 | /// * Operation: RX | ||
| 66 | HeaderValid = (1 << 4), | ||
| 67 | /// Header CRC error. | ||
| 68 | /// | ||
| 69 | /// * Packet type: LoRa | ||
| 70 | /// * Operation: RX | ||
| 71 | HeaderErr = (1 << 5), | ||
| 72 | /// Dual meaning error. | ||
| 73 | /// | ||
| 74 | /// For GFSK RX this indicates a preamble, syncword, address, CRC, or length | ||
| 75 | /// error. | ||
| 76 | /// | ||
| 77 | /// For LoRa RX this indicates a CRC error. | ||
| 78 | Err = (1 << 6), | ||
| 79 | /// Channel activity detection finished. | ||
| 80 | /// | ||
| 81 | /// * Packet type: LoRa | ||
| 82 | /// * Operation: CAD | ||
| 83 | CadDone = (1 << 7), | ||
| 84 | /// Channel activity detected. | ||
| 85 | /// | ||
| 86 | /// * Packet type: LoRa | ||
| 87 | /// * Operation: CAD | ||
| 88 | CadDetected = (1 << 8), | ||
| 89 | /// RX or TX timeout. | ||
| 90 | /// | ||
| 91 | /// * Packet type: LoRa and GFSK | ||
| 92 | /// * Operation: RX and TX | ||
| 93 | Timeout = (1 << 9), | ||
| 94 | } | ||
| 95 | |||
| 96 | impl Irq { | ||
| 97 | /// Get the bitmask for an IRQ. | ||
| 98 | /// | ||
| 99 | /// # Example | ||
| 100 | /// | ||
| 101 | /// ``` | ||
| 102 | /// use stm32wl_hal::subghz::Irq; | ||
| 103 | /// | ||
| 104 | /// assert_eq!(Irq::TxDone.mask(), 0x0001); | ||
| 105 | /// assert_eq!(Irq::Timeout.mask(), 0x0200); | ||
| 106 | /// ``` | ||
| 107 | pub const fn mask(self) -> u16 { | ||
| 108 | self as u16 | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | /// Argument for [`set_irq_cfg`]. | ||
| 113 | /// | ||
| 114 | /// [`set_irq_cfg`]: crate::subghz::SubGhz::set_irq_cfg | ||
| 115 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 116 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 117 | pub struct CfgIrq { | ||
| 118 | buf: [u8; 9], | ||
| 119 | } | ||
| 120 | |||
| 121 | impl CfgIrq { | ||
| 122 | /// Create a new `CfgIrq`. | ||
| 123 | /// | ||
| 124 | /// This is the same as `default`, but in a `const` function. | ||
| 125 | /// | ||
| 126 | /// The default value has all interrupts disabled on all lines. | ||
| 127 | /// | ||
| 128 | /// # Example | ||
| 129 | /// | ||
| 130 | /// ``` | ||
| 131 | /// use stm32wl_hal::subghz::CfgIrq; | ||
| 132 | /// | ||
| 133 | /// const IRQ_CFG: CfgIrq = CfgIrq::new(); | ||
| 134 | /// ``` | ||
| 135 | pub const fn new() -> CfgIrq { | ||
| 136 | CfgIrq { | ||
| 137 | buf: [ | ||
| 138 | super::OpCode::CfgDioIrq as u8, | ||
| 139 | 0x00, | ||
| 140 | 0x00, | ||
| 141 | 0x00, | ||
| 142 | 0x00, | ||
| 143 | 0x00, | ||
| 144 | 0x00, | ||
| 145 | 0x00, | ||
| 146 | 0x00, | ||
| 147 | ], | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | /// Enable an interrupt. | ||
| 152 | /// | ||
| 153 | /// # Example | ||
| 154 | /// | ||
| 155 | /// ``` | ||
| 156 | /// use stm32wl_hal::subghz::{CfgIrq, Irq, IrqLine}; | ||
| 157 | /// | ||
| 158 | /// const IRQ_CFG: CfgIrq = CfgIrq::new() | ||
| 159 | /// .irq_enable(IrqLine::Global, Irq::TxDone) | ||
| 160 | /// .irq_enable(IrqLine::Global, Irq::Timeout); | ||
| 161 | /// # assert_eq!(IRQ_CFG.as_slice()[1], 0x02); | ||
| 162 | /// # assert_eq!(IRQ_CFG.as_slice()[2], 0x01); | ||
| 163 | /// # assert_eq!(IRQ_CFG.as_slice()[3], 0x00); | ||
| 164 | /// ``` | ||
| 165 | #[must_use = "irq_enable returns a modified CfgIrq"] | ||
| 166 | pub const fn irq_enable(mut self, line: IrqLine, irq: Irq) -> CfgIrq { | ||
| 167 | let mask: u16 = irq as u16; | ||
| 168 | let offset: usize = line.offset(); | ||
| 169 | self.buf[offset] |= ((mask >> 8) & 0xFF) as u8; | ||
| 170 | self.buf[offset + 1] |= (mask & 0xFF) as u8; | ||
| 171 | self | ||
| 172 | } | ||
| 173 | |||
| 174 | /// Enable an interrupt on all lines. | ||
| 175 | /// | ||
| 176 | /// As far as I can tell with empirical testing all IRQ lines need to be | ||
| 177 | /// enabled for the internal interrupt to be pending in the NVIC. | ||
| 178 | /// | ||
| 179 | /// # Example | ||
| 180 | /// | ||
| 181 | /// ``` | ||
| 182 | /// use stm32wl_hal::subghz::{CfgIrq, Irq}; | ||
| 183 | /// | ||
| 184 | /// const IRQ_CFG: CfgIrq = CfgIrq::new() | ||
| 185 | /// .irq_enable_all(Irq::TxDone) | ||
| 186 | /// .irq_enable_all(Irq::Timeout); | ||
| 187 | /// # assert_eq!(IRQ_CFG.as_slice()[1], 0x02); | ||
| 188 | /// # assert_eq!(IRQ_CFG.as_slice()[2], 0x01); | ||
| 189 | /// # assert_eq!(IRQ_CFG.as_slice()[3], 0x02); | ||
| 190 | /// # assert_eq!(IRQ_CFG.as_slice()[4], 0x01); | ||
| 191 | /// # assert_eq!(IRQ_CFG.as_slice()[5], 0x02); | ||
| 192 | /// # assert_eq!(IRQ_CFG.as_slice()[6], 0x01); | ||
| 193 | /// # assert_eq!(IRQ_CFG.as_slice()[7], 0x02); | ||
| 194 | /// # assert_eq!(IRQ_CFG.as_slice()[8], 0x01); | ||
| 195 | /// ``` | ||
| 196 | #[must_use = "irq_enable_all returns a modified CfgIrq"] | ||
| 197 | pub const fn irq_enable_all(mut self, irq: Irq) -> CfgIrq { | ||
| 198 | let mask: [u8; 2] = irq.mask().to_be_bytes(); | ||
| 199 | |||
| 200 | self.buf[1] |= mask[0]; | ||
| 201 | self.buf[2] |= mask[1]; | ||
| 202 | self.buf[3] |= mask[0]; | ||
| 203 | self.buf[4] |= mask[1]; | ||
| 204 | self.buf[5] |= mask[0]; | ||
| 205 | self.buf[6] |= mask[1]; | ||
| 206 | self.buf[7] |= mask[0]; | ||
| 207 | self.buf[8] |= mask[1]; | ||
| 208 | |||
| 209 | self | ||
| 210 | } | ||
| 211 | |||
| 212 | /// Disable an interrupt. | ||
| 213 | /// | ||
| 214 | /// # Example | ||
| 215 | /// | ||
| 216 | /// ``` | ||
| 217 | /// use stm32wl_hal::subghz::{CfgIrq, Irq, IrqLine}; | ||
| 218 | /// | ||
| 219 | /// const IRQ_CFG: CfgIrq = CfgIrq::new() | ||
| 220 | /// .irq_enable(IrqLine::Global, Irq::TxDone) | ||
| 221 | /// .irq_enable(IrqLine::Global, Irq::Timeout) | ||
| 222 | /// .irq_disable(IrqLine::Global, Irq::TxDone) | ||
| 223 | /// .irq_disable(IrqLine::Global, Irq::Timeout); | ||
| 224 | /// # assert_eq!(IRQ_CFG.as_slice()[1], 0x00); | ||
| 225 | /// # assert_eq!(IRQ_CFG.as_slice()[2], 0x00); | ||
| 226 | /// # assert_eq!(IRQ_CFG.as_slice()[3], 0x00); | ||
| 227 | /// ``` | ||
| 228 | #[must_use = "irq_disable returns a modified CfgIrq"] | ||
| 229 | pub const fn irq_disable(mut self, line: IrqLine, irq: Irq) -> CfgIrq { | ||
| 230 | let mask: u16 = !(irq as u16); | ||
| 231 | let offset: usize = line.offset(); | ||
| 232 | self.buf[offset] &= ((mask >> 8) & 0xFF) as u8; | ||
| 233 | self.buf[offset + 1] &= (mask & 0xFF) as u8; | ||
| 234 | self | ||
| 235 | } | ||
| 236 | |||
| 237 | /// Disable an interrupt on all lines. | ||
| 238 | /// | ||
| 239 | /// # Example | ||
| 240 | /// | ||
| 241 | /// ``` | ||
| 242 | /// use stm32wl_hal::subghz::{CfgIrq, Irq}; | ||
| 243 | /// | ||
| 244 | /// const IRQ_CFG: CfgIrq = CfgIrq::new() | ||
| 245 | /// .irq_enable_all(Irq::TxDone) | ||
| 246 | /// .irq_enable_all(Irq::Timeout) | ||
| 247 | /// .irq_disable_all(Irq::TxDone) | ||
| 248 | /// .irq_disable_all(Irq::Timeout); | ||
| 249 | /// # assert_eq!(IRQ_CFG, CfgIrq::new()); | ||
| 250 | /// ``` | ||
| 251 | #[must_use = "irq_disable_all returns a modified CfgIrq"] | ||
| 252 | pub const fn irq_disable_all(mut self, irq: Irq) -> CfgIrq { | ||
| 253 | let mask: [u8; 2] = (!irq.mask()).to_be_bytes(); | ||
| 254 | |||
| 255 | self.buf[1] &= mask[0]; | ||
| 256 | self.buf[2] &= mask[1]; | ||
| 257 | self.buf[3] &= mask[0]; | ||
| 258 | self.buf[4] &= mask[1]; | ||
| 259 | self.buf[5] &= mask[0]; | ||
| 260 | self.buf[6] &= mask[1]; | ||
| 261 | self.buf[7] &= mask[0]; | ||
| 262 | self.buf[8] &= mask[1]; | ||
| 263 | |||
| 264 | self | ||
| 265 | } | ||
| 266 | |||
| 267 | /// Extracts a slice containing the packet. | ||
| 268 | /// | ||
| 269 | /// # Example | ||
| 270 | /// | ||
| 271 | /// ``` | ||
| 272 | /// use stm32wl_hal::subghz::{CfgIrq, Irq}; | ||
| 273 | /// | ||
| 274 | /// const IRQ_CFG: CfgIrq = CfgIrq::new() | ||
| 275 | /// .irq_enable_all(Irq::TxDone) | ||
| 276 | /// .irq_enable_all(Irq::Timeout); | ||
| 277 | /// | ||
| 278 | /// assert_eq!( | ||
| 279 | /// IRQ_CFG.as_slice(), | ||
| 280 | /// &[0x08, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01] | ||
| 281 | /// ); | ||
| 282 | /// ``` | ||
| 283 | pub const fn as_slice(&self) -> &[u8] { | ||
| 284 | &self.buf | ||
| 285 | } | ||
| 286 | } | ||
| 287 | |||
| 288 | impl Default for CfgIrq { | ||
| 289 | fn default() -> Self { | ||
| 290 | Self::new() | ||
| 291 | } | ||
| 292 | } | ||
diff --git a/embassy-stm32/src/subghz/lora_sync_word.rs b/embassy-stm32/src/subghz/lora_sync_word.rs new file mode 100644 index 000000000..2c163104e --- /dev/null +++ b/embassy-stm32/src/subghz/lora_sync_word.rs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | /// LoRa synchronization word. | ||
| 2 | /// | ||
| 3 | /// Argument of [`set_lora_sync_word`][crate::subghz::SubGhz::set_lora_sync_word]. | ||
| 4 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 6 | pub enum LoRaSyncWord { | ||
| 7 | /// LoRa private network. | ||
| 8 | Private, | ||
| 9 | /// LoRa public network. | ||
| 10 | Public, | ||
| 11 | } | ||
| 12 | |||
| 13 | impl LoRaSyncWord { | ||
| 14 | pub(crate) const fn bytes(self) -> [u8; 2] { | ||
| 15 | match self { | ||
| 16 | LoRaSyncWord::Private => [0x14, 0x24], | ||
| 17 | LoRaSyncWord::Public => [0x34, 0x44], | ||
| 18 | } | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/embassy-stm32/src/subghz/mod.rs b/embassy-stm32/src/subghz/mod.rs new file mode 100644 index 000000000..c37c0e8e2 --- /dev/null +++ b/embassy-stm32/src/subghz/mod.rs | |||
| @@ -0,0 +1,1681 @@ | |||
| 1 | //! Sub-GHz radio operating in the 150 - 960 MHz ISM band | ||
| 2 | //! | ||
| 3 | //! ## LoRa user notice | ||
| 4 | //! | ||
| 5 | //! The Sub-GHz radio may have an undocumented erratum, see this ST community | ||
| 6 | //! post for more information: [link] | ||
| 7 | //! | ||
| 8 | //! [link]: https://community.st.com/s/question/0D53W00000hR8kpSAC/stm32wl55-erratum-clairification | ||
| 9 | //! | ||
| 10 | //! NOTE: This HAL is based on https://github.com/newAM/stm32wl-hal, but adopted for use with the stm32-metapac | ||
| 11 | //! and SPI HALs. | ||
| 12 | |||
| 13 | mod bit_sync; | ||
| 14 | mod cad_params; | ||
| 15 | mod calibrate; | ||
| 16 | mod fallback_mode; | ||
| 17 | mod hse_trim; | ||
| 18 | mod irq; | ||
| 19 | mod lora_sync_word; | ||
| 20 | mod mod_params; | ||
| 21 | mod ocp; | ||
| 22 | mod op_error; | ||
| 23 | mod pa_config; | ||
| 24 | mod packet_params; | ||
| 25 | mod packet_status; | ||
| 26 | mod packet_type; | ||
| 27 | mod pkt_ctrl; | ||
| 28 | mod pmode; | ||
| 29 | mod pwr_ctrl; | ||
| 30 | mod reg_mode; | ||
| 31 | mod rf_frequency; | ||
| 32 | mod rx_timeout_stop; | ||
| 33 | mod sleep_cfg; | ||
| 34 | mod smps; | ||
| 35 | mod standby_clk; | ||
| 36 | mod stats; | ||
| 37 | mod status; | ||
| 38 | mod tcxo_mode; | ||
| 39 | mod timeout; | ||
| 40 | mod tx_params; | ||
| 41 | mod value_error; | ||
| 42 | |||
| 43 | pub use bit_sync::BitSync; | ||
| 44 | pub use cad_params::{CadParams, ExitMode, NbCadSymbol}; | ||
| 45 | pub use calibrate::{Calibrate, CalibrateImage}; | ||
| 46 | pub use fallback_mode::FallbackMode; | ||
| 47 | pub use hse_trim::HseTrim; | ||
| 48 | pub use irq::{CfgIrq, Irq, IrqLine}; | ||
| 49 | pub use lora_sync_word::LoRaSyncWord; | ||
| 50 | pub use mod_params::BpskModParams; | ||
| 51 | pub use mod_params::{CodingRate, LoRaBandwidth, LoRaModParams, SpreadingFactor}; | ||
| 52 | pub use mod_params::{FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape}; | ||
| 53 | pub use ocp::Ocp; | ||
| 54 | pub use op_error::OpError; | ||
| 55 | pub use pa_config::{PaConfig, PaSel}; | ||
| 56 | pub use packet_params::{ | ||
| 57 | AddrComp, BpskPacketParams, CrcType, GenericPacketParams, HeaderType, LoRaPacketParams, | ||
| 58 | PreambleDetection, | ||
| 59 | }; | ||
| 60 | pub use packet_status::{FskPacketStatus, LoRaPacketStatus}; | ||
| 61 | pub use packet_type::PacketType; | ||
| 62 | pub use pkt_ctrl::{InfSeqSel, PktCtrl}; | ||
| 63 | pub use pmode::PMode; | ||
| 64 | pub use pwr_ctrl::{CurrentLim, PwrCtrl}; | ||
| 65 | pub use reg_mode::RegMode; | ||
| 66 | pub use rf_frequency::RfFreq; | ||
| 67 | pub use rx_timeout_stop::RxTimeoutStop; | ||
| 68 | pub use sleep_cfg::{SleepCfg, Startup}; | ||
| 69 | pub use smps::SmpsDrv; | ||
| 70 | pub use standby_clk::StandbyClk; | ||
| 71 | pub use stats::{FskStats, LoRaStats, Stats}; | ||
| 72 | pub use status::{CmdStatus, Status, StatusMode}; | ||
| 73 | pub use tcxo_mode::{TcxoMode, TcxoTrim}; | ||
| 74 | pub use timeout::Timeout; | ||
| 75 | pub use tx_params::{RampTime, TxParams}; | ||
| 76 | pub use value_error::ValueError; | ||
| 77 | |||
| 78 | use embassy_hal_common::ratio::Ratio; | ||
| 79 | |||
| 80 | use crate::{ | ||
| 81 | dma::NoDma, | ||
| 82 | pac, | ||
| 83 | peripherals::SUBGHZSPI, | ||
| 84 | rcc::sealed::RccPeripheral, | ||
| 85 | spi::{ByteOrder, Config as SpiConfig, MisoPin, MosiPin, SckPin, Spi}, | ||
| 86 | time::Hertz, | ||
| 87 | }; | ||
| 88 | use embassy::util::Unborrow; | ||
| 89 | use embedded_hal::{ | ||
| 90 | blocking::spi::{Transfer, Write}, | ||
| 91 | spi::MODE_0, | ||
| 92 | }; | ||
| 93 | |||
| 94 | /// Passthrough for SPI errors (for now) | ||
| 95 | pub type Error = crate::spi::Error; | ||
| 96 | |||
| 97 | struct Nss { | ||
| 98 | _priv: (), | ||
| 99 | } | ||
| 100 | |||
| 101 | impl Nss { | ||
| 102 | pub fn new() -> Nss { | ||
| 103 | Self::clear(); | ||
| 104 | Nss { _priv: () } | ||
| 105 | } | ||
| 106 | |||
| 107 | /// Clear NSS, enabling SPI transactions | ||
| 108 | #[inline(always)] | ||
| 109 | fn clear() { | ||
| 110 | let pwr = pac::PWR; | ||
| 111 | unsafe { | ||
| 112 | pwr.subghzspicr() | ||
| 113 | .modify(|w| w.set_nss(pac::pwr::vals::Nss::LOW)); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | /// Set NSS, disabling SPI transactions | ||
| 118 | #[inline(always)] | ||
| 119 | fn set() { | ||
| 120 | let pwr = pac::PWR; | ||
| 121 | unsafe { | ||
| 122 | pwr.subghzspicr() | ||
| 123 | .modify(|w| w.set_nss(pac::pwr::vals::Nss::HIGH)); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | impl Drop for Nss { | ||
| 129 | fn drop(&mut self) { | ||
| 130 | Self::set() | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | /// Wakeup the radio from sleep mode. | ||
| 135 | /// | ||
| 136 | /// # Safety | ||
| 137 | /// | ||
| 138 | /// 1. This must not be called when the SubGHz radio is in use. | ||
| 139 | /// 2. This must not be called when the SubGHz SPI bus is in use. | ||
| 140 | /// | ||
| 141 | /// # Example | ||
| 142 | /// | ||
| 143 | /// See [`SubGhz::set_sleep`] | ||
| 144 | #[inline] | ||
| 145 | unsafe fn wakeup() { | ||
| 146 | Nss::clear(); | ||
| 147 | // RM0453 rev 2 page 171 section 5.7.2 "Sleep mode" | ||
| 148 | // on a firmware request via the sub-GHz radio SPI NSS signal | ||
| 149 | // (keeping sub-GHz radio SPI NSS low for at least 20 μs) | ||
| 150 | // | ||
| 151 | // I have found this to be a more reliable mechanism for ensuring NSS is | ||
| 152 | // pulled low for long enough to wake the radio. | ||
| 153 | while rfbusys() {} | ||
| 154 | Nss::set(); | ||
| 155 | } | ||
| 156 | |||
| 157 | /// Returns `true` if the radio is busy. | ||
| 158 | /// | ||
| 159 | /// This may not be set immediately after NSS going low. | ||
| 160 | /// | ||
| 161 | /// See RM0461 Rev 4 section 5.3 page 181 "Radio busy management" for more | ||
| 162 | /// details. | ||
| 163 | #[inline] | ||
| 164 | fn rfbusys() -> bool { | ||
| 165 | // safety: atmoic read with no side-effects | ||
| 166 | //unsafe { (*pac::PWR::ptr()).sr2.read().rfbusys().is_busy() } | ||
| 167 | let pwr = pac::PWR; | ||
| 168 | unsafe { pwr.sr2().read().rfbusys() == pac::pwr::vals::Rfbusys::BUSY } | ||
| 169 | } | ||
| 170 | |||
| 171 | /* | ||
| 172 | /// Returns `true` if the radio is busy or NSS is low. | ||
| 173 | /// | ||
| 174 | /// See RM0461 Rev 4 section 5.3 page 181 "Radio busy management" for more | ||
| 175 | /// details. | ||
| 176 | #[inline] | ||
| 177 | fn rfbusyms() -> bool { | ||
| 178 | let pwr = pac::PWR; | ||
| 179 | unsafe { pwr.sr2().read().rfbusyms() == pac::pwr::vals::Rfbusyms::BUSY } | ||
| 180 | } | ||
| 181 | */ | ||
| 182 | |||
| 183 | /// Sub-GHz radio peripheral | ||
| 184 | pub struct SubGhz<'d, Tx, Rx> { | ||
| 185 | spi: Spi<'d, SUBGHZSPI, Tx, Rx>, | ||
| 186 | } | ||
| 187 | |||
| 188 | impl<'d, Tx, Rx> SubGhz<'d, Tx, Rx> { | ||
| 189 | fn pulse_radio_reset() { | ||
| 190 | let rcc = pac::RCC; | ||
| 191 | unsafe { | ||
| 192 | rcc.csr().modify(|w| w.set_rfrst(true)); | ||
| 193 | rcc.csr().modify(|w| w.set_rfrst(false)); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | // TODO: This should be replaced with async handling based on IRQ | ||
| 198 | fn poll_not_busy(&self) { | ||
| 199 | let mut count: u32 = 1_000_000; | ||
| 200 | while rfbusys() { | ||
| 201 | count -= 1; | ||
| 202 | if count == 0 { | ||
| 203 | let pwr = pac::PWR; | ||
| 204 | unsafe { | ||
| 205 | panic!( | ||
| 206 | "rfbusys timeout pwr.sr2=0x{:X} pwr.subghzspicr=0x{:X} pwr.cr1=0x{:X}", | ||
| 207 | pwr.sr2().read().0, | ||
| 208 | pwr.subghzspicr().read().0, | ||
| 209 | pwr.cr1().read().0 | ||
| 210 | ); | ||
| 211 | } | ||
| 212 | } | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | /// Create a new sub-GHz radio driver from a peripheral. | ||
| 217 | /// | ||
| 218 | /// This will reset the radio and the SPI bus, and enable the peripheral | ||
| 219 | /// clock. | ||
| 220 | pub fn new( | ||
| 221 | peri: impl Unborrow<Target = SUBGHZSPI> + 'd, | ||
| 222 | sck: impl Unborrow<Target = impl SckPin<SUBGHZSPI>>, | ||
| 223 | mosi: impl Unborrow<Target = impl MosiPin<SUBGHZSPI>>, | ||
| 224 | miso: impl Unborrow<Target = impl MisoPin<SUBGHZSPI>>, | ||
| 225 | txdma: impl Unborrow<Target = Tx>, | ||
| 226 | rxdma: impl Unborrow<Target = Rx>, | ||
| 227 | ) -> Self { | ||
| 228 | Self::pulse_radio_reset(); | ||
| 229 | |||
| 230 | // see RM0453 rev 1 section 7.2.13 page 291 | ||
| 231 | // The SUBGHZSPI_SCK frequency is obtained by PCLK3 divided by two. | ||
| 232 | // The SUBGHZSPI_SCK clock maximum speed must not exceed 16 MHz. | ||
| 233 | let clk = Hertz(core::cmp::min(SUBGHZSPI::frequency().0 / 2, 16_000_000)); | ||
| 234 | let mut config = SpiConfig::default(); | ||
| 235 | config.mode = MODE_0; | ||
| 236 | config.byte_order = ByteOrder::MsbFirst; | ||
| 237 | let spi = Spi::new(peri, sck, mosi, miso, txdma, rxdma, clk, config); | ||
| 238 | |||
| 239 | unsafe { wakeup() }; | ||
| 240 | |||
| 241 | SubGhz { spi } | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | impl<'d> SubGhz<'d, NoDma, NoDma> { | ||
| 246 | fn read(&mut self, opcode: OpCode, data: &mut [u8]) -> Result<(), Error> { | ||
| 247 | self.poll_not_busy(); | ||
| 248 | { | ||
| 249 | let _nss: Nss = Nss::new(); | ||
| 250 | self.spi.write(&[opcode as u8])?; | ||
| 251 | self.spi.transfer(data)?; | ||
| 252 | } | ||
| 253 | self.poll_not_busy(); | ||
| 254 | Ok(()) | ||
| 255 | } | ||
| 256 | |||
| 257 | /// Read one byte from the sub-Ghz radio. | ||
| 258 | fn read_1(&mut self, opcode: OpCode) -> Result<u8, Error> { | ||
| 259 | let mut buf: [u8; 1] = [0; 1]; | ||
| 260 | self.read(opcode, &mut buf)?; | ||
| 261 | Ok(buf[0]) | ||
| 262 | } | ||
| 263 | |||
| 264 | /// Read a fixed number of bytes from the sub-Ghz radio. | ||
| 265 | fn read_n<const N: usize>(&mut self, opcode: OpCode) -> Result<[u8; N], Error> { | ||
| 266 | let mut buf: [u8; N] = [0; N]; | ||
| 267 | self.read(opcode, &mut buf)?; | ||
| 268 | Ok(buf) | ||
| 269 | } | ||
| 270 | |||
| 271 | fn write(&mut self, data: &[u8]) -> Result<(), Error> { | ||
| 272 | self.poll_not_busy(); | ||
| 273 | { | ||
| 274 | let _nss: Nss = Nss::new(); | ||
| 275 | self.spi.write(data)?; | ||
| 276 | } | ||
| 277 | self.poll_not_busy(); | ||
| 278 | Ok(()) | ||
| 279 | } | ||
| 280 | |||
| 281 | pub fn write_buffer(&mut self, offset: u8, data: &[u8]) -> Result<(), Error> { | ||
| 282 | self.poll_not_busy(); | ||
| 283 | { | ||
| 284 | let _nss: Nss = Nss::new(); | ||
| 285 | self.spi.write(&[OpCode::WriteBuffer as u8, offset])?; | ||
| 286 | self.spi.write(data)?; | ||
| 287 | } | ||
| 288 | self.poll_not_busy(); | ||
| 289 | |||
| 290 | Ok(()) | ||
| 291 | } | ||
| 292 | |||
| 293 | /// Read the radio buffer at the given offset. | ||
| 294 | /// | ||
| 295 | /// The offset and length of a received packet is provided by | ||
| 296 | /// [`rx_buffer_status`](Self::rx_buffer_status). | ||
| 297 | pub fn read_buffer(&mut self, offset: u8, buf: &mut [u8]) -> Result<Status, Error> { | ||
| 298 | let mut status_buf: [u8; 1] = [0]; | ||
| 299 | |||
| 300 | self.poll_not_busy(); | ||
| 301 | { | ||
| 302 | let _nss: Nss = Nss::new(); | ||
| 303 | self.spi.write(&[OpCode::ReadBuffer as u8, offset])?; | ||
| 304 | self.spi.transfer(&mut status_buf)?; | ||
| 305 | self.spi.transfer(buf)?; | ||
| 306 | } | ||
| 307 | self.poll_not_busy(); | ||
| 308 | |||
| 309 | Ok(status_buf[0].into()) | ||
| 310 | } | ||
| 311 | } | ||
| 312 | |||
| 313 | // helper to pack register writes into a single buffer to avoid multiple DMA | ||
| 314 | // transfers | ||
| 315 | macro_rules! wr_reg { | ||
| 316 | [$reg:ident, $($data:expr),+] => { | ||
| 317 | &[ | ||
| 318 | OpCode::WriteRegister as u8, | ||
| 319 | Register::$reg.address().to_be_bytes()[0], | ||
| 320 | Register::$reg.address().to_be_bytes()[1], | ||
| 321 | $($data),+ | ||
| 322 | ] | ||
| 323 | }; | ||
| 324 | } | ||
| 325 | |||
| 326 | // 5.8.2 | ||
| 327 | /// Register access | ||
| 328 | impl<'d> SubGhz<'d, NoDma, NoDma> { | ||
| 329 | // register write with variable length data | ||
| 330 | fn write_register(&mut self, register: Register, data: &[u8]) -> Result<(), Error> { | ||
| 331 | let addr: [u8; 2] = register.address().to_be_bytes(); | ||
| 332 | |||
| 333 | self.poll_not_busy(); | ||
| 334 | { | ||
| 335 | let _nss: Nss = Nss::new(); | ||
| 336 | self.spi | ||
| 337 | .write(&[OpCode::WriteRegister as u8, addr[0], addr[1]])?; | ||
| 338 | self.spi.write(data)?; | ||
| 339 | } | ||
| 340 | self.poll_not_busy(); | ||
| 341 | |||
| 342 | Ok(()) | ||
| 343 | } | ||
| 344 | |||
| 345 | /// Set the LoRa bit synchronization. | ||
| 346 | pub fn set_bit_sync(&mut self, bs: BitSync) -> Result<(), Error> { | ||
| 347 | self.write(wr_reg![GBSYNC, bs.as_bits()]) | ||
| 348 | } | ||
| 349 | |||
| 350 | /// Set the generic packet control register. | ||
| 351 | pub fn set_pkt_ctrl(&mut self, pkt_ctrl: PktCtrl) -> Result<(), Error> { | ||
| 352 | self.write(wr_reg![GPKTCTL1A, pkt_ctrl.as_bits()]) | ||
| 353 | } | ||
| 354 | |||
| 355 | /// Set the initial value for generic packet whitening. | ||
| 356 | /// | ||
| 357 | /// This sets the first 8 bits, the 9th bit is set with | ||
| 358 | /// [`set_pkt_ctrl`](Self::set_pkt_ctrl). | ||
| 359 | pub fn set_init_whitening(&mut self, init: u8) -> Result<(), Error> { | ||
| 360 | self.write(wr_reg![GWHITEINIRL, init]) | ||
| 361 | } | ||
| 362 | |||
| 363 | /// Set the initial value for generic packet CRC polynomial. | ||
| 364 | /// | ||
| 365 | /// # Example | ||
| 366 | /// | ||
| 367 | /// ```no_run | ||
| 368 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 369 | /// sg.set_crc_polynomial(0x1D0F)?; | ||
| 370 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 371 | /// ``` | ||
| 372 | pub fn set_crc_polynomial(&mut self, polynomial: u16) -> Result<(), Error> { | ||
| 373 | let bytes: [u8; 2] = polynomial.to_be_bytes(); | ||
| 374 | self.write(wr_reg![GCRCINIRH, bytes[0], bytes[1]]) | ||
| 375 | } | ||
| 376 | |||
| 377 | /// Set the generic packet CRC polynomial. | ||
| 378 | /// | ||
| 379 | /// # Example | ||
| 380 | /// | ||
| 381 | /// ```no_run | ||
| 382 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 383 | /// sg.set_initial_crc_polynomial(0x1021)?; | ||
| 384 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 385 | /// ``` | ||
| 386 | pub fn set_initial_crc_polynomial(&mut self, polynomial: u16) -> Result<(), Error> { | ||
| 387 | let bytes: [u8; 2] = polynomial.to_be_bytes(); | ||
| 388 | self.write(wr_reg![GCRCPOLRH, bytes[0], bytes[1]]) | ||
| 389 | } | ||
| 390 | |||
| 391 | /// Set the synchronization word registers. | ||
| 392 | /// | ||
| 393 | /// # Example | ||
| 394 | /// | ||
| 395 | /// ```no_run | ||
| 396 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 397 | /// const SYNC_WORD: [u8; 8] = [0x79, 0x80, 0x0C, 0xC0, 0x29, 0x95, 0xF8, 0x4A]; | ||
| 398 | /// | ||
| 399 | /// sg.set_sync_word(&SYNC_WORD)?; | ||
| 400 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 401 | /// ``` | ||
| 402 | pub fn set_sync_word(&mut self, sync_word: &[u8; 8]) -> Result<(), Error> { | ||
| 403 | self.write_register(Register::GSYNC7, sync_word) | ||
| 404 | } | ||
| 405 | |||
| 406 | /// Set the LoRa synchronization word registers. | ||
| 407 | /// | ||
| 408 | /// # Example | ||
| 409 | /// | ||
| 410 | /// ```no_run | ||
| 411 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 412 | /// use embassy_stm32::subghz::{LoRaSyncWord, PacketType}; | ||
| 413 | /// | ||
| 414 | /// sg.set_packet_type(PacketType::LoRa)?; | ||
| 415 | /// sg.set_lora_sync_word(LoRaSyncWord::Public)?; | ||
| 416 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 417 | /// ``` | ||
| 418 | pub fn set_lora_sync_word(&mut self, sync_word: LoRaSyncWord) -> Result<(), Error> { | ||
| 419 | let bytes: [u8; 2] = sync_word.bytes(); | ||
| 420 | self.write(wr_reg![LSYNCH, bytes[0], bytes[1]]) | ||
| 421 | } | ||
| 422 | |||
| 423 | /// Set the RX gain control. | ||
| 424 | /// | ||
| 425 | /// # Example | ||
| 426 | /// | ||
| 427 | /// ```no_run | ||
| 428 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 429 | /// use embassy_stm32::subghz::PMode; | ||
| 430 | /// | ||
| 431 | /// sg.set_rx_gain(PMode::Boost)?; | ||
| 432 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 433 | /// ``` | ||
| 434 | pub fn set_rx_gain(&mut self, pmode: PMode) -> Result<(), Error> { | ||
| 435 | self.write(wr_reg![RXGAINC, pmode as u8]) | ||
| 436 | } | ||
| 437 | |||
| 438 | /// Set the power amplifier over current protection. | ||
| 439 | /// | ||
| 440 | /// # Example | ||
| 441 | /// | ||
| 442 | /// Maximum 60mA for LP PA mode. | ||
| 443 | /// | ||
| 444 | /// ```no_run | ||
| 445 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 446 | /// use embassy_stm32::subghz::Ocp; | ||
| 447 | /// | ||
| 448 | /// sg.set_pa_ocp(Ocp::Max60m)?; | ||
| 449 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 450 | /// ``` | ||
| 451 | /// | ||
| 452 | /// Maximum 60mA for HP PA mode. | ||
| 453 | /// | ||
| 454 | /// ```no_run | ||
| 455 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 456 | /// use embassy_stm32::subghz::Ocp; | ||
| 457 | /// | ||
| 458 | /// sg.set_pa_ocp(Ocp::Max140m)?; | ||
| 459 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 460 | /// ``` | ||
| 461 | pub fn set_pa_ocp(&mut self, ocp: Ocp) -> Result<(), Error> { | ||
| 462 | self.write(wr_reg![PAOCP, ocp as u8]) | ||
| 463 | } | ||
| 464 | |||
| 465 | /// Set the HSE32 crystal OSC_IN load capaitor trimming. | ||
| 466 | /// | ||
| 467 | /// # Example | ||
| 468 | /// | ||
| 469 | /// Set the trim to the lowest value. | ||
| 470 | /// | ||
| 471 | /// ```no_run | ||
| 472 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 473 | /// use embassy_stm32::subghz::HseTrim; | ||
| 474 | /// | ||
| 475 | /// sg.set_hse_in_trim(HseTrim::MIN)?; | ||
| 476 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 477 | /// ``` | ||
| 478 | pub fn set_hse_in_trim(&mut self, trim: HseTrim) -> Result<(), Error> { | ||
| 479 | self.write(wr_reg![HSEINTRIM, trim.into()]) | ||
| 480 | } | ||
| 481 | |||
| 482 | /// Set the HSE32 crystal OSC_OUT load capaitor trimming. | ||
| 483 | /// | ||
| 484 | /// # Example | ||
| 485 | /// | ||
| 486 | /// Set the trim to the lowest value. | ||
| 487 | /// | ||
| 488 | /// ```no_run | ||
| 489 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 490 | /// use embassy_stm32::subghz::HseTrim; | ||
| 491 | /// | ||
| 492 | /// sg.set_hse_out_trim(HseTrim::MIN)?; | ||
| 493 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 494 | /// ``` | ||
| 495 | pub fn set_hse_out_trim(&mut self, trim: HseTrim) -> Result<(), Error> { | ||
| 496 | self.write(wr_reg![HSEOUTTRIM, trim.into()]) | ||
| 497 | } | ||
| 498 | |||
| 499 | /// Set the SMPS clock detection enabled. | ||
| 500 | /// | ||
| 501 | /// SMPS clock detection must be enabled fore enabling the SMPS. | ||
| 502 | pub fn set_smps_clock_det_en(&mut self, en: bool) -> Result<(), Error> { | ||
| 503 | self.write(wr_reg![SMPSC0, (en as u8) << 6]) | ||
| 504 | } | ||
| 505 | |||
| 506 | /// Set the power current limiting. | ||
| 507 | pub fn set_pwr_ctrl(&mut self, pwr_ctrl: PwrCtrl) -> Result<(), Error> { | ||
| 508 | self.write(wr_reg![PC, pwr_ctrl.as_bits()]) | ||
| 509 | } | ||
| 510 | |||
| 511 | /// Set the maximum SMPS drive capability. | ||
| 512 | pub fn set_smps_drv(&mut self, drv: SmpsDrv) -> Result<(), Error> { | ||
| 513 | self.write(wr_reg![SMPSC2, (drv as u8) << 1]) | ||
| 514 | } | ||
| 515 | } | ||
| 516 | |||
| 517 | // 5.8.3 | ||
| 518 | /// Operating mode commands | ||
| 519 | impl<'d> SubGhz<'d, NoDma, NoDma> { | ||
| 520 | /// Put the radio into sleep mode. | ||
| 521 | /// | ||
| 522 | /// This command is only accepted in standby mode. | ||
| 523 | /// The cfg argument allows some optional functions to be maintained | ||
| 524 | /// in sleep mode. | ||
| 525 | /// | ||
| 526 | /// # Safety | ||
| 527 | /// | ||
| 528 | /// 1. After the `set_sleep` command, the sub-GHz radio NSS must not go low | ||
| 529 | /// for 500 μs. | ||
| 530 | /// No reason is provided, the reference manual (RM0453 rev 2) simply | ||
| 531 | /// says "you must". | ||
| 532 | /// 2. The radio cannot be used while in sleep mode. | ||
| 533 | /// 3. The radio must be woken up with [`wakeup`] before resuming use. | ||
| 534 | /// | ||
| 535 | /// # Example | ||
| 536 | /// | ||
| 537 | /// Put the radio into sleep mode. | ||
| 538 | /// | ||
| 539 | /// ```no_run | ||
| 540 | /// # let dp = unsafe { embassy_stm32::pac::Peripherals::steal() }; | ||
| 541 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 542 | /// use embassy_stm32::{ | ||
| 543 | /// subghz::{wakeup, SleepCfg, StandbyClk}, | ||
| 544 | /// }; | ||
| 545 | /// | ||
| 546 | /// sg.set_standby(StandbyClk::Rc)?; | ||
| 547 | /// unsafe { sg.set_sleep(SleepCfg::default())? }; | ||
| 548 | /// embassy::time::Timer::after(embassy::time::Duration::from_micros(500)).await; | ||
| 549 | /// unsafe { wakeup() }; | ||
| 550 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 551 | /// ``` | ||
| 552 | pub unsafe fn set_sleep(&mut self, cfg: SleepCfg) -> Result<(), Error> { | ||
| 553 | self.write(&[OpCode::SetSleep as u8, u8::from(cfg)]) | ||
| 554 | } | ||
| 555 | |||
| 556 | /// Put the radio into standby mode. | ||
| 557 | /// | ||
| 558 | /// # Examples | ||
| 559 | /// | ||
| 560 | /// Put the radio into standby mode using the RC 13MHz clock. | ||
| 561 | /// | ||
| 562 | /// ```no_run | ||
| 563 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 564 | /// use embassy_stm32::subghz::StandbyClk; | ||
| 565 | /// | ||
| 566 | /// sg.set_standby(StandbyClk::Rc)?; | ||
| 567 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 568 | /// ``` | ||
| 569 | /// | ||
| 570 | /// Put the radio into standby mode using the HSE32 clock. | ||
| 571 | /// | ||
| 572 | /// ```no_run | ||
| 573 | /// # let mut dp = unsafe { embassy_stm32::pac::Peripherals::steal() }; | ||
| 574 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 575 | /// use embassy_stm32::subghz::StandbyClk; | ||
| 576 | /// | ||
| 577 | /// dp.RCC | ||
| 578 | /// .cr | ||
| 579 | /// .modify(|_, w| w.hseon().enabled().hsebyppwr().vddtcxo()); | ||
| 580 | /// while dp.RCC.cr.read().hserdy().is_not_ready() {} | ||
| 581 | /// | ||
| 582 | /// sg.set_standby(StandbyClk::Hse)?; | ||
| 583 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 584 | /// ``` | ||
| 585 | pub fn set_standby(&mut self, standby_clk: StandbyClk) -> Result<(), Error> { | ||
| 586 | self.write(&[OpCode::SetStandby as u8, u8::from(standby_clk)]) | ||
| 587 | } | ||
| 588 | |||
| 589 | /// Put the subghz radio into frequency synthesis mode. | ||
| 590 | /// | ||
| 591 | /// The RF-PLL frequency must be set with [`set_rf_frequency`] before using | ||
| 592 | /// this command. | ||
| 593 | /// | ||
| 594 | /// Check the datasheet for more information, this is a test command but | ||
| 595 | /// I honestly do not see any use for it. Please update this description | ||
| 596 | /// if you know more than I do. | ||
| 597 | /// | ||
| 598 | /// # Example | ||
| 599 | /// | ||
| 600 | /// ```no_run | ||
| 601 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 602 | /// use embassy_stm32::subghz::RfFreq; | ||
| 603 | /// | ||
| 604 | /// sg.set_rf_frequency(&RfFreq::from_frequency(915_000_000))?; | ||
| 605 | /// sg.set_fs()?; | ||
| 606 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 607 | /// ``` | ||
| 608 | /// | ||
| 609 | /// [`set_rf_frequency`]: crate::subghz::SubGhz::set_rf_frequency | ||
| 610 | pub fn set_fs(&mut self) -> Result<(), Error> { | ||
| 611 | self.write(&[OpCode::SetFs.into()]) | ||
| 612 | } | ||
| 613 | |||
| 614 | /// Set the sub-GHz radio in TX mode. | ||
| 615 | /// | ||
| 616 | /// # Example | ||
| 617 | /// | ||
| 618 | /// Transmit with no timeout. | ||
| 619 | /// | ||
| 620 | /// ```no_run | ||
| 621 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 622 | /// use embassy_stm32::subghz::Timeout; | ||
| 623 | /// | ||
| 624 | /// sg.set_tx(Timeout::DISABLED)?; | ||
| 625 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 626 | /// ``` | ||
| 627 | pub fn set_tx(&mut self, timeout: Timeout) -> Result<(), Error> { | ||
| 628 | let tobits: u32 = timeout.into_bits(); | ||
| 629 | self.write(&[ | ||
| 630 | OpCode::SetTx.into(), | ||
| 631 | (tobits >> 16) as u8, | ||
| 632 | (tobits >> 8) as u8, | ||
| 633 | tobits as u8, | ||
| 634 | ]) | ||
| 635 | } | ||
| 636 | |||
| 637 | /// Set the sub-GHz radio in RX mode. | ||
| 638 | /// | ||
| 639 | /// # Example | ||
| 640 | /// | ||
| 641 | /// Receive with a 1 second timeout. | ||
| 642 | /// | ||
| 643 | /// ```no_run | ||
| 644 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 645 | /// use core::time::Duration; | ||
| 646 | /// use embassy_stm32::subghz::Timeout; | ||
| 647 | /// | ||
| 648 | /// sg.set_rx(Timeout::from_duration_sat(Duration::from_secs(1)))?; | ||
| 649 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 650 | /// ``` | ||
| 651 | pub fn set_rx(&mut self, timeout: Timeout) -> Result<(), Error> { | ||
| 652 | let tobits: u32 = timeout.into_bits(); | ||
| 653 | self.write(&[ | ||
| 654 | OpCode::SetRx.into(), | ||
| 655 | (tobits >> 16) as u8, | ||
| 656 | (tobits >> 8) as u8, | ||
| 657 | tobits as u8, | ||
| 658 | ]) | ||
| 659 | } | ||
| 660 | |||
| 661 | /// Allows selection of the receiver event which stops the RX timeout timer. | ||
| 662 | /// | ||
| 663 | /// # Example | ||
| 664 | /// | ||
| 665 | /// Set the RX timeout timer to stop on preamble detection. | ||
| 666 | /// | ||
| 667 | /// ```no_run | ||
| 668 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 669 | /// use embassy_stm32::subghz::RxTimeoutStop; | ||
| 670 | /// | ||
| 671 | /// sg.set_rx_timeout_stop(RxTimeoutStop::Preamble)?; | ||
| 672 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 673 | /// ``` | ||
| 674 | pub fn set_rx_timeout_stop(&mut self, rx_timeout_stop: RxTimeoutStop) -> Result<(), Error> { | ||
| 675 | self.write(&[ | ||
| 676 | OpCode::SetStopRxTimerOnPreamble.into(), | ||
| 677 | rx_timeout_stop.into(), | ||
| 678 | ]) | ||
| 679 | } | ||
| 680 | |||
| 681 | /// Put the radio in non-continuous RX mode. | ||
| 682 | /// | ||
| 683 | /// This command must be sent in Standby mode. | ||
| 684 | /// This command is only functional with FSK and LoRa packet type. | ||
| 685 | /// | ||
| 686 | /// The following steps are performed: | ||
| 687 | /// 1. Save sub-GHz radio configuration. | ||
| 688 | /// 2. Enter Receive mode and listen for a preamble for the specified `rx_period`. | ||
| 689 | /// 3. Upon the detection of a preamble, the `rx_period` timeout is stopped | ||
| 690 | /// and restarted with the value 2 x `rx_period` + `sleep_period`. | ||
| 691 | /// During this new period, the sub-GHz radio looks for the detection of | ||
| 692 | /// a synchronization word when in (G)FSK modulation mode, | ||
| 693 | /// or a header when in LoRa modulation mode. | ||
| 694 | /// 4. If no packet is received during the listen period defined by | ||
| 695 | /// 2 x `rx_period` + `sleep_period`, the sleep mode is entered for a | ||
| 696 | /// duration of `sleep_period`. At the end of the receive period, | ||
| 697 | /// the sub-GHz radio takes some time to save the context before starting | ||
| 698 | /// the sleep period. | ||
| 699 | /// 5. After the sleep period, a new listening period is automatically | ||
| 700 | /// started. The sub-GHz radio restores the sub-GHz radio configuration | ||
| 701 | /// and continuous with step 2. | ||
| 702 | /// | ||
| 703 | /// The listening mode is terminated in one of the following cases: | ||
| 704 | /// * if a packet is received during the listening period: the sub-GHz radio | ||
| 705 | /// issues a [`RxDone`] interrupt and enters standby mode. | ||
| 706 | /// * if [`set_standby`] is sent during the listening period or after the | ||
| 707 | /// sub-GHz has been requested to exit sleep mode by sub-GHz radio SPI NSS | ||
| 708 | /// | ||
| 709 | /// # Example | ||
| 710 | /// | ||
| 711 | /// ```no_run | ||
| 712 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 713 | /// use core::time::Duration; | ||
| 714 | /// use embassy_stm32::subghz::{StandbyClk, Timeout}; | ||
| 715 | /// | ||
| 716 | /// const RX_PERIOD: Timeout = Timeout::from_duration_sat(Duration::from_millis(100)); | ||
| 717 | /// const SLEEP_PERIOD: Timeout = Timeout::from_duration_sat(Duration::from_secs(1)); | ||
| 718 | /// | ||
| 719 | /// sg.set_standby(StandbyClk::Rc)?; | ||
| 720 | /// sg.set_rx_duty_cycle(RX_PERIOD, SLEEP_PERIOD)?; | ||
| 721 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 722 | /// ``` | ||
| 723 | /// | ||
| 724 | /// [`RxDone`]: crate::subghz::Irq::RxDone | ||
| 725 | /// [`set_rf_frequency`]: crate::subghz::SubGhz::set_rf_frequency | ||
| 726 | /// [`set_standby`]: crate::subghz::SubGhz::set_standby | ||
| 727 | pub fn set_rx_duty_cycle( | ||
| 728 | &mut self, | ||
| 729 | rx_period: Timeout, | ||
| 730 | sleep_period: Timeout, | ||
| 731 | ) -> Result<(), Error> { | ||
| 732 | let rx_period_bits: u32 = rx_period.into_bits(); | ||
| 733 | let sleep_period_bits: u32 = sleep_period.into_bits(); | ||
| 734 | self.write(&[ | ||
| 735 | OpCode::SetRxDutyCycle.into(), | ||
| 736 | (rx_period_bits >> 16) as u8, | ||
| 737 | (rx_period_bits >> 8) as u8, | ||
| 738 | rx_period_bits as u8, | ||
| 739 | (sleep_period_bits >> 16) as u8, | ||
| 740 | (sleep_period_bits >> 8) as u8, | ||
| 741 | sleep_period_bits as u8, | ||
| 742 | ]) | ||
| 743 | } | ||
| 744 | |||
| 745 | /// Channel Activity Detection (CAD) with LoRa packets. | ||
| 746 | /// | ||
| 747 | /// The channel activity detection (CAD) is a specific LoRa operation mode, | ||
| 748 | /// where the sub-GHz radio searches for a LoRa radio signal. | ||
| 749 | /// After the search is completed, the Standby mode is automatically | ||
| 750 | /// entered, CAD is done and IRQ is generated. | ||
| 751 | /// When a LoRa radio signal is detected, the CAD detected IRQ is also | ||
| 752 | /// generated. | ||
| 753 | /// | ||
| 754 | /// The length of the search must be configured with [`set_cad_params`] | ||
| 755 | /// prior to calling `set_cad`. | ||
| 756 | /// | ||
| 757 | /// # Example | ||
| 758 | /// | ||
| 759 | /// ```no_run | ||
| 760 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 761 | /// use core::time::Duration; | ||
| 762 | /// use embassy_stm32::subghz::{CadParams, ExitMode, NbCadSymbol, StandbyClk, Timeout}; | ||
| 763 | /// | ||
| 764 | /// const RX_PERIOD: Timeout = Timeout::from_duration_sat(Duration::from_millis(100)); | ||
| 765 | /// const SLEEP_PERIOD: Timeout = Timeout::from_duration_sat(Duration::from_secs(1)); | ||
| 766 | /// const CAD_PARAMS: CadParams = CadParams::new() | ||
| 767 | /// .set_num_symbol(NbCadSymbol::S4) | ||
| 768 | /// .set_det_peak(0x18) | ||
| 769 | /// .set_det_min(0x10) | ||
| 770 | /// .set_exit_mode(ExitMode::Standby); | ||
| 771 | /// | ||
| 772 | /// sg.set_standby(StandbyClk::Rc)?; | ||
| 773 | /// sg.set_cad_params(&CAD_PARAMS)?; | ||
| 774 | /// sg.set_cad()?; | ||
| 775 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 776 | /// ``` | ||
| 777 | /// | ||
| 778 | /// [`set_cad_params`]: crate::subghz::SubGhz::set_cad_params | ||
| 779 | pub fn set_cad(&mut self) -> Result<(), Error> { | ||
| 780 | self.write(&[OpCode::SetCad.into()]) | ||
| 781 | } | ||
| 782 | |||
| 783 | /// Generate a continuous transmit tone at the RF-PLL frequency. | ||
| 784 | /// | ||
| 785 | /// The sub-GHz radio remains in continuous transmit tone mode until a mode | ||
| 786 | /// configuration command is received. | ||
| 787 | /// | ||
| 788 | /// # Example | ||
| 789 | /// | ||
| 790 | /// ```no_run | ||
| 791 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 792 | /// sg.set_tx_continuous_wave()?; | ||
| 793 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 794 | /// ``` | ||
| 795 | pub fn set_tx_continuous_wave(&mut self) -> Result<(), Error> { | ||
| 796 | self.write(&[OpCode::SetTxContinuousWave as u8]) | ||
| 797 | } | ||
| 798 | |||
| 799 | /// Generate an infinite preamble at the RF-PLL frequency. | ||
| 800 | /// | ||
| 801 | /// The preamble is an alternating 0s and 1s sequence in generic (G)FSK and | ||
| 802 | /// (G)MSK modulations. | ||
| 803 | /// The preamble is symbol 0 in LoRa modulation. | ||
| 804 | /// The sub-GHz radio remains in infinite preamble mode until a mode | ||
| 805 | /// configuration command is received. | ||
| 806 | /// | ||
| 807 | /// # Example | ||
| 808 | /// | ||
| 809 | /// ```no_run | ||
| 810 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 811 | /// sg.set_tx_continuous_preamble()?; | ||
| 812 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 813 | /// ``` | ||
| 814 | pub fn set_tx_continuous_preamble(&mut self) -> Result<(), Error> { | ||
| 815 | self.write(&[OpCode::SetTxContinuousPreamble as u8]) | ||
| 816 | } | ||
| 817 | } | ||
| 818 | |||
| 819 | // 5.8.4 | ||
| 820 | /// Radio configuration commands | ||
| 821 | impl<'d> SubGhz<'d, NoDma, NoDma> { | ||
| 822 | /// Set the packet type (modulation scheme). | ||
| 823 | /// | ||
| 824 | /// # Examples | ||
| 825 | /// | ||
| 826 | /// FSK (frequency shift keying): | ||
| 827 | /// | ||
| 828 | /// ```no_run | ||
| 829 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 830 | /// use embassy_stm32::subghz::PacketType; | ||
| 831 | /// | ||
| 832 | /// sg.set_packet_type(PacketType::Fsk)?; | ||
| 833 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 834 | /// ``` | ||
| 835 | /// | ||
| 836 | /// LoRa (long range): | ||
| 837 | /// | ||
| 838 | /// ```no_run | ||
| 839 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 840 | /// use embassy_stm32::subghz::PacketType; | ||
| 841 | /// | ||
| 842 | /// sg.set_packet_type(PacketType::LoRa)?; | ||
| 843 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 844 | /// ``` | ||
| 845 | /// | ||
| 846 | /// BPSK (binary phase shift keying): | ||
| 847 | /// | ||
| 848 | /// ```no_run | ||
| 849 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 850 | /// use embassy_stm32::subghz::PacketType; | ||
| 851 | /// | ||
| 852 | /// sg.set_packet_type(PacketType::Bpsk)?; | ||
| 853 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 854 | /// ``` | ||
| 855 | /// | ||
| 856 | /// MSK (minimum shift keying): | ||
| 857 | /// | ||
| 858 | /// ```no_run | ||
| 859 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 860 | /// use embassy_stm32::subghz::PacketType; | ||
| 861 | /// | ||
| 862 | /// sg.set_packet_type(PacketType::Msk)?; | ||
| 863 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 864 | /// ``` | ||
| 865 | pub fn set_packet_type(&mut self, packet_type: PacketType) -> Result<(), Error> { | ||
| 866 | self.write(&[OpCode::SetPacketType as u8, packet_type as u8]) | ||
| 867 | } | ||
| 868 | |||
| 869 | /// Get the packet type. | ||
| 870 | /// | ||
| 871 | /// # Example | ||
| 872 | /// | ||
| 873 | /// ```no_run | ||
| 874 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 875 | /// use embassy_stm32::subghz::PacketType; | ||
| 876 | /// | ||
| 877 | /// sg.set_packet_type(PacketType::LoRa)?; | ||
| 878 | /// assert_eq!(sg.packet_type()?, Ok(PacketType::LoRa)); | ||
| 879 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 880 | /// ``` | ||
| 881 | pub fn packet_type(&mut self) -> Result<Result<PacketType, u8>, Error> { | ||
| 882 | let pkt_type: [u8; 2] = self.read_n(OpCode::GetPacketType)?; | ||
| 883 | Ok(PacketType::from_raw(pkt_type[1])) | ||
| 884 | } | ||
| 885 | |||
| 886 | /// Set the radio carrier frequency. | ||
| 887 | /// | ||
| 888 | /// # Example | ||
| 889 | /// | ||
| 890 | /// Set the frequency to 915MHz (Australia and North America). | ||
| 891 | /// | ||
| 892 | /// ```no_run | ||
| 893 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 894 | /// use embassy_stm32::subghz::RfFreq; | ||
| 895 | /// | ||
| 896 | /// sg.set_rf_frequency(&RfFreq::F915)?; | ||
| 897 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 898 | /// ``` | ||
| 899 | pub fn set_rf_frequency(&mut self, freq: &RfFreq) -> Result<(), Error> { | ||
| 900 | self.write(freq.as_slice()) | ||
| 901 | } | ||
| 902 | |||
| 903 | /// Set the transmit output power and the PA ramp-up time. | ||
| 904 | /// | ||
| 905 | /// # Example | ||
| 906 | /// | ||
| 907 | /// Set the output power to +10 dBm (low power mode) and a ramp up time of | ||
| 908 | /// 40 microseconds. | ||
| 909 | /// | ||
| 910 | /// ```no_run | ||
| 911 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 912 | /// use embassy_stm32::subghz::{PaConfig, PaSel, RampTime, TxParams}; | ||
| 913 | /// | ||
| 914 | /// const TX_PARAMS: TxParams = TxParams::new() | ||
| 915 | /// .set_ramp_time(RampTime::Micros40) | ||
| 916 | /// .set_power(0x0D); | ||
| 917 | /// const PA_CONFIG: PaConfig = PaConfig::new() | ||
| 918 | /// .set_pa(PaSel::Lp) | ||
| 919 | /// .set_pa_duty_cycle(0x1) | ||
| 920 | /// .set_hp_max(0x0); | ||
| 921 | /// | ||
| 922 | /// sg.set_pa_config(&PA_CONFIG)?; | ||
| 923 | /// sg.set_tx_params(&TX_PARAMS)?; | ||
| 924 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 925 | /// ``` | ||
| 926 | pub fn set_tx_params(&mut self, params: &TxParams) -> Result<(), Error> { | ||
| 927 | self.write(params.as_slice()) | ||
| 928 | } | ||
| 929 | |||
| 930 | /// Power amplifier configuation. | ||
| 931 | /// | ||
| 932 | /// Used to customize the maximum output power and efficiency. | ||
| 933 | /// | ||
| 934 | /// # Example | ||
| 935 | /// | ||
| 936 | /// Set the output power to +22 dBm (high power mode) and a ramp up time of | ||
| 937 | /// 200 microseconds. | ||
| 938 | /// | ||
| 939 | /// ```no_run | ||
| 940 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 941 | /// use embassy_stm32::subghz::{PaConfig, PaSel, RampTime, TxParams}; | ||
| 942 | /// | ||
| 943 | /// const TX_PARAMS: TxParams = TxParams::new() | ||
| 944 | /// .set_ramp_time(RampTime::Micros200) | ||
| 945 | /// .set_power(0x16); | ||
| 946 | /// const PA_CONFIG: PaConfig = PaConfig::new() | ||
| 947 | /// .set_pa(PaSel::Hp) | ||
| 948 | /// .set_pa_duty_cycle(0x4) | ||
| 949 | /// .set_hp_max(0x7); | ||
| 950 | /// | ||
| 951 | /// sg.set_pa_config(&PA_CONFIG)?; | ||
| 952 | /// sg.set_tx_params(&TX_PARAMS)?; | ||
| 953 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 954 | /// ``` | ||
| 955 | pub fn set_pa_config(&mut self, pa_config: &PaConfig) -> Result<(), Error> { | ||
| 956 | self.write(pa_config.as_slice()) | ||
| 957 | } | ||
| 958 | |||
| 959 | /// Operating mode to enter after a successful packet transmission or | ||
| 960 | /// packet reception. | ||
| 961 | /// | ||
| 962 | /// # Example | ||
| 963 | /// | ||
| 964 | /// Set the fallback mode to standby mode. | ||
| 965 | /// | ||
| 966 | /// ```no_run | ||
| 967 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 968 | /// use embassy_stm32::subghz::FallbackMode; | ||
| 969 | /// | ||
| 970 | /// sg.set_tx_rx_fallback_mode(FallbackMode::Standby)?; | ||
| 971 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 972 | /// ``` | ||
| 973 | pub fn set_tx_rx_fallback_mode(&mut self, fm: FallbackMode) -> Result<(), Error> { | ||
| 974 | self.write(&[OpCode::SetTxRxFallbackMode as u8, fm as u8]) | ||
| 975 | } | ||
| 976 | |||
| 977 | /// Set channel activity detection (CAD) parameters. | ||
| 978 | /// | ||
| 979 | /// # Example | ||
| 980 | /// | ||
| 981 | /// ```no_run | ||
| 982 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 983 | /// use core::time::Duration; | ||
| 984 | /// use embassy_stm32::subghz::{CadParams, ExitMode, NbCadSymbol, StandbyClk, Timeout}; | ||
| 985 | /// | ||
| 986 | /// const RX_PERIOD: Timeout = Timeout::from_duration_sat(Duration::from_millis(100)); | ||
| 987 | /// const SLEEP_PERIOD: Timeout = Timeout::from_duration_sat(Duration::from_secs(1)); | ||
| 988 | /// const CAD_PARAMS: CadParams = CadParams::new() | ||
| 989 | /// .set_num_symbol(NbCadSymbol::S4) | ||
| 990 | /// .set_det_peak(0x18) | ||
| 991 | /// .set_det_min(0x10) | ||
| 992 | /// .set_exit_mode(ExitMode::Standby); | ||
| 993 | /// | ||
| 994 | /// sg.set_standby(StandbyClk::Rc)?; | ||
| 995 | /// sg.set_cad_params(&CAD_PARAMS)?; | ||
| 996 | /// sg.set_cad()?; | ||
| 997 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 998 | /// ``` | ||
| 999 | pub fn set_cad_params(&mut self, params: &CadParams) -> Result<(), Error> { | ||
| 1000 | self.write(params.as_slice()) | ||
| 1001 | } | ||
| 1002 | |||
| 1003 | /// Set the data buffer base address for the packet handling in TX and RX. | ||
| 1004 | /// | ||
| 1005 | /// There is a 256B TX buffer and a 256B RX buffer. | ||
| 1006 | /// These buffers are not memory mapped, they are accessed via the | ||
| 1007 | /// [`read_buffer`] and [`write_buffer`] methods. | ||
| 1008 | /// | ||
| 1009 | /// # Example | ||
| 1010 | /// | ||
| 1011 | /// Set the TX and RX buffer base to the start. | ||
| 1012 | /// | ||
| 1013 | /// ```no_run | ||
| 1014 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1015 | /// sg.set_buffer_base_address(0, 0)?; | ||
| 1016 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1017 | /// ``` | ||
| 1018 | /// | ||
| 1019 | /// [`read_buffer`]: SubGhz::read_buffer | ||
| 1020 | /// [`write_buffer`]: SubGhz::write_buffer | ||
| 1021 | pub fn set_buffer_base_address(&mut self, tx: u8, rx: u8) -> Result<(), Error> { | ||
| 1022 | self.write(&[OpCode::SetBufferBaseAddress as u8, tx, rx]) | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | /// Set the (G)FSK modulation parameters. | ||
| 1026 | /// | ||
| 1027 | /// # Example | ||
| 1028 | /// | ||
| 1029 | /// ```no_run | ||
| 1030 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1031 | /// use embassy_stm32::subghz::{ | ||
| 1032 | /// FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape, PacketType, | ||
| 1033 | /// }; | ||
| 1034 | /// | ||
| 1035 | /// const BITRATE: FskBitrate = FskBitrate::from_bps(32_000); | ||
| 1036 | /// const PULSE_SHAPE: FskPulseShape = FskPulseShape::Bt03; | ||
| 1037 | /// const BW: FskBandwidth = FskBandwidth::Bw9; | ||
| 1038 | /// const FDEV: FskFdev = FskFdev::from_hertz(31_250); | ||
| 1039 | /// | ||
| 1040 | /// const MOD_PARAMS: FskModParams = FskModParams::new() | ||
| 1041 | /// .set_bitrate(BITRATE) | ||
| 1042 | /// .set_pulse_shape(PULSE_SHAPE) | ||
| 1043 | /// .set_bandwidth(BW) | ||
| 1044 | /// .set_fdev(FDEV); | ||
| 1045 | /// | ||
| 1046 | /// sg.set_packet_type(PacketType::Fsk)?; | ||
| 1047 | /// sg.set_fsk_mod_params(&MOD_PARAMS)?; | ||
| 1048 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1049 | /// ``` | ||
| 1050 | pub fn set_fsk_mod_params(&mut self, params: &FskModParams) -> Result<(), Error> { | ||
| 1051 | self.write(params.as_slice()) | ||
| 1052 | } | ||
| 1053 | |||
| 1054 | /// Set the LoRa modulation parameters. | ||
| 1055 | /// | ||
| 1056 | /// # Example | ||
| 1057 | /// | ||
| 1058 | /// ```no_run | ||
| 1059 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1060 | /// use embassy_stm32::subghz::{ | ||
| 1061 | /// CodingRate, LoRaBandwidth, LoRaModParams, PacketType, SpreadingFactor, | ||
| 1062 | /// }; | ||
| 1063 | /// | ||
| 1064 | /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new() | ||
| 1065 | /// .set_sf(SpreadingFactor::Sf7) | ||
| 1066 | /// .set_bw(LoRaBandwidth::Bw125) | ||
| 1067 | /// .set_cr(CodingRate::Cr45) | ||
| 1068 | /// .set_ldro_en(false); | ||
| 1069 | /// | ||
| 1070 | /// sg.set_packet_type(PacketType::LoRa)?; | ||
| 1071 | /// sg.set_lora_mod_params(&MOD_PARAMS)?; | ||
| 1072 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1073 | /// ``` | ||
| 1074 | pub fn set_lora_mod_params(&mut self, params: &LoRaModParams) -> Result<(), Error> { | ||
| 1075 | self.write(params.as_slice()) | ||
| 1076 | } | ||
| 1077 | |||
| 1078 | /// Set the BPSK modulation parameters. | ||
| 1079 | /// | ||
| 1080 | /// # Example | ||
| 1081 | /// | ||
| 1082 | /// ```no_run | ||
| 1083 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1084 | /// use embassy_stm32::subghz::{BpskModParams, FskBitrate, PacketType}; | ||
| 1085 | /// | ||
| 1086 | /// const MOD_PARAMS: BpskModParams = BpskModParams::new().set_bitrate(FskBitrate::from_bps(600)); | ||
| 1087 | /// | ||
| 1088 | /// sg.set_packet_type(PacketType::Bpsk)?; | ||
| 1089 | /// sg.set_bpsk_mod_params(&MOD_PARAMS)?; | ||
| 1090 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1091 | /// ``` | ||
| 1092 | pub fn set_bpsk_mod_params(&mut self, params: &BpskModParams) -> Result<(), Error> { | ||
| 1093 | self.write(params.as_slice()) | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | /// Set the generic (FSK) packet parameters. | ||
| 1097 | /// | ||
| 1098 | /// # Example | ||
| 1099 | /// | ||
| 1100 | /// ```no_run | ||
| 1101 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1102 | /// use embassy_stm32::subghz::{ | ||
| 1103 | /// AddrComp, CrcType, GenericPacketParams, HeaderType, PacketType, PreambleDetection, | ||
| 1104 | /// }; | ||
| 1105 | /// | ||
| 1106 | /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new() | ||
| 1107 | /// .set_preamble_len(8) | ||
| 1108 | /// .set_preamble_detection(PreambleDetection::Disabled) | ||
| 1109 | /// .set_sync_word_len(2) | ||
| 1110 | /// .set_addr_comp(AddrComp::Disabled) | ||
| 1111 | /// .set_header_type(HeaderType::Fixed) | ||
| 1112 | /// .set_payload_len(128) | ||
| 1113 | /// .set_crc_type(CrcType::Byte2) | ||
| 1114 | /// .set_whitening_enable(true); | ||
| 1115 | /// | ||
| 1116 | /// sg.set_packet_type(PacketType::Fsk)?; | ||
| 1117 | /// sg.set_packet_params(&PKT_PARAMS)?; | ||
| 1118 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1119 | /// ``` | ||
| 1120 | pub fn set_packet_params(&mut self, params: &GenericPacketParams) -> Result<(), Error> { | ||
| 1121 | self.write(params.as_slice()) | ||
| 1122 | } | ||
| 1123 | |||
| 1124 | /// Set the BPSK packet parameters. | ||
| 1125 | /// | ||
| 1126 | /// # Example | ||
| 1127 | /// | ||
| 1128 | /// ```no_run | ||
| 1129 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1130 | /// use embassy_stm32::subghz::{BpskPacketParams, PacketType}; | ||
| 1131 | /// | ||
| 1132 | /// sg.set_packet_type(PacketType::Bpsk)?; | ||
| 1133 | /// sg.set_bpsk_packet_params(&BpskPacketParams::new().set_payload_len(64))?; | ||
| 1134 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1135 | /// ``` | ||
| 1136 | pub fn set_bpsk_packet_params(&mut self, params: &BpskPacketParams) -> Result<(), Error> { | ||
| 1137 | self.write(params.as_slice()) | ||
| 1138 | } | ||
| 1139 | |||
| 1140 | /// Set the LoRa packet parameters. | ||
| 1141 | /// | ||
| 1142 | /// # Example | ||
| 1143 | /// | ||
| 1144 | /// ```no_run | ||
| 1145 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1146 | /// use embassy_stm32::subghz::{HeaderType, LoRaPacketParams, PacketType}; | ||
| 1147 | /// | ||
| 1148 | /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new() | ||
| 1149 | /// .set_preamble_len(5 * 8) | ||
| 1150 | /// .set_header_type(HeaderType::Fixed) | ||
| 1151 | /// .set_payload_len(64) | ||
| 1152 | /// .set_crc_en(true) | ||
| 1153 | /// .set_invert_iq(true); | ||
| 1154 | /// | ||
| 1155 | /// sg.set_packet_type(PacketType::LoRa)?; | ||
| 1156 | /// sg.set_lora_packet_params(&PKT_PARAMS)?; | ||
| 1157 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1158 | /// ``` | ||
| 1159 | pub fn set_lora_packet_params(&mut self, params: &LoRaPacketParams) -> Result<(), Error> { | ||
| 1160 | self.write(params.as_slice()) | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | /// Set the number of LoRa symbols to be received before starting the | ||
| 1164 | /// reception of a LoRa packet. | ||
| 1165 | /// | ||
| 1166 | /// Packet reception is started after `n` + 1 symbols are detected. | ||
| 1167 | /// | ||
| 1168 | /// # Example | ||
| 1169 | /// | ||
| 1170 | /// Start reception after a single LoRa word is detected | ||
| 1171 | /// | ||
| 1172 | /// ```no_run | ||
| 1173 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1174 | /// | ||
| 1175 | /// // ... setup the radio for LoRa RX | ||
| 1176 | /// | ||
| 1177 | /// sg.set_lora_symb_timeout(0)?; | ||
| 1178 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1179 | /// ``` | ||
| 1180 | pub fn set_lora_symb_timeout(&mut self, n: u8) -> Result<(), Error> { | ||
| 1181 | self.write(&[OpCode::SetLoRaSymbTimeout.into(), n]) | ||
| 1182 | } | ||
| 1183 | } | ||
| 1184 | |||
| 1185 | // 5.8.5 | ||
| 1186 | /// Communication status and information commands | ||
| 1187 | impl<'d> SubGhz<'d, NoDma, NoDma> { | ||
| 1188 | /// Get the radio status. | ||
| 1189 | /// | ||
| 1190 | /// The hardware (or documentation) appears to have many bugs where this | ||
| 1191 | /// will return reserved values. | ||
| 1192 | /// See this thread in the ST community for details: [link] | ||
| 1193 | /// | ||
| 1194 | /// ```no_run | ||
| 1195 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1196 | /// use embassy_stm32::subghz::Status; | ||
| 1197 | /// | ||
| 1198 | /// let status: Status = sg.status()?; | ||
| 1199 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1200 | /// ``` | ||
| 1201 | /// | ||
| 1202 | /// [link]: https://community.st.com/s/question/0D53W00000hR9GQSA0/stm32wl55-getstatus-command-returns-reserved-cmdstatus | ||
| 1203 | pub fn status(&mut self) -> Result<Status, Error> { | ||
| 1204 | Ok(self.read_1(OpCode::GetStatus)?.into()) | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | /// Get the RX buffer status. | ||
| 1208 | /// | ||
| 1209 | /// The return tuple is (status, payload_length, buffer_pointer). | ||
| 1210 | /// | ||
| 1211 | /// # Example | ||
| 1212 | /// | ||
| 1213 | /// ```no_run | ||
| 1214 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1215 | /// use embassy_stm32::subghz::{CmdStatus, Timeout}; | ||
| 1216 | /// | ||
| 1217 | /// sg.set_rx(Timeout::DISABLED)?; | ||
| 1218 | /// loop { | ||
| 1219 | /// let (status, len, ptr) = sg.rx_buffer_status()?; | ||
| 1220 | /// | ||
| 1221 | /// if status.cmd() == Ok(CmdStatus::Avaliable) { | ||
| 1222 | /// let mut buf: [u8; 256] = [0; 256]; | ||
| 1223 | /// let data: &mut [u8] = &mut buf[..usize::from(len)]; | ||
| 1224 | /// sg.read_buffer(ptr, data)?; | ||
| 1225 | /// // ... do things with the data | ||
| 1226 | /// break; | ||
| 1227 | /// } | ||
| 1228 | /// } | ||
| 1229 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1230 | /// ``` | ||
| 1231 | pub fn rx_buffer_status(&mut self) -> Result<(Status, u8, u8), Error> { | ||
| 1232 | let data: [u8; 3] = self.read_n(OpCode::GetRxBufferStatus)?; | ||
| 1233 | Ok((data[0].into(), data[1], data[2])) | ||
| 1234 | } | ||
| 1235 | |||
| 1236 | /// Returns information on the last received (G)FSK packet. | ||
| 1237 | /// | ||
| 1238 | /// # Example | ||
| 1239 | /// | ||
| 1240 | /// ```no_run | ||
| 1241 | /// # use std::fmt::Write; | ||
| 1242 | /// # let mut uart = String::new(); | ||
| 1243 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1244 | /// use embassy_stm32::subghz::{CmdStatus, Timeout}; | ||
| 1245 | /// | ||
| 1246 | /// sg.set_rx(Timeout::DISABLED)?; | ||
| 1247 | /// loop { | ||
| 1248 | /// let pkt_status = sg.fsk_packet_status()?; | ||
| 1249 | /// | ||
| 1250 | /// if pkt_status.status().cmd() == Ok(CmdStatus::Avaliable) { | ||
| 1251 | /// let rssi = pkt_status.rssi_avg(); | ||
| 1252 | /// writeln!(&mut uart, "Avg RSSI: {} dBm", rssi); | ||
| 1253 | /// break; | ||
| 1254 | /// } | ||
| 1255 | /// } | ||
| 1256 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1257 | /// ``` | ||
| 1258 | pub fn fsk_packet_status(&mut self) -> Result<FskPacketStatus, Error> { | ||
| 1259 | Ok(FskPacketStatus::from(self.read_n(OpCode::GetPacketStatus)?)) | ||
| 1260 | } | ||
| 1261 | |||
| 1262 | /// Returns information on the last received LoRa packet. | ||
| 1263 | /// | ||
| 1264 | /// # Example | ||
| 1265 | /// | ||
| 1266 | /// ```no_run | ||
| 1267 | /// # use std::fmt::Write; | ||
| 1268 | /// # let mut uart = String::new(); | ||
| 1269 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1270 | /// use embassy_stm32::subghz::{CmdStatus, Timeout}; | ||
| 1271 | /// | ||
| 1272 | /// sg.set_rx(Timeout::DISABLED)?; | ||
| 1273 | /// loop { | ||
| 1274 | /// let pkt_status = sg.lora_packet_status()?; | ||
| 1275 | /// | ||
| 1276 | /// if pkt_status.status().cmd() == Ok(CmdStatus::Avaliable) { | ||
| 1277 | /// let snr = pkt_status.snr_pkt(); | ||
| 1278 | /// writeln!(&mut uart, "SNR: {} dB", snr); | ||
| 1279 | /// break; | ||
| 1280 | /// } | ||
| 1281 | /// } | ||
| 1282 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1283 | /// ``` | ||
| 1284 | pub fn lora_packet_status(&mut self) -> Result<LoRaPacketStatus, Error> { | ||
| 1285 | Ok(LoRaPacketStatus::from( | ||
| 1286 | self.read_n(OpCode::GetPacketStatus)?, | ||
| 1287 | )) | ||
| 1288 | } | ||
| 1289 | |||
| 1290 | /// Get the instantaneous signal strength during packet reception. | ||
| 1291 | /// | ||
| 1292 | /// The units are in dbm. | ||
| 1293 | /// | ||
| 1294 | /// # Example | ||
| 1295 | /// | ||
| 1296 | /// Log the instantaneous signal strength to UART. | ||
| 1297 | /// | ||
| 1298 | /// ```no_run | ||
| 1299 | /// # use std::fmt::Write; | ||
| 1300 | /// # let mut uart = String::new(); | ||
| 1301 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1302 | /// use embassy_stm32::subghz::{CmdStatus, Timeout}; | ||
| 1303 | /// | ||
| 1304 | /// sg.set_rx(Timeout::DISABLED)?; | ||
| 1305 | /// let (_, rssi) = sg.rssi_inst()?; | ||
| 1306 | /// writeln!(&mut uart, "RSSI: {} dBm", rssi); | ||
| 1307 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1308 | /// ``` | ||
| 1309 | pub fn rssi_inst(&mut self) -> Result<(Status, Ratio<i16>), Error> { | ||
| 1310 | let data: [u8; 2] = self.read_n(OpCode::GetRssiInst)?; | ||
| 1311 | let status: Status = data[0].into(); | ||
| 1312 | let rssi: Ratio<i16> = Ratio::new_raw(i16::from(data[1]), -2); | ||
| 1313 | |||
| 1314 | Ok((status, rssi)) | ||
| 1315 | } | ||
| 1316 | |||
| 1317 | /// (G)FSK packet stats. | ||
| 1318 | /// | ||
| 1319 | /// # Example | ||
| 1320 | /// | ||
| 1321 | /// ```no_run | ||
| 1322 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1323 | /// use embassy_stm32::subghz::{FskStats, Stats}; | ||
| 1324 | /// | ||
| 1325 | /// let stats: Stats<FskStats> = sg.fsk_stats()?; | ||
| 1326 | /// // ... use stats | ||
| 1327 | /// sg.reset_stats()?; | ||
| 1328 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1329 | /// ``` | ||
| 1330 | pub fn fsk_stats(&mut self) -> Result<Stats<FskStats>, Error> { | ||
| 1331 | let data: [u8; 7] = self.read_n(OpCode::GetStats)?; | ||
| 1332 | Ok(Stats::from_raw_fsk(data)) | ||
| 1333 | } | ||
| 1334 | |||
| 1335 | /// LoRa packet stats. | ||
| 1336 | /// | ||
| 1337 | /// # Example | ||
| 1338 | /// | ||
| 1339 | /// ```no_run | ||
| 1340 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1341 | /// use embassy_stm32::subghz::{LoRaStats, Stats}; | ||
| 1342 | /// | ||
| 1343 | /// let stats: Stats<LoRaStats> = sg.lora_stats()?; | ||
| 1344 | /// // ... use stats | ||
| 1345 | /// sg.reset_stats()?; | ||
| 1346 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1347 | /// ``` | ||
| 1348 | pub fn lora_stats(&mut self) -> Result<Stats<LoRaStats>, Error> { | ||
| 1349 | let data: [u8; 7] = self.read_n(OpCode::GetStats)?; | ||
| 1350 | Ok(Stats::from_raw_lora(data)) | ||
| 1351 | } | ||
| 1352 | |||
| 1353 | /// Reset the stats as reported in [`lora_stats`] and [`fsk_stats`]. | ||
| 1354 | /// | ||
| 1355 | /// # Example | ||
| 1356 | /// | ||
| 1357 | /// ```no_run | ||
| 1358 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1359 | /// | ||
| 1360 | /// sg.reset_stats()?; | ||
| 1361 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1362 | /// ``` | ||
| 1363 | /// | ||
| 1364 | /// [`lora_stats`]: crate::subghz::SubGhz::lora_stats | ||
| 1365 | /// [`fsk_stats`]: crate::subghz::SubGhz::fsk_stats | ||
| 1366 | pub fn reset_stats(&mut self) -> Result<(), Error> { | ||
| 1367 | const RESET_STATS: [u8; 7] = [0x00; 7]; | ||
| 1368 | self.write(&RESET_STATS) | ||
| 1369 | } | ||
| 1370 | } | ||
| 1371 | |||
| 1372 | // 5.8.6 | ||
| 1373 | /// IRQ commands | ||
| 1374 | impl<'d> SubGhz<'d, NoDma, NoDma> { | ||
| 1375 | /// Set the interrupt configuration. | ||
| 1376 | /// | ||
| 1377 | /// # Example | ||
| 1378 | /// | ||
| 1379 | /// Enable TX and timeout interrupts. | ||
| 1380 | /// | ||
| 1381 | /// ```no_run | ||
| 1382 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1383 | /// use embassy_stm32::subghz::{CfgIrq, Irq}; | ||
| 1384 | /// | ||
| 1385 | /// const IRQ_CFG: CfgIrq = CfgIrq::new() | ||
| 1386 | /// .irq_enable_all(Irq::TxDone) | ||
| 1387 | /// .irq_enable_all(Irq::Timeout); | ||
| 1388 | /// sg.set_irq_cfg(&IRQ_CFG)?; | ||
| 1389 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1390 | /// ``` | ||
| 1391 | pub fn set_irq_cfg(&mut self, cfg: &CfgIrq) -> Result<(), Error> { | ||
| 1392 | self.write(cfg.as_slice()) | ||
| 1393 | } | ||
| 1394 | |||
| 1395 | /// Get the IRQ status. | ||
| 1396 | /// | ||
| 1397 | /// # Example | ||
| 1398 | /// | ||
| 1399 | /// Wait for TX to complete or timeout. | ||
| 1400 | /// | ||
| 1401 | /// ```no_run | ||
| 1402 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1403 | /// use embassy_stm32::subghz::Irq; | ||
| 1404 | /// | ||
| 1405 | /// loop { | ||
| 1406 | /// let (_, irq_status) = sg.irq_status()?; | ||
| 1407 | /// sg.clear_irq_status(irq_status)?; | ||
| 1408 | /// if irq_status & Irq::TxDone.mask() != 0 { | ||
| 1409 | /// // handle TX done | ||
| 1410 | /// break; | ||
| 1411 | /// } | ||
| 1412 | /// if irq_status & Irq::Timeout.mask() != 0 { | ||
| 1413 | /// // handle timeout | ||
| 1414 | /// break; | ||
| 1415 | /// } | ||
| 1416 | /// } | ||
| 1417 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1418 | /// ``` | ||
| 1419 | pub fn irq_status(&mut self) -> Result<(Status, u16), Error> { | ||
| 1420 | let data: [u8; 3] = self.read_n(OpCode::GetIrqStatus)?; | ||
| 1421 | let irq_status: u16 = u16::from_be_bytes([data[1], data[2]]); | ||
| 1422 | Ok((data[0].into(), irq_status)) | ||
| 1423 | } | ||
| 1424 | |||
| 1425 | /// Clear the IRQ status. | ||
| 1426 | /// | ||
| 1427 | /// # Example | ||
| 1428 | /// | ||
| 1429 | /// Clear the [`TxDone`] and [`RxDone`] interrupts. | ||
| 1430 | /// | ||
| 1431 | /// ```no_run | ||
| 1432 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1433 | /// use embassy_stm32::subghz::Irq; | ||
| 1434 | /// | ||
| 1435 | /// sg.clear_irq_status(Irq::TxDone.mask() | Irq::RxDone.mask())?; | ||
| 1436 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1437 | /// ``` | ||
| 1438 | /// | ||
| 1439 | /// [`TxDone`]: crate::subghz::Irq::TxDone | ||
| 1440 | /// [`RxDone`]: crate::subghz::Irq::RxDone | ||
| 1441 | pub fn clear_irq_status(&mut self, mask: u16) -> Result<(), Error> { | ||
| 1442 | self.write(&[OpCode::ClrIrqStatus as u8, (mask >> 8) as u8, mask as u8]) | ||
| 1443 | } | ||
| 1444 | } | ||
| 1445 | |||
| 1446 | // 5.8.7 | ||
| 1447 | /// Miscellaneous commands | ||
| 1448 | impl<'d> SubGhz<'d, NoDma, NoDma> { | ||
| 1449 | /// Calibrate one or several blocks at any time when in standby mode. | ||
| 1450 | /// | ||
| 1451 | /// The blocks to calibrate are defined by `cal` argument. | ||
| 1452 | /// When the calibration is ongoing, BUSY is set. | ||
| 1453 | /// A falling edge on BUSY indicates the end of all enabled calibrations. | ||
| 1454 | /// | ||
| 1455 | /// This function will not poll for BUSY. | ||
| 1456 | /// | ||
| 1457 | /// # Example | ||
| 1458 | /// | ||
| 1459 | /// Calibrate the RC 13 MHz and PLL. | ||
| 1460 | /// | ||
| 1461 | /// ```no_run | ||
| 1462 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1463 | /// use embassy_stm32::subghz::{Calibrate, StandbyClk, SubGhz}; | ||
| 1464 | /// | ||
| 1465 | /// sg.set_standby(StandbyClk::Rc)?; | ||
| 1466 | /// sg.calibrate(Calibrate::Rc13M.mask() | Calibrate::Pll.mask())?; | ||
| 1467 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1468 | /// ``` | ||
| 1469 | pub fn calibrate(&mut self, cal: u8) -> Result<(), Error> { | ||
| 1470 | // bit 7 is reserved and must be kept at reset value. | ||
| 1471 | self.write(&[OpCode::Calibrate as u8, cal & 0x7F]) | ||
| 1472 | } | ||
| 1473 | |||
| 1474 | /// Calibrate the image at the given frequencies. | ||
| 1475 | /// | ||
| 1476 | /// Requires the radio to be in standby mode. | ||
| 1477 | /// | ||
| 1478 | /// # Example | ||
| 1479 | /// | ||
| 1480 | /// Calibrate the image for the 430 - 440 MHz ISM band. | ||
| 1481 | /// | ||
| 1482 | /// ```no_run | ||
| 1483 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1484 | /// use embassy_stm32::subghz::{CalibrateImage, StandbyClk}; | ||
| 1485 | /// | ||
| 1486 | /// sg.set_standby(StandbyClk::Rc)?; | ||
| 1487 | /// sg.calibrate_image(CalibrateImage::ISM_430_440)?; | ||
| 1488 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1489 | /// ``` | ||
| 1490 | pub fn calibrate_image(&mut self, cal: CalibrateImage) -> Result<(), Error> { | ||
| 1491 | self.write(&[OpCode::CalibrateImage as u8, cal.0, cal.1]) | ||
| 1492 | } | ||
| 1493 | |||
| 1494 | /// Set the radio power supply. | ||
| 1495 | /// | ||
| 1496 | /// # Examples | ||
| 1497 | /// | ||
| 1498 | /// Use the linear dropout regulator (LDO): | ||
| 1499 | /// | ||
| 1500 | /// ```no_run | ||
| 1501 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1502 | /// use embassy_stm32::subghz::RegMode; | ||
| 1503 | /// | ||
| 1504 | /// sg.set_regulator_mode(RegMode::Ldo)?; | ||
| 1505 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1506 | /// ``` | ||
| 1507 | /// | ||
| 1508 | /// Use the switch mode power supply (SPMS): | ||
| 1509 | /// | ||
| 1510 | /// ```no_run | ||
| 1511 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1512 | /// use embassy_stm32::subghz::RegMode; | ||
| 1513 | /// | ||
| 1514 | /// sg.set_regulator_mode(RegMode::Smps)?; | ||
| 1515 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1516 | /// ``` | ||
| 1517 | pub fn set_regulator_mode(&mut self, reg_mode: RegMode) -> Result<(), Error> { | ||
| 1518 | self.write(&[OpCode::SetRegulatorMode as u8, reg_mode as u8]) | ||
| 1519 | } | ||
| 1520 | |||
| 1521 | /// Get the radio operational errors. | ||
| 1522 | /// | ||
| 1523 | /// # Example | ||
| 1524 | /// | ||
| 1525 | /// ```no_run | ||
| 1526 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1527 | /// use embassy_stm32::subghz::OpError; | ||
| 1528 | /// | ||
| 1529 | /// let (status, error_mask) = sg.op_error()?; | ||
| 1530 | /// if error_mask & OpError::PllLockError.mask() != 0 { | ||
| 1531 | /// // ... handle PLL lock error | ||
| 1532 | /// } | ||
| 1533 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1534 | /// ``` | ||
| 1535 | pub fn op_error(&mut self) -> Result<(Status, u16), Error> { | ||
| 1536 | let data: [u8; 3] = self.read_n(OpCode::GetError)?; | ||
| 1537 | Ok((data[0].into(), u16::from_le_bytes([data[1], data[2]]))) | ||
| 1538 | } | ||
| 1539 | |||
| 1540 | /// Clear all errors as reported by [`op_error`]. | ||
| 1541 | /// | ||
| 1542 | /// # Example | ||
| 1543 | /// | ||
| 1544 | /// ```no_run | ||
| 1545 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1546 | /// use embassy_stm32::subghz::OpError; | ||
| 1547 | /// | ||
| 1548 | /// let (status, error_mask) = sg.op_error()?; | ||
| 1549 | /// // ignore all errors | ||
| 1550 | /// if error_mask != 0 { | ||
| 1551 | /// sg.clear_error()?; | ||
| 1552 | /// } | ||
| 1553 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1554 | /// ``` | ||
| 1555 | /// | ||
| 1556 | /// [`op_error`]: crate::subghz::SubGhz::op_error | ||
| 1557 | pub fn clear_error(&mut self) -> Result<(), Error> { | ||
| 1558 | self.write(&[OpCode::ClrError as u8, 0x00]) | ||
| 1559 | } | ||
| 1560 | } | ||
| 1561 | |||
| 1562 | // 5.8.8 | ||
| 1563 | /// Set TCXO mode command | ||
| 1564 | impl<'d> SubGhz<'d, NoDma, NoDma> { | ||
| 1565 | /// Set the TCXO trim and HSE32 ready timeout. | ||
| 1566 | /// | ||
| 1567 | /// # Example | ||
| 1568 | /// | ||
| 1569 | /// Setup the TCXO with 1.7V trim and a 10ms timeout. | ||
| 1570 | /// | ||
| 1571 | /// ```no_run | ||
| 1572 | /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); | ||
| 1573 | /// use embassy_stm32::subghz::{TcxoMode, TcxoTrim, Timeout}; | ||
| 1574 | /// | ||
| 1575 | /// const TCXO_MODE: TcxoMode = TcxoMode::new() | ||
| 1576 | /// .set_txco_trim(TcxoTrim::Volts1pt7) | ||
| 1577 | /// .set_timeout(Timeout::from_millis_sat(10)); | ||
| 1578 | /// sg.set_tcxo_mode(&TCXO_MODE)?; | ||
| 1579 | /// # Ok::<(), embassy_stm32::subghz::Error>(()) | ||
| 1580 | /// ``` | ||
| 1581 | pub fn set_tcxo_mode(&mut self, tcxo_mode: &TcxoMode) -> Result<(), Error> { | ||
| 1582 | self.write(tcxo_mode.as_slice()) | ||
| 1583 | } | ||
| 1584 | } | ||
| 1585 | |||
| 1586 | /// sub-GHz radio opcodes. | ||
| 1587 | /// | ||
| 1588 | /// See Table 41 "Sub-GHz radio SPI commands overview" | ||
| 1589 | #[repr(u8)] | ||
| 1590 | #[derive(Debug, Clone, Copy)] | ||
| 1591 | #[allow(dead_code)] | ||
| 1592 | pub(crate) enum OpCode { | ||
| 1593 | Calibrate = 0x89, | ||
| 1594 | CalibrateImage = 0x98, | ||
| 1595 | CfgDioIrq = 0x08, | ||
| 1596 | ClrError = 0x07, | ||
| 1597 | ClrIrqStatus = 0x02, | ||
| 1598 | GetError = 0x17, | ||
| 1599 | GetIrqStatus = 0x12, | ||
| 1600 | GetPacketStatus = 0x14, | ||
| 1601 | GetPacketType = 0x11, | ||
| 1602 | GetRssiInst = 0x15, | ||
| 1603 | GetRxBufferStatus = 0x13, | ||
| 1604 | GetStats = 0x10, | ||
| 1605 | GetStatus = 0xC0, | ||
| 1606 | ReadBuffer = 0x1E, | ||
| 1607 | RegRegister = 0x1D, | ||
| 1608 | ResetStats = 0x00, | ||
| 1609 | SetBufferBaseAddress = 0x8F, | ||
| 1610 | SetCad = 0xC5, | ||
| 1611 | SetCadParams = 0x88, | ||
| 1612 | SetFs = 0xC1, | ||
| 1613 | SetLoRaSymbTimeout = 0xA0, | ||
| 1614 | SetModulationParams = 0x8B, | ||
| 1615 | SetPacketParams = 0x8C, | ||
| 1616 | SetPacketType = 0x8A, | ||
| 1617 | SetPaConfig = 0x95, | ||
| 1618 | SetRegulatorMode = 0x96, | ||
| 1619 | SetRfFrequency = 0x86, | ||
| 1620 | SetRx = 0x82, | ||
| 1621 | SetRxDutyCycle = 0x94, | ||
| 1622 | SetSleep = 0x84, | ||
| 1623 | SetStandby = 0x80, | ||
| 1624 | SetStopRxTimerOnPreamble = 0x9F, | ||
| 1625 | SetTcxoMode = 0x97, | ||
| 1626 | SetTx = 0x83, | ||
| 1627 | SetTxContinuousPreamble = 0xD2, | ||
| 1628 | SetTxContinuousWave = 0xD1, | ||
| 1629 | SetTxParams = 0x8E, | ||
| 1630 | SetTxRxFallbackMode = 0x93, | ||
| 1631 | WriteBuffer = 0x0E, | ||
| 1632 | WriteRegister = 0x0D, | ||
| 1633 | } | ||
| 1634 | |||
| 1635 | impl From<OpCode> for u8 { | ||
| 1636 | fn from(opcode: OpCode) -> Self { | ||
| 1637 | opcode as u8 | ||
| 1638 | } | ||
| 1639 | } | ||
| 1640 | |||
| 1641 | #[repr(u16)] | ||
| 1642 | #[allow(clippy::upper_case_acronyms)] | ||
| 1643 | pub(crate) enum Register { | ||
| 1644 | /// Generic bit synchronization. | ||
| 1645 | GBSYNC = 0x06AC, | ||
| 1646 | /// Generic packet control. | ||
| 1647 | GPKTCTL1A = 0x06B8, | ||
| 1648 | /// Generic whitening. | ||
| 1649 | GWHITEINIRL = 0x06B9, | ||
| 1650 | /// Generic CRC initial. | ||
| 1651 | GCRCINIRH = 0x06BC, | ||
| 1652 | /// Generic CRC polynomial. | ||
| 1653 | GCRCPOLRH = 0x06BE, | ||
| 1654 | /// Generic synchronization word 7. | ||
| 1655 | GSYNC7 = 0x06C0, | ||
| 1656 | /// LoRa synchronization word MSB. | ||
| 1657 | LSYNCH = 0x0740, | ||
| 1658 | /// LoRa synchronization word LSB. | ||
| 1659 | #[allow(dead_code)] | ||
| 1660 | LSYNCL = 0x0741, | ||
| 1661 | /// Receiver gain control. | ||
| 1662 | RXGAINC = 0x08AC, | ||
| 1663 | /// PA over current protection. | ||
| 1664 | PAOCP = 0x08E7, | ||
| 1665 | /// HSE32 OSC_IN capacitor trim. | ||
| 1666 | HSEINTRIM = 0x0911, | ||
| 1667 | /// HSE32 OSC_OUT capacitor trim. | ||
| 1668 | HSEOUTTRIM = 0x0912, | ||
| 1669 | /// SMPS control 0. | ||
| 1670 | SMPSC0 = 0x0916, | ||
| 1671 | /// Power control. | ||
| 1672 | PC = 0x091A, | ||
| 1673 | /// SMPS control 2. | ||
| 1674 | SMPSC2 = 0x0923, | ||
| 1675 | } | ||
| 1676 | |||
| 1677 | impl Register { | ||
| 1678 | pub const fn address(self) -> u16 { | ||
| 1679 | self as u16 | ||
| 1680 | } | ||
| 1681 | } | ||
diff --git a/embassy-stm32/src/subghz/mod_params.rs b/embassy-stm32/src/subghz/mod_params.rs new file mode 100644 index 000000000..3a5cb199a --- /dev/null +++ b/embassy-stm32/src/subghz/mod_params.rs | |||
| @@ -0,0 +1,996 @@ | |||
| 1 | /// Bandwidth options for [`FskModParams`]. | ||
| 2 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 3 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 4 | pub enum FskBandwidth { | ||
| 5 | /// 4.8 kHz double-sideband | ||
| 6 | Bw4 = 0x1F, | ||
| 7 | /// 5.8 kHz double-sideband | ||
| 8 | Bw5 = 0x17, | ||
| 9 | /// 7.3 kHz double-sideband | ||
| 10 | Bw7 = 0x0F, | ||
| 11 | /// 9.7 kHz double-sideband | ||
| 12 | Bw9 = 0x1E, | ||
| 13 | /// 11.7 kHz double-sideband | ||
| 14 | Bw11 = 0x16, | ||
| 15 | /// 14.6 kHz double-sideband | ||
| 16 | Bw14 = 0x0E, | ||
| 17 | /// 19.5 kHz double-sideband | ||
| 18 | Bw19 = 0x1D, | ||
| 19 | /// 23.4 kHz double-sideband | ||
| 20 | Bw23 = 0x15, | ||
| 21 | /// 29.3 kHz double-sideband | ||
| 22 | Bw29 = 0x0D, | ||
| 23 | /// 39.0 kHz double-sideband | ||
| 24 | Bw39 = 0x1C, | ||
| 25 | /// 46.9 kHz double-sideband | ||
| 26 | Bw46 = 0x14, | ||
| 27 | /// 58.6 kHz double-sideband | ||
| 28 | Bw58 = 0x0C, | ||
| 29 | /// 78.2 kHz double-sideband | ||
| 30 | Bw78 = 0x1B, | ||
| 31 | /// 93.8 kHz double-sideband | ||
| 32 | Bw93 = 0x13, | ||
| 33 | /// 117.3 kHz double-sideband | ||
| 34 | Bw117 = 0x0B, | ||
| 35 | /// 156.2 kHz double-sideband | ||
| 36 | Bw156 = 0x1A, | ||
| 37 | /// 187.2 kHz double-sideband | ||
| 38 | Bw187 = 0x12, | ||
| 39 | /// 234.3 kHz double-sideband | ||
| 40 | Bw234 = 0x0A, | ||
| 41 | /// 312.0 kHz double-sideband | ||
| 42 | Bw312 = 0x19, | ||
| 43 | /// 373.6 kHz double-sideband | ||
| 44 | Bw373 = 0x11, | ||
| 45 | /// 467.0 kHz double-sideband | ||
| 46 | Bw467 = 0x09, | ||
| 47 | } | ||
| 48 | |||
| 49 | impl FskBandwidth { | ||
| 50 | /// Get the bandwidth in hertz. | ||
| 51 | /// | ||
| 52 | /// # Example | ||
| 53 | /// | ||
| 54 | /// ``` | ||
| 55 | /// use stm32wl_hal::subghz::FskBandwidth; | ||
| 56 | /// | ||
| 57 | /// assert_eq!(FskBandwidth::Bw4.hertz(), 4_800); | ||
| 58 | /// assert_eq!(FskBandwidth::Bw5.hertz(), 5_800); | ||
| 59 | /// assert_eq!(FskBandwidth::Bw7.hertz(), 7_300); | ||
| 60 | /// assert_eq!(FskBandwidth::Bw9.hertz(), 9_700); | ||
| 61 | /// assert_eq!(FskBandwidth::Bw11.hertz(), 11_700); | ||
| 62 | /// assert_eq!(FskBandwidth::Bw14.hertz(), 14_600); | ||
| 63 | /// assert_eq!(FskBandwidth::Bw19.hertz(), 19_500); | ||
| 64 | /// assert_eq!(FskBandwidth::Bw23.hertz(), 23_400); | ||
| 65 | /// assert_eq!(FskBandwidth::Bw29.hertz(), 29_300); | ||
| 66 | /// assert_eq!(FskBandwidth::Bw39.hertz(), 39_000); | ||
| 67 | /// assert_eq!(FskBandwidth::Bw46.hertz(), 46_900); | ||
| 68 | /// assert_eq!(FskBandwidth::Bw58.hertz(), 58_600); | ||
| 69 | /// assert_eq!(FskBandwidth::Bw78.hertz(), 78_200); | ||
| 70 | /// assert_eq!(FskBandwidth::Bw93.hertz(), 93_800); | ||
| 71 | /// assert_eq!(FskBandwidth::Bw117.hertz(), 117_300); | ||
| 72 | /// assert_eq!(FskBandwidth::Bw156.hertz(), 156_200); | ||
| 73 | /// assert_eq!(FskBandwidth::Bw187.hertz(), 187_200); | ||
| 74 | /// assert_eq!(FskBandwidth::Bw234.hertz(), 234_300); | ||
| 75 | /// assert_eq!(FskBandwidth::Bw312.hertz(), 312_000); | ||
| 76 | /// assert_eq!(FskBandwidth::Bw373.hertz(), 373_600); | ||
| 77 | /// assert_eq!(FskBandwidth::Bw467.hertz(), 467_000); | ||
| 78 | /// ``` | ||
| 79 | pub const fn hertz(&self) -> u32 { | ||
| 80 | match self { | ||
| 81 | FskBandwidth::Bw4 => 4_800, | ||
| 82 | FskBandwidth::Bw5 => 5_800, | ||
| 83 | FskBandwidth::Bw7 => 7_300, | ||
| 84 | FskBandwidth::Bw9 => 9_700, | ||
| 85 | FskBandwidth::Bw11 => 11_700, | ||
| 86 | FskBandwidth::Bw14 => 14_600, | ||
| 87 | FskBandwidth::Bw19 => 19_500, | ||
| 88 | FskBandwidth::Bw23 => 23_400, | ||
| 89 | FskBandwidth::Bw29 => 29_300, | ||
| 90 | FskBandwidth::Bw39 => 39_000, | ||
| 91 | FskBandwidth::Bw46 => 46_900, | ||
| 92 | FskBandwidth::Bw58 => 58_600, | ||
| 93 | FskBandwidth::Bw78 => 78_200, | ||
| 94 | FskBandwidth::Bw93 => 93_800, | ||
| 95 | FskBandwidth::Bw117 => 117_300, | ||
| 96 | FskBandwidth::Bw156 => 156_200, | ||
| 97 | FskBandwidth::Bw187 => 187_200, | ||
| 98 | FskBandwidth::Bw234 => 234_300, | ||
| 99 | FskBandwidth::Bw312 => 312_000, | ||
| 100 | FskBandwidth::Bw373 => 373_600, | ||
| 101 | FskBandwidth::Bw467 => 467_000, | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | /// Convert from a raw bit value. | ||
| 106 | /// | ||
| 107 | /// Invalid values will be returned in the `Err` variant of the result. | ||
| 108 | /// | ||
| 109 | /// # Example | ||
| 110 | /// | ||
| 111 | /// ``` | ||
| 112 | /// use stm32wl_hal::subghz::FskBandwidth; | ||
| 113 | /// | ||
| 114 | /// assert_eq!(FskBandwidth::from_bits(0x1F), Ok(FskBandwidth::Bw4)); | ||
| 115 | /// assert_eq!(FskBandwidth::from_bits(0x17), Ok(FskBandwidth::Bw5)); | ||
| 116 | /// assert_eq!(FskBandwidth::from_bits(0x0F), Ok(FskBandwidth::Bw7)); | ||
| 117 | /// assert_eq!(FskBandwidth::from_bits(0x1E), Ok(FskBandwidth::Bw9)); | ||
| 118 | /// assert_eq!(FskBandwidth::from_bits(0x16), Ok(FskBandwidth::Bw11)); | ||
| 119 | /// assert_eq!(FskBandwidth::from_bits(0x0E), Ok(FskBandwidth::Bw14)); | ||
| 120 | /// assert_eq!(FskBandwidth::from_bits(0x1D), Ok(FskBandwidth::Bw19)); | ||
| 121 | /// assert_eq!(FskBandwidth::from_bits(0x15), Ok(FskBandwidth::Bw23)); | ||
| 122 | /// assert_eq!(FskBandwidth::from_bits(0x0D), Ok(FskBandwidth::Bw29)); | ||
| 123 | /// assert_eq!(FskBandwidth::from_bits(0x1C), Ok(FskBandwidth::Bw39)); | ||
| 124 | /// assert_eq!(FskBandwidth::from_bits(0x14), Ok(FskBandwidth::Bw46)); | ||
| 125 | /// assert_eq!(FskBandwidth::from_bits(0x0C), Ok(FskBandwidth::Bw58)); | ||
| 126 | /// assert_eq!(FskBandwidth::from_bits(0x1B), Ok(FskBandwidth::Bw78)); | ||
| 127 | /// assert_eq!(FskBandwidth::from_bits(0x13), Ok(FskBandwidth::Bw93)); | ||
| 128 | /// assert_eq!(FskBandwidth::from_bits(0x0B), Ok(FskBandwidth::Bw117)); | ||
| 129 | /// assert_eq!(FskBandwidth::from_bits(0x1A), Ok(FskBandwidth::Bw156)); | ||
| 130 | /// assert_eq!(FskBandwidth::from_bits(0x12), Ok(FskBandwidth::Bw187)); | ||
| 131 | /// assert_eq!(FskBandwidth::from_bits(0x0A), Ok(FskBandwidth::Bw234)); | ||
| 132 | /// assert_eq!(FskBandwidth::from_bits(0x19), Ok(FskBandwidth::Bw312)); | ||
| 133 | /// assert_eq!(FskBandwidth::from_bits(0x11), Ok(FskBandwidth::Bw373)); | ||
| 134 | /// assert_eq!(FskBandwidth::from_bits(0x09), Ok(FskBandwidth::Bw467)); | ||
| 135 | /// assert_eq!(FskBandwidth::from_bits(0x00), Err(0x00)); | ||
| 136 | /// ``` | ||
| 137 | pub const fn from_bits(bits: u8) -> Result<Self, u8> { | ||
| 138 | match bits { | ||
| 139 | 0x1F => Ok(Self::Bw4), | ||
| 140 | 0x17 => Ok(Self::Bw5), | ||
| 141 | 0x0F => Ok(Self::Bw7), | ||
| 142 | 0x1E => Ok(Self::Bw9), | ||
| 143 | 0x16 => Ok(Self::Bw11), | ||
| 144 | 0x0E => Ok(Self::Bw14), | ||
| 145 | 0x1D => Ok(Self::Bw19), | ||
| 146 | 0x15 => Ok(Self::Bw23), | ||
| 147 | 0x0D => Ok(Self::Bw29), | ||
| 148 | 0x1C => Ok(Self::Bw39), | ||
| 149 | 0x14 => Ok(Self::Bw46), | ||
| 150 | 0x0C => Ok(Self::Bw58), | ||
| 151 | 0x1B => Ok(Self::Bw78), | ||
| 152 | 0x13 => Ok(Self::Bw93), | ||
| 153 | 0x0B => Ok(Self::Bw117), | ||
| 154 | 0x1A => Ok(Self::Bw156), | ||
| 155 | 0x12 => Ok(Self::Bw187), | ||
| 156 | 0x0A => Ok(Self::Bw234), | ||
| 157 | 0x19 => Ok(Self::Bw312), | ||
| 158 | 0x11 => Ok(Self::Bw373), | ||
| 159 | 0x09 => Ok(Self::Bw467), | ||
| 160 | x => Err(x), | ||
| 161 | } | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | impl Ord for FskBandwidth { | ||
| 166 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { | ||
| 167 | self.hertz().cmp(&other.hertz()) | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | impl PartialOrd for FskBandwidth { | ||
| 172 | fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { | ||
| 173 | Some(self.hertz().cmp(&other.hertz())) | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | /// Pulse shaping options for [`FskModParams`]. | ||
| 178 | #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] | ||
| 179 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 180 | pub enum FskPulseShape { | ||
| 181 | /// No filtering applied. | ||
| 182 | None = 0b00, | ||
| 183 | /// Gaussian BT 0.3 | ||
| 184 | Bt03 = 0x08, | ||
| 185 | /// Gaussian BT 0.5 | ||
| 186 | Bt05 = 0x09, | ||
| 187 | /// Gaussian BT 0.7 | ||
| 188 | Bt07 = 0x0A, | ||
| 189 | /// Gaussian BT 1.0 | ||
| 190 | Bt10 = 0x0B, | ||
| 191 | } | ||
| 192 | |||
| 193 | /// Bitrate argument for [`FskModParams::set_bitrate`] and | ||
| 194 | /// [`BpskModParams::set_bitrate`]. | ||
| 195 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 196 | pub struct FskBitrate { | ||
| 197 | bits: u32, | ||
| 198 | } | ||
| 199 | |||
| 200 | impl FskBitrate { | ||
| 201 | /// Create a new `FskBitrate` from a bitrate in bits per second. | ||
| 202 | /// | ||
| 203 | /// This the resulting value will be rounded down, and will saturate if | ||
| 204 | /// `bps` is outside of the theoretical limits. | ||
| 205 | /// | ||
| 206 | /// # Example | ||
| 207 | /// | ||
| 208 | /// ``` | ||
| 209 | /// use stm32wl_hal::subghz::FskBitrate; | ||
| 210 | /// | ||
| 211 | /// const BITRATE: FskBitrate = FskBitrate::from_bps(9600); | ||
| 212 | /// assert_eq!(BITRATE.as_bps(), 9600); | ||
| 213 | /// ``` | ||
| 214 | pub const fn from_bps(bps: u32) -> Self { | ||
| 215 | const MAX: u32 = 0x00FF_FFFF; | ||
| 216 | if bps == 0 { | ||
| 217 | Self { bits: MAX } | ||
| 218 | } else { | ||
| 219 | let bits: u32 = 32 * 32_000_000 / bps; | ||
| 220 | if bits > MAX { | ||
| 221 | Self { bits: MAX } | ||
| 222 | } else { | ||
| 223 | Self { bits } | ||
| 224 | } | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | /// Create a new `FskBitrate` from a raw bit value. | ||
| 229 | /// | ||
| 230 | /// bits = 32 × 32 MHz / bitrate | ||
| 231 | /// | ||
| 232 | /// **Note:** Only the first 24 bits of the `u32` are used, the `bits` | ||
| 233 | /// argument will be masked. | ||
| 234 | /// | ||
| 235 | /// # Example | ||
| 236 | /// | ||
| 237 | /// ``` | ||
| 238 | /// use stm32wl_hal::subghz::FskBitrate; | ||
| 239 | /// | ||
| 240 | /// const BITRATE: FskBitrate = FskBitrate::from_raw(0x7D00); | ||
| 241 | /// assert_eq!(BITRATE.as_bps(), 32_000); | ||
| 242 | /// ``` | ||
| 243 | pub const fn from_raw(bits: u32) -> Self { | ||
| 244 | Self { | ||
| 245 | bits: bits & 0x00FF_FFFF, | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | /// Return the bitrate in bits per second, rounded down. | ||
| 250 | /// | ||
| 251 | /// # Example | ||
| 252 | /// | ||
| 253 | /// ``` | ||
| 254 | /// use stm32wl_hal::subghz::FskBitrate; | ||
| 255 | /// | ||
| 256 | /// const BITS_PER_SEC: u32 = 9600; | ||
| 257 | /// const BITRATE: FskBitrate = FskBitrate::from_bps(BITS_PER_SEC); | ||
| 258 | /// assert_eq!(BITRATE.as_bps(), BITS_PER_SEC); | ||
| 259 | /// ``` | ||
| 260 | pub const fn as_bps(&self) -> u32 { | ||
| 261 | if self.bits == 0 { | ||
| 262 | 0 | ||
| 263 | } else { | ||
| 264 | 32 * 32_000_000 / self.bits | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | pub(crate) const fn into_bits(self) -> u32 { | ||
| 269 | self.bits | ||
| 270 | } | ||
| 271 | } | ||
| 272 | |||
| 273 | impl Ord for FskBitrate { | ||
| 274 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { | ||
| 275 | self.as_bps().cmp(&other.as_bps()) | ||
| 276 | } | ||
| 277 | } | ||
| 278 | |||
| 279 | impl PartialOrd for FskBitrate { | ||
| 280 | fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { | ||
| 281 | Some(self.as_bps().cmp(&other.as_bps())) | ||
| 282 | } | ||
| 283 | } | ||
| 284 | |||
| 285 | /// Frequency deviation argument for [`FskModParams::set_fdev`] | ||
| 286 | #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] | ||
| 287 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 288 | pub struct FskFdev { | ||
| 289 | bits: u32, | ||
| 290 | } | ||
| 291 | |||
| 292 | impl FskFdev { | ||
| 293 | /// Create a new `FskFdev` from a frequency deviation in hertz, rounded | ||
| 294 | /// down. | ||
| 295 | /// | ||
| 296 | /// # Example | ||
| 297 | /// | ||
| 298 | /// ``` | ||
| 299 | /// use stm32wl_hal::subghz::FskFdev; | ||
| 300 | /// | ||
| 301 | /// const FDEV: FskFdev = FskFdev::from_hertz(31_250); | ||
| 302 | /// assert_eq!(FDEV.as_hertz(), 31_250); | ||
| 303 | /// ``` | ||
| 304 | pub const fn from_hertz(hz: u32) -> Self { | ||
| 305 | Self { | ||
| 306 | bits: ((hz as u64) * (1 << 25) / 32_000_000) as u32 & 0x00FF_FFFF, | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | /// Create a new `FskFdev` from a raw bit value. | ||
| 311 | /// | ||
| 312 | /// bits = fdev × 2<sup>25</sup> / 32 MHz | ||
| 313 | /// | ||
| 314 | /// **Note:** Only the first 24 bits of the `u32` are used, the `bits` | ||
| 315 | /// argument will be masked. | ||
| 316 | /// | ||
| 317 | /// # Example | ||
| 318 | /// | ||
| 319 | /// ``` | ||
| 320 | /// use stm32wl_hal::subghz::FskFdev; | ||
| 321 | /// | ||
| 322 | /// const FDEV: FskFdev = FskFdev::from_raw(0x8000); | ||
| 323 | /// assert_eq!(FDEV.as_hertz(), 31_250); | ||
| 324 | /// ``` | ||
| 325 | pub const fn from_raw(bits: u32) -> Self { | ||
| 326 | Self { | ||
| 327 | bits: bits & 0x00FF_FFFF, | ||
| 328 | } | ||
| 329 | } | ||
| 330 | |||
| 331 | /// Return the frequency deviation in hertz, rounded down. | ||
| 332 | /// | ||
| 333 | /// # Example | ||
| 334 | /// | ||
| 335 | /// ``` | ||
| 336 | /// use stm32wl_hal::subghz::FskFdev; | ||
| 337 | /// | ||
| 338 | /// const HERTZ: u32 = 31_250; | ||
| 339 | /// const FDEV: FskFdev = FskFdev::from_hertz(HERTZ); | ||
| 340 | /// assert_eq!(FDEV.as_hertz(), HERTZ); | ||
| 341 | /// ``` | ||
| 342 | pub const fn as_hertz(&self) -> u32 { | ||
| 343 | ((self.bits as u64) * 32_000_000 / (1 << 25)) as u32 | ||
| 344 | } | ||
| 345 | |||
| 346 | pub(crate) const fn into_bits(self) -> u32 { | ||
| 347 | self.bits | ||
| 348 | } | ||
| 349 | } | ||
| 350 | |||
| 351 | /// (G)FSK modulation paramters. | ||
| 352 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 353 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 354 | pub struct FskModParams { | ||
| 355 | buf: [u8; 9], | ||
| 356 | } | ||
| 357 | |||
| 358 | impl FskModParams { | ||
| 359 | /// Create a new `FskModParams` struct. | ||
| 360 | /// | ||
| 361 | /// This is the same as `default`, but in a `const` function. | ||
| 362 | /// | ||
| 363 | /// # Example | ||
| 364 | /// | ||
| 365 | /// ``` | ||
| 366 | /// use stm32wl_hal::subghz::FskModParams; | ||
| 367 | /// | ||
| 368 | /// const MOD_PARAMS: FskModParams = FskModParams::new(); | ||
| 369 | /// ``` | ||
| 370 | pub const fn new() -> FskModParams { | ||
| 371 | FskModParams { | ||
| 372 | buf: [ | ||
| 373 | super::OpCode::SetModulationParams as u8, | ||
| 374 | 0x00, | ||
| 375 | 0x00, | ||
| 376 | 0x00, | ||
| 377 | 0x00, | ||
| 378 | 0x00, | ||
| 379 | 0x00, | ||
| 380 | 0x00, | ||
| 381 | 0x00, | ||
| 382 | ], | ||
| 383 | } | ||
| 384 | .set_bitrate(FskBitrate::from_bps(50_000)) | ||
| 385 | .set_pulse_shape(FskPulseShape::None) | ||
| 386 | .set_bandwidth(FskBandwidth::Bw58) | ||
| 387 | .set_fdev(FskFdev::from_hertz(25_000)) | ||
| 388 | } | ||
| 389 | |||
| 390 | /// Get the bitrate. | ||
| 391 | /// | ||
| 392 | /// # Example | ||
| 393 | /// | ||
| 394 | /// Setting the bitrate to 32,000 bits per second. | ||
| 395 | /// | ||
| 396 | /// ``` | ||
| 397 | /// use stm32wl_hal::subghz::{FskBitrate, FskModParams}; | ||
| 398 | /// | ||
| 399 | /// const BITRATE: FskBitrate = FskBitrate::from_bps(32_000); | ||
| 400 | /// const MOD_PARAMS: FskModParams = FskModParams::new().set_bitrate(BITRATE); | ||
| 401 | /// assert_eq!(MOD_PARAMS.bitrate(), BITRATE); | ||
| 402 | /// ``` | ||
| 403 | pub const fn bitrate(&self) -> FskBitrate { | ||
| 404 | let raw: u32 = u32::from_be_bytes([0, self.buf[1], self.buf[2], self.buf[3]]); | ||
| 405 | FskBitrate::from_raw(raw) | ||
| 406 | } | ||
| 407 | |||
| 408 | /// Set the bitrate. | ||
| 409 | /// | ||
| 410 | /// # Example | ||
| 411 | /// | ||
| 412 | /// Setting the bitrate to 32,000 bits per second. | ||
| 413 | /// | ||
| 414 | /// ``` | ||
| 415 | /// use stm32wl_hal::subghz::{FskBitrate, FskModParams}; | ||
| 416 | /// | ||
| 417 | /// const BITRATE: FskBitrate = FskBitrate::from_bps(32_000); | ||
| 418 | /// const MOD_PARAMS: FskModParams = FskModParams::new().set_bitrate(BITRATE); | ||
| 419 | /// # assert_eq!(MOD_PARAMS.as_slice()[1], 0x00); | ||
| 420 | /// # assert_eq!(MOD_PARAMS.as_slice()[2], 0x7D); | ||
| 421 | /// # assert_eq!(MOD_PARAMS.as_slice()[3], 0x00); | ||
| 422 | /// ``` | ||
| 423 | #[must_use = "set_bitrate returns a modified FskModParams"] | ||
| 424 | pub const fn set_bitrate(mut self, bitrate: FskBitrate) -> FskModParams { | ||
| 425 | let bits: u32 = bitrate.into_bits(); | ||
| 426 | self.buf[1] = ((bits >> 16) & 0xFF) as u8; | ||
| 427 | self.buf[2] = ((bits >> 8) & 0xFF) as u8; | ||
| 428 | self.buf[3] = (bits & 0xFF) as u8; | ||
| 429 | self | ||
| 430 | } | ||
| 431 | |||
| 432 | /// Set the pulse shaping. | ||
| 433 | /// | ||
| 434 | /// # Example | ||
| 435 | /// | ||
| 436 | /// ``` | ||
| 437 | /// use stm32wl_hal::subghz::{FskModParams, FskPulseShape}; | ||
| 438 | /// | ||
| 439 | /// const MOD_PARAMS: FskModParams = FskModParams::new().set_pulse_shape(FskPulseShape::Bt03); | ||
| 440 | /// # assert_eq!(MOD_PARAMS.as_slice()[4], 0x08); | ||
| 441 | /// ``` | ||
| 442 | #[must_use = "set_pulse_shape returns a modified FskModParams"] | ||
| 443 | pub const fn set_pulse_shape(mut self, shape: FskPulseShape) -> FskModParams { | ||
| 444 | self.buf[4] = shape as u8; | ||
| 445 | self | ||
| 446 | } | ||
| 447 | |||
| 448 | /// Get the bandwidth. | ||
| 449 | /// | ||
| 450 | /// Values that do not correspond to a valid [`FskBandwidth`] will be | ||
| 451 | /// returned in the `Err` variant of the result. | ||
| 452 | /// | ||
| 453 | /// # Example | ||
| 454 | /// | ||
| 455 | /// ``` | ||
| 456 | /// use stm32wl_hal::subghz::{FskBandwidth, FskModParams}; | ||
| 457 | /// | ||
| 458 | /// const MOD_PARAMS: FskModParams = FskModParams::new().set_bandwidth(FskBandwidth::Bw9); | ||
| 459 | /// assert_eq!(MOD_PARAMS.bandwidth(), Ok(FskBandwidth::Bw9)); | ||
| 460 | /// ``` | ||
| 461 | pub const fn bandwidth(&self) -> Result<FskBandwidth, u8> { | ||
| 462 | FskBandwidth::from_bits(self.buf[5]) | ||
| 463 | } | ||
| 464 | |||
| 465 | /// Set the bandwidth. | ||
| 466 | /// | ||
| 467 | /// # Example | ||
| 468 | /// | ||
| 469 | /// ``` | ||
| 470 | /// use stm32wl_hal::subghz::{FskBandwidth, FskModParams}; | ||
| 471 | /// | ||
| 472 | /// const MOD_PARAMS: FskModParams = FskModParams::new().set_bandwidth(FskBandwidth::Bw9); | ||
| 473 | /// # assert_eq!(MOD_PARAMS.as_slice()[5], 0x1E); | ||
| 474 | /// ``` | ||
| 475 | #[must_use = "set_pulse_shape returns a modified FskModParams"] | ||
| 476 | pub const fn set_bandwidth(mut self, bw: FskBandwidth) -> FskModParams { | ||
| 477 | self.buf[5] = bw as u8; | ||
| 478 | self | ||
| 479 | } | ||
| 480 | |||
| 481 | /// Get the frequency deviation. | ||
| 482 | /// | ||
| 483 | /// # Example | ||
| 484 | /// | ||
| 485 | /// ``` | ||
| 486 | /// use stm32wl_hal::subghz::{FskFdev, FskModParams}; | ||
| 487 | /// | ||
| 488 | /// const FDEV: FskFdev = FskFdev::from_hertz(31_250); | ||
| 489 | /// const MOD_PARAMS: FskModParams = FskModParams::new().set_fdev(FDEV); | ||
| 490 | /// assert_eq!(MOD_PARAMS.fdev(), FDEV); | ||
| 491 | /// ``` | ||
| 492 | pub const fn fdev(&self) -> FskFdev { | ||
| 493 | let raw: u32 = u32::from_be_bytes([0, self.buf[6], self.buf[7], self.buf[8]]); | ||
| 494 | FskFdev::from_raw(raw) | ||
| 495 | } | ||
| 496 | |||
| 497 | /// Set the frequency deviation. | ||
| 498 | /// | ||
| 499 | /// # Example | ||
| 500 | /// | ||
| 501 | /// ``` | ||
| 502 | /// use stm32wl_hal::subghz::{FskFdev, FskModParams}; | ||
| 503 | /// | ||
| 504 | /// const FDEV: FskFdev = FskFdev::from_hertz(31_250); | ||
| 505 | /// const MOD_PARAMS: FskModParams = FskModParams::new().set_fdev(FDEV); | ||
| 506 | /// # assert_eq!(MOD_PARAMS.as_slice()[6], 0x00); | ||
| 507 | /// # assert_eq!(MOD_PARAMS.as_slice()[7], 0x80); | ||
| 508 | /// # assert_eq!(MOD_PARAMS.as_slice()[8], 0x00); | ||
| 509 | /// ``` | ||
| 510 | #[must_use = "set_fdev returns a modified FskModParams"] | ||
| 511 | pub const fn set_fdev(mut self, fdev: FskFdev) -> FskModParams { | ||
| 512 | let bits: u32 = fdev.into_bits(); | ||
| 513 | self.buf[6] = ((bits >> 16) & 0xFF) as u8; | ||
| 514 | self.buf[7] = ((bits >> 8) & 0xFF) as u8; | ||
| 515 | self.buf[8] = (bits & 0xFF) as u8; | ||
| 516 | self | ||
| 517 | } | ||
| 518 | /// Returns `true` if the modulation parameters are valid. | ||
| 519 | /// | ||
| 520 | /// The bandwidth must be chosen so that: | ||
| 521 | /// | ||
| 522 | /// [`FskBandwidth`] > [`FskBitrate`] + 2 × [`FskFdev`] + frequency error | ||
| 523 | /// | ||
| 524 | /// Where frequency error = 2 × HSE32<sub>FREQ</sub> error. | ||
| 525 | /// | ||
| 526 | /// The datasheet (DS13293 Rev 1) gives these requirements for the HSE32 | ||
| 527 | /// frequency tolerance: | ||
| 528 | /// | ||
| 529 | /// * Initial: ±10 ppm | ||
| 530 | /// * Over temperature (-20 to 70 °C): ±10 ppm | ||
| 531 | /// * Aging over 10 years: ±10 ppm | ||
| 532 | /// | ||
| 533 | /// # Example | ||
| 534 | /// | ||
| 535 | /// Checking valid parameters at compile-time | ||
| 536 | /// | ||
| 537 | /// ``` | ||
| 538 | /// extern crate static_assertions as sa; | ||
| 539 | /// use stm32wl_hal::subghz::{FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape}; | ||
| 540 | /// | ||
| 541 | /// const MOD_PARAMS: FskModParams = FskModParams::new() | ||
| 542 | /// .set_bitrate(FskBitrate::from_bps(20_000)) | ||
| 543 | /// .set_pulse_shape(FskPulseShape::Bt03) | ||
| 544 | /// .set_bandwidth(FskBandwidth::Bw58) | ||
| 545 | /// .set_fdev(FskFdev::from_hertz(10_000)); | ||
| 546 | /// | ||
| 547 | /// // 30 PPM is wost case (if the HSE32 crystal meets requirements) | ||
| 548 | /// sa::const_assert!(MOD_PARAMS.is_valid(30)); | ||
| 549 | /// ``` | ||
| 550 | #[must_use = "the return value indicates if the modulation parameters are valid"] | ||
| 551 | pub const fn is_valid(&self, ppm: u8) -> bool { | ||
| 552 | let bw: u32 = match self.bandwidth() { | ||
| 553 | Ok(bw) => bw.hertz(), | ||
| 554 | Err(_) => return false, | ||
| 555 | }; | ||
| 556 | let br: u32 = self.bitrate().as_bps(); | ||
| 557 | let fdev: u32 = self.fdev().as_hertz(); | ||
| 558 | let hse_err: u32 = 32 * (ppm as u32); | ||
| 559 | let freq_err: u32 = 2 * hse_err; | ||
| 560 | |||
| 561 | bw > br + 2 * fdev + freq_err | ||
| 562 | } | ||
| 563 | |||
| 564 | /// Extracts a slice containing the packet. | ||
| 565 | /// | ||
| 566 | /// # Example | ||
| 567 | /// | ||
| 568 | /// ``` | ||
| 569 | /// use stm32wl_hal::subghz::{FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape}; | ||
| 570 | /// | ||
| 571 | /// const BITRATE: FskBitrate = FskBitrate::from_bps(20_000); | ||
| 572 | /// const PULSE_SHAPE: FskPulseShape = FskPulseShape::Bt03; | ||
| 573 | /// const BW: FskBandwidth = FskBandwidth::Bw58; | ||
| 574 | /// const FDEV: FskFdev = FskFdev::from_hertz(10_000); | ||
| 575 | /// | ||
| 576 | /// const MOD_PARAMS: FskModParams = FskModParams::new() | ||
| 577 | /// .set_bitrate(BITRATE) | ||
| 578 | /// .set_pulse_shape(PULSE_SHAPE) | ||
| 579 | /// .set_bandwidth(BW) | ||
| 580 | /// .set_fdev(FDEV); | ||
| 581 | /// | ||
| 582 | /// assert_eq!( | ||
| 583 | /// MOD_PARAMS.as_slice(), | ||
| 584 | /// &[0x8B, 0x00, 0xC8, 0x00, 0x08, 0x0C, 0x00, 0x28, 0xF5] | ||
| 585 | /// ); | ||
| 586 | /// ``` | ||
| 587 | pub const fn as_slice(&self) -> &[u8] { | ||
| 588 | &self.buf | ||
| 589 | } | ||
| 590 | } | ||
| 591 | |||
| 592 | impl Default for FskModParams { | ||
| 593 | fn default() -> Self { | ||
| 594 | Self::new() | ||
| 595 | } | ||
| 596 | } | ||
| 597 | |||
| 598 | /// LoRa spreading factor. | ||
| 599 | /// | ||
| 600 | /// Argument of [`LoRaModParams::set_sf`]. | ||
| 601 | #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] | ||
| 602 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 603 | #[repr(u8)] | ||
| 604 | pub enum SpreadingFactor { | ||
| 605 | /// Spreading factor 5. | ||
| 606 | Sf5 = 0x05, | ||
| 607 | /// Spreading factor 6. | ||
| 608 | Sf6 = 0x06, | ||
| 609 | /// Spreading factor 7. | ||
| 610 | Sf7 = 0x07, | ||
| 611 | /// Spreading factor 8. | ||
| 612 | Sf8 = 0x08, | ||
| 613 | /// Spreading factor 9. | ||
| 614 | Sf9 = 0x09, | ||
| 615 | /// Spreading factor 10. | ||
| 616 | Sf10 = 0xA0, | ||
| 617 | /// Spreading factor 11. | ||
| 618 | Sf11 = 0xB0, | ||
| 619 | /// Spreading factor 12. | ||
| 620 | Sf12 = 0xC0, | ||
| 621 | } | ||
| 622 | |||
| 623 | impl From<SpreadingFactor> for u8 { | ||
| 624 | fn from(sf: SpreadingFactor) -> Self { | ||
| 625 | sf as u8 | ||
| 626 | } | ||
| 627 | } | ||
| 628 | |||
| 629 | /// LoRa bandwidth. | ||
| 630 | /// | ||
| 631 | /// Argument of [`LoRaModParams::set_bw`]. | ||
| 632 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 633 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 634 | #[repr(u8)] | ||
| 635 | pub enum LoRaBandwidth { | ||
| 636 | /// 7.81 kHz | ||
| 637 | Bw7 = 0x00, | ||
| 638 | /// 10.42 kHz | ||
| 639 | Bw10 = 0x08, | ||
| 640 | /// 15.63 kHz | ||
| 641 | Bw15 = 0x01, | ||
| 642 | /// 20.83 kHz | ||
| 643 | Bw20 = 0x09, | ||
| 644 | /// 31.25 kHz | ||
| 645 | Bw31 = 0x02, | ||
| 646 | /// 41.67 kHz | ||
| 647 | Bw41 = 0x0A, | ||
| 648 | /// 62.50 kHz | ||
| 649 | Bw62 = 0x03, | ||
| 650 | /// 125 kHz | ||
| 651 | Bw125 = 0x04, | ||
| 652 | /// 250 kHz | ||
| 653 | Bw250 = 0x05, | ||
| 654 | /// 500 kHz | ||
| 655 | Bw500 = 0x06, | ||
| 656 | } | ||
| 657 | |||
| 658 | impl LoRaBandwidth { | ||
| 659 | /// Get the bandwidth in hertz. | ||
| 660 | /// | ||
| 661 | /// # Example | ||
| 662 | /// | ||
| 663 | /// ``` | ||
| 664 | /// use stm32wl_hal::subghz::LoRaBandwidth; | ||
| 665 | /// | ||
| 666 | /// assert_eq!(LoRaBandwidth::Bw7.hertz(), 7_810); | ||
| 667 | /// assert_eq!(LoRaBandwidth::Bw10.hertz(), 10_420); | ||
| 668 | /// assert_eq!(LoRaBandwidth::Bw15.hertz(), 15_630); | ||
| 669 | /// assert_eq!(LoRaBandwidth::Bw20.hertz(), 20_830); | ||
| 670 | /// assert_eq!(LoRaBandwidth::Bw31.hertz(), 31_250); | ||
| 671 | /// assert_eq!(LoRaBandwidth::Bw41.hertz(), 41_670); | ||
| 672 | /// assert_eq!(LoRaBandwidth::Bw62.hertz(), 62_500); | ||
| 673 | /// assert_eq!(LoRaBandwidth::Bw125.hertz(), 125_000); | ||
| 674 | /// assert_eq!(LoRaBandwidth::Bw250.hertz(), 250_000); | ||
| 675 | /// assert_eq!(LoRaBandwidth::Bw500.hertz(), 500_000); | ||
| 676 | /// ``` | ||
| 677 | pub const fn hertz(&self) -> u32 { | ||
| 678 | match self { | ||
| 679 | LoRaBandwidth::Bw7 => 7_810, | ||
| 680 | LoRaBandwidth::Bw10 => 10_420, | ||
| 681 | LoRaBandwidth::Bw15 => 15_630, | ||
| 682 | LoRaBandwidth::Bw20 => 20_830, | ||
| 683 | LoRaBandwidth::Bw31 => 31_250, | ||
| 684 | LoRaBandwidth::Bw41 => 41_670, | ||
| 685 | LoRaBandwidth::Bw62 => 62_500, | ||
| 686 | LoRaBandwidth::Bw125 => 125_000, | ||
| 687 | LoRaBandwidth::Bw250 => 250_000, | ||
| 688 | LoRaBandwidth::Bw500 => 500_000, | ||
| 689 | } | ||
| 690 | } | ||
| 691 | } | ||
| 692 | |||
| 693 | impl Ord for LoRaBandwidth { | ||
| 694 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { | ||
| 695 | self.hertz().cmp(&other.hertz()) | ||
| 696 | } | ||
| 697 | } | ||
| 698 | |||
| 699 | impl PartialOrd for LoRaBandwidth { | ||
| 700 | fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { | ||
| 701 | Some(self.hertz().cmp(&other.hertz())) | ||
| 702 | } | ||
| 703 | } | ||
| 704 | |||
| 705 | /// LoRa forward error correction coding rate. | ||
| 706 | /// | ||
| 707 | /// Argument of [`LoRaModParams::set_cr`]. | ||
| 708 | #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] | ||
| 709 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 710 | #[repr(u8)] | ||
| 711 | pub enum CodingRate { | ||
| 712 | /// No forward error correction coding rate 4/4 | ||
| 713 | Cr44 = 0x00, | ||
| 714 | /// Forward error correction coding rate 4/5 | ||
| 715 | Cr45 = 0x1, | ||
| 716 | /// Forward error correction coding rate 4/6 | ||
| 717 | Cr46 = 0x2, | ||
| 718 | /// Forward error correction coding rate 4/7 | ||
| 719 | Cr47 = 0x3, | ||
| 720 | /// Forward error correction coding rate 4/8 | ||
| 721 | Cr48 = 0x4, | ||
| 722 | } | ||
| 723 | |||
| 724 | /// LoRa modulation paramters. | ||
| 725 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 726 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 727 | |||
| 728 | pub struct LoRaModParams { | ||
| 729 | buf: [u8; 5], | ||
| 730 | } | ||
| 731 | |||
| 732 | impl LoRaModParams { | ||
| 733 | /// Create a new `LoRaModParams` struct. | ||
| 734 | /// | ||
| 735 | /// This is the same as `default`, but in a `const` function. | ||
| 736 | /// | ||
| 737 | /// # Example | ||
| 738 | /// | ||
| 739 | /// ``` | ||
| 740 | /// use stm32wl_hal::subghz::LoRaModParams; | ||
| 741 | /// | ||
| 742 | /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new(); | ||
| 743 | /// assert_eq!(MOD_PARAMS, LoRaModParams::default()); | ||
| 744 | /// ``` | ||
| 745 | pub const fn new() -> LoRaModParams { | ||
| 746 | LoRaModParams { | ||
| 747 | buf: [ | ||
| 748 | super::OpCode::SetModulationParams as u8, | ||
| 749 | 0x05, // valid spreading factor | ||
| 750 | 0x00, | ||
| 751 | 0x00, | ||
| 752 | 0x00, | ||
| 753 | ], | ||
| 754 | } | ||
| 755 | } | ||
| 756 | |||
| 757 | /// Set the spreading factor. | ||
| 758 | /// | ||
| 759 | /// # Example | ||
| 760 | /// | ||
| 761 | /// ``` | ||
| 762 | /// use stm32wl_hal::subghz::{LoRaModParams, SpreadingFactor}; | ||
| 763 | /// | ||
| 764 | /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new().set_sf(SpreadingFactor::Sf7); | ||
| 765 | /// # assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x07, 0x00, 0x00, 0x00]); | ||
| 766 | /// ``` | ||
| 767 | #[must_use = "set_sf returns a modified LoRaModParams"] | ||
| 768 | pub const fn set_sf(mut self, sf: SpreadingFactor) -> Self { | ||
| 769 | self.buf[1] = sf as u8; | ||
| 770 | self | ||
| 771 | } | ||
| 772 | |||
| 773 | /// Set the bandwidth. | ||
| 774 | /// | ||
| 775 | /// # Example | ||
| 776 | /// | ||
| 777 | /// ``` | ||
| 778 | /// use stm32wl_hal::subghz::{LoRaBandwidth, LoRaModParams}; | ||
| 779 | /// | ||
| 780 | /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new().set_bw(LoRaBandwidth::Bw125); | ||
| 781 | /// # assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x05, 0x04, 0x00, 0x00]); | ||
| 782 | /// ``` | ||
| 783 | #[must_use = "set_bw returns a modified LoRaModParams"] | ||
| 784 | pub const fn set_bw(mut self, bw: LoRaBandwidth) -> Self { | ||
| 785 | self.buf[2] = bw as u8; | ||
| 786 | self | ||
| 787 | } | ||
| 788 | |||
| 789 | /// Set the forward error correction coding rate. | ||
| 790 | /// | ||
| 791 | /// # Example | ||
| 792 | /// | ||
| 793 | /// ``` | ||
| 794 | /// use stm32wl_hal::subghz::{CodingRate, LoRaModParams}; | ||
| 795 | /// | ||
| 796 | /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new().set_cr(CodingRate::Cr45); | ||
| 797 | /// # assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x05, 0x00, 0x01, 0x00]); | ||
| 798 | /// ``` | ||
| 799 | #[must_use = "set_cr returns a modified LoRaModParams"] | ||
| 800 | pub const fn set_cr(mut self, cr: CodingRate) -> Self { | ||
| 801 | self.buf[3] = cr as u8; | ||
| 802 | self | ||
| 803 | } | ||
| 804 | |||
| 805 | /// Set low data rate optimization enable. | ||
| 806 | /// | ||
| 807 | /// # Example | ||
| 808 | /// | ||
| 809 | /// ``` | ||
| 810 | /// use stm32wl_hal::subghz::LoRaModParams; | ||
| 811 | /// | ||
| 812 | /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new().set_ldro_en(true); | ||
| 813 | /// # assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x05, 0x00, 0x00, 0x01]); | ||
| 814 | /// ``` | ||
| 815 | #[must_use = "set_ldro_en returns a modified LoRaModParams"] | ||
| 816 | pub const fn set_ldro_en(mut self, en: bool) -> Self { | ||
| 817 | self.buf[4] = en as u8; | ||
| 818 | self | ||
| 819 | } | ||
| 820 | |||
| 821 | /// Extracts a slice containing the packet. | ||
| 822 | /// | ||
| 823 | /// # Example | ||
| 824 | /// | ||
| 825 | /// ``` | ||
| 826 | /// use stm32wl_hal::subghz::{CodingRate, LoRaBandwidth, LoRaModParams, SpreadingFactor}; | ||
| 827 | /// | ||
| 828 | /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new() | ||
| 829 | /// .set_sf(SpreadingFactor::Sf7) | ||
| 830 | /// .set_bw(LoRaBandwidth::Bw125) | ||
| 831 | /// .set_cr(CodingRate::Cr45) | ||
| 832 | /// .set_ldro_en(false); | ||
| 833 | /// | ||
| 834 | /// assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x07, 0x04, 0x01, 0x00]); | ||
| 835 | /// ``` | ||
| 836 | pub const fn as_slice(&self) -> &[u8] { | ||
| 837 | &self.buf | ||
| 838 | } | ||
| 839 | } | ||
| 840 | |||
| 841 | impl Default for LoRaModParams { | ||
| 842 | fn default() -> Self { | ||
| 843 | Self::new() | ||
| 844 | } | ||
| 845 | } | ||
| 846 | |||
| 847 | /// BPSK modulation paramters. | ||
| 848 | /// | ||
| 849 | /// **Note:** There is no method to set the pulse shape because there is only | ||
| 850 | /// one valid pulse shape (Gaussian BT 0.5). | ||
| 851 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 852 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 853 | pub struct BpskModParams { | ||
| 854 | buf: [u8; 5], | ||
| 855 | } | ||
| 856 | |||
| 857 | impl BpskModParams { | ||
| 858 | /// Create a new `BpskModParams` struct. | ||
| 859 | /// | ||
| 860 | /// This is the same as `default`, but in a `const` function. | ||
| 861 | /// | ||
| 862 | /// # Example | ||
| 863 | /// | ||
| 864 | /// ``` | ||
| 865 | /// use stm32wl_hal::subghz::BpskModParams; | ||
| 866 | /// | ||
| 867 | /// const MOD_PARAMS: BpskModParams = BpskModParams::new(); | ||
| 868 | /// assert_eq!(MOD_PARAMS, BpskModParams::default()); | ||
| 869 | /// ``` | ||
| 870 | pub const fn new() -> BpskModParams { | ||
| 871 | const OPCODE: u8 = super::OpCode::SetModulationParams as u8; | ||
| 872 | BpskModParams { | ||
| 873 | buf: [OPCODE, 0x1A, 0x0A, 0xAA, 0x16], | ||
| 874 | } | ||
| 875 | } | ||
| 876 | |||
| 877 | /// Set the bitrate. | ||
| 878 | /// | ||
| 879 | /// # Example | ||
| 880 | /// | ||
| 881 | /// Setting the bitrate to 600 bits per second. | ||
| 882 | /// | ||
| 883 | /// ``` | ||
| 884 | /// use stm32wl_hal::subghz::{BpskModParams, FskBitrate}; | ||
| 885 | /// | ||
| 886 | /// const BITRATE: FskBitrate = FskBitrate::from_bps(600); | ||
| 887 | /// const MOD_PARAMS: BpskModParams = BpskModParams::new().set_bitrate(BITRATE); | ||
| 888 | /// # assert_eq!(MOD_PARAMS.as_slice()[1], 0x1A); | ||
| 889 | /// # assert_eq!(MOD_PARAMS.as_slice()[2], 0x0A); | ||
| 890 | /// # assert_eq!(MOD_PARAMS.as_slice()[3], 0xAA); | ||
| 891 | /// ``` | ||
| 892 | #[must_use = "set_bitrate returns a modified BpskModParams"] | ||
| 893 | pub const fn set_bitrate(mut self, bitrate: FskBitrate) -> BpskModParams { | ||
| 894 | let bits: u32 = bitrate.into_bits(); | ||
| 895 | self.buf[1] = ((bits >> 16) & 0xFF) as u8; | ||
| 896 | self.buf[2] = ((bits >> 8) & 0xFF) as u8; | ||
| 897 | self.buf[3] = (bits & 0xFF) as u8; | ||
| 898 | self | ||
| 899 | } | ||
| 900 | |||
| 901 | /// Extracts a slice containing the packet. | ||
| 902 | /// | ||
| 903 | /// # Example | ||
| 904 | /// | ||
| 905 | /// ``` | ||
| 906 | /// use stm32wl_hal::subghz::{BpskModParams, FskBitrate}; | ||
| 907 | /// | ||
| 908 | /// const BITRATE: FskBitrate = FskBitrate::from_bps(100); | ||
| 909 | /// const MOD_PARAMS: BpskModParams = BpskModParams::new().set_bitrate(BITRATE); | ||
| 910 | /// assert_eq!(MOD_PARAMS.as_slice(), [0x8B, 0x9C, 0x40, 0x00, 0x16]); | ||
| 911 | /// ``` | ||
| 912 | pub const fn as_slice(&self) -> &[u8] { | ||
| 913 | &self.buf | ||
| 914 | } | ||
| 915 | } | ||
| 916 | |||
| 917 | impl Default for BpskModParams { | ||
| 918 | fn default() -> Self { | ||
| 919 | Self::new() | ||
| 920 | } | ||
| 921 | } | ||
| 922 | |||
| 923 | #[cfg(test)] | ||
| 924 | mod test { | ||
| 925 | use super::{FskBandwidth, FskBitrate, FskFdev, LoRaBandwidth}; | ||
| 926 | |||
| 927 | #[test] | ||
| 928 | fn fsk_bw_ord() { | ||
| 929 | assert!((FskBandwidth::Bw4 as u8) > (FskBandwidth::Bw5 as u8)); | ||
| 930 | assert!(FskBandwidth::Bw4 < FskBandwidth::Bw5); | ||
| 931 | assert!(FskBandwidth::Bw5 > FskBandwidth::Bw4); | ||
| 932 | } | ||
| 933 | |||
| 934 | #[test] | ||
| 935 | fn lora_bw_ord() { | ||
| 936 | assert!((LoRaBandwidth::Bw10 as u8) > (LoRaBandwidth::Bw15 as u8)); | ||
| 937 | assert!(LoRaBandwidth::Bw10 < LoRaBandwidth::Bw15); | ||
| 938 | assert!(LoRaBandwidth::Bw15 > LoRaBandwidth::Bw10); | ||
| 939 | } | ||
| 940 | |||
| 941 | #[test] | ||
| 942 | fn fsk_bitrate_ord() { | ||
| 943 | assert!(FskBitrate::from_bps(9600) > FskBitrate::from_bps(4800)); | ||
| 944 | assert!(FskBitrate::from_bps(4800) < FskBitrate::from_bps(9600)); | ||
| 945 | } | ||
| 946 | |||
| 947 | #[test] | ||
| 948 | fn fsk_bitrate_as_bps_limits() { | ||
| 949 | const ZERO: FskBitrate = FskBitrate::from_raw(0); | ||
| 950 | const ONE: FskBitrate = FskBitrate::from_raw(1); | ||
| 951 | const MAX: FskBitrate = FskBitrate::from_raw(u32::MAX); | ||
| 952 | |||
| 953 | assert_eq!(ZERO.as_bps(), 0); | ||
| 954 | assert_eq!(ONE.as_bps(), 1_024_000_000); | ||
| 955 | assert_eq!(MAX.as_bps(), 61); | ||
| 956 | } | ||
| 957 | |||
| 958 | #[test] | ||
| 959 | fn fsk_bitrate_from_bps_limits() { | ||
| 960 | const ZERO: FskBitrate = FskBitrate::from_bps(0); | ||
| 961 | const ONE: FskBitrate = FskBitrate::from_bps(1); | ||
| 962 | const MAX: FskBitrate = FskBitrate::from_bps(u32::MAX); | ||
| 963 | |||
| 964 | assert_eq!(ZERO.as_bps(), 61); | ||
| 965 | assert_eq!(ONE.as_bps(), 61); | ||
| 966 | assert_eq!(MAX.as_bps(), 0); | ||
| 967 | } | ||
| 968 | |||
| 969 | #[test] | ||
| 970 | fn fsk_fdev_ord() { | ||
| 971 | assert!(FskFdev::from_hertz(30_000) > FskFdev::from_hertz(20_000)); | ||
| 972 | assert!(FskFdev::from_hertz(20_000) < FskFdev::from_hertz(30_000)); | ||
| 973 | } | ||
| 974 | |||
| 975 | #[test] | ||
| 976 | fn fsk_fdev_as_hertz_limits() { | ||
| 977 | const ZERO: FskFdev = FskFdev::from_raw(0); | ||
| 978 | const ONE: FskFdev = FskFdev::from_raw(1); | ||
| 979 | const MAX: FskFdev = FskFdev::from_raw(u32::MAX); | ||
| 980 | |||
| 981 | assert_eq!(ZERO.as_hertz(), 0); | ||
| 982 | assert_eq!(ONE.as_hertz(), 0); | ||
| 983 | assert_eq!(MAX.as_hertz(), 15_999_999); | ||
| 984 | } | ||
| 985 | |||
| 986 | #[test] | ||
| 987 | fn fsk_fdev_from_hertz_limits() { | ||
| 988 | const ZERO: FskFdev = FskFdev::from_hertz(0); | ||
| 989 | const ONE: FskFdev = FskFdev::from_hertz(1); | ||
| 990 | const MAX: FskFdev = FskFdev::from_hertz(u32::MAX); | ||
| 991 | |||
| 992 | assert_eq!(ZERO.as_hertz(), 0); | ||
| 993 | assert_eq!(ONE.as_hertz(), 0); | ||
| 994 | assert_eq!(MAX.as_hertz(), 6_967_294); | ||
| 995 | } | ||
| 996 | } | ||
diff --git a/embassy-stm32/src/subghz/ocp.rs b/embassy-stm32/src/subghz/ocp.rs new file mode 100644 index 000000000..88eea1a2a --- /dev/null +++ b/embassy-stm32/src/subghz/ocp.rs | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | /// Power amplifier over current protection. | ||
| 2 | /// | ||
| 3 | /// Used by [`set_pa_ocp`]. | ||
| 4 | /// | ||
| 5 | /// [`set_pa_ocp`]: crate::subghz::SubGhz::set_pa_ocp | ||
| 6 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] | ||
| 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 8 | #[repr(u8)] | ||
| 9 | pub enum Ocp { | ||
| 10 | /// Maximum 60mA current for LP PA mode. | ||
| 11 | Max60m = 0x18, | ||
| 12 | /// Maximum 140mA for HP PA mode. | ||
| 13 | Max140m = 0x38, | ||
| 14 | } | ||
diff --git a/embassy-stm32/src/subghz/op_error.rs b/embassy-stm32/src/subghz/op_error.rs new file mode 100644 index 000000000..35ebda8a0 --- /dev/null +++ b/embassy-stm32/src/subghz/op_error.rs | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | /// Operation Errors. | ||
| 2 | /// | ||
| 3 | /// Returned by [`op_error`]. | ||
| 4 | /// | ||
| 5 | /// [`op_error`]: crate::subghz::SubGhz::op_error | ||
| 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 8 | #[repr(u8)] | ||
| 9 | pub enum OpError { | ||
| 10 | /// PA ramping failed | ||
| 11 | PaRampError = 8, | ||
| 12 | /// RF-PLL locking failed | ||
| 13 | PllLockError = 6, | ||
| 14 | /// HSE32 clock startup failed | ||
| 15 | XoscStartError = 5, | ||
| 16 | /// Image calibration failed | ||
| 17 | ImageCalibrationError = 4, | ||
| 18 | /// RF-ADC calibration failed | ||
| 19 | AdcCalibrationError = 3, | ||
| 20 | /// RF-PLL calibration failed | ||
| 21 | PllCalibrationError = 2, | ||
| 22 | /// Sub-GHz radio RC 13 MHz oscillator | ||
| 23 | RC13MCalibrationError = 1, | ||
| 24 | /// Sub-GHz radio RC 64 kHz oscillator | ||
| 25 | RC64KCalibrationError = 0, | ||
| 26 | } | ||
| 27 | |||
| 28 | impl OpError { | ||
| 29 | /// Get the bitmask for the error. | ||
| 30 | /// | ||
| 31 | /// # Example | ||
| 32 | /// | ||
| 33 | /// ``` | ||
| 34 | /// use stm32wl_hal::subghz::OpError; | ||
| 35 | /// | ||
| 36 | /// assert_eq!(OpError::PaRampError.mask(), 0b1_0000_0000); | ||
| 37 | /// assert_eq!(OpError::PllLockError.mask(), 0b0_0100_0000); | ||
| 38 | /// assert_eq!(OpError::XoscStartError.mask(), 0b0_0010_0000); | ||
| 39 | /// assert_eq!(OpError::ImageCalibrationError.mask(), 0b0_0001_0000); | ||
| 40 | /// assert_eq!(OpError::AdcCalibrationError.mask(), 0b0_0000_1000); | ||
| 41 | /// assert_eq!(OpError::PllCalibrationError.mask(), 0b0_0000_0100); | ||
| 42 | /// assert_eq!(OpError::RC13MCalibrationError.mask(), 0b0_0000_0010); | ||
| 43 | /// assert_eq!(OpError::RC64KCalibrationError.mask(), 0b0_0000_0001); | ||
| 44 | /// ``` | ||
| 45 | pub const fn mask(self) -> u16 { | ||
| 46 | 1 << (self as u8) | ||
| 47 | } | ||
| 48 | } | ||
diff --git a/embassy-stm32/src/subghz/pa_config.rs b/embassy-stm32/src/subghz/pa_config.rs new file mode 100644 index 000000000..83c510aac --- /dev/null +++ b/embassy-stm32/src/subghz/pa_config.rs | |||
| @@ -0,0 +1,161 @@ | |||
| 1 | /// Power amplifier configuration paramters. | ||
| 2 | /// | ||
| 3 | /// Argument of [`set_pa_config`]. | ||
| 4 | /// | ||
| 5 | /// [`set_pa_config`]: crate::subghz::SubGhz::set_pa_config | ||
| 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 8 | pub struct PaConfig { | ||
| 9 | buf: [u8; 5], | ||
| 10 | } | ||
| 11 | |||
| 12 | impl PaConfig { | ||
| 13 | /// Create a new `PaConfig` struct. | ||
| 14 | /// | ||
| 15 | /// This is the same as `default`, but in a `const` function. | ||
| 16 | /// | ||
| 17 | /// # Example | ||
| 18 | /// | ||
| 19 | /// ``` | ||
| 20 | /// use stm32wl_hal::subghz::PaConfig; | ||
| 21 | /// | ||
| 22 | /// const PA_CONFIG: PaConfig = PaConfig::new(); | ||
| 23 | /// ``` | ||
| 24 | pub const fn new() -> PaConfig { | ||
| 25 | PaConfig { | ||
| 26 | buf: [super::OpCode::SetPaConfig as u8, 0x01, 0x00, 0x01, 0x01], | ||
| 27 | } | ||
| 28 | } | ||
| 29 | |||
| 30 | /// Set the power amplifier duty cycle (conduit angle) control. | ||
| 31 | /// | ||
| 32 | /// **Note:** Only the first 3 bits of the `pa_duty_cycle` argument are used. | ||
| 33 | /// | ||
| 34 | /// Duty cycle = 0.2 + 0.04 × bits | ||
| 35 | /// | ||
| 36 | /// # Caution | ||
| 37 | /// | ||
| 38 | /// The following restrictions must be observed to avoid over-stress on the PA: | ||
| 39 | /// * LP PA mode with synthesis frequency > 400 MHz, PaDutyCycle must be < 0x7. | ||
| 40 | /// * LP PA mode with synthesis frequency < 400 MHz, PaDutyCycle must be < 0x4. | ||
| 41 | /// * HP PA mode, PaDutyCycle must be < 0x4 | ||
| 42 | /// | ||
| 43 | /// # Example | ||
| 44 | /// | ||
| 45 | /// ``` | ||
| 46 | /// use stm32wl_hal::subghz::{PaConfig, PaSel}; | ||
| 47 | /// | ||
| 48 | /// const PA_CONFIG: PaConfig = PaConfig::new().set_pa(PaSel::Lp).set_pa_duty_cycle(0x4); | ||
| 49 | /// # assert_eq!(PA_CONFIG.as_slice()[1], 0x04); | ||
| 50 | /// ``` | ||
| 51 | #[must_use = "set_pa_duty_cycle returns a modified PaConfig"] | ||
| 52 | pub const fn set_pa_duty_cycle(mut self, pa_duty_cycle: u8) -> PaConfig { | ||
| 53 | self.buf[1] = pa_duty_cycle & 0b111; | ||
| 54 | self | ||
| 55 | } | ||
| 56 | |||
| 57 | /// Set the high power amplifier output power. | ||
| 58 | /// | ||
| 59 | /// **Note:** Only the first 3 bits of the `hp_max` argument are used. | ||
| 60 | /// | ||
| 61 | /// # Example | ||
| 62 | /// | ||
| 63 | /// ``` | ||
| 64 | /// use stm32wl_hal::subghz::{PaConfig, PaSel}; | ||
| 65 | /// | ||
| 66 | /// const PA_CONFIG: PaConfig = PaConfig::new().set_pa(PaSel::Hp).set_hp_max(0x2); | ||
| 67 | /// # assert_eq!(PA_CONFIG.as_slice()[2], 0x02); | ||
| 68 | /// ``` | ||
| 69 | #[must_use = "set_hp_max returns a modified PaConfig"] | ||
| 70 | pub const fn set_hp_max(mut self, hp_max: u8) -> PaConfig { | ||
| 71 | self.buf[2] = hp_max & 0b111; | ||
| 72 | self | ||
| 73 | } | ||
| 74 | |||
| 75 | /// Set the power amplifier to use, low or high power. | ||
| 76 | /// | ||
| 77 | /// # Example | ||
| 78 | /// | ||
| 79 | /// ``` | ||
| 80 | /// use stm32wl_hal::subghz::{PaConfig, PaSel}; | ||
| 81 | /// | ||
| 82 | /// const PA_CONFIG_HP: PaConfig = PaConfig::new().set_pa(PaSel::Hp); | ||
| 83 | /// const PA_CONFIG_LP: PaConfig = PaConfig::new().set_pa(PaSel::Lp); | ||
| 84 | /// # assert_eq!(PA_CONFIG_HP.as_slice()[3], 0x00); | ||
| 85 | /// # assert_eq!(PA_CONFIG_LP.as_slice()[3], 0x01); | ||
| 86 | /// ``` | ||
| 87 | #[must_use = "set_pa returns a modified PaConfig"] | ||
| 88 | pub const fn set_pa(mut self, pa: PaSel) -> PaConfig { | ||
| 89 | self.buf[3] = pa as u8; | ||
| 90 | self | ||
| 91 | } | ||
| 92 | |||
| 93 | /// Extracts a slice containing the packet. | ||
| 94 | /// | ||
| 95 | /// # Example | ||
| 96 | /// | ||
| 97 | /// ``` | ||
| 98 | /// use stm32wl_hal::subghz::{PaConfig, PaSel}; | ||
| 99 | /// | ||
| 100 | /// const PA_CONFIG: PaConfig = PaConfig::new() | ||
| 101 | /// .set_pa(PaSel::Hp) | ||
| 102 | /// .set_pa_duty_cycle(0x2) | ||
| 103 | /// .set_hp_max(0x3); | ||
| 104 | /// | ||
| 105 | /// assert_eq!(PA_CONFIG.as_slice(), &[0x95, 0x2, 0x03, 0x00, 0x01]); | ||
| 106 | /// ``` | ||
| 107 | pub const fn as_slice(&self) -> &[u8] { | ||
| 108 | &self.buf | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | impl Default for PaConfig { | ||
| 113 | fn default() -> Self { | ||
| 114 | Self::new() | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | /// Power amplifier selection. | ||
| 119 | /// | ||
| 120 | /// Argument of [`PaConfig::set_pa`]. | ||
| 121 | #[repr(u8)] | ||
| 122 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 123 | pub enum PaSel { | ||
| 124 | /// High power amplifier. | ||
| 125 | Hp = 0b0, | ||
| 126 | /// Low power amplifier. | ||
| 127 | Lp = 0b1, | ||
| 128 | } | ||
| 129 | |||
| 130 | impl PartialOrd for PaSel { | ||
| 131 | fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { | ||
| 132 | Some(self.cmp(other)) | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | impl Ord for PaSel { | ||
| 137 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { | ||
| 138 | match (self, other) { | ||
| 139 | (PaSel::Hp, PaSel::Hp) | (PaSel::Lp, PaSel::Lp) => core::cmp::Ordering::Equal, | ||
| 140 | (PaSel::Hp, PaSel::Lp) => core::cmp::Ordering::Greater, | ||
| 141 | (PaSel::Lp, PaSel::Hp) => core::cmp::Ordering::Less, | ||
| 142 | } | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | impl Default for PaSel { | ||
| 147 | fn default() -> Self { | ||
| 148 | PaSel::Lp | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | #[cfg(test)] | ||
| 153 | mod test { | ||
| 154 | use super::PaSel; | ||
| 155 | |||
| 156 | #[test] | ||
| 157 | fn pa_sel_ord() { | ||
| 158 | assert!(PaSel::Lp < PaSel::Hp); | ||
| 159 | assert!(PaSel::Hp > PaSel::Lp); | ||
| 160 | } | ||
| 161 | } | ||
diff --git a/embassy-stm32/src/subghz/packet_params.rs b/embassy-stm32/src/subghz/packet_params.rs new file mode 100644 index 000000000..712dbaee5 --- /dev/null +++ b/embassy-stm32/src/subghz/packet_params.rs | |||
| @@ -0,0 +1,537 @@ | |||
| 1 | /// Preamble detection length for [`GenericPacketParams`]. | ||
| 2 | #[repr(u8)] | ||
| 3 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] | ||
| 4 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 5 | pub enum PreambleDetection { | ||
| 6 | /// Preamble detection disabled. | ||
| 7 | Disabled = 0x0, | ||
| 8 | /// 8-bit preamble detection. | ||
| 9 | Bit8 = 0x4, | ||
| 10 | /// 16-bit preamble detection. | ||
| 11 | Bit16 = 0x5, | ||
| 12 | /// 24-bit preamble detection. | ||
| 13 | Bit24 = 0x6, | ||
| 14 | /// 32-bit preamble detection. | ||
| 15 | Bit32 = 0x7, | ||
| 16 | } | ||
| 17 | |||
| 18 | /// Address comparison/filtering for [`GenericPacketParams`]. | ||
| 19 | #[repr(u8)] | ||
| 20 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 21 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 22 | pub enum AddrComp { | ||
| 23 | /// Address comparison/filtering disabled. | ||
| 24 | Disabled = 0x0, | ||
| 25 | /// Address comparison/filtering on node address. | ||
| 26 | Node = 0x1, | ||
| 27 | /// Address comparison/filtering on node and broadcast addresses. | ||
| 28 | Broadcast = 0x2, | ||
| 29 | } | ||
| 30 | |||
| 31 | /// Packet header type. | ||
| 32 | /// | ||
| 33 | /// Argument of [`GenericPacketParams::set_header_type`] and | ||
| 34 | /// [`LoRaPacketParams::set_header_type`]. | ||
| 35 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 36 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 37 | pub enum HeaderType { | ||
| 38 | /// Fixed; payload length and header field not added to packet. | ||
| 39 | Fixed, | ||
| 40 | /// Variable; payload length and header field added to packet. | ||
| 41 | Variable, | ||
| 42 | } | ||
| 43 | |||
| 44 | impl HeaderType { | ||
| 45 | pub(crate) const fn to_bits_generic(self) -> u8 { | ||
| 46 | match self { | ||
| 47 | HeaderType::Fixed => 0, | ||
| 48 | HeaderType::Variable => 1, | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | pub(crate) const fn to_bits_lora(self) -> u8 { | ||
| 53 | match self { | ||
| 54 | HeaderType::Fixed => 1, | ||
| 55 | HeaderType::Variable => 0, | ||
| 56 | } | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | /// CRC type definition for [`GenericPacketParams`]. | ||
| 61 | #[repr(u8)] | ||
| 62 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 63 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 64 | pub enum CrcType { | ||
| 65 | /// 1-byte CRC. | ||
| 66 | Byte1 = 0x0, | ||
| 67 | /// CRC disabled. | ||
| 68 | Disabled = 0x1, | ||
| 69 | /// 2-byte CRC. | ||
| 70 | Byte2 = 0x2, | ||
| 71 | /// 1-byte inverted CRC. | ||
| 72 | Byte1Inverted = 0x4, | ||
| 73 | /// 2-byte inverted CRC. | ||
| 74 | Byte2Inverted = 0x6, | ||
| 75 | } | ||
| 76 | |||
| 77 | /// Packet parameters for [`set_packet_params`]. | ||
| 78 | /// | ||
| 79 | /// [`set_packet_params`]: crate::subghz::SubGhz::set_packet_params | ||
| 80 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 81 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 82 | pub struct GenericPacketParams { | ||
| 83 | buf: [u8; 10], | ||
| 84 | } | ||
| 85 | |||
| 86 | impl GenericPacketParams { | ||
| 87 | /// Create a new `GenericPacketParams`. | ||
| 88 | /// | ||
| 89 | /// This is the same as `default`, but in a `const` function. | ||
| 90 | /// | ||
| 91 | /// # Example | ||
| 92 | /// | ||
| 93 | /// ``` | ||
| 94 | /// use stm32wl_hal::subghz::GenericPacketParams; | ||
| 95 | /// | ||
| 96 | /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new(); | ||
| 97 | /// assert_eq!(PKT_PARAMS, GenericPacketParams::default()); | ||
| 98 | /// ``` | ||
| 99 | pub const fn new() -> GenericPacketParams { | ||
| 100 | const OPCODE: u8 = super::OpCode::SetPacketParams as u8; | ||
| 101 | // const variable ensure the compile always optimizes the methods | ||
| 102 | const NEW: GenericPacketParams = GenericPacketParams { | ||
| 103 | buf: [OPCODE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], | ||
| 104 | } | ||
| 105 | .set_preamble_len(1) | ||
| 106 | .set_preamble_detection(PreambleDetection::Disabled) | ||
| 107 | .set_sync_word_len(0) | ||
| 108 | .set_addr_comp(AddrComp::Disabled) | ||
| 109 | .set_header_type(HeaderType::Fixed) | ||
| 110 | .set_payload_len(1); | ||
| 111 | |||
| 112 | NEW | ||
| 113 | } | ||
| 114 | |||
| 115 | /// Preamble length in number of symbols. | ||
| 116 | /// | ||
| 117 | /// Values of zero are invalid, and will automatically be set to 1. | ||
| 118 | /// | ||
| 119 | /// # Example | ||
| 120 | /// | ||
| 121 | /// ``` | ||
| 122 | /// use stm32wl_hal::subghz::GenericPacketParams; | ||
| 123 | /// | ||
| 124 | /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_preamble_len(0x1234); | ||
| 125 | /// # assert_eq!(PKT_PARAMS.as_slice()[1], 0x12); | ||
| 126 | /// # assert_eq!(PKT_PARAMS.as_slice()[2], 0x34); | ||
| 127 | /// ``` | ||
| 128 | #[must_use = "preamble_length returns a modified GenericPacketParams"] | ||
| 129 | pub const fn set_preamble_len(mut self, mut len: u16) -> GenericPacketParams { | ||
| 130 | if len == 0 { | ||
| 131 | len = 1 | ||
| 132 | } | ||
| 133 | self.buf[1] = ((len >> 8) & 0xFF) as u8; | ||
| 134 | self.buf[2] = (len & 0xFF) as u8; | ||
| 135 | self | ||
| 136 | } | ||
| 137 | |||
| 138 | /// Preabmle detection length in number of bit symbols. | ||
| 139 | /// | ||
| 140 | /// # Example | ||
| 141 | /// | ||
| 142 | /// ``` | ||
| 143 | /// use stm32wl_hal::subghz::{GenericPacketParams, PreambleDetection}; | ||
| 144 | /// | ||
| 145 | /// const PKT_PARAMS: GenericPacketParams = | ||
| 146 | /// GenericPacketParams::new().set_preamble_detection(PreambleDetection::Bit8); | ||
| 147 | /// # assert_eq!(PKT_PARAMS.as_slice()[3], 0x4); | ||
| 148 | /// ``` | ||
| 149 | #[must_use = "set_preamble_detection returns a modified GenericPacketParams"] | ||
| 150 | pub const fn set_preamble_detection( | ||
| 151 | mut self, | ||
| 152 | pb_det: PreambleDetection, | ||
| 153 | ) -> GenericPacketParams { | ||
| 154 | self.buf[3] = pb_det as u8; | ||
| 155 | self | ||
| 156 | } | ||
| 157 | |||
| 158 | /// Sync word length in number of bit symbols. | ||
| 159 | /// | ||
| 160 | /// Valid values are `0x00` - `0x40` for 0 to 64-bits respectively. | ||
| 161 | /// Values that exceed the maximum will saturate at `0x40`. | ||
| 162 | /// | ||
| 163 | /// # Example | ||
| 164 | /// | ||
| 165 | /// Set the sync word length to 4 bytes (16 bits). | ||
| 166 | /// | ||
| 167 | /// ``` | ||
| 168 | /// use stm32wl_hal::subghz::GenericPacketParams; | ||
| 169 | /// | ||
| 170 | /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_sync_word_len(16); | ||
| 171 | /// # assert_eq!(PKT_PARAMS.as_slice()[4], 0x10); | ||
| 172 | /// ``` | ||
| 173 | #[must_use = "set_sync_word_len returns a modified GenericPacketParams"] | ||
| 174 | pub const fn set_sync_word_len(mut self, len: u8) -> GenericPacketParams { | ||
| 175 | const MAX: u8 = 0x40; | ||
| 176 | if len > MAX { | ||
| 177 | self.buf[4] = MAX; | ||
| 178 | } else { | ||
| 179 | self.buf[4] = len; | ||
| 180 | } | ||
| 181 | self | ||
| 182 | } | ||
| 183 | |||
| 184 | /// Address comparison/filtering. | ||
| 185 | /// | ||
| 186 | /// # Example | ||
| 187 | /// | ||
| 188 | /// Enable address on the node address. | ||
| 189 | /// | ||
| 190 | /// ``` | ||
| 191 | /// use stm32wl_hal::subghz::{AddrComp, GenericPacketParams}; | ||
| 192 | /// | ||
| 193 | /// const PKT_PARAMS: GenericPacketParams = | ||
| 194 | /// GenericPacketParams::new().set_addr_comp(AddrComp::Node); | ||
| 195 | /// # assert_eq!(PKT_PARAMS.as_slice()[5], 0x01); | ||
| 196 | /// ``` | ||
| 197 | #[must_use = "set_addr_comp returns a modified GenericPacketParams"] | ||
| 198 | pub const fn set_addr_comp(mut self, addr_comp: AddrComp) -> GenericPacketParams { | ||
| 199 | self.buf[5] = addr_comp as u8; | ||
| 200 | self | ||
| 201 | } | ||
| 202 | |||
| 203 | /// Header type definition. | ||
| 204 | /// | ||
| 205 | /// **Note:** The reference manual calls this packet type, but that results | ||
| 206 | /// in a conflicting variable name for the modulation scheme, which the | ||
| 207 | /// reference manual also calls packet type. | ||
| 208 | /// | ||
| 209 | /// # Example | ||
| 210 | /// | ||
| 211 | /// Set the header type to a variable length. | ||
| 212 | /// | ||
| 213 | /// ``` | ||
| 214 | /// use stm32wl_hal::subghz::{GenericPacketParams, HeaderType}; | ||
| 215 | /// | ||
| 216 | /// const PKT_PARAMS: GenericPacketParams = | ||
| 217 | /// GenericPacketParams::new().set_header_type(HeaderType::Variable); | ||
| 218 | /// # assert_eq!(PKT_PARAMS.as_slice()[6], 0x01); | ||
| 219 | /// ``` | ||
| 220 | #[must_use = "set_header_type returns a modified GenericPacketParams"] | ||
| 221 | pub const fn set_header_type(mut self, header_type: HeaderType) -> GenericPacketParams { | ||
| 222 | self.buf[6] = header_type.to_bits_generic(); | ||
| 223 | self | ||
| 224 | } | ||
| 225 | |||
| 226 | /// Set the payload length in bytes. | ||
| 227 | /// | ||
| 228 | /// # Example | ||
| 229 | /// | ||
| 230 | /// ``` | ||
| 231 | /// use stm32wl_hal::subghz::GenericPacketParams; | ||
| 232 | /// | ||
| 233 | /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_payload_len(12); | ||
| 234 | /// # assert_eq!(PKT_PARAMS.as_slice()[7], 12); | ||
| 235 | /// ``` | ||
| 236 | #[must_use = "set_payload_len returns a modified GenericPacketParams"] | ||
| 237 | pub const fn set_payload_len(mut self, len: u8) -> GenericPacketParams { | ||
| 238 | self.buf[7] = len; | ||
| 239 | self | ||
| 240 | } | ||
| 241 | |||
| 242 | /// CRC type definition. | ||
| 243 | /// | ||
| 244 | /// # Example | ||
| 245 | /// | ||
| 246 | /// ``` | ||
| 247 | /// use stm32wl_hal::subghz::{CrcType, GenericPacketParams}; | ||
| 248 | /// | ||
| 249 | /// const PKT_PARAMS: GenericPacketParams = | ||
| 250 | /// GenericPacketParams::new().set_crc_type(CrcType::Byte2Inverted); | ||
| 251 | /// # assert_eq!(PKT_PARAMS.as_slice()[8], 0x6); | ||
| 252 | /// ``` | ||
| 253 | #[must_use = "set_payload_len returns a modified GenericPacketParams"] | ||
| 254 | pub const fn set_crc_type(mut self, crc_type: CrcType) -> GenericPacketParams { | ||
| 255 | self.buf[8] = crc_type as u8; | ||
| 256 | self | ||
| 257 | } | ||
| 258 | |||
| 259 | /// Whitening enable. | ||
| 260 | /// | ||
| 261 | /// # Example | ||
| 262 | /// | ||
| 263 | /// Enable whitening. | ||
| 264 | /// | ||
| 265 | /// ``` | ||
| 266 | /// use stm32wl_hal::subghz::GenericPacketParams; | ||
| 267 | /// | ||
| 268 | /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_whitening_enable(true); | ||
| 269 | /// # assert_eq!(PKT_PARAMS.as_slice()[9], 1); | ||
| 270 | /// ``` | ||
| 271 | #[must_use = "set_whitening_enable returns a modified GenericPacketParams"] | ||
| 272 | pub const fn set_whitening_enable(mut self, en: bool) -> GenericPacketParams { | ||
| 273 | self.buf[9] = en as u8; | ||
| 274 | self | ||
| 275 | } | ||
| 276 | |||
| 277 | /// Extracts a slice containing the packet. | ||
| 278 | /// | ||
| 279 | /// # Example | ||
| 280 | /// | ||
| 281 | /// ``` | ||
| 282 | /// use stm32wl_hal::subghz::{ | ||
| 283 | /// AddrComp, CrcType, GenericPacketParams, HeaderType, PreambleDetection, | ||
| 284 | /// }; | ||
| 285 | /// | ||
| 286 | /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new() | ||
| 287 | /// .set_preamble_len(8) | ||
| 288 | /// .set_preamble_detection(PreambleDetection::Disabled) | ||
| 289 | /// .set_sync_word_len(2) | ||
| 290 | /// .set_addr_comp(AddrComp::Disabled) | ||
| 291 | /// .set_header_type(HeaderType::Fixed) | ||
| 292 | /// .set_payload_len(128) | ||
| 293 | /// .set_crc_type(CrcType::Byte2) | ||
| 294 | /// .set_whitening_enable(true); | ||
| 295 | /// | ||
| 296 | /// assert_eq!( | ||
| 297 | /// PKT_PARAMS.as_slice(), | ||
| 298 | /// &[0x8C, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x80, 0x02, 0x01] | ||
| 299 | /// ); | ||
| 300 | /// ``` | ||
| 301 | pub const fn as_slice(&self) -> &[u8] { | ||
| 302 | &self.buf | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | impl Default for GenericPacketParams { | ||
| 307 | fn default() -> Self { | ||
| 308 | Self::new() | ||
| 309 | } | ||
| 310 | } | ||
| 311 | |||
| 312 | /// Packet parameters for [`set_lora_packet_params`]. | ||
| 313 | /// | ||
| 314 | /// [`set_lora_packet_params`]: crate::subghz::SubGhz::set_lora_packet_params | ||
| 315 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 316 | pub struct LoRaPacketParams { | ||
| 317 | buf: [u8; 7], | ||
| 318 | } | ||
| 319 | |||
| 320 | impl LoRaPacketParams { | ||
| 321 | /// Create a new `GenericPacketParams`. | ||
| 322 | /// | ||
| 323 | /// This is the same as `default`, but in a `const` function. | ||
| 324 | /// | ||
| 325 | /// # Example | ||
| 326 | /// | ||
| 327 | /// ``` | ||
| 328 | /// use stm32wl_hal::subghz::LoRaPacketParams; | ||
| 329 | /// | ||
| 330 | /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new(); | ||
| 331 | /// assert_eq!(PKT_PARAMS, LoRaPacketParams::default()); | ||
| 332 | /// ``` | ||
| 333 | pub const fn new() -> LoRaPacketParams { | ||
| 334 | const OPCODE: u8 = super::OpCode::SetPacketParams as u8; | ||
| 335 | // const variable ensure the compile always optimizes the methods | ||
| 336 | const NEW: LoRaPacketParams = LoRaPacketParams { | ||
| 337 | buf: [OPCODE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], | ||
| 338 | } | ||
| 339 | .set_preamble_len(1) | ||
| 340 | .set_header_type(HeaderType::Fixed) | ||
| 341 | .set_payload_len(1) | ||
| 342 | .set_crc_en(true) | ||
| 343 | .set_invert_iq(false); | ||
| 344 | |||
| 345 | NEW | ||
| 346 | } | ||
| 347 | |||
| 348 | /// Preamble length in number of symbols. | ||
| 349 | /// | ||
| 350 | /// Values of zero are invalid, and will automatically be set to 1. | ||
| 351 | /// | ||
| 352 | /// # Example | ||
| 353 | /// | ||
| 354 | /// ``` | ||
| 355 | /// use stm32wl_hal::subghz::LoRaPacketParams; | ||
| 356 | /// | ||
| 357 | /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_preamble_len(0x1234); | ||
| 358 | /// # assert_eq!(PKT_PARAMS.as_slice()[1], 0x12); | ||
| 359 | /// # assert_eq!(PKT_PARAMS.as_slice()[2], 0x34); | ||
| 360 | /// ``` | ||
| 361 | #[must_use = "preamble_length returns a modified LoRaPacketParams"] | ||
| 362 | pub const fn set_preamble_len(mut self, mut len: u16) -> LoRaPacketParams { | ||
| 363 | if len == 0 { | ||
| 364 | len = 1 | ||
| 365 | } | ||
| 366 | self.buf[1] = ((len >> 8) & 0xFF) as u8; | ||
| 367 | self.buf[2] = (len & 0xFF) as u8; | ||
| 368 | self | ||
| 369 | } | ||
| 370 | |||
| 371 | /// Header type (fixed or variable). | ||
| 372 | /// | ||
| 373 | /// # Example | ||
| 374 | /// | ||
| 375 | /// Set the payload type to a fixed length. | ||
| 376 | /// | ||
| 377 | /// ``` | ||
| 378 | /// use stm32wl_hal::subghz::{HeaderType, LoRaPacketParams}; | ||
| 379 | /// | ||
| 380 | /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_header_type(HeaderType::Fixed); | ||
| 381 | /// # assert_eq!(PKT_PARAMS.as_slice()[3], 0x01); | ||
| 382 | /// ``` | ||
| 383 | #[must_use = "set_header_type returns a modified LoRaPacketParams"] | ||
| 384 | pub const fn set_header_type(mut self, header_type: HeaderType) -> LoRaPacketParams { | ||
| 385 | self.buf[3] = header_type.to_bits_lora(); | ||
| 386 | self | ||
| 387 | } | ||
| 388 | |||
| 389 | /// Set the payload length in bytes. | ||
| 390 | /// | ||
| 391 | /// # Example | ||
| 392 | /// | ||
| 393 | /// ``` | ||
| 394 | /// use stm32wl_hal::subghz::LoRaPacketParams; | ||
| 395 | /// | ||
| 396 | /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_payload_len(12); | ||
| 397 | /// # assert_eq!(PKT_PARAMS.as_slice()[4], 12); | ||
| 398 | /// ``` | ||
| 399 | #[must_use = "set_payload_len returns a modified LoRaPacketParams"] | ||
| 400 | pub const fn set_payload_len(mut self, len: u8) -> LoRaPacketParams { | ||
| 401 | self.buf[4] = len; | ||
| 402 | self | ||
| 403 | } | ||
| 404 | |||
| 405 | /// CRC enable. | ||
| 406 | /// | ||
| 407 | /// # Example | ||
| 408 | /// | ||
| 409 | /// Enable CRC. | ||
| 410 | /// | ||
| 411 | /// ``` | ||
| 412 | /// use stm32wl_hal::subghz::LoRaPacketParams; | ||
| 413 | /// | ||
| 414 | /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_crc_en(true); | ||
| 415 | /// # assert_eq!(PKT_PARAMS.as_slice()[5], 0x1); | ||
| 416 | /// ``` | ||
| 417 | #[must_use = "set_crc_en returns a modified LoRaPacketParams"] | ||
| 418 | pub const fn set_crc_en(mut self, en: bool) -> LoRaPacketParams { | ||
| 419 | self.buf[5] = en as u8; | ||
| 420 | self | ||
| 421 | } | ||
| 422 | |||
| 423 | /// IQ setup. | ||
| 424 | /// | ||
| 425 | /// # Example | ||
| 426 | /// | ||
| 427 | /// Use an inverted IQ setup. | ||
| 428 | /// | ||
| 429 | /// ``` | ||
| 430 | /// use stm32wl_hal::subghz::LoRaPacketParams; | ||
| 431 | /// | ||
| 432 | /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_invert_iq(true); | ||
| 433 | /// # assert_eq!(PKT_PARAMS.as_slice()[6], 0x1); | ||
| 434 | /// ``` | ||
| 435 | #[must_use = "set_invert_iq returns a modified LoRaPacketParams"] | ||
| 436 | pub const fn set_invert_iq(mut self, invert: bool) -> LoRaPacketParams { | ||
| 437 | self.buf[6] = invert as u8; | ||
| 438 | self | ||
| 439 | } | ||
| 440 | |||
| 441 | /// Extracts a slice containing the packet. | ||
| 442 | /// | ||
| 443 | /// # Example | ||
| 444 | /// | ||
| 445 | /// ``` | ||
| 446 | /// use stm32wl_hal::subghz::{HeaderType, LoRaPacketParams}; | ||
| 447 | /// | ||
| 448 | /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new() | ||
| 449 | /// .set_preamble_len(5 * 8) | ||
| 450 | /// .set_header_type(HeaderType::Fixed) | ||
| 451 | /// .set_payload_len(64) | ||
| 452 | /// .set_crc_en(true) | ||
| 453 | /// .set_invert_iq(true); | ||
| 454 | /// | ||
| 455 | /// assert_eq!( | ||
| 456 | /// PKT_PARAMS.as_slice(), | ||
| 457 | /// &[0x8C, 0x00, 0x28, 0x01, 0x40, 0x01, 0x01] | ||
| 458 | /// ); | ||
| 459 | /// ``` | ||
| 460 | pub const fn as_slice(&self) -> &[u8] { | ||
| 461 | &self.buf | ||
| 462 | } | ||
| 463 | } | ||
| 464 | |||
| 465 | impl Default for LoRaPacketParams { | ||
| 466 | fn default() -> Self { | ||
| 467 | Self::new() | ||
| 468 | } | ||
| 469 | } | ||
| 470 | |||
| 471 | /// Packet parameters for [`set_lora_packet_params`]. | ||
| 472 | /// | ||
| 473 | /// [`set_lora_packet_params`]: crate::subghz::SubGhz::set_lora_packet_params | ||
| 474 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 475 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 476 | pub struct BpskPacketParams { | ||
| 477 | buf: [u8; 2], | ||
| 478 | } | ||
| 479 | |||
| 480 | impl BpskPacketParams { | ||
| 481 | /// Create a new `BpskPacketParams`. | ||
| 482 | /// | ||
| 483 | /// This is the same as `default`, but in a `const` function. | ||
| 484 | /// | ||
| 485 | /// # Example | ||
| 486 | /// | ||
| 487 | /// ``` | ||
| 488 | /// use stm32wl_hal::subghz::BpskPacketParams; | ||
| 489 | /// | ||
| 490 | /// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new(); | ||
| 491 | /// assert_eq!(PKT_PARAMS, BpskPacketParams::default()); | ||
| 492 | /// ``` | ||
| 493 | pub const fn new() -> BpskPacketParams { | ||
| 494 | BpskPacketParams { | ||
| 495 | buf: [super::OpCode::SetPacketParams as u8, 0x00], | ||
| 496 | } | ||
| 497 | } | ||
| 498 | |||
| 499 | /// Set the payload length in bytes. | ||
| 500 | /// | ||
| 501 | /// The length includes preamble, sync word, device ID, and CRC. | ||
| 502 | /// | ||
| 503 | /// # Example | ||
| 504 | /// | ||
| 505 | /// ``` | ||
| 506 | /// use stm32wl_hal::subghz::BpskPacketParams; | ||
| 507 | /// | ||
| 508 | /// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new().set_payload_len(12); | ||
| 509 | /// # assert_eq!(PKT_PARAMS.as_slice()[1], 12); | ||
| 510 | /// ``` | ||
| 511 | #[must_use = "set_payload_len returns a modified BpskPacketParams"] | ||
| 512 | pub const fn set_payload_len(mut self, len: u8) -> BpskPacketParams { | ||
| 513 | self.buf[1] = len; | ||
| 514 | self | ||
| 515 | } | ||
| 516 | |||
| 517 | /// Extracts a slice containing the packet. | ||
| 518 | /// | ||
| 519 | /// # Example | ||
| 520 | /// | ||
| 521 | /// ``` | ||
| 522 | /// use stm32wl_hal::subghz::{BpskPacketParams, HeaderType}; | ||
| 523 | /// | ||
| 524 | /// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new().set_payload_len(24); | ||
| 525 | /// | ||
| 526 | /// assert_eq!(PKT_PARAMS.as_slice(), &[0x8C, 24]); | ||
| 527 | /// ``` | ||
| 528 | pub const fn as_slice(&self) -> &[u8] { | ||
| 529 | &self.buf | ||
| 530 | } | ||
| 531 | } | ||
| 532 | |||
| 533 | impl Default for BpskPacketParams { | ||
| 534 | fn default() -> Self { | ||
| 535 | Self::new() | ||
| 536 | } | ||
| 537 | } | ||
diff --git a/embassy-stm32/src/subghz/packet_status.rs b/embassy-stm32/src/subghz/packet_status.rs new file mode 100644 index 000000000..c5316dc5f --- /dev/null +++ b/embassy-stm32/src/subghz/packet_status.rs | |||
| @@ -0,0 +1,279 @@ | |||
| 1 | use embassy_hal_common::ratio::Ratio; | ||
| 2 | |||
| 3 | use crate::subghz::status::Status; | ||
| 4 | |||
| 5 | /// (G)FSK packet status. | ||
| 6 | /// | ||
| 7 | /// Returned by [`fsk_packet_status`]. | ||
| 8 | /// | ||
| 9 | /// [`fsk_packet_status`]: crate::subghz::SubGhz::fsk_packet_status | ||
| 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 11 | pub struct FskPacketStatus { | ||
| 12 | buf: [u8; 4], | ||
| 13 | } | ||
| 14 | |||
| 15 | impl From<[u8; 4]> for FskPacketStatus { | ||
| 16 | fn from(buf: [u8; 4]) -> Self { | ||
| 17 | FskPacketStatus { buf } | ||
| 18 | } | ||
| 19 | } | ||
| 20 | |||
| 21 | impl FskPacketStatus { | ||
| 22 | /// Get the status. | ||
| 23 | /// | ||
| 24 | /// # Example | ||
| 25 | /// | ||
| 26 | /// ``` | ||
| 27 | /// use stm32wl_hal::subghz::{CmdStatus, FskPacketStatus, Status, StatusMode}; | ||
| 28 | /// | ||
| 29 | /// let example_data_from_radio: [u8; 4] = [0x54, 0, 0, 0]; | ||
| 30 | /// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio); | ||
| 31 | /// let status: Status = pkt_status.status(); | ||
| 32 | /// assert_eq!(status.mode(), Ok(StatusMode::Rx)); | ||
| 33 | /// assert_eq!(status.cmd(), Ok(CmdStatus::Avaliable)); | ||
| 34 | /// ``` | ||
| 35 | pub const fn status(&self) -> Status { | ||
| 36 | Status::from_raw(self.buf[0]) | ||
| 37 | } | ||
| 38 | |||
| 39 | /// Returns `true` if a preabmle error occured. | ||
| 40 | pub const fn preamble_error(&self) -> bool { | ||
| 41 | (self.buf[1] & (1 << 7)) != 0 | ||
| 42 | } | ||
| 43 | |||
| 44 | /// Returns `true` if a synchronization error occured. | ||
| 45 | pub const fn sync_err(&self) -> bool { | ||
| 46 | (self.buf[1] & (1 << 6)) != 0 | ||
| 47 | } | ||
| 48 | |||
| 49 | /// Returns `true` if an address error occured. | ||
| 50 | pub const fn adrs_err(&self) -> bool { | ||
| 51 | (self.buf[1] & (1 << 5)) != 0 | ||
| 52 | } | ||
| 53 | |||
| 54 | /// Returns `true` if an crc error occured. | ||
| 55 | pub const fn crc_err(&self) -> bool { | ||
| 56 | (self.buf[1] & (1 << 4)) != 0 | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Returns `true` if a length error occured. | ||
| 60 | pub const fn length_err(&self) -> bool { | ||
| 61 | (self.buf[1] & (1 << 3)) != 0 | ||
| 62 | } | ||
| 63 | |||
| 64 | /// Returns `true` if an abort error occured. | ||
| 65 | pub const fn abort_err(&self) -> bool { | ||
| 66 | (self.buf[1] & (1 << 2)) != 0 | ||
| 67 | } | ||
| 68 | |||
| 69 | /// Returns `true` if a packet is received. | ||
| 70 | pub const fn pkt_received(&self) -> bool { | ||
| 71 | (self.buf[1] & (1 << 1)) != 0 | ||
| 72 | } | ||
| 73 | |||
| 74 | /// Returns `true` when a packet has been sent. | ||
| 75 | pub const fn pkt_sent(&self) -> bool { | ||
| 76 | (self.buf[1] & 1) != 0 | ||
| 77 | } | ||
| 78 | |||
| 79 | /// RSSI level when the synchronization address is detected. | ||
| 80 | /// | ||
| 81 | /// Units are in dBm. | ||
| 82 | /// | ||
| 83 | /// # Example | ||
| 84 | /// | ||
| 85 | /// ``` | ||
| 86 | /// use stm32wl_hal::{subghz::FskPacketStatus, Ratio}; | ||
| 87 | /// | ||
| 88 | /// let example_data_from_radio: [u8; 4] = [0, 0, 80, 0]; | ||
| 89 | /// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio); | ||
| 90 | /// assert_eq!(pkt_status.rssi_sync().to_integer(), -40); | ||
| 91 | /// ``` | ||
| 92 | pub fn rssi_sync(&self) -> Ratio<i16> { | ||
| 93 | Ratio::new_raw(i16::from(self.buf[2]), -2) | ||
| 94 | } | ||
| 95 | |||
| 96 | /// Return the RSSI level over the received packet. | ||
| 97 | /// | ||
| 98 | /// Units are in dBm. | ||
| 99 | /// | ||
| 100 | /// # Example | ||
| 101 | /// | ||
| 102 | /// ``` | ||
| 103 | /// use stm32wl_hal::{subghz::FskPacketStatus, Ratio}; | ||
| 104 | /// | ||
| 105 | /// let example_data_from_radio: [u8; 4] = [0, 0, 0, 100]; | ||
| 106 | /// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio); | ||
| 107 | /// assert_eq!(pkt_status.rssi_avg().to_integer(), -50); | ||
| 108 | /// ``` | ||
| 109 | pub fn rssi_avg(&self) -> Ratio<i16> { | ||
| 110 | Ratio::new_raw(i16::from(self.buf[3]), -2) | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | #[cfg(feature = "defmt")] | ||
| 115 | impl defmt::Format for FskPacketStatus { | ||
| 116 | fn format(&self, fmt: defmt::Formatter) { | ||
| 117 | defmt::write!( | ||
| 118 | fmt, | ||
| 119 | r#"FskPacketStatus {{ | ||
| 120 | status: {}, | ||
| 121 | preamble_error: {}, | ||
| 122 | sync_err: {}, | ||
| 123 | adrs_err: {}, | ||
| 124 | crc_err: {}, | ||
| 125 | length_err: {}, | ||
| 126 | abort_err: {}, | ||
| 127 | pkt_received: {}, | ||
| 128 | pkt_sent: {}, | ||
| 129 | rssi_sync: {}, | ||
| 130 | rssi_avg: {}, | ||
| 131 | }}"#, | ||
| 132 | self.status(), | ||
| 133 | self.preamble_error(), | ||
| 134 | self.sync_err(), | ||
| 135 | self.adrs_err(), | ||
| 136 | self.crc_err(), | ||
| 137 | self.length_err(), | ||
| 138 | self.abort_err(), | ||
| 139 | self.pkt_received(), | ||
| 140 | self.pkt_sent(), | ||
| 141 | self.rssi_sync().to_integer(), | ||
| 142 | self.rssi_avg().to_integer() | ||
| 143 | ) | ||
| 144 | } | ||
| 145 | } | ||
| 146 | |||
| 147 | impl core::fmt::Display for FskPacketStatus { | ||
| 148 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 149 | f.debug_struct("FskPacketStatus") | ||
| 150 | .field("status", &self.status()) | ||
| 151 | .field("preamble_error", &self.preamble_error()) | ||
| 152 | .field("sync_err", &self.sync_err()) | ||
| 153 | .field("adrs_err", &self.adrs_err()) | ||
| 154 | .field("crc_err", &self.crc_err()) | ||
| 155 | .field("length_err", &self.length_err()) | ||
| 156 | .field("abort_err", &self.abort_err()) | ||
| 157 | .field("pkt_received", &self.pkt_received()) | ||
| 158 | .field("pkt_sent", &self.pkt_sent()) | ||
| 159 | .field("rssi_sync", &self.rssi_sync().to_integer()) | ||
| 160 | .field("rssi_avg", &self.rssi_avg().to_integer()) | ||
| 161 | .finish() | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 165 | /// (G)FSK packet status. | ||
| 166 | /// | ||
| 167 | /// Returned by [`lora_packet_status`]. | ||
| 168 | /// | ||
| 169 | /// [`lora_packet_status`]: crate::subghz::SubGhz::lora_packet_status | ||
| 170 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 171 | pub struct LoRaPacketStatus { | ||
| 172 | buf: [u8; 4], | ||
| 173 | } | ||
| 174 | |||
| 175 | impl From<[u8; 4]> for LoRaPacketStatus { | ||
| 176 | fn from(buf: [u8; 4]) -> Self { | ||
| 177 | LoRaPacketStatus { buf } | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | impl LoRaPacketStatus { | ||
| 182 | /// Get the status. | ||
| 183 | /// | ||
| 184 | /// # Example | ||
| 185 | /// | ||
| 186 | /// ``` | ||
| 187 | /// use stm32wl_hal::subghz::{CmdStatus, LoRaPacketStatus, Status, StatusMode}; | ||
| 188 | /// | ||
| 189 | /// let example_data_from_radio: [u8; 4] = [0x54, 0, 0, 0]; | ||
| 190 | /// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio); | ||
| 191 | /// let status: Status = pkt_status.status(); | ||
| 192 | /// assert_eq!(status.mode(), Ok(StatusMode::Rx)); | ||
| 193 | /// assert_eq!(status.cmd(), Ok(CmdStatus::Avaliable)); | ||
| 194 | /// ``` | ||
| 195 | pub const fn status(&self) -> Status { | ||
| 196 | Status::from_raw(self.buf[0]) | ||
| 197 | } | ||
| 198 | |||
| 199 | /// Average RSSI level over the received packet. | ||
| 200 | /// | ||
| 201 | /// Units are in dBm. | ||
| 202 | /// | ||
| 203 | /// # Example | ||
| 204 | /// | ||
| 205 | /// ``` | ||
| 206 | /// use stm32wl_hal::{subghz::LoRaPacketStatus, Ratio}; | ||
| 207 | /// | ||
| 208 | /// let example_data_from_radio: [u8; 4] = [0, 80, 0, 0]; | ||
| 209 | /// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio); | ||
| 210 | /// assert_eq!(pkt_status.rssi_pkt().to_integer(), -40); | ||
| 211 | /// ``` | ||
| 212 | pub fn rssi_pkt(&self) -> Ratio<i16> { | ||
| 213 | Ratio::new_raw(i16::from(self.buf[1]), -2) | ||
| 214 | } | ||
| 215 | |||
| 216 | /// Estimation of SNR over the received packet. | ||
| 217 | /// | ||
| 218 | /// Units are in dB. | ||
| 219 | /// | ||
| 220 | /// # Example | ||
| 221 | /// | ||
| 222 | /// ``` | ||
| 223 | /// use stm32wl_hal::{subghz::LoRaPacketStatus, Ratio}; | ||
| 224 | /// | ||
| 225 | /// let example_data_from_radio: [u8; 4] = [0, 0, 40, 0]; | ||
| 226 | /// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio); | ||
| 227 | /// assert_eq!(pkt_status.snr_pkt().to_integer(), 10); | ||
| 228 | /// ``` | ||
| 229 | pub fn snr_pkt(&self) -> Ratio<i16> { | ||
| 230 | Ratio::new_raw(i16::from(self.buf[2]), 4) | ||
| 231 | } | ||
| 232 | |||
| 233 | /// Estimation of RSSI level of the LoRa signal after despreading. | ||
| 234 | /// | ||
| 235 | /// Units are in dBm. | ||
| 236 | /// | ||
| 237 | /// # Example | ||
| 238 | /// | ||
| 239 | /// ``` | ||
| 240 | /// use stm32wl_hal::{subghz::LoRaPacketStatus, Ratio}; | ||
| 241 | /// | ||
| 242 | /// let example_data_from_radio: [u8; 4] = [0, 0, 0, 80]; | ||
| 243 | /// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio); | ||
| 244 | /// assert_eq!(pkt_status.signal_rssi_pkt().to_integer(), -40); | ||
| 245 | /// ``` | ||
| 246 | pub fn signal_rssi_pkt(&self) -> Ratio<i16> { | ||
| 247 | Ratio::new_raw(i16::from(self.buf[3]), -2) | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | #[cfg(feature = "defmt")] | ||
| 252 | impl defmt::Format for LoRaPacketStatus { | ||
| 253 | fn format(&self, fmt: defmt::Formatter) { | ||
| 254 | defmt::write!( | ||
| 255 | fmt, | ||
| 256 | r#"LoRaPacketStatus {{ | ||
| 257 | status: {}, | ||
| 258 | rssi_pkt: {}, | ||
| 259 | snr_pkt: {}, | ||
| 260 | signal_rssi_pkt: {}, | ||
| 261 | }}"#, | ||
| 262 | self.status(), | ||
| 263 | self.rssi_pkt().to_integer(), | ||
| 264 | self.snr_pkt().to_integer(), | ||
| 265 | self.signal_rssi_pkt().to_integer(), | ||
| 266 | ) | ||
| 267 | } | ||
| 268 | } | ||
| 269 | |||
| 270 | impl core::fmt::Display for LoRaPacketStatus { | ||
| 271 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 272 | f.debug_struct("LoRaPacketStatus") | ||
| 273 | .field("status", &self.status()) | ||
| 274 | .field("rssi_pkt", &self.rssi_pkt().to_integer()) | ||
| 275 | .field("snr_pkt", &self.snr_pkt().to_integer()) | ||
| 276 | .field("signal_rssi_pkt", &self.signal_rssi_pkt().to_integer()) | ||
| 277 | .finish() | ||
| 278 | } | ||
| 279 | } | ||
diff --git a/embassy-stm32/src/subghz/packet_type.rs b/embassy-stm32/src/subghz/packet_type.rs new file mode 100644 index 000000000..d953a6b9e --- /dev/null +++ b/embassy-stm32/src/subghz/packet_type.rs | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | /// Packet type definition. | ||
| 2 | /// | ||
| 3 | /// Argument of [`set_packet_type`] | ||
| 4 | /// | ||
| 5 | /// [`set_packet_type`]: crate::subghz::SubGhz::set_packet_type | ||
| 6 | #[repr(u8)] | ||
| 7 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 8 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 9 | pub enum PacketType { | ||
| 10 | /// FSK (frequency shift keying) generic packet type. | ||
| 11 | Fsk = 0, | ||
| 12 | /// LoRa (long range) packet type. | ||
| 13 | LoRa = 1, | ||
| 14 | /// BPSK (binary phase shift keying) packet type. | ||
| 15 | Bpsk = 2, | ||
| 16 | /// MSK (minimum shift keying) generic packet type. | ||
| 17 | Msk = 3, | ||
| 18 | } | ||
| 19 | |||
| 20 | impl PacketType { | ||
| 21 | /// Create a new `PacketType` from bits. | ||
| 22 | /// | ||
| 23 | /// # Example | ||
| 24 | /// | ||
| 25 | /// ``` | ||
| 26 | /// use stm32wl_hal::subghz::PacketType; | ||
| 27 | /// | ||
| 28 | /// assert_eq!(PacketType::from_raw(0), Ok(PacketType::Fsk)); | ||
| 29 | /// assert_eq!(PacketType::from_raw(1), Ok(PacketType::LoRa)); | ||
| 30 | /// assert_eq!(PacketType::from_raw(2), Ok(PacketType::Bpsk)); | ||
| 31 | /// assert_eq!(PacketType::from_raw(3), Ok(PacketType::Msk)); | ||
| 32 | /// // Other values are reserved | ||
| 33 | /// assert_eq!(PacketType::from_raw(4), Err(4)); | ||
| 34 | /// ``` | ||
| 35 | pub const fn from_raw(bits: u8) -> Result<PacketType, u8> { | ||
| 36 | match bits { | ||
| 37 | 0 => Ok(PacketType::Fsk), | ||
| 38 | 1 => Ok(PacketType::LoRa), | ||
| 39 | 2 => Ok(PacketType::Bpsk), | ||
| 40 | 3 => Ok(PacketType::Msk), | ||
| 41 | _ => Err(bits), | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
diff --git a/embassy-stm32/src/subghz/pkt_ctrl.rs b/embassy-stm32/src/subghz/pkt_ctrl.rs new file mode 100644 index 000000000..b4775d574 --- /dev/null +++ b/embassy-stm32/src/subghz/pkt_ctrl.rs | |||
| @@ -0,0 +1,247 @@ | |||
| 1 | /// Generic packet infinite sequence selection. | ||
| 2 | /// | ||
| 3 | /// Argument of [`PktCtrl::set_inf_seq_sel`]. | ||
| 4 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 6 | pub enum InfSeqSel { | ||
| 7 | /// Preamble `0x5555`. | ||
| 8 | Five = 0b00, | ||
| 9 | /// Preamble `0x0000`. | ||
| 10 | Zero = 0b01, | ||
| 11 | /// Preamble `0xFFFF`. | ||
| 12 | One = 0b10, | ||
| 13 | /// PRBS9. | ||
| 14 | Prbs9 = 0b11, | ||
| 15 | } | ||
| 16 | |||
| 17 | impl Default for InfSeqSel { | ||
| 18 | fn default() -> Self { | ||
| 19 | InfSeqSel::Five | ||
| 20 | } | ||
| 21 | } | ||
| 22 | |||
| 23 | /// Generic packet control. | ||
| 24 | /// | ||
| 25 | /// Argument of [`set_pkt_ctrl`](crate::subghz::SubGhz::set_pkt_ctrl). | ||
| 26 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 27 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 28 | pub struct PktCtrl { | ||
| 29 | val: u8, | ||
| 30 | } | ||
| 31 | |||
| 32 | impl PktCtrl { | ||
| 33 | /// Reset value of the packet control register. | ||
| 34 | pub const RESET: PktCtrl = PktCtrl { val: 0x21 }; | ||
| 35 | |||
| 36 | /// Create a new [`PktCtrl`] structure from a raw value. | ||
| 37 | /// | ||
| 38 | /// Reserved bits will be masked. | ||
| 39 | pub const fn from_raw(raw: u8) -> Self { | ||
| 40 | Self { val: raw & 0x3F } | ||
| 41 | } | ||
| 42 | |||
| 43 | /// Get the raw value of the [`PktCtrl`] register. | ||
| 44 | pub const fn as_bits(&self) -> u8 { | ||
| 45 | self.val | ||
| 46 | } | ||
| 47 | |||
| 48 | /// Generic packet synchronization word detection enable. | ||
| 49 | /// | ||
| 50 | /// # Example | ||
| 51 | /// | ||
| 52 | /// ``` | ||
| 53 | /// use stm32wl_hal::subghz::PktCtrl; | ||
| 54 | /// | ||
| 55 | /// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_sync_det_en(true); | ||
| 56 | /// ``` | ||
| 57 | #[must_use = "set_sync_det_en returns a modified PktCtrl"] | ||
| 58 | pub const fn set_sync_det_en(mut self, en: bool) -> PktCtrl { | ||
| 59 | if en { | ||
| 60 | self.val |= 1 << 5; | ||
| 61 | } else { | ||
| 62 | self.val &= !(1 << 5); | ||
| 63 | } | ||
| 64 | self | ||
| 65 | } | ||
| 66 | |||
| 67 | /// Returns `true` if generic packet synchronization word detection is | ||
| 68 | /// enabled. | ||
| 69 | /// | ||
| 70 | /// # Example | ||
| 71 | /// | ||
| 72 | /// ``` | ||
| 73 | /// use stm32wl_hal::subghz::PktCtrl; | ||
| 74 | /// | ||
| 75 | /// let pc: PktCtrl = PktCtrl::RESET; | ||
| 76 | /// assert_eq!(pc.sync_det_en(), true); | ||
| 77 | /// let pc: PktCtrl = pc.set_sync_det_en(false); | ||
| 78 | /// assert_eq!(pc.sync_det_en(), false); | ||
| 79 | /// let pc: PktCtrl = pc.set_sync_det_en(true); | ||
| 80 | /// assert_eq!(pc.sync_det_en(), true); | ||
| 81 | /// ``` | ||
| 82 | pub const fn sync_det_en(&self) -> bool { | ||
| 83 | self.val & (1 << 5) != 0 | ||
| 84 | } | ||
| 85 | |||
| 86 | /// Generic packet continuous transmit enable. | ||
| 87 | /// | ||
| 88 | /// # Example | ||
| 89 | /// | ||
| 90 | /// ``` | ||
| 91 | /// use stm32wl_hal::subghz::PktCtrl; | ||
| 92 | /// | ||
| 93 | /// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_cont_tx_en(true); | ||
| 94 | /// ``` | ||
| 95 | #[must_use = "set_cont_tx_en returns a modified PktCtrl"] | ||
| 96 | pub const fn set_cont_tx_en(mut self, en: bool) -> PktCtrl { | ||
| 97 | if en { | ||
| 98 | self.val |= 1 << 4; | ||
| 99 | } else { | ||
| 100 | self.val &= !(1 << 4); | ||
| 101 | } | ||
| 102 | self | ||
| 103 | } | ||
| 104 | |||
| 105 | /// Returns `true` if generic packet continuous transmit is enabled. | ||
| 106 | /// | ||
| 107 | /// # Example | ||
| 108 | /// | ||
| 109 | /// ``` | ||
| 110 | /// use stm32wl_hal::subghz::PktCtrl; | ||
| 111 | /// | ||
| 112 | /// let pc: PktCtrl = PktCtrl::RESET; | ||
| 113 | /// assert_eq!(pc.cont_tx_en(), false); | ||
| 114 | /// let pc: PktCtrl = pc.set_cont_tx_en(true); | ||
| 115 | /// assert_eq!(pc.cont_tx_en(), true); | ||
| 116 | /// let pc: PktCtrl = pc.set_cont_tx_en(false); | ||
| 117 | /// assert_eq!(pc.cont_tx_en(), false); | ||
| 118 | /// ``` | ||
| 119 | pub const fn cont_tx_en(&self) -> bool { | ||
| 120 | self.val & (1 << 4) != 0 | ||
| 121 | } | ||
| 122 | |||
| 123 | /// Set the continuous sequence type. | ||
| 124 | #[must_use = "set_inf_seq_sel returns a modified PktCtrl"] | ||
| 125 | pub const fn set_inf_seq_sel(mut self, sel: InfSeqSel) -> PktCtrl { | ||
| 126 | self.val &= !(0b11 << 2); | ||
| 127 | self.val |= (sel as u8) << 2; | ||
| 128 | self | ||
| 129 | } | ||
| 130 | |||
| 131 | /// Get the continuous sequence type. | ||
| 132 | /// | ||
| 133 | /// # Example | ||
| 134 | /// | ||
| 135 | /// ``` | ||
| 136 | /// use stm32wl_hal::subghz::{InfSeqSel, PktCtrl}; | ||
| 137 | /// | ||
| 138 | /// let pc: PktCtrl = PktCtrl::RESET; | ||
| 139 | /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Five); | ||
| 140 | /// | ||
| 141 | /// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Zero); | ||
| 142 | /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Zero); | ||
| 143 | /// | ||
| 144 | /// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::One); | ||
| 145 | /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::One); | ||
| 146 | /// | ||
| 147 | /// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Prbs9); | ||
| 148 | /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Prbs9); | ||
| 149 | /// | ||
| 150 | /// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Five); | ||
| 151 | /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Five); | ||
| 152 | /// ``` | ||
| 153 | pub const fn inf_seq_sel(&self) -> InfSeqSel { | ||
| 154 | match (self.val >> 2) & 0b11 { | ||
| 155 | 0b00 => InfSeqSel::Five, | ||
| 156 | 0b01 => InfSeqSel::Zero, | ||
| 157 | 0b10 => InfSeqSel::One, | ||
| 158 | _ => InfSeqSel::Prbs9, | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | /// Enable infinute sequence generation. | ||
| 163 | /// | ||
| 164 | /// # Example | ||
| 165 | /// | ||
| 166 | /// ``` | ||
| 167 | /// use stm32wl_hal::subghz::PktCtrl; | ||
| 168 | /// | ||
| 169 | /// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_inf_seq_en(true); | ||
| 170 | /// ``` | ||
| 171 | #[must_use = "set_inf_seq_en returns a modified PktCtrl"] | ||
| 172 | pub const fn set_inf_seq_en(mut self, en: bool) -> PktCtrl { | ||
| 173 | if en { | ||
| 174 | self.val |= 1 << 1; | ||
| 175 | } else { | ||
| 176 | self.val &= !(1 << 1); | ||
| 177 | } | ||
| 178 | self | ||
| 179 | } | ||
| 180 | |||
| 181 | /// Returns `true` if infinute sequence generation is enabled. | ||
| 182 | /// | ||
| 183 | /// # Example | ||
| 184 | /// | ||
| 185 | /// ``` | ||
| 186 | /// use stm32wl_hal::subghz::PktCtrl; | ||
| 187 | /// | ||
| 188 | /// let pc: PktCtrl = PktCtrl::RESET; | ||
| 189 | /// assert_eq!(pc.inf_seq_en(), false); | ||
| 190 | /// let pc: PktCtrl = pc.set_inf_seq_en(true); | ||
| 191 | /// assert_eq!(pc.inf_seq_en(), true); | ||
| 192 | /// let pc: PktCtrl = pc.set_inf_seq_en(false); | ||
| 193 | /// assert_eq!(pc.inf_seq_en(), false); | ||
| 194 | /// ``` | ||
| 195 | pub const fn inf_seq_en(&self) -> bool { | ||
| 196 | self.val & (1 << 1) != 0 | ||
| 197 | } | ||
| 198 | |||
| 199 | /// Set the value of bit-8 (9th bit) for generic packet whitening. | ||
| 200 | /// | ||
| 201 | /// # Example | ||
| 202 | /// | ||
| 203 | /// ``` | ||
| 204 | /// use stm32wl_hal::subghz::PktCtrl; | ||
| 205 | /// | ||
| 206 | /// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_whitening_init(true); | ||
| 207 | /// ``` | ||
| 208 | #[must_use = "set_whitening_init returns a modified PktCtrl"] | ||
| 209 | pub const fn set_whitening_init(mut self, val: bool) -> PktCtrl { | ||
| 210 | if val { | ||
| 211 | self.val |= 1; | ||
| 212 | } else { | ||
| 213 | self.val &= !1; | ||
| 214 | } | ||
| 215 | self | ||
| 216 | } | ||
| 217 | |||
| 218 | /// Returns `true` if bit-8 of the generic packet whitening is set. | ||
| 219 | /// | ||
| 220 | /// # Example | ||
| 221 | /// | ||
| 222 | /// ``` | ||
| 223 | /// use stm32wl_hal::subghz::PktCtrl; | ||
| 224 | /// | ||
| 225 | /// let pc: PktCtrl = PktCtrl::RESET; | ||
| 226 | /// assert_eq!(pc.whitening_init(), true); | ||
| 227 | /// let pc: PktCtrl = pc.set_whitening_init(false); | ||
| 228 | /// assert_eq!(pc.whitening_init(), false); | ||
| 229 | /// let pc: PktCtrl = pc.set_whitening_init(true); | ||
| 230 | /// assert_eq!(pc.whitening_init(), true); | ||
| 231 | /// ``` | ||
| 232 | pub const fn whitening_init(&self) -> bool { | ||
| 233 | self.val & 0b1 != 0 | ||
| 234 | } | ||
| 235 | } | ||
| 236 | |||
| 237 | impl From<PktCtrl> for u8 { | ||
| 238 | fn from(pc: PktCtrl) -> Self { | ||
| 239 | pc.val | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | impl Default for PktCtrl { | ||
| 244 | fn default() -> Self { | ||
| 245 | Self::RESET | ||
| 246 | } | ||
| 247 | } | ||
diff --git a/embassy-stm32/src/subghz/pmode.rs b/embassy-stm32/src/subghz/pmode.rs new file mode 100644 index 000000000..990be2fc1 --- /dev/null +++ b/embassy-stm32/src/subghz/pmode.rs | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | /// RX gain power modes. | ||
| 2 | /// | ||
| 3 | /// Argument of [`set_rx_gain`]. | ||
| 4 | /// | ||
| 5 | /// [`set_rx_gain`]: crate::subghz::SubGhz::set_rx_gain | ||
| 6 | #[repr(u8)] | ||
| 7 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 8 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 9 | pub enum PMode { | ||
| 10 | /// Power saving mode. | ||
| 11 | /// | ||
| 12 | /// Reduces sensitivity. | ||
| 13 | #[allow(clippy::identity_op)] | ||
| 14 | PowerSaving = (0x25 << 2) | 0b00, | ||
| 15 | /// Boost mode level 1. | ||
| 16 | /// | ||
| 17 | /// Improves sensitivity at detriment of power consumption. | ||
| 18 | Boost1 = (0x25 << 2) | 0b01, | ||
| 19 | /// Boost mode level 2. | ||
| 20 | /// | ||
| 21 | /// Improves a set further sensitivity at detriment of power consumption. | ||
| 22 | Boost2 = (0x25 << 2) | 0b10, | ||
| 23 | /// Boost mode. | ||
| 24 | /// | ||
| 25 | /// Best receiver sensitivity. | ||
| 26 | Boost = (0x25 << 2) | 0b11, | ||
| 27 | } | ||
diff --git a/embassy-stm32/src/subghz/pwr_ctrl.rs b/embassy-stm32/src/subghz/pwr_ctrl.rs new file mode 100644 index 000000000..d0de06f1f --- /dev/null +++ b/embassy-stm32/src/subghz/pwr_ctrl.rs | |||
| @@ -0,0 +1,160 @@ | |||
| 1 | /// Power-supply current limit. | ||
| 2 | /// | ||
| 3 | /// Argument of [`PwrCtrl::set_current_lim`]. | ||
| 4 | #[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)] | ||
| 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 6 | #[repr(u8)] | ||
| 7 | pub enum CurrentLim { | ||
| 8 | /// 25 mA | ||
| 9 | Milli25 = 0x0, | ||
| 10 | /// 50 mA (default) | ||
| 11 | Milli50 = 0x1, | ||
| 12 | /// 100 mA | ||
| 13 | Milli100 = 0x2, | ||
| 14 | /// 200 mA | ||
| 15 | Milli200 = 0x3, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl CurrentLim { | ||
| 19 | /// Get the SMPS drive value as milliamps. | ||
| 20 | /// | ||
| 21 | /// # Example | ||
| 22 | /// | ||
| 23 | /// ``` | ||
| 24 | /// use stm32wl_hal::subghz::CurrentLim; | ||
| 25 | /// | ||
| 26 | /// assert_eq!(CurrentLim::Milli25.as_milliamps(), 25); | ||
| 27 | /// assert_eq!(CurrentLim::Milli50.as_milliamps(), 50); | ||
| 28 | /// assert_eq!(CurrentLim::Milli100.as_milliamps(), 100); | ||
| 29 | /// assert_eq!(CurrentLim::Milli200.as_milliamps(), 200); | ||
| 30 | /// ``` | ||
| 31 | pub const fn as_milliamps(&self) -> u8 { | ||
| 32 | match self { | ||
| 33 | CurrentLim::Milli25 => 25, | ||
| 34 | CurrentLim::Milli50 => 50, | ||
| 35 | CurrentLim::Milli100 => 100, | ||
| 36 | CurrentLim::Milli200 => 200, | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | impl Default for CurrentLim { | ||
| 42 | fn default() -> Self { | ||
| 43 | CurrentLim::Milli50 | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | /// Power control. | ||
| 48 | /// | ||
| 49 | /// Argument of [`set_bit_sync`](crate::subghz::SubGhz::set_bit_sync). | ||
| 50 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 51 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 52 | pub struct PwrCtrl { | ||
| 53 | val: u8, | ||
| 54 | } | ||
| 55 | |||
| 56 | impl PwrCtrl { | ||
| 57 | /// Power control register reset value. | ||
| 58 | pub const RESET: PwrCtrl = PwrCtrl { val: 0x50 }; | ||
| 59 | |||
| 60 | /// Create a new [`PwrCtrl`] structure from a raw value. | ||
| 61 | /// | ||
| 62 | /// Reserved bits will be masked. | ||
| 63 | pub const fn from_raw(raw: u8) -> Self { | ||
| 64 | Self { val: raw & 0x70 } | ||
| 65 | } | ||
| 66 | |||
| 67 | /// Get the raw value of the [`PwrCtrl`] register. | ||
| 68 | pub const fn as_bits(&self) -> u8 { | ||
| 69 | self.val | ||
| 70 | } | ||
| 71 | |||
| 72 | /// Set the current limiter enable. | ||
| 73 | /// | ||
| 74 | /// # Example | ||
| 75 | /// | ||
| 76 | /// ``` | ||
| 77 | /// use stm32wl_hal::subghz::PwrCtrl; | ||
| 78 | /// | ||
| 79 | /// const PWR_CTRL: PwrCtrl = PwrCtrl::RESET.set_current_lim_en(true); | ||
| 80 | /// # assert_eq!(u8::from(PWR_CTRL), 0x50u8); | ||
| 81 | /// ``` | ||
| 82 | #[must_use = "set_current_lim_en returns a modified PwrCtrl"] | ||
| 83 | pub const fn set_current_lim_en(mut self, en: bool) -> PwrCtrl { | ||
| 84 | if en { | ||
| 85 | self.val |= 1 << 6; | ||
| 86 | } else { | ||
| 87 | self.val &= !(1 << 6); | ||
| 88 | } | ||
| 89 | self | ||
| 90 | } | ||
| 91 | |||
| 92 | /// Returns `true` if current limiting is enabled | ||
| 93 | /// | ||
| 94 | /// # Example | ||
| 95 | /// | ||
| 96 | /// ``` | ||
| 97 | /// use stm32wl_hal::subghz::PwrCtrl; | ||
| 98 | /// | ||
| 99 | /// let pc: PwrCtrl = PwrCtrl::RESET; | ||
| 100 | /// assert_eq!(pc.current_limit_en(), true); | ||
| 101 | /// let pc: PwrCtrl = pc.set_current_lim_en(false); | ||
| 102 | /// assert_eq!(pc.current_limit_en(), false); | ||
| 103 | /// let pc: PwrCtrl = pc.set_current_lim_en(true); | ||
| 104 | /// assert_eq!(pc.current_limit_en(), true); | ||
| 105 | /// ``` | ||
| 106 | pub const fn current_limit_en(&self) -> bool { | ||
| 107 | self.val & (1 << 6) != 0 | ||
| 108 | } | ||
| 109 | |||
| 110 | /// Set the current limit. | ||
| 111 | #[must_use = "set_current_lim returns a modified PwrCtrl"] | ||
| 112 | pub const fn set_current_lim(mut self, lim: CurrentLim) -> PwrCtrl { | ||
| 113 | self.val &= !(0x30); | ||
| 114 | self.val |= (lim as u8) << 4; | ||
| 115 | self | ||
| 116 | } | ||
| 117 | |||
| 118 | /// Get the current limit. | ||
| 119 | /// | ||
| 120 | /// # Example | ||
| 121 | /// | ||
| 122 | /// ``` | ||
| 123 | /// use stm32wl_hal::subghz::{CurrentLim, PwrCtrl}; | ||
| 124 | /// | ||
| 125 | /// let pc: PwrCtrl = PwrCtrl::RESET; | ||
| 126 | /// assert_eq!(pc.current_lim(), CurrentLim::Milli50); | ||
| 127 | /// | ||
| 128 | /// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli25); | ||
| 129 | /// assert_eq!(pc.current_lim(), CurrentLim::Milli25); | ||
| 130 | /// | ||
| 131 | /// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli50); | ||
| 132 | /// assert_eq!(pc.current_lim(), CurrentLim::Milli50); | ||
| 133 | /// | ||
| 134 | /// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli100); | ||
| 135 | /// assert_eq!(pc.current_lim(), CurrentLim::Milli100); | ||
| 136 | /// | ||
| 137 | /// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli200); | ||
| 138 | /// assert_eq!(pc.current_lim(), CurrentLim::Milli200); | ||
| 139 | /// ``` | ||
| 140 | pub const fn current_lim(&self) -> CurrentLim { | ||
| 141 | match (self.val >> 4) & 0b11 { | ||
| 142 | 0x0 => CurrentLim::Milli25, | ||
| 143 | 0x1 => CurrentLim::Milli50, | ||
| 144 | 0x2 => CurrentLim::Milli100, | ||
| 145 | _ => CurrentLim::Milli200, | ||
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | impl From<PwrCtrl> for u8 { | ||
| 151 | fn from(bs: PwrCtrl) -> Self { | ||
| 152 | bs.val | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | impl Default for PwrCtrl { | ||
| 157 | fn default() -> Self { | ||
| 158 | Self::RESET | ||
| 159 | } | ||
| 160 | } | ||
diff --git a/embassy-stm32/src/subghz/reg_mode.rs b/embassy-stm32/src/subghz/reg_mode.rs new file mode 100644 index 000000000..b83226954 --- /dev/null +++ b/embassy-stm32/src/subghz/reg_mode.rs | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | /// Radio power supply selection. | ||
| 2 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] | ||
| 3 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 4 | #[repr(u8)] | ||
| 5 | pub enum RegMode { | ||
| 6 | /// Linear dropout regulator | ||
| 7 | Ldo = 0b0, | ||
| 8 | /// Switch mode power supply. | ||
| 9 | /// | ||
| 10 | /// Used in standby with HSE32, FS, RX, and TX modes. | ||
| 11 | Smps = 0b1, | ||
| 12 | } | ||
| 13 | |||
| 14 | impl Default for RegMode { | ||
| 15 | fn default() -> Self { | ||
| 16 | RegMode::Ldo | ||
| 17 | } | ||
| 18 | } | ||
diff --git a/embassy-stm32/src/subghz/rf_frequency.rs b/embassy-stm32/src/subghz/rf_frequency.rs new file mode 100644 index 000000000..7face3d0d --- /dev/null +++ b/embassy-stm32/src/subghz/rf_frequency.rs | |||
| @@ -0,0 +1,138 @@ | |||
| 1 | /// RF frequency structure. | ||
| 2 | /// | ||
| 3 | /// Argument of [`set_rf_frequency`]. | ||
| 4 | /// | ||
| 5 | /// [`set_rf_frequency`]: crate::subghz::SubGhz::set_rf_frequency | ||
| 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] | ||
| 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 8 | pub struct RfFreq { | ||
| 9 | buf: [u8; 5], | ||
| 10 | } | ||
| 11 | |||
| 12 | impl RfFreq { | ||
| 13 | /// 915MHz, often used in Australia and North America. | ||
| 14 | /// | ||
| 15 | /// # Example | ||
| 16 | /// | ||
| 17 | /// ``` | ||
| 18 | /// use stm32wl_hal::subghz::RfFreq; | ||
| 19 | /// | ||
| 20 | /// assert_eq!(RfFreq::F915.freq(), 915_000_000); | ||
| 21 | /// ``` | ||
| 22 | pub const F915: RfFreq = RfFreq::from_raw(0x39_30_00_00); | ||
| 23 | |||
| 24 | /// 868MHz, often used in Europe. | ||
| 25 | /// | ||
| 26 | /// # Example | ||
| 27 | /// | ||
| 28 | /// ``` | ||
| 29 | /// use stm32wl_hal::subghz::RfFreq; | ||
| 30 | /// | ||
| 31 | /// assert_eq!(RfFreq::F868.freq(), 868_000_000); | ||
| 32 | /// ``` | ||
| 33 | pub const F868: RfFreq = RfFreq::from_raw(0x36_40_00_00); | ||
| 34 | |||
| 35 | /// 433MHz, often used in Europe. | ||
| 36 | /// | ||
| 37 | /// # Example | ||
| 38 | /// | ||
| 39 | /// ``` | ||
| 40 | /// use stm32wl_hal::subghz::RfFreq; | ||
| 41 | /// | ||
| 42 | /// assert_eq!(RfFreq::F433.freq(), 433_000_000); | ||
| 43 | /// ``` | ||
| 44 | pub const F433: RfFreq = RfFreq::from_raw(0x1B_10_00_00); | ||
| 45 | |||
| 46 | /// Create a new `RfFreq` from a raw bit value. | ||
| 47 | /// | ||
| 48 | /// The equation used to get the PLL frequency from the raw bits is: | ||
| 49 | /// | ||
| 50 | /// RF<sub>PLL</sub> = 32e6 × bits / 2<sup>25</sup> | ||
| 51 | /// | ||
| 52 | /// # Example | ||
| 53 | /// | ||
| 54 | /// ``` | ||
| 55 | /// use stm32wl_hal::subghz::RfFreq; | ||
| 56 | /// | ||
| 57 | /// const FREQ: RfFreq = RfFreq::from_raw(0x39300000); | ||
| 58 | /// assert_eq!(FREQ, RfFreq::F915); | ||
| 59 | /// ``` | ||
| 60 | pub const fn from_raw(bits: u32) -> RfFreq { | ||
| 61 | RfFreq { | ||
| 62 | buf: [ | ||
| 63 | super::OpCode::SetRfFrequency as u8, | ||
| 64 | ((bits >> 24) & 0xFF) as u8, | ||
| 65 | ((bits >> 16) & 0xFF) as u8, | ||
| 66 | ((bits >> 8) & 0xFF) as u8, | ||
| 67 | (bits & 0xFF) as u8, | ||
| 68 | ], | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | /// Create a new `RfFreq` from a PLL frequency. | ||
| 73 | /// | ||
| 74 | /// The equation used to get the raw bits from the PLL frequency is: | ||
| 75 | /// | ||
| 76 | /// bits = RF<sub>PLL</sub> * 2<sup>25</sup> / 32e6 | ||
| 77 | /// | ||
| 78 | /// # Example | ||
| 79 | /// | ||
| 80 | /// ``` | ||
| 81 | /// use stm32wl_hal::subghz::RfFreq; | ||
| 82 | /// | ||
| 83 | /// const FREQ: RfFreq = RfFreq::from_frequency(915_000_000); | ||
| 84 | /// assert_eq!(FREQ, RfFreq::F915); | ||
| 85 | /// ``` | ||
| 86 | pub const fn from_frequency(freq: u32) -> RfFreq { | ||
| 87 | Self::from_raw((((freq as u64) * (1 << 25)) / 32_000_000) as u32) | ||
| 88 | } | ||
| 89 | |||
| 90 | // Get the frequency bit value. | ||
| 91 | const fn as_bits(&self) -> u32 { | ||
| 92 | ((self.buf[1] as u32) << 24) | ||
| 93 | | ((self.buf[2] as u32) << 16) | ||
| 94 | | ((self.buf[3] as u32) << 8) | ||
| 95 | | (self.buf[4] as u32) | ||
| 96 | } | ||
| 97 | |||
| 98 | /// Get the actual frequency. | ||
| 99 | /// | ||
| 100 | /// # Example | ||
| 101 | /// | ||
| 102 | /// ``` | ||
| 103 | /// use stm32wl_hal::subghz::RfFreq; | ||
| 104 | /// | ||
| 105 | /// assert_eq!(RfFreq::from_raw(0x39300000).freq(), 915_000_000); | ||
| 106 | /// ``` | ||
| 107 | pub fn freq(&self) -> u32 { | ||
| 108 | (32_000_000 * (self.as_bits() as u64) / (1 << 25)) as u32 | ||
| 109 | } | ||
| 110 | |||
| 111 | /// Extracts a slice containing the packet. | ||
| 112 | /// | ||
| 113 | /// # Example | ||
| 114 | /// | ||
| 115 | /// ``` | ||
| 116 | /// use stm32wl_hal::subghz::RfFreq; | ||
| 117 | /// | ||
| 118 | /// assert_eq!(RfFreq::F915.as_slice(), &[0x86, 0x39, 0x30, 0x00, 0x00]); | ||
| 119 | /// ``` | ||
| 120 | pub const fn as_slice(&self) -> &[u8] { | ||
| 121 | &self.buf | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | #[cfg(test)] | ||
| 126 | mod test { | ||
| 127 | use super::RfFreq; | ||
| 128 | |||
| 129 | #[test] | ||
| 130 | fn max() { | ||
| 131 | assert_eq!(RfFreq::from_raw(u32::MAX).freq(), 4_095_999_999); | ||
| 132 | } | ||
| 133 | |||
| 134 | #[test] | ||
| 135 | fn min() { | ||
| 136 | assert_eq!(RfFreq::from_raw(u32::MIN).freq(), 0); | ||
| 137 | } | ||
| 138 | } | ||
diff --git a/embassy-stm32/src/subghz/rx_timeout_stop.rs b/embassy-stm32/src/subghz/rx_timeout_stop.rs new file mode 100644 index 000000000..f057d3573 --- /dev/null +++ b/embassy-stm32/src/subghz/rx_timeout_stop.rs | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | /// Receiver event which stops the RX timeout timer. | ||
| 2 | /// | ||
| 3 | /// Used by [`set_rx_timeout_stop`]. | ||
| 4 | /// | ||
| 5 | /// [`set_rx_timeout_stop`]: crate::subghz::SubGhz::set_rx_timeout_stop | ||
| 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 8 | #[repr(u8)] | ||
| 9 | pub enum RxTimeoutStop { | ||
| 10 | /// Receive timeout stopped on synchronization word detection in generic | ||
| 11 | /// packet mode or header detection in LoRa packet mode. | ||
| 12 | Sync = 0b0, | ||
| 13 | /// Receive timeout stopped on preamble detection. | ||
| 14 | Preamble = 0b1, | ||
| 15 | } | ||
| 16 | |||
| 17 | impl From<RxTimeoutStop> for u8 { | ||
| 18 | fn from(rx_ts: RxTimeoutStop) -> Self { | ||
| 19 | rx_ts as u8 | ||
| 20 | } | ||
| 21 | } | ||
diff --git a/embassy-stm32/src/subghz/sleep_cfg.rs b/embassy-stm32/src/subghz/sleep_cfg.rs new file mode 100644 index 000000000..1aaa2c943 --- /dev/null +++ b/embassy-stm32/src/subghz/sleep_cfg.rs | |||
| @@ -0,0 +1,109 @@ | |||
| 1 | /// Startup configurations when exiting sleep mode. | ||
| 2 | /// | ||
| 3 | /// Argument of [`SleepCfg::set_startup`]. | ||
| 4 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 6 | #[repr(u8)] | ||
| 7 | pub enum Startup { | ||
| 8 | /// Cold startup when exiting Sleep mode, configuration registers reset. | ||
| 9 | Cold = 0, | ||
| 10 | /// Warm startup when exiting Sleep mode, | ||
| 11 | /// configuration registers kept in retention. | ||
| 12 | /// | ||
| 13 | /// **Note:** Only the configuration of the activated modem, | ||
| 14 | /// before going to sleep mode, is retained. | ||
| 15 | /// The configuration of the other modes is lost and must be re-configured | ||
| 16 | /// when exiting sleep mode. | ||
| 17 | Warm = 1, | ||
| 18 | } | ||
| 19 | |||
| 20 | impl Default for Startup { | ||
| 21 | fn default() -> Self { | ||
| 22 | Startup::Warm | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | /// Sleep configuration. | ||
| 27 | /// | ||
| 28 | /// Argument of [`set_sleep`]. | ||
| 29 | /// | ||
| 30 | /// [`set_sleep`]: crate::subghz::SubGhz::set_sleep | ||
| 31 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 32 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 33 | pub struct SleepCfg(u8); | ||
| 34 | |||
| 35 | impl SleepCfg { | ||
| 36 | /// Create a new `SleepCfg` structure. | ||
| 37 | /// | ||
| 38 | /// This is the same as `default`, but in a `const` function. | ||
| 39 | /// | ||
| 40 | /// The defaults are a warm startup, with RTC wakeup enabled. | ||
| 41 | /// | ||
| 42 | /// # Example | ||
| 43 | /// | ||
| 44 | /// ``` | ||
| 45 | /// use stm32wl_hal::subghz::SleepCfg; | ||
| 46 | /// | ||
| 47 | /// const SLEEP_CFG: SleepCfg = SleepCfg::new(); | ||
| 48 | /// assert_eq!(SLEEP_CFG, SleepCfg::default()); | ||
| 49 | /// # assert_eq!(u8::from(SLEEP_CFG), 0b101); | ||
| 50 | /// ``` | ||
| 51 | pub const fn new() -> SleepCfg { | ||
| 52 | SleepCfg(0) | ||
| 53 | .set_startup(Startup::Warm) | ||
| 54 | .set_rtc_wakeup_en(true) | ||
| 55 | } | ||
| 56 | |||
| 57 | /// Set the startup mode. | ||
| 58 | /// | ||
| 59 | /// # Example | ||
| 60 | /// | ||
| 61 | /// ``` | ||
| 62 | /// use stm32wl_hal::subghz::{SleepCfg, Startup}; | ||
| 63 | /// | ||
| 64 | /// const SLEEP_CFG: SleepCfg = SleepCfg::new().set_startup(Startup::Cold); | ||
| 65 | /// # assert_eq!(u8::from(SLEEP_CFG), 0b001); | ||
| 66 | /// # assert_eq!(u8::from(SLEEP_CFG.set_startup(Startup::Warm)), 0b101); | ||
| 67 | /// ``` | ||
| 68 | pub const fn set_startup(mut self, startup: Startup) -> SleepCfg { | ||
| 69 | if startup as u8 == 1 { | ||
| 70 | self.0 |= 1 << 2 | ||
| 71 | } else { | ||
| 72 | self.0 &= !(1 << 2) | ||
| 73 | } | ||
| 74 | self | ||
| 75 | } | ||
| 76 | |||
| 77 | /// Set the RTC wakeup enable. | ||
| 78 | /// | ||
| 79 | /// # Example | ||
| 80 | /// | ||
| 81 | /// ``` | ||
| 82 | /// use stm32wl_hal::subghz::SleepCfg; | ||
| 83 | /// | ||
| 84 | /// const SLEEP_CFG: SleepCfg = SleepCfg::new().set_rtc_wakeup_en(false); | ||
| 85 | /// # assert_eq!(u8::from(SLEEP_CFG), 0b100); | ||
| 86 | /// # assert_eq!(u8::from(SLEEP_CFG.set_rtc_wakeup_en(true)), 0b101); | ||
| 87 | /// ``` | ||
| 88 | #[must_use = "set_rtc_wakeup_en returns a modified SleepCfg"] | ||
| 89 | pub const fn set_rtc_wakeup_en(mut self, en: bool) -> SleepCfg { | ||
| 90 | if en { | ||
| 91 | self.0 |= 0b1 | ||
| 92 | } else { | ||
| 93 | self.0 &= !0b1 | ||
| 94 | } | ||
| 95 | self | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | impl From<SleepCfg> for u8 { | ||
| 100 | fn from(sc: SleepCfg) -> Self { | ||
| 101 | sc.0 | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | impl Default for SleepCfg { | ||
| 106 | fn default() -> Self { | ||
| 107 | Self::new() | ||
| 108 | } | ||
| 109 | } | ||
diff --git a/embassy-stm32/src/subghz/smps.rs b/embassy-stm32/src/subghz/smps.rs new file mode 100644 index 000000000..59947f2a3 --- /dev/null +++ b/embassy-stm32/src/subghz/smps.rs | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | /// SMPS maximum drive capability. | ||
| 2 | /// | ||
| 3 | /// Argument of [`set_smps_drv`](crate::subghz::SubGhz::set_smps_drv). | ||
| 4 | #[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)] | ||
| 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 6 | #[repr(u8)] | ||
| 7 | pub enum SmpsDrv { | ||
| 8 | /// 20 mA | ||
| 9 | Milli20 = 0x0, | ||
| 10 | /// 40 mA | ||
| 11 | Milli40 = 0x1, | ||
| 12 | /// 60 mA | ||
| 13 | Milli60 = 0x2, | ||
| 14 | /// 100 mA (default) | ||
| 15 | Milli100 = 0x3, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl SmpsDrv { | ||
| 19 | /// Get the SMPS drive value as milliamps. | ||
| 20 | /// | ||
| 21 | /// # Example | ||
| 22 | /// | ||
| 23 | /// ``` | ||
| 24 | /// use stm32wl_hal::subghz::SmpsDrv; | ||
| 25 | /// | ||
| 26 | /// assert_eq!(SmpsDrv::Milli20.as_milliamps(), 20); | ||
| 27 | /// assert_eq!(SmpsDrv::Milli40.as_milliamps(), 40); | ||
| 28 | /// assert_eq!(SmpsDrv::Milli60.as_milliamps(), 60); | ||
| 29 | /// assert_eq!(SmpsDrv::Milli100.as_milliamps(), 100); | ||
| 30 | /// ``` | ||
| 31 | pub const fn as_milliamps(&self) -> u8 { | ||
| 32 | match self { | ||
| 33 | SmpsDrv::Milli20 => 20, | ||
| 34 | SmpsDrv::Milli40 => 40, | ||
| 35 | SmpsDrv::Milli60 => 60, | ||
| 36 | SmpsDrv::Milli100 => 100, | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | impl Default for SmpsDrv { | ||
| 42 | fn default() -> Self { | ||
| 43 | SmpsDrv::Milli100 | ||
| 44 | } | ||
| 45 | } | ||
diff --git a/embassy-stm32/src/subghz/standby_clk.rs b/embassy-stm32/src/subghz/standby_clk.rs new file mode 100644 index 000000000..2e6a03306 --- /dev/null +++ b/embassy-stm32/src/subghz/standby_clk.rs | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | /// Clock in standby mode. | ||
| 2 | /// | ||
| 3 | /// Used by [`set_standby`]. | ||
| 4 | /// | ||
| 5 | /// [`set_standby`]: crate::subghz::SubGhz::set_standby | ||
| 6 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] | ||
| 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 8 | #[repr(u8)] | ||
| 9 | pub enum StandbyClk { | ||
| 10 | /// RC 13 MHz used in standby mode. | ||
| 11 | Rc = 0b0, | ||
| 12 | /// HSE32 used in standby mode. | ||
| 13 | Hse = 0b1, | ||
| 14 | } | ||
| 15 | |||
| 16 | impl From<StandbyClk> for u8 { | ||
| 17 | fn from(sc: StandbyClk) -> Self { | ||
| 18 | sc as u8 | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/embassy-stm32/src/subghz/stats.rs b/embassy-stm32/src/subghz/stats.rs new file mode 100644 index 000000000..52a2252f8 --- /dev/null +++ b/embassy-stm32/src/subghz/stats.rs | |||
| @@ -0,0 +1,184 @@ | |||
| 1 | use crate::subghz::status::Status; | ||
| 2 | |||
| 3 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 4 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 5 | pub struct LoRaStats; | ||
| 6 | |||
| 7 | impl LoRaStats { | ||
| 8 | pub const fn new() -> Self { | ||
| 9 | Self {} | ||
| 10 | } | ||
| 11 | } | ||
| 12 | |||
| 13 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 14 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 15 | pub struct FskStats; | ||
| 16 | |||
| 17 | impl FskStats { | ||
| 18 | pub const fn new() -> Self { | ||
| 19 | Self {} | ||
| 20 | } | ||
| 21 | } | ||
| 22 | |||
| 23 | /// Packet statistics. | ||
| 24 | /// | ||
| 25 | /// Returned by [`fsk_stats`] and [`lora_stats`]. | ||
| 26 | /// | ||
| 27 | /// [`fsk_stats`]: crate::subghz::SubGhz::fsk_stats | ||
| 28 | /// [`lora_stats`]: crate::subghz::SubGhz::lora_stats | ||
| 29 | #[derive(Debug, Eq, PartialEq, Clone, Copy)] | ||
| 30 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 31 | pub struct Stats<ModType> { | ||
| 32 | status: Status, | ||
| 33 | pkt_rx: u16, | ||
| 34 | pkt_crc: u16, | ||
| 35 | pkt_len_or_hdr_err: u16, | ||
| 36 | ty: ModType, | ||
| 37 | } | ||
| 38 | |||
| 39 | impl<ModType> Stats<ModType> { | ||
| 40 | const fn from_buf(buf: [u8; 7], ty: ModType) -> Stats<ModType> { | ||
| 41 | Stats { | ||
| 42 | status: Status::from_raw(buf[0]), | ||
| 43 | pkt_rx: u16::from_be_bytes([buf[1], buf[2]]), | ||
| 44 | pkt_crc: u16::from_be_bytes([buf[3], buf[4]]), | ||
| 45 | pkt_len_or_hdr_err: u16::from_be_bytes([buf[5], buf[6]]), | ||
| 46 | ty, | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | /// Get the radio status returned with the packet statistics. | ||
| 51 | /// | ||
| 52 | /// # Example | ||
| 53 | /// | ||
| 54 | /// ``` | ||
| 55 | /// use stm32wl_hal::subghz::{CmdStatus, FskStats, Stats, StatusMode}; | ||
| 56 | /// | ||
| 57 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0]; | ||
| 58 | /// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio); | ||
| 59 | /// assert_eq!(stats.status().mode(), Ok(StatusMode::Rx)); | ||
| 60 | /// assert_eq!(stats.status().cmd(), Ok(CmdStatus::Avaliable)); | ||
| 61 | /// ``` | ||
| 62 | pub const fn status(&self) -> Status { | ||
| 63 | self.status | ||
| 64 | } | ||
| 65 | |||
| 66 | /// Number of packets received. | ||
| 67 | /// | ||
| 68 | /// # Example | ||
| 69 | /// | ||
| 70 | /// ``` | ||
| 71 | /// use stm32wl_hal::subghz::{FskStats, Stats}; | ||
| 72 | /// | ||
| 73 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 3, 0, 0, 0, 0]; | ||
| 74 | /// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio); | ||
| 75 | /// assert_eq!(stats.pkt_rx(), 3); | ||
| 76 | /// ``` | ||
| 77 | pub const fn pkt_rx(&self) -> u16 { | ||
| 78 | self.pkt_rx | ||
| 79 | } | ||
| 80 | |||
| 81 | /// Number of packets received with a payload CRC error | ||
| 82 | /// | ||
| 83 | /// # Example | ||
| 84 | /// | ||
| 85 | /// ``` | ||
| 86 | /// use stm32wl_hal::subghz::{LoRaStats, Stats}; | ||
| 87 | /// | ||
| 88 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 1, 0, 0]; | ||
| 89 | /// let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio); | ||
| 90 | /// assert_eq!(stats.pkt_crc(), 1); | ||
| 91 | /// ``` | ||
| 92 | pub const fn pkt_crc(&self) -> u16 { | ||
| 93 | self.pkt_crc | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | impl Stats<FskStats> { | ||
| 98 | /// Create a new FSK packet statistics structure from a raw buffer. | ||
| 99 | /// | ||
| 100 | /// # Example | ||
| 101 | /// | ||
| 102 | /// ``` | ||
| 103 | /// use stm32wl_hal::subghz::{FskStats, Stats}; | ||
| 104 | /// | ||
| 105 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0]; | ||
| 106 | /// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio); | ||
| 107 | /// ``` | ||
| 108 | pub const fn from_raw_fsk(buf: [u8; 7]) -> Stats<FskStats> { | ||
| 109 | Self::from_buf(buf, FskStats::new()) | ||
| 110 | } | ||
| 111 | |||
| 112 | /// Number of packets received with a payload length error. | ||
| 113 | /// | ||
| 114 | /// # Example | ||
| 115 | /// | ||
| 116 | /// ``` | ||
| 117 | /// use stm32wl_hal::subghz::{FskStats, Stats}; | ||
| 118 | /// | ||
| 119 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 1]; | ||
| 120 | /// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio); | ||
| 121 | /// assert_eq!(stats.pkt_len_err(), 1); | ||
| 122 | /// ``` | ||
| 123 | pub const fn pkt_len_err(&self) -> u16 { | ||
| 124 | self.pkt_len_or_hdr_err | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | impl Stats<LoRaStats> { | ||
| 129 | /// Create a new LoRa packet statistics structure from a raw buffer. | ||
| 130 | /// | ||
| 131 | /// # Example | ||
| 132 | /// | ||
| 133 | /// ``` | ||
| 134 | /// use stm32wl_hal::subghz::{LoRaStats, Stats}; | ||
| 135 | /// | ||
| 136 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0]; | ||
| 137 | /// let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio); | ||
| 138 | /// ``` | ||
| 139 | pub const fn from_raw_lora(buf: [u8; 7]) -> Stats<LoRaStats> { | ||
| 140 | Self::from_buf(buf, LoRaStats::new()) | ||
| 141 | } | ||
| 142 | |||
| 143 | /// Number of packets received with a header CRC error. | ||
| 144 | /// | ||
| 145 | /// # Example | ||
| 146 | /// | ||
| 147 | /// ``` | ||
| 148 | /// use stm32wl_hal::subghz::{LoRaStats, Stats}; | ||
| 149 | /// | ||
| 150 | /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 1]; | ||
| 151 | /// let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio); | ||
| 152 | /// assert_eq!(stats.pkt_hdr_err(), 1); | ||
| 153 | /// ``` | ||
| 154 | pub const fn pkt_hdr_err(&self) -> u16 { | ||
| 155 | self.pkt_len_or_hdr_err | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | impl core::fmt::Display for Stats<FskStats> { | ||
| 160 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 161 | f.debug_struct("Stats") | ||
| 162 | .field("status", &self.status()) | ||
| 163 | .field("pkt_rx", &self.pkt_rx()) | ||
| 164 | .field("pkt_crc", &self.pkt_crc()) | ||
| 165 | .field("pkt_len_err", &self.pkt_len_err()) | ||
| 166 | .finish() | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | #[cfg(test)] | ||
| 171 | mod test { | ||
| 172 | use crate::subghz::{CmdStatus, LoRaStats, Stats, StatusMode}; | ||
| 173 | |||
| 174 | #[test] | ||
| 175 | fn mixed() { | ||
| 176 | let example_data_from_radio: [u8; 7] = [0x54, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]; | ||
| 177 | let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio); | ||
| 178 | assert_eq!(stats.status().mode(), Ok(StatusMode::Rx)); | ||
| 179 | assert_eq!(stats.status().cmd(), Ok(CmdStatus::Avaliable)); | ||
| 180 | assert_eq!(stats.pkt_rx(), 0x0102); | ||
| 181 | assert_eq!(stats.pkt_crc(), 0x0304); | ||
| 182 | assert_eq!(stats.pkt_hdr_err(), 0x0506); | ||
| 183 | } | ||
| 184 | } | ||
diff --git a/embassy-stm32/src/subghz/status.rs b/embassy-stm32/src/subghz/status.rs new file mode 100644 index 000000000..0b8e6da73 --- /dev/null +++ b/embassy-stm32/src/subghz/status.rs | |||
| @@ -0,0 +1,202 @@ | |||
| 1 | /// sub-GHz radio operating mode. | ||
| 2 | /// | ||
| 3 | /// See `Get_Status` under section 5.8.5 "Communcation status information commands" | ||
| 4 | /// in the reference manual. | ||
| 5 | /// | ||
| 6 | /// This is returned by [`Status::mode`]. | ||
| 7 | #[repr(u8)] | ||
| 8 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 9 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 10 | pub enum StatusMode { | ||
| 11 | /// Standby mode with RC 13MHz. | ||
| 12 | StandbyRc = 0x2, | ||
| 13 | /// Standby mode with HSE32. | ||
| 14 | StandbyHse = 0x3, | ||
| 15 | /// Frequency Synthesis mode. | ||
| 16 | Fs = 0x4, | ||
| 17 | /// Receive mode. | ||
| 18 | Rx = 0x5, | ||
| 19 | /// Transmit mode. | ||
| 20 | Tx = 0x6, | ||
| 21 | } | ||
| 22 | |||
| 23 | impl StatusMode { | ||
| 24 | /// Create a new `StatusMode` from bits. | ||
| 25 | /// | ||
| 26 | /// # Example | ||
| 27 | /// | ||
| 28 | /// ``` | ||
| 29 | /// use stm32wl_hal::subghz::StatusMode; | ||
| 30 | /// | ||
| 31 | /// assert_eq!(StatusMode::from_raw(0x2), Ok(StatusMode::StandbyRc)); | ||
| 32 | /// assert_eq!(StatusMode::from_raw(0x3), Ok(StatusMode::StandbyHse)); | ||
| 33 | /// assert_eq!(StatusMode::from_raw(0x4), Ok(StatusMode::Fs)); | ||
| 34 | /// assert_eq!(StatusMode::from_raw(0x5), Ok(StatusMode::Rx)); | ||
| 35 | /// assert_eq!(StatusMode::from_raw(0x6), Ok(StatusMode::Tx)); | ||
| 36 | /// // Other values are reserved | ||
| 37 | /// assert_eq!(StatusMode::from_raw(0), Err(0)); | ||
| 38 | /// ``` | ||
| 39 | pub const fn from_raw(bits: u8) -> Result<Self, u8> { | ||
| 40 | match bits { | ||
| 41 | 0x2 => Ok(StatusMode::StandbyRc), | ||
| 42 | 0x3 => Ok(StatusMode::StandbyHse), | ||
| 43 | 0x4 => Ok(StatusMode::Fs), | ||
| 44 | 0x5 => Ok(StatusMode::Rx), | ||
| 45 | 0x6 => Ok(StatusMode::Tx), | ||
| 46 | _ => Err(bits), | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | /// Command status. | ||
| 52 | /// | ||
| 53 | /// See `Get_Status` under section 5.8.5 "Communcation status information commands" | ||
| 54 | /// in the reference manual. | ||
| 55 | /// | ||
| 56 | /// This is returned by [`Status::cmd`]. | ||
| 57 | #[repr(u8)] | ||
| 58 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 59 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 60 | pub enum CmdStatus { | ||
| 61 | /// Data available to host. | ||
| 62 | /// | ||
| 63 | /// Packet received successfully and data can be retrieved. | ||
| 64 | Avaliable = 0x2, | ||
| 65 | /// Command time out. | ||
| 66 | /// | ||
| 67 | /// Command took too long to complete triggering a sub-GHz radio watchdog | ||
| 68 | /// timeout. | ||
| 69 | Timeout = 0x3, | ||
| 70 | /// Command processing error. | ||
| 71 | /// | ||
| 72 | /// Invalid opcode or incorrect number of parameters. | ||
| 73 | ProcessingError = 0x4, | ||
| 74 | /// Command execution failure. | ||
| 75 | /// | ||
| 76 | /// Command successfully received but cannot be executed at this time, | ||
| 77 | /// requested operating mode cannot be entered or requested data cannot be | ||
| 78 | /// sent. | ||
| 79 | ExecutionFailure = 0x5, | ||
| 80 | /// Transmit command completed. | ||
| 81 | /// | ||
| 82 | /// Current packet transmission completed. | ||
| 83 | Complete = 0x6, | ||
| 84 | } | ||
| 85 | |||
| 86 | impl CmdStatus { | ||
| 87 | /// Create a new `CmdStatus` from bits. | ||
| 88 | /// | ||
| 89 | /// # Example | ||
| 90 | /// | ||
| 91 | /// ``` | ||
| 92 | /// use stm32wl_hal::subghz::CmdStatus; | ||
| 93 | /// | ||
| 94 | /// assert_eq!(CmdStatus::from_raw(0x2), Ok(CmdStatus::Avaliable)); | ||
| 95 | /// assert_eq!(CmdStatus::from_raw(0x3), Ok(CmdStatus::Timeout)); | ||
| 96 | /// assert_eq!(CmdStatus::from_raw(0x4), Ok(CmdStatus::ProcessingError)); | ||
| 97 | /// assert_eq!(CmdStatus::from_raw(0x5), Ok(CmdStatus::ExecutionFailure)); | ||
| 98 | /// assert_eq!(CmdStatus::from_raw(0x6), Ok(CmdStatus::Complete)); | ||
| 99 | /// // Other values are reserved | ||
| 100 | /// assert_eq!(CmdStatus::from_raw(0), Err(0)); | ||
| 101 | /// ``` | ||
| 102 | pub const fn from_raw(bits: u8) -> Result<Self, u8> { | ||
| 103 | match bits { | ||
| 104 | 0x2 => Ok(CmdStatus::Avaliable), | ||
| 105 | 0x3 => Ok(CmdStatus::Timeout), | ||
| 106 | 0x4 => Ok(CmdStatus::ProcessingError), | ||
| 107 | 0x5 => Ok(CmdStatus::ExecutionFailure), | ||
| 108 | 0x6 => Ok(CmdStatus::Complete), | ||
| 109 | _ => Err(bits), | ||
| 110 | } | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | /// Radio status. | ||
| 115 | /// | ||
| 116 | /// This is returned by [`status`]. | ||
| 117 | /// | ||
| 118 | /// [`status`]: crate::subghz::SubGhz::status | ||
| 119 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 120 | pub struct Status(u8); | ||
| 121 | |||
| 122 | impl From<u8> for Status { | ||
| 123 | fn from(x: u8) -> Self { | ||
| 124 | Status(x) | ||
| 125 | } | ||
| 126 | } | ||
| 127 | impl From<Status> for u8 { | ||
| 128 | fn from(x: Status) -> Self { | ||
| 129 | x.0 | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | impl Status { | ||
| 134 | /// Create a new `Status` from a raw `u8` value. | ||
| 135 | /// | ||
| 136 | /// This is the same as `Status::from(u8)`, but in a `const` function. | ||
| 137 | /// | ||
| 138 | /// # Example | ||
| 139 | /// | ||
| 140 | /// ``` | ||
| 141 | /// use stm32wl_hal::subghz::{CmdStatus, Status, StatusMode}; | ||
| 142 | /// | ||
| 143 | /// const STATUS: Status = Status::from_raw(0x54_u8); | ||
| 144 | /// assert_eq!(STATUS.mode(), Ok(StatusMode::Rx)); | ||
| 145 | /// assert_eq!(STATUS.cmd(), Ok(CmdStatus::Avaliable)); | ||
| 146 | /// ``` | ||
| 147 | pub const fn from_raw(value: u8) -> Status { | ||
| 148 | Status(value) | ||
| 149 | } | ||
| 150 | |||
| 151 | /// sub-GHz radio operating mode. | ||
| 152 | /// | ||
| 153 | /// # Example | ||
| 154 | /// | ||
| 155 | /// ``` | ||
| 156 | /// use stm32wl_hal::subghz::{Status, StatusMode}; | ||
| 157 | /// | ||
| 158 | /// let status: Status = 0xACu8.into(); | ||
| 159 | /// assert_eq!(status.mode(), Ok(StatusMode::StandbyRc)); | ||
| 160 | /// ``` | ||
| 161 | pub const fn mode(&self) -> Result<StatusMode, u8> { | ||
| 162 | StatusMode::from_raw((self.0 >> 4) & 0b111) | ||
| 163 | } | ||
| 164 | |||
| 165 | /// Command status. | ||
| 166 | /// | ||
| 167 | /// For some reason `Err(1)` is a pretty common return value for this, | ||
| 168 | /// despite being a reserved value. | ||
| 169 | /// | ||
| 170 | /// # Example | ||
| 171 | /// | ||
| 172 | /// ``` | ||
| 173 | /// use stm32wl_hal::subghz::{CmdStatus, Status}; | ||
| 174 | /// | ||
| 175 | /// let status: Status = 0xACu8.into(); | ||
| 176 | /// assert_eq!(status.cmd(), Ok(CmdStatus::Complete)); | ||
| 177 | /// ``` | ||
| 178 | pub const fn cmd(&self) -> Result<CmdStatus, u8> { | ||
| 179 | CmdStatus::from_raw((self.0 >> 1) & 0b111) | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | impl core::fmt::Display for Status { | ||
| 184 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 185 | f.debug_struct("Status") | ||
| 186 | .field("mode", &self.mode()) | ||
| 187 | .field("cmd", &self.cmd()) | ||
| 188 | .finish() | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 192 | #[cfg(feature = "defmt")] | ||
| 193 | impl defmt::Format for Status { | ||
| 194 | fn format(&self, fmt: defmt::Formatter) { | ||
| 195 | defmt::write!( | ||
| 196 | fmt, | ||
| 197 | "Status {{ mode: {}, cmd: {} }}", | ||
| 198 | self.mode(), | ||
| 199 | self.cmd() | ||
| 200 | ) | ||
| 201 | } | ||
| 202 | } | ||
diff --git a/embassy-stm32/src/subghz/tcxo_mode.rs b/embassy-stm32/src/subghz/tcxo_mode.rs new file mode 100644 index 000000000..a80c493f5 --- /dev/null +++ b/embassy-stm32/src/subghz/tcxo_mode.rs | |||
| @@ -0,0 +1,170 @@ | |||
| 1 | use crate::subghz::timeout::Timeout; | ||
| 2 | |||
| 3 | /// TCXO trim. | ||
| 4 | /// | ||
| 5 | /// **Note:** To use V<sub>DDTCXO</sub>, the V<sub>DDRF</sub> supply must be at | ||
| 6 | /// least + 200 mV higher than the selected `TcxoTrim` voltage level. | ||
| 7 | /// | ||
| 8 | /// Used by [`TcxoMode`]. | ||
| 9 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] | ||
| 10 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 11 | #[repr(u8)] | ||
| 12 | pub enum TcxoTrim { | ||
| 13 | /// 1.6V | ||
| 14 | Volts1pt6 = 0x0, | ||
| 15 | /// 1.7V | ||
| 16 | Volts1pt7 = 0x1, | ||
| 17 | /// 1.8V | ||
| 18 | Volts1pt8 = 0x2, | ||
| 19 | /// 2.2V | ||
| 20 | Volts2pt2 = 0x3, | ||
| 21 | /// 2.4V | ||
| 22 | Volts2pt4 = 0x4, | ||
| 23 | /// 2.7V | ||
| 24 | Volts2pt7 = 0x5, | ||
| 25 | /// 3.0V | ||
| 26 | Volts3pt0 = 0x6, | ||
| 27 | /// 3.3V | ||
| 28 | Volts3pt3 = 0x7, | ||
| 29 | } | ||
| 30 | |||
| 31 | impl core::fmt::Display for TcxoTrim { | ||
| 32 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 33 | match self { | ||
| 34 | TcxoTrim::Volts1pt6 => write!(f, "1.6V"), | ||
| 35 | TcxoTrim::Volts1pt7 => write!(f, "1.7V"), | ||
| 36 | TcxoTrim::Volts1pt8 => write!(f, "1.8V"), | ||
| 37 | TcxoTrim::Volts2pt2 => write!(f, "2.2V"), | ||
| 38 | TcxoTrim::Volts2pt4 => write!(f, "2.4V"), | ||
| 39 | TcxoTrim::Volts2pt7 => write!(f, "2.7V"), | ||
| 40 | TcxoTrim::Volts3pt0 => write!(f, "3.0V"), | ||
| 41 | TcxoTrim::Volts3pt3 => write!(f, "3.3V"), | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | impl TcxoTrim { | ||
| 47 | /// Get the value of the TXCO trim in millivolts. | ||
| 48 | /// | ||
| 49 | /// # Example | ||
| 50 | /// | ||
| 51 | /// ``` | ||
| 52 | /// use stm32wl_hal::subghz::TcxoTrim; | ||
| 53 | /// | ||
| 54 | /// assert_eq!(TcxoTrim::Volts1pt6.as_millivolts(), 1600); | ||
| 55 | /// assert_eq!(TcxoTrim::Volts1pt7.as_millivolts(), 1700); | ||
| 56 | /// assert_eq!(TcxoTrim::Volts1pt8.as_millivolts(), 1800); | ||
| 57 | /// assert_eq!(TcxoTrim::Volts2pt2.as_millivolts(), 2200); | ||
| 58 | /// assert_eq!(TcxoTrim::Volts2pt4.as_millivolts(), 2400); | ||
| 59 | /// assert_eq!(TcxoTrim::Volts2pt7.as_millivolts(), 2700); | ||
| 60 | /// assert_eq!(TcxoTrim::Volts3pt0.as_millivolts(), 3000); | ||
| 61 | /// assert_eq!(TcxoTrim::Volts3pt3.as_millivolts(), 3300); | ||
| 62 | /// ``` | ||
| 63 | pub const fn as_millivolts(&self) -> u16 { | ||
| 64 | match self { | ||
| 65 | TcxoTrim::Volts1pt6 => 1600, | ||
| 66 | TcxoTrim::Volts1pt7 => 1700, | ||
| 67 | TcxoTrim::Volts1pt8 => 1800, | ||
| 68 | TcxoTrim::Volts2pt2 => 2200, | ||
| 69 | TcxoTrim::Volts2pt4 => 2400, | ||
| 70 | TcxoTrim::Volts2pt7 => 2700, | ||
| 71 | TcxoTrim::Volts3pt0 => 3000, | ||
| 72 | TcxoTrim::Volts3pt3 => 3300, | ||
| 73 | } | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | /// TCXO trim and HSE32 ready timeout. | ||
| 78 | /// | ||
| 79 | /// Argument of [`set_tcxo_mode`]. | ||
| 80 | /// | ||
| 81 | /// [`set_tcxo_mode`]: crate::subghz::SubGhz::set_tcxo_mode | ||
| 82 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 83 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 84 | pub struct TcxoMode { | ||
| 85 | buf: [u8; 5], | ||
| 86 | } | ||
| 87 | |||
| 88 | impl TcxoMode { | ||
| 89 | /// Create a new `TcxoMode` struct. | ||
| 90 | /// | ||
| 91 | /// This is the same as `default`, but in a `const` function. | ||
| 92 | /// | ||
| 93 | /// # Example | ||
| 94 | /// | ||
| 95 | /// ``` | ||
| 96 | /// use stm32wl_hal::subghz::TcxoMode; | ||
| 97 | /// | ||
| 98 | /// const TCXO_MODE: TcxoMode = TcxoMode::new(); | ||
| 99 | /// ``` | ||
| 100 | pub const fn new() -> TcxoMode { | ||
| 101 | TcxoMode { | ||
| 102 | buf: [super::OpCode::SetTcxoMode as u8, 0x00, 0x00, 0x00, 0x00], | ||
| 103 | } | ||
| 104 | } | ||
| 105 | |||
| 106 | /// Set the TCXO trim. | ||
| 107 | /// | ||
| 108 | /// **Note:** To use V<sub>DDTCXO</sub>, the V<sub>DDRF</sub> supply must be | ||
| 109 | /// at least + 200 mV higher than the selected `TcxoTrim` voltage level. | ||
| 110 | /// | ||
| 111 | /// # Example | ||
| 112 | /// | ||
| 113 | /// ``` | ||
| 114 | /// use stm32wl_hal::subghz::{TcxoMode, TcxoTrim}; | ||
| 115 | /// | ||
| 116 | /// const TCXO_MODE: TcxoMode = TcxoMode::new().set_txco_trim(TcxoTrim::Volts1pt6); | ||
| 117 | /// # assert_eq!(TCXO_MODE.as_slice()[1], 0x00); | ||
| 118 | /// ``` | ||
| 119 | #[must_use = "set_txco_trim returns a modified TcxoMode"] | ||
| 120 | pub const fn set_txco_trim(mut self, tcxo_trim: TcxoTrim) -> TcxoMode { | ||
| 121 | self.buf[1] = tcxo_trim as u8; | ||
| 122 | self | ||
| 123 | } | ||
| 124 | |||
| 125 | /// Set the ready timeout duration. | ||
| 126 | /// | ||
| 127 | /// # Example | ||
| 128 | /// | ||
| 129 | /// ``` | ||
| 130 | /// use core::time::Duration; | ||
| 131 | /// use stm32wl_hal::subghz::{TcxoMode, Timeout}; | ||
| 132 | /// | ||
| 133 | /// // 15.625 ms timeout | ||
| 134 | /// const TIMEOUT: Timeout = Timeout::from_duration_sat(Duration::from_millis(15_625)); | ||
| 135 | /// const TCXO_MODE: TcxoMode = TcxoMode::new().set_timeout(TIMEOUT); | ||
| 136 | /// # assert_eq!(TCXO_MODE.as_slice()[2], 0x0F); | ||
| 137 | /// # assert_eq!(TCXO_MODE.as_slice()[3], 0x42); | ||
| 138 | /// # assert_eq!(TCXO_MODE.as_slice()[4], 0x40); | ||
| 139 | /// ``` | ||
| 140 | #[must_use = "set_timeout returns a modified TcxoMode"] | ||
| 141 | pub const fn set_timeout(mut self, timeout: Timeout) -> TcxoMode { | ||
| 142 | let timeout_bits: u32 = timeout.into_bits(); | ||
| 143 | self.buf[2] = ((timeout_bits >> 16) & 0xFF) as u8; | ||
| 144 | self.buf[3] = ((timeout_bits >> 8) & 0xFF) as u8; | ||
| 145 | self.buf[4] = (timeout_bits & 0xFF) as u8; | ||
| 146 | self | ||
| 147 | } | ||
| 148 | |||
| 149 | /// Extracts a slice containing the packet. | ||
| 150 | /// | ||
| 151 | /// # Example | ||
| 152 | /// | ||
| 153 | /// ``` | ||
| 154 | /// use stm32wl_hal::subghz::{TcxoMode, TcxoTrim, Timeout}; | ||
| 155 | /// | ||
| 156 | /// const TCXO_MODE: TcxoMode = TcxoMode::new() | ||
| 157 | /// .set_txco_trim(TcxoTrim::Volts1pt7) | ||
| 158 | /// .set_timeout(Timeout::from_raw(0x123456)); | ||
| 159 | /// assert_eq!(TCXO_MODE.as_slice(), &[0x97, 0x1, 0x12, 0x34, 0x56]); | ||
| 160 | /// ``` | ||
| 161 | pub const fn as_slice(&self) -> &[u8] { | ||
| 162 | &self.buf | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | impl Default for TcxoMode { | ||
| 167 | fn default() -> Self { | ||
| 168 | Self::new() | ||
| 169 | } | ||
| 170 | } | ||
diff --git a/embassy-stm32/src/subghz/timeout.rs b/embassy-stm32/src/subghz/timeout.rs new file mode 100644 index 000000000..580d0a640 --- /dev/null +++ b/embassy-stm32/src/subghz/timeout.rs | |||
| @@ -0,0 +1,469 @@ | |||
| 1 | use core::time::Duration; | ||
| 2 | |||
| 3 | use crate::subghz::value_error::ValueError; | ||
| 4 | |||
| 5 | const fn abs_diff(a: u64, b: u64) -> u64 { | ||
| 6 | if a > b { | ||
| 7 | a - b | ||
| 8 | } else { | ||
| 9 | b - a | ||
| 10 | } | ||
| 11 | } | ||
| 12 | |||
| 13 | /// Timeout argument. | ||
| 14 | /// | ||
| 15 | /// This is used by: | ||
| 16 | /// * [`set_rx`] | ||
| 17 | /// * [`set_tx`] | ||
| 18 | /// * [`TcxoMode`] | ||
| 19 | /// | ||
| 20 | /// Each timeout has 3 bytes, with a resolution of 15.625µs per bit, giving a | ||
| 21 | /// range of 0s to 262.143984375s. | ||
| 22 | /// | ||
| 23 | /// [`set_rx`]: crate::subghz::SubGhz::set_rx | ||
| 24 | /// [`set_tx`]: crate::subghz::SubGhz::set_tx | ||
| 25 | /// [`TcxoMode`]: crate::subghz::TcxoMode | ||
| 26 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] | ||
| 27 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 28 | pub struct Timeout { | ||
| 29 | bits: u32, | ||
| 30 | } | ||
| 31 | |||
| 32 | impl Timeout { | ||
| 33 | const BITS_PER_MILLI: u32 = 64; // 1e-3 / 15.625e-6 | ||
| 34 | const BITS_PER_SEC: u32 = 64_000; // 1 / 15.625e-6 | ||
| 35 | |||
| 36 | /// Disable the timeout (0s timeout). | ||
| 37 | /// | ||
| 38 | /// # Example | ||
| 39 | /// | ||
| 40 | /// ``` | ||
| 41 | /// use core::time::Duration; | ||
| 42 | /// use stm32wl_hal::subghz::Timeout; | ||
| 43 | /// | ||
| 44 | /// const TIMEOUT: Timeout = Timeout::DISABLED; | ||
| 45 | /// assert_eq!(TIMEOUT.as_duration(), Duration::from_secs(0)); | ||
| 46 | /// ``` | ||
| 47 | pub const DISABLED: Timeout = Timeout { bits: 0x0 }; | ||
| 48 | |||
| 49 | /// Minimum timeout, 15.625µs. | ||
| 50 | /// | ||
| 51 | /// # Example | ||
| 52 | /// | ||
| 53 | /// ``` | ||
| 54 | /// use core::time::Duration; | ||
| 55 | /// use stm32wl_hal::subghz::Timeout; | ||
| 56 | /// | ||
| 57 | /// const TIMEOUT: Timeout = Timeout::MIN; | ||
| 58 | /// assert_eq!(TIMEOUT.into_bits(), 1); | ||
| 59 | /// ``` | ||
| 60 | pub const MIN: Timeout = Timeout { bits: 1 }; | ||
| 61 | |||
| 62 | /// Maximum timeout, 262.143984375s. | ||
| 63 | /// | ||
| 64 | /// # Example | ||
| 65 | /// | ||
| 66 | /// ``` | ||
| 67 | /// use core::time::Duration; | ||
| 68 | /// use stm32wl_hal::subghz::Timeout; | ||
| 69 | /// | ||
| 70 | /// const TIMEOUT: Timeout = Timeout::MAX; | ||
| 71 | /// assert_eq!(TIMEOUT.as_duration(), Duration::from_nanos(262_143_984_375)); | ||
| 72 | /// ``` | ||
| 73 | pub const MAX: Timeout = Timeout { bits: 0x00FF_FFFF }; | ||
| 74 | |||
| 75 | /// Timeout resolution in nanoseconds, 15.625µs. | ||
| 76 | pub const RESOLUTION_NANOS: u16 = 15_625; | ||
| 77 | |||
| 78 | /// Timeout resolution, 15.625µs. | ||
| 79 | /// | ||
| 80 | /// # Example | ||
| 81 | /// | ||
| 82 | /// ``` | ||
| 83 | /// use stm32wl_hal::subghz::Timeout; | ||
| 84 | /// | ||
| 85 | /// assert_eq!( | ||
| 86 | /// Timeout::RESOLUTION.as_nanos(), | ||
| 87 | /// Timeout::RESOLUTION_NANOS as u128 | ||
| 88 | /// ); | ||
| 89 | /// ``` | ||
| 90 | pub const RESOLUTION: Duration = Duration::from_nanos(Self::RESOLUTION_NANOS as u64); | ||
| 91 | |||
| 92 | /// Create a new timeout from a [`Duration`]. | ||
| 93 | /// | ||
| 94 | /// This will return the nearest timeout value possible, or a | ||
| 95 | /// [`ValueError`] if the value is out of bounds. | ||
| 96 | /// | ||
| 97 | /// Use [`from_millis_sat`](Self::from_millis_sat) for runtime timeout | ||
| 98 | /// construction. | ||
| 99 | /// This is not _that_ useful right now, it is simply future proofing for a | ||
| 100 | /// time when `Result::unwrap` is avaliable for `const fn`. | ||
| 101 | /// | ||
| 102 | /// # Example | ||
| 103 | /// | ||
| 104 | /// Value within bounds: | ||
| 105 | /// | ||
| 106 | /// ``` | ||
| 107 | /// use core::time::Duration; | ||
| 108 | /// use stm32wl_hal::subghz::{Timeout, ValueError}; | ||
| 109 | /// | ||
| 110 | /// const MIN: Duration = Timeout::RESOLUTION; | ||
| 111 | /// assert_eq!(Timeout::from_duration(MIN).unwrap(), Timeout::MIN); | ||
| 112 | /// ``` | ||
| 113 | /// | ||
| 114 | /// Value too low: | ||
| 115 | /// | ||
| 116 | /// ``` | ||
| 117 | /// use core::time::Duration; | ||
| 118 | /// use stm32wl_hal::subghz::{Timeout, ValueError}; | ||
| 119 | /// | ||
| 120 | /// const LOWER_LIMIT_NANOS: u128 = 7813; | ||
| 121 | /// const TOO_LOW_NANOS: u128 = LOWER_LIMIT_NANOS - 1; | ||
| 122 | /// const TOO_LOW_DURATION: Duration = Duration::from_nanos(TOO_LOW_NANOS as u64); | ||
| 123 | /// assert_eq!( | ||
| 124 | /// Timeout::from_duration(TOO_LOW_DURATION), | ||
| 125 | /// Err(ValueError::too_low(TOO_LOW_NANOS, LOWER_LIMIT_NANOS)) | ||
| 126 | /// ); | ||
| 127 | /// ``` | ||
| 128 | /// | ||
| 129 | /// Value too high: | ||
| 130 | /// | ||
| 131 | /// ``` | ||
| 132 | /// use core::time::Duration; | ||
| 133 | /// use stm32wl_hal::subghz::{Timeout, ValueError}; | ||
| 134 | /// | ||
| 135 | /// const UPPER_LIMIT_NANOS: u128 = Timeout::MAX.as_nanos() as u128 + 7812; | ||
| 136 | /// const TOO_HIGH_NANOS: u128 = UPPER_LIMIT_NANOS + 1; | ||
| 137 | /// const TOO_HIGH_DURATION: Duration = Duration::from_nanos(TOO_HIGH_NANOS as u64); | ||
| 138 | /// assert_eq!( | ||
| 139 | /// Timeout::from_duration(TOO_HIGH_DURATION), | ||
| 140 | /// Err(ValueError::too_high(TOO_HIGH_NANOS, UPPER_LIMIT_NANOS)) | ||
| 141 | /// ); | ||
| 142 | /// ``` | ||
| 143 | pub const fn from_duration(duration: Duration) -> Result<Timeout, ValueError<u128>> { | ||
| 144 | // at the time of development many methods in | ||
| 145 | // `core::Duration` were not `const fn`, which leads to the hacks | ||
| 146 | // you see here. | ||
| 147 | let nanos: u128 = duration.as_nanos(); | ||
| 148 | const UPPER_LIMIT: u128 = | ||
| 149 | Timeout::MAX.as_nanos() as u128 + (Timeout::RESOLUTION_NANOS as u128) / 2; | ||
| 150 | const LOWER_LIMIT: u128 = (((Timeout::RESOLUTION_NANOS as u128) + 1) / 2) as u128; | ||
| 151 | |||
| 152 | if nanos > UPPER_LIMIT { | ||
| 153 | Err(ValueError::too_high(nanos, UPPER_LIMIT)) | ||
| 154 | } else if nanos < LOWER_LIMIT { | ||
| 155 | Err(ValueError::too_low(nanos, LOWER_LIMIT)) | ||
| 156 | } else { | ||
| 157 | // safe to truncate here because of previous bounds check. | ||
| 158 | let duration_nanos: u64 = nanos as u64; | ||
| 159 | |||
| 160 | let div_floor: u64 = duration_nanos / (Self::RESOLUTION_NANOS as u64); | ||
| 161 | let div_ceil: u64 = 1 + (duration_nanos - 1) / (Self::RESOLUTION_NANOS as u64); | ||
| 162 | |||
| 163 | let timeout_ceil: Timeout = Timeout::from_raw(div_ceil as u32); | ||
| 164 | let timeout_floor: Timeout = Timeout::from_raw(div_floor as u32); | ||
| 165 | |||
| 166 | let error_ceil: u64 = abs_diff(timeout_ceil.as_nanos(), duration_nanos); | ||
| 167 | let error_floor: u64 = abs_diff(timeout_floor.as_nanos(), duration_nanos); | ||
| 168 | |||
| 169 | if error_ceil < error_floor { | ||
| 170 | Ok(timeout_ceil) | ||
| 171 | } else { | ||
| 172 | Ok(timeout_floor) | ||
| 173 | } | ||
| 174 | } | ||
| 175 | } | ||
| 176 | |||
| 177 | /// Create a new timeout from a [`Duration`]. | ||
| 178 | /// | ||
| 179 | /// This will return the nearest timeout value possible, saturating at the | ||
| 180 | /// limits. | ||
| 181 | /// | ||
| 182 | /// This is an expensive function to call outside of `const` contexts. | ||
| 183 | /// Use [`from_millis_sat`](Self::from_millis_sat) for runtime timeout | ||
| 184 | /// construction. | ||
| 185 | /// | ||
| 186 | /// # Example | ||
| 187 | /// | ||
| 188 | /// ``` | ||
| 189 | /// use core::time::Duration; | ||
| 190 | /// use stm32wl_hal::subghz::Timeout; | ||
| 191 | /// | ||
| 192 | /// const DURATION_MAX_NS: u64 = 262_143_984_376; | ||
| 193 | /// | ||
| 194 | /// assert_eq!( | ||
| 195 | /// Timeout::from_duration_sat(Duration::from_millis(0)), | ||
| 196 | /// Timeout::MIN | ||
| 197 | /// ); | ||
| 198 | /// assert_eq!( | ||
| 199 | /// Timeout::from_duration_sat(Duration::from_nanos(DURATION_MAX_NS)), | ||
| 200 | /// Timeout::MAX | ||
| 201 | /// ); | ||
| 202 | /// assert_eq!( | ||
| 203 | /// Timeout::from_duration_sat(Timeout::RESOLUTION).into_bits(), | ||
| 204 | /// 1 | ||
| 205 | /// ); | ||
| 206 | /// ``` | ||
| 207 | pub const fn from_duration_sat(duration: Duration) -> Timeout { | ||
| 208 | // at the time of development many methods in | ||
| 209 | // `core::Duration` were not `const fn`, which leads to the hacks | ||
| 210 | // you see here. | ||
| 211 | let nanos: u128 = duration.as_nanos(); | ||
| 212 | const UPPER_LIMIT: u128 = Timeout::MAX.as_nanos() as u128; | ||
| 213 | |||
| 214 | if nanos > UPPER_LIMIT { | ||
| 215 | Timeout::MAX | ||
| 216 | } else if nanos < (Timeout::RESOLUTION_NANOS as u128) { | ||
| 217 | Timeout::from_raw(1) | ||
| 218 | } else { | ||
| 219 | // safe to truncate here because of previous bounds check. | ||
| 220 | let duration_nanos: u64 = duration.as_nanos() as u64; | ||
| 221 | |||
| 222 | let div_floor: u64 = duration_nanos / (Self::RESOLUTION_NANOS as u64); | ||
| 223 | let div_ceil: u64 = 1 + (duration_nanos - 1) / (Self::RESOLUTION_NANOS as u64); | ||
| 224 | |||
| 225 | let timeout_ceil: Timeout = Timeout::from_raw(div_ceil as u32); | ||
| 226 | let timeout_floor: Timeout = Timeout::from_raw(div_floor as u32); | ||
| 227 | |||
| 228 | let error_ceil: u64 = abs_diff(timeout_ceil.as_nanos(), duration_nanos); | ||
| 229 | let error_floor: u64 = abs_diff(timeout_floor.as_nanos(), duration_nanos); | ||
| 230 | |||
| 231 | if error_ceil < error_floor { | ||
| 232 | timeout_ceil | ||
| 233 | } else { | ||
| 234 | timeout_floor | ||
| 235 | } | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | /// Create a new timeout from a milliseconds value. | ||
| 240 | /// | ||
| 241 | /// This will round towards zero and saturate at the limits. | ||
| 242 | /// | ||
| 243 | /// This is the preferred method to call when you need to generate a | ||
| 244 | /// timeout value at runtime. | ||
| 245 | /// | ||
| 246 | /// # Example | ||
| 247 | /// | ||
| 248 | /// ``` | ||
| 249 | /// use stm32wl_hal::subghz::Timeout; | ||
| 250 | /// | ||
| 251 | /// assert_eq!(Timeout::from_millis_sat(0), Timeout::MIN); | ||
| 252 | /// assert_eq!(Timeout::from_millis_sat(262_144), Timeout::MAX); | ||
| 253 | /// assert_eq!(Timeout::from_millis_sat(1).into_bits(), 64); | ||
| 254 | /// ``` | ||
| 255 | pub const fn from_millis_sat(millis: u32) -> Timeout { | ||
| 256 | if millis == 0 { | ||
| 257 | Timeout::MIN | ||
| 258 | } else if millis >= 262_144 { | ||
| 259 | Timeout::MAX | ||
| 260 | } else { | ||
| 261 | Timeout::from_raw(millis * Self::BITS_PER_MILLI) | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | /// Create a timeout from raw bits, where each bit has the resolution of | ||
| 266 | /// [`Timeout::RESOLUTION`]. | ||
| 267 | /// | ||
| 268 | /// **Note:** Only the first 24 bits of the `u32` are used, the `bits` | ||
| 269 | /// argument will be masked. | ||
| 270 | /// | ||
| 271 | /// # Example | ||
| 272 | /// | ||
| 273 | /// ``` | ||
| 274 | /// use stm32wl_hal::subghz::Timeout; | ||
| 275 | /// | ||
| 276 | /// assert_eq!(Timeout::from_raw(u32::MAX), Timeout::MAX); | ||
| 277 | /// assert_eq!(Timeout::from_raw(0x00_FF_FF_FF), Timeout::MAX); | ||
| 278 | /// assert_eq!(Timeout::from_raw(1).as_duration(), Timeout::RESOLUTION); | ||
| 279 | /// assert_eq!(Timeout::from_raw(0), Timeout::DISABLED); | ||
| 280 | /// ``` | ||
| 281 | pub const fn from_raw(bits: u32) -> Timeout { | ||
| 282 | Timeout { | ||
| 283 | bits: bits & 0x00FF_FFFF, | ||
| 284 | } | ||
| 285 | } | ||
| 286 | |||
| 287 | /// Get the timeout as nanoseconds. | ||
| 288 | /// | ||
| 289 | /// # Example | ||
| 290 | /// | ||
| 291 | /// ``` | ||
| 292 | /// use stm32wl_hal::subghz::Timeout; | ||
| 293 | /// | ||
| 294 | /// assert_eq!(Timeout::MAX.as_nanos(), 262_143_984_375); | ||
| 295 | /// assert_eq!(Timeout::DISABLED.as_nanos(), 0); | ||
| 296 | /// assert_eq!(Timeout::from_raw(1).as_nanos(), 15_625); | ||
| 297 | /// assert_eq!(Timeout::from_raw(64_000).as_nanos(), 1_000_000_000); | ||
| 298 | /// ``` | ||
| 299 | pub const fn as_nanos(&self) -> u64 { | ||
| 300 | (self.bits as u64) * (Timeout::RESOLUTION_NANOS as u64) | ||
| 301 | } | ||
| 302 | |||
| 303 | /// Get the timeout as microseconds, rounding towards zero. | ||
| 304 | /// | ||
| 305 | /// # Example | ||
| 306 | /// | ||
| 307 | /// ``` | ||
| 308 | /// use stm32wl_hal::subghz::Timeout; | ||
| 309 | /// | ||
| 310 | /// assert_eq!(Timeout::MAX.as_micros(), 262_143_984); | ||
| 311 | /// assert_eq!(Timeout::DISABLED.as_micros(), 0); | ||
| 312 | /// assert_eq!(Timeout::from_raw(1).as_micros(), 15); | ||
| 313 | /// assert_eq!(Timeout::from_raw(64_000).as_micros(), 1_000_000); | ||
| 314 | /// ``` | ||
| 315 | pub const fn as_micros(&self) -> u32 { | ||
| 316 | (self.as_nanos() / 1_000) as u32 | ||
| 317 | } | ||
| 318 | |||
| 319 | /// Get the timeout as milliseconds, rounding towards zero. | ||
| 320 | /// | ||
| 321 | /// # Example | ||
| 322 | /// | ||
| 323 | /// ``` | ||
| 324 | /// use stm32wl_hal::subghz::Timeout; | ||
| 325 | /// | ||
| 326 | /// assert_eq!(Timeout::MAX.as_millis(), 262_143); | ||
| 327 | /// assert_eq!(Timeout::DISABLED.as_millis(), 0); | ||
| 328 | /// assert_eq!(Timeout::from_raw(1).as_millis(), 0); | ||
| 329 | /// assert_eq!(Timeout::from_raw(64_000).as_millis(), 1_000); | ||
| 330 | /// ``` | ||
| 331 | pub const fn as_millis(&self) -> u32 { | ||
| 332 | self.into_bits() / Self::BITS_PER_MILLI | ||
| 333 | } | ||
| 334 | |||
| 335 | /// Get the timeout as seconds, rounding towards zero. | ||
| 336 | /// | ||
| 337 | /// # Example | ||
| 338 | /// | ||
| 339 | /// ``` | ||
| 340 | /// use stm32wl_hal::subghz::Timeout; | ||
| 341 | /// | ||
| 342 | /// assert_eq!(Timeout::MAX.as_secs(), 262); | ||
| 343 | /// assert_eq!(Timeout::DISABLED.as_secs(), 0); | ||
| 344 | /// assert_eq!(Timeout::from_raw(1).as_secs(), 0); | ||
| 345 | /// assert_eq!(Timeout::from_raw(64_000).as_secs(), 1); | ||
| 346 | /// ``` | ||
| 347 | pub const fn as_secs(&self) -> u16 { | ||
| 348 | (self.into_bits() / Self::BITS_PER_SEC) as u16 | ||
| 349 | } | ||
| 350 | |||
| 351 | /// Get the timeout as a [`Duration`]. | ||
| 352 | /// | ||
| 353 | /// # Example | ||
| 354 | /// | ||
| 355 | /// ``` | ||
| 356 | /// use core::time::Duration; | ||
| 357 | /// use stm32wl_hal::subghz::Timeout; | ||
| 358 | /// | ||
| 359 | /// assert_eq!( | ||
| 360 | /// Timeout::MAX.as_duration(), | ||
| 361 | /// Duration::from_nanos(262_143_984_375) | ||
| 362 | /// ); | ||
| 363 | /// assert_eq!(Timeout::DISABLED.as_duration(), Duration::from_nanos(0)); | ||
| 364 | /// assert_eq!(Timeout::from_raw(1).as_duration(), Timeout::RESOLUTION); | ||
| 365 | /// ``` | ||
| 366 | pub const fn as_duration(&self) -> Duration { | ||
| 367 | Duration::from_nanos((self.bits as u64) * (Timeout::RESOLUTION_NANOS as u64)) | ||
| 368 | } | ||
| 369 | |||
| 370 | /// Get the bit value for the timeout. | ||
| 371 | /// | ||
| 372 | /// # Example | ||
| 373 | /// | ||
| 374 | /// ``` | ||
| 375 | /// use stm32wl_hal::subghz::Timeout; | ||
| 376 | /// | ||
| 377 | /// assert_eq!(Timeout::from_raw(u32::MAX).into_bits(), 0x00FF_FFFF); | ||
| 378 | /// assert_eq!(Timeout::from_raw(1).into_bits(), 1); | ||
| 379 | /// ``` | ||
| 380 | pub const fn into_bits(self) -> u32 { | ||
| 381 | self.bits | ||
| 382 | } | ||
| 383 | |||
| 384 | /// Get the byte value for the timeout. | ||
| 385 | /// | ||
| 386 | /// # Example | ||
| 387 | /// | ||
| 388 | /// ``` | ||
| 389 | /// use stm32wl_hal::subghz::Timeout; | ||
| 390 | /// | ||
| 391 | /// assert_eq!(Timeout::from_raw(u32::MAX).as_bytes(), [0xFF, 0xFF, 0xFF]); | ||
| 392 | /// assert_eq!(Timeout::from_raw(1).as_bytes(), [0, 0, 1]); | ||
| 393 | /// ``` | ||
| 394 | pub const fn as_bytes(self) -> [u8; 3] { | ||
| 395 | [ | ||
| 396 | ((self.bits >> 16) & 0xFF) as u8, | ||
| 397 | ((self.bits >> 8) & 0xFF) as u8, | ||
| 398 | (self.bits & 0xFF) as u8, | ||
| 399 | ] | ||
| 400 | } | ||
| 401 | } | ||
| 402 | |||
| 403 | impl From<Timeout> for Duration { | ||
| 404 | fn from(to: Timeout) -> Self { | ||
| 405 | to.as_duration() | ||
| 406 | } | ||
| 407 | } | ||
| 408 | |||
| 409 | impl From<Timeout> for [u8; 3] { | ||
| 410 | fn from(to: Timeout) -> Self { | ||
| 411 | to.as_bytes() | ||
| 412 | } | ||
| 413 | } | ||
| 414 | |||
| 415 | impl From<Timeout> for embassy::time::Duration { | ||
| 416 | fn from(to: Timeout) -> Self { | ||
| 417 | embassy::time::Duration::from_micros(to.as_micros().into()) | ||
| 418 | } | ||
| 419 | } | ||
| 420 | |||
| 421 | #[cfg(test)] | ||
| 422 | mod tests { | ||
| 423 | use super::{Timeout, ValueError}; | ||
| 424 | use core::time::Duration; | ||
| 425 | |||
| 426 | #[test] | ||
| 427 | fn saturate() { | ||
| 428 | assert_eq!( | ||
| 429 | Timeout::from_duration_sat(Duration::from_secs(u64::MAX)), | ||
| 430 | Timeout::MAX | ||
| 431 | ); | ||
| 432 | } | ||
| 433 | |||
| 434 | #[test] | ||
| 435 | fn rounding() { | ||
| 436 | const NANO1: Duration = Duration::from_nanos(1); | ||
| 437 | let res_sub_1_ns: Duration = Timeout::RESOLUTION - NANO1; | ||
| 438 | let res_add_1_ns: Duration = Timeout::RESOLUTION + NANO1; | ||
| 439 | assert_eq!(Timeout::from_duration_sat(res_sub_1_ns).into_bits(), 1); | ||
| 440 | assert_eq!(Timeout::from_duration_sat(res_add_1_ns).into_bits(), 1); | ||
| 441 | } | ||
| 442 | |||
| 443 | #[test] | ||
| 444 | fn lower_limit() { | ||
| 445 | let low: Duration = (Timeout::RESOLUTION + Duration::from_nanos(1)) / 2; | ||
| 446 | assert_eq!(Timeout::from_duration(low), Ok(Timeout::from_raw(1))); | ||
| 447 | |||
| 448 | let too_low: Duration = low - Duration::from_nanos(1); | ||
| 449 | assert_eq!( | ||
| 450 | Timeout::from_duration(too_low), | ||
| 451 | Err(ValueError::too_low(too_low.as_nanos(), low.as_nanos())) | ||
| 452 | ); | ||
| 453 | } | ||
| 454 | |||
| 455 | #[test] | ||
| 456 | fn upper_limit() { | ||
| 457 | let high: Duration = Timeout::MAX.as_duration() + Timeout::RESOLUTION / 2; | ||
| 458 | assert_eq!( | ||
| 459 | Timeout::from_duration(high), | ||
| 460 | Ok(Timeout::from_raw(0xFFFFFF)) | ||
| 461 | ); | ||
| 462 | |||
| 463 | let too_high: Duration = high + Duration::from_nanos(1); | ||
| 464 | assert_eq!( | ||
| 465 | Timeout::from_duration(too_high), | ||
| 466 | Err(ValueError::too_high(too_high.as_nanos(), high.as_nanos())) | ||
| 467 | ); | ||
| 468 | } | ||
| 469 | } | ||
diff --git a/embassy-stm32/src/subghz/tx_params.rs b/embassy-stm32/src/subghz/tx_params.rs new file mode 100644 index 000000000..17f052bcb --- /dev/null +++ b/embassy-stm32/src/subghz/tx_params.rs | |||
| @@ -0,0 +1,166 @@ | |||
| 1 | /// Power amplifier ramp time for FSK, MSK, and LoRa modulation. | ||
| 2 | /// | ||
| 3 | /// Argument of [`set_ramp_time`][`crate::subghz::TxParams::set_ramp_time`]. | ||
| 4 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] | ||
| 5 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 6 | #[repr(u8)] | ||
| 7 | pub enum RampTime { | ||
| 8 | /// 10µs | ||
| 9 | Micros10 = 0x00, | ||
| 10 | /// 20µs | ||
| 11 | Micros20 = 0x01, | ||
| 12 | /// 40µs | ||
| 13 | Micros40 = 0x02, | ||
| 14 | /// 80µs | ||
| 15 | Micros80 = 0x03, | ||
| 16 | /// 200µs | ||
| 17 | Micros200 = 0x04, | ||
| 18 | /// 800µs | ||
| 19 | Micros800 = 0x05, | ||
| 20 | /// 1.7ms | ||
| 21 | Micros1700 = 0x06, | ||
| 22 | /// 3.4ms | ||
| 23 | Micros3400 = 0x07, | ||
| 24 | } | ||
| 25 | |||
| 26 | impl From<RampTime> for u8 { | ||
| 27 | fn from(rt: RampTime) -> Self { | ||
| 28 | rt as u8 | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | impl From<RampTime> for core::time::Duration { | ||
| 33 | fn from(rt: RampTime) -> Self { | ||
| 34 | match rt { | ||
| 35 | RampTime::Micros10 => core::time::Duration::from_micros(10), | ||
| 36 | RampTime::Micros20 => core::time::Duration::from_micros(20), | ||
| 37 | RampTime::Micros40 => core::time::Duration::from_micros(40), | ||
| 38 | RampTime::Micros80 => core::time::Duration::from_micros(80), | ||
| 39 | RampTime::Micros200 => core::time::Duration::from_micros(200), | ||
| 40 | RampTime::Micros800 => core::time::Duration::from_micros(800), | ||
| 41 | RampTime::Micros1700 => core::time::Duration::from_micros(1700), | ||
| 42 | RampTime::Micros3400 => core::time::Duration::from_micros(3400), | ||
| 43 | } | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | impl From<RampTime> for embassy::time::Duration { | ||
| 48 | fn from(rt: RampTime) -> Self { | ||
| 49 | match rt { | ||
| 50 | RampTime::Micros10 => embassy::time::Duration::from_micros(10), | ||
| 51 | RampTime::Micros20 => embassy::time::Duration::from_micros(20), | ||
| 52 | RampTime::Micros40 => embassy::time::Duration::from_micros(40), | ||
| 53 | RampTime::Micros80 => embassy::time::Duration::from_micros(80), | ||
| 54 | RampTime::Micros200 => embassy::time::Duration::from_micros(200), | ||
| 55 | RampTime::Micros800 => embassy::time::Duration::from_micros(800), | ||
| 56 | RampTime::Micros1700 => embassy::time::Duration::from_micros(1700), | ||
| 57 | RampTime::Micros3400 => embassy::time::Duration::from_micros(3400), | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | /// Transmit parameters, output power and power amplifier ramp up time. | ||
| 62 | /// | ||
| 63 | /// Argument of [`set_tx_params`][`crate::subghz::SubGhz::set_tx_params`]. | ||
| 64 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 65 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 66 | pub struct TxParams { | ||
| 67 | buf: [u8; 3], | ||
| 68 | } | ||
| 69 | |||
| 70 | impl TxParams { | ||
| 71 | /// Create a new `TxParams` struct. | ||
| 72 | /// | ||
| 73 | /// This is the same as `default`, but in a `const` function. | ||
| 74 | /// | ||
| 75 | /// # Example | ||
| 76 | /// | ||
| 77 | /// ``` | ||
| 78 | /// use stm32wl_hal::subghz::TxParams; | ||
| 79 | /// | ||
| 80 | /// const TX_PARAMS: TxParams = TxParams::new(); | ||
| 81 | /// assert_eq!(TX_PARAMS, TxParams::default()); | ||
| 82 | /// ``` | ||
| 83 | pub const fn new() -> TxParams { | ||
| 84 | TxParams { | ||
| 85 | buf: [super::OpCode::SetTxParams as u8, 0x00, 0x00], | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | /// Set the output power. | ||
| 90 | /// | ||
| 91 | /// For low power selected in [`set_pa_config`]: | ||
| 92 | /// | ||
| 93 | /// * 0x0E: +14 dB | ||
| 94 | /// * ... | ||
| 95 | /// * 0x00: 0 dB | ||
| 96 | /// * ... | ||
| 97 | /// * 0xEF: -17 dB | ||
| 98 | /// * Others: reserved | ||
| 99 | /// | ||
| 100 | /// For high power selected in [`set_pa_config`]: | ||
| 101 | /// | ||
| 102 | /// * 0x16: +22 dB | ||
| 103 | /// * ... | ||
| 104 | /// * 0x00: 0 dB | ||
| 105 | /// * ... | ||
| 106 | /// * 0xF7: -9 dB | ||
| 107 | /// * Others: reserved | ||
| 108 | /// | ||
| 109 | /// # Example | ||
| 110 | /// | ||
| 111 | /// Set the output power to 0 dB. | ||
| 112 | /// | ||
| 113 | /// ``` | ||
| 114 | /// use stm32wl_hal::subghz::{RampTime, TxParams}; | ||
| 115 | /// | ||
| 116 | /// const TX_PARAMS: TxParams = TxParams::new().set_power(0x00); | ||
| 117 | /// # assert_eq!(TX_PARAMS.as_slice()[1], 0x00); | ||
| 118 | /// ``` | ||
| 119 | /// | ||
| 120 | /// [`set_pa_config`]: crate::subghz::SubGhz::set_pa_config | ||
| 121 | #[must_use = "set_power returns a modified TxParams"] | ||
| 122 | pub const fn set_power(mut self, power: u8) -> TxParams { | ||
| 123 | self.buf[1] = power; | ||
| 124 | self | ||
| 125 | } | ||
| 126 | |||
| 127 | /// Set the Power amplifier ramp time for FSK, MSK, and LoRa modulation. | ||
| 128 | /// | ||
| 129 | /// # Example | ||
| 130 | /// | ||
| 131 | /// Set the ramp time to 200 microseconds. | ||
| 132 | /// | ||
| 133 | /// ``` | ||
| 134 | /// use stm32wl_hal::subghz::{RampTime, TxParams}; | ||
| 135 | /// | ||
| 136 | /// const TX_PARAMS: TxParams = TxParams::new().set_ramp_time(RampTime::Micros200); | ||
| 137 | /// # assert_eq!(TX_PARAMS.as_slice()[2], 0x04); | ||
| 138 | /// ``` | ||
| 139 | #[must_use = "set_ramp_time returns a modified TxParams"] | ||
| 140 | pub const fn set_ramp_time(mut self, rt: RampTime) -> TxParams { | ||
| 141 | self.buf[2] = rt as u8; | ||
| 142 | self | ||
| 143 | } | ||
| 144 | |||
| 145 | /// Extracts a slice containing the packet. | ||
| 146 | /// | ||
| 147 | /// # Example | ||
| 148 | /// | ||
| 149 | /// ``` | ||
| 150 | /// use stm32wl_hal::subghz::{RampTime, TxParams}; | ||
| 151 | /// | ||
| 152 | /// const TX_PARAMS: TxParams = TxParams::new() | ||
| 153 | /// .set_ramp_time(RampTime::Micros80) | ||
| 154 | /// .set_power(0x0E); | ||
| 155 | /// assert_eq!(TX_PARAMS.as_slice(), &[0x8E, 0x0E, 0x03]); | ||
| 156 | /// ``` | ||
| 157 | pub const fn as_slice(&self) -> &[u8] { | ||
| 158 | &self.buf | ||
| 159 | } | ||
| 160 | } | ||
| 161 | |||
| 162 | impl Default for TxParams { | ||
| 163 | fn default() -> Self { | ||
| 164 | Self::new() | ||
| 165 | } | ||
| 166 | } | ||
diff --git a/embassy-stm32/src/subghz/value_error.rs b/embassy-stm32/src/subghz/value_error.rs new file mode 100644 index 000000000..0c470cfae --- /dev/null +++ b/embassy-stm32/src/subghz/value_error.rs | |||
| @@ -0,0 +1,129 @@ | |||
| 1 | /// Error for a value that is out-of-bounds. | ||
| 2 | /// | ||
| 3 | /// Used by [`Timeout::from_duration`]. | ||
| 4 | /// | ||
| 5 | /// [`Timeout::from_duration`]: crate::subghz::Timeout::from_duration | ||
| 6 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
| 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 8 | pub struct ValueError<T> { | ||
| 9 | value: T, | ||
| 10 | limit: T, | ||
| 11 | over: bool, | ||
| 12 | } | ||
| 13 | |||
| 14 | impl<T> ValueError<T> { | ||
| 15 | /// Create a new `ValueError` for a value that exceeded an upper bound. | ||
| 16 | /// | ||
| 17 | /// Unfortunately panic is not avaliable in `const fn`, so there are no | ||
| 18 | /// guarantees on the value being greater than the limit. | ||
| 19 | /// | ||
| 20 | /// # Example | ||
| 21 | /// | ||
| 22 | /// ``` | ||
| 23 | /// use stm32wl_hal::subghz::ValueError; | ||
| 24 | /// | ||
| 25 | /// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8); | ||
| 26 | /// assert!(ERROR.over()); | ||
| 27 | /// assert!(!ERROR.under()); | ||
| 28 | /// ``` | ||
| 29 | pub const fn too_high(value: T, limit: T) -> ValueError<T> { | ||
| 30 | ValueError { | ||
| 31 | value, | ||
| 32 | limit, | ||
| 33 | over: true, | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | /// Create a new `ValueError` for a value that exceeded a lower bound. | ||
| 38 | /// | ||
| 39 | /// Unfortunately panic is not avaliable in `const fn`, so there are no | ||
| 40 | /// guarantees on the value being less than the limit. | ||
| 41 | /// | ||
| 42 | /// # Example | ||
| 43 | /// | ||
| 44 | /// ``` | ||
| 45 | /// use stm32wl_hal::subghz::ValueError; | ||
| 46 | /// | ||
| 47 | /// const ERROR: ValueError<u8> = ValueError::too_low(200u8, 201u8); | ||
| 48 | /// assert!(ERROR.under()); | ||
| 49 | /// assert!(!ERROR.over()); | ||
| 50 | /// ``` | ||
| 51 | pub const fn too_low(value: T, limit: T) -> ValueError<T> { | ||
| 52 | ValueError { | ||
| 53 | value, | ||
| 54 | limit, | ||
| 55 | over: false, | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Get the value that caused the error. | ||
| 60 | /// | ||
| 61 | /// # Example | ||
| 62 | /// | ||
| 63 | /// ``` | ||
| 64 | /// use stm32wl_hal::subghz::ValueError; | ||
| 65 | /// | ||
| 66 | /// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8); | ||
| 67 | /// assert_eq!(ERROR.value(), &101u8); | ||
| 68 | /// ``` | ||
| 69 | pub const fn value(&self) -> &T { | ||
| 70 | &self.value | ||
| 71 | } | ||
| 72 | |||
| 73 | /// Get the limit for the value. | ||
| 74 | /// | ||
| 75 | /// # Example | ||
| 76 | /// | ||
| 77 | /// ``` | ||
| 78 | /// use stm32wl_hal::subghz::ValueError; | ||
| 79 | /// | ||
| 80 | /// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8); | ||
| 81 | /// assert_eq!(ERROR.limit(), &100u8); | ||
| 82 | /// ``` | ||
| 83 | pub const fn limit(&self) -> &T { | ||
| 84 | &self.limit | ||
| 85 | } | ||
| 86 | |||
| 87 | /// Returns `true` if the value was over the limit. | ||
| 88 | /// | ||
| 89 | /// # Example | ||
| 90 | /// | ||
| 91 | /// ``` | ||
| 92 | /// use stm32wl_hal::subghz::ValueError; | ||
| 93 | /// | ||
| 94 | /// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8); | ||
| 95 | /// assert!(ERROR.over()); | ||
| 96 | /// assert!(!ERROR.under()); | ||
| 97 | /// ``` | ||
| 98 | pub const fn over(&self) -> bool { | ||
| 99 | self.over | ||
| 100 | } | ||
| 101 | |||
| 102 | /// Returns `true` if the value was under the limit. | ||
| 103 | /// | ||
| 104 | /// # Example | ||
| 105 | /// | ||
| 106 | /// ``` | ||
| 107 | /// use stm32wl_hal::subghz::ValueError; | ||
| 108 | /// | ||
| 109 | /// const ERROR: ValueError<u8> = ValueError::too_low(200u8, 201u8); | ||
| 110 | /// assert!(ERROR.under()); | ||
| 111 | /// assert!(!ERROR.over()); | ||
| 112 | /// ``` | ||
| 113 | pub const fn under(&self) -> bool { | ||
| 114 | !self.over | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | impl<T> core::fmt::Display for ValueError<T> | ||
| 119 | where | ||
| 120 | T: core::fmt::Display, | ||
| 121 | { | ||
| 122 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 123 | if self.over { | ||
| 124 | write!(f, "Value is too high {} > {}", self.value, self.limit) | ||
| 125 | } else { | ||
| 126 | write!(f, "Value is too low {} < {}", self.value, self.limit) | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
diff --git a/examples/stm32wl55/Cargo.toml b/examples/stm32wl55/Cargo.toml index a7313e33f..1bdfe9bc9 100644 --- a/examples/stm32wl55/Cargo.toml +++ b/examples/stm32wl55/Cargo.toml | |||
| @@ -19,7 +19,7 @@ defmt-error = [] | |||
| 19 | [dependencies] | 19 | [dependencies] |
| 20 | embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-trace"] } | 20 | embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-trace"] } |
| 21 | embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] } | 21 | embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] } |
| 22 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "defmt-trace", "stm32wl55jc_cm4", "time-driver-tim2", "memory-x"] } | 22 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "defmt-trace", "stm32wl55jc_cm4", "time-driver-tim2", "memory-x", "subghz"] } |
| 23 | embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" } | 23 | embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" } |
| 24 | 24 | ||
| 25 | defmt = "0.2.0" | 25 | defmt = "0.2.0" |
diff --git a/examples/stm32wl55/src/bin/subghz.rs b/examples/stm32wl55/src/bin/subghz.rs new file mode 100644 index 000000000..1e406886a --- /dev/null +++ b/examples/stm32wl55/src/bin/subghz.rs | |||
| @@ -0,0 +1,129 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![macro_use] | ||
| 4 | #![allow(dead_code)] | ||
| 5 | #![feature(generic_associated_types)] | ||
| 6 | #![feature(type_alias_impl_trait)] | ||
| 7 | |||
| 8 | #[path = "../example_common.rs"] | ||
| 9 | mod example_common; | ||
| 10 | |||
| 11 | use embassy::{traits::gpio::WaitForRisingEdge, util::InterruptFuture}; | ||
| 12 | use embassy_stm32::{ | ||
| 13 | dbgmcu::Dbgmcu, | ||
| 14 | dma::NoDma, | ||
| 15 | exti::ExtiInput, | ||
| 16 | gpio::{Input, Level, Output, Pull, Speed}, | ||
| 17 | interrupt, | ||
| 18 | subghz::*, | ||
| 19 | Peripherals, | ||
| 20 | }; | ||
| 21 | use embedded_hal::digital::v2::OutputPin; | ||
| 22 | use example_common::unwrap; | ||
| 23 | |||
| 24 | const PING_DATA: &str = "PING"; | ||
| 25 | const DATA_LEN: u8 = PING_DATA.len() as u8; | ||
| 26 | const PING_DATA_BYTES: &[u8] = PING_DATA.as_bytes(); | ||
| 27 | const PREAMBLE_LEN: u16 = 5 * 8; | ||
| 28 | |||
| 29 | const RF_FREQ: RfFreq = RfFreq::from_frequency(867_500_000); | ||
| 30 | |||
| 31 | const SYNC_WORD: [u8; 8] = [0x79, 0x80, 0x0C, 0xC0, 0x29, 0x95, 0xF8, 0x4A]; | ||
| 32 | const SYNC_WORD_LEN: u8 = SYNC_WORD.len() as u8; | ||
| 33 | const SYNC_WORD_LEN_BITS: u8 = SYNC_WORD_LEN * 8; | ||
| 34 | |||
| 35 | const TX_BUF_OFFSET: u8 = 128; | ||
| 36 | const RX_BUF_OFFSET: u8 = 0; | ||
| 37 | const LORA_PACKET_PARAMS: LoRaPacketParams = LoRaPacketParams::new() | ||
| 38 | .set_crc_en(true) | ||
| 39 | .set_preamble_len(PREAMBLE_LEN) | ||
| 40 | .set_payload_len(DATA_LEN) | ||
| 41 | .set_invert_iq(false) | ||
| 42 | .set_header_type(HeaderType::Fixed); | ||
| 43 | |||
| 44 | const LORA_MOD_PARAMS: LoRaModParams = LoRaModParams::new() | ||
| 45 | .set_bw(LoRaBandwidth::Bw125) | ||
| 46 | .set_cr(CodingRate::Cr45) | ||
| 47 | .set_ldro_en(true) | ||
| 48 | .set_sf(SpreadingFactor::Sf7); | ||
| 49 | |||
| 50 | // configuration for +10 dBm output power | ||
| 51 | // see table 35 "PA optimal setting and operating modes" | ||
| 52 | const PA_CONFIG: PaConfig = PaConfig::new() | ||
| 53 | .set_pa_duty_cycle(0x1) | ||
| 54 | .set_hp_max(0x0) | ||
| 55 | .set_pa(PaSel::Lp); | ||
| 56 | |||
| 57 | const TCXO_MODE: TcxoMode = TcxoMode::new() | ||
| 58 | .set_txco_trim(TcxoTrim::Volts1pt7) | ||
| 59 | .set_timeout(Timeout::from_duration_sat( | ||
| 60 | core::time::Duration::from_millis(10), | ||
| 61 | )); | ||
| 62 | |||
| 63 | const TX_PARAMS: TxParams = TxParams::new() | ||
| 64 | .set_power(0x0D) | ||
| 65 | .set_ramp_time(RampTime::Micros40); | ||
| 66 | |||
| 67 | fn config() -> embassy_stm32::Config { | ||
| 68 | let mut config = embassy_stm32::Config::default(); | ||
| 69 | config.rcc = config.rcc.clock_src(embassy_stm32::rcc::ClockSrc::HSE32); | ||
| 70 | config | ||
| 71 | } | ||
| 72 | |||
| 73 | #[embassy::main(config = "config()")] | ||
| 74 | async fn main(_spawner: embassy::executor::Spawner, p: Peripherals) { | ||
| 75 | unsafe { | ||
| 76 | Dbgmcu::enable_all(); | ||
| 77 | } | ||
| 78 | |||
| 79 | let mut led1 = Output::new(p.PB15, Level::High, Speed::Low); | ||
| 80 | let mut led2 = Output::new(p.PB9, Level::Low, Speed::Low); | ||
| 81 | let mut led3 = Output::new(p.PB11, Level::Low, Speed::Low); | ||
| 82 | |||
| 83 | let button = Input::new(p.PA0, Pull::Up); | ||
| 84 | let mut pin = ExtiInput::new(button, p.EXTI0); | ||
| 85 | |||
| 86 | let mut radio_irq = interrupt::take!(SUBGHZ_RADIO); | ||
| 87 | let mut radio = SubGhz::new(p.SUBGHZSPI, p.PA5, p.PA7, p.PA6, NoDma, NoDma); | ||
| 88 | |||
| 89 | defmt::info!("Radio ready for use"); | ||
| 90 | |||
| 91 | unwrap!(led1.set_low()); | ||
| 92 | |||
| 93 | unwrap!(led2.set_high()); | ||
| 94 | |||
| 95 | unwrap!(radio.set_standby(StandbyClk::Rc)); | ||
| 96 | unwrap!(radio.set_tcxo_mode(&TCXO_MODE)); | ||
| 97 | unwrap!(radio.set_standby(StandbyClk::Hse)); | ||
| 98 | unwrap!(radio.set_regulator_mode(RegMode::Ldo)); | ||
| 99 | unwrap!(radio.set_buffer_base_address(TX_BUF_OFFSET, RX_BUF_OFFSET)); | ||
| 100 | unwrap!(radio.set_pa_config(&PA_CONFIG)); | ||
| 101 | unwrap!(radio.set_pa_ocp(Ocp::Max60m)); | ||
| 102 | unwrap!(radio.set_tx_params(&TX_PARAMS)); | ||
| 103 | unwrap!(radio.set_packet_type(PacketType::LoRa)); | ||
| 104 | unwrap!(radio.set_lora_sync_word(LoRaSyncWord::Public)); | ||
| 105 | unwrap!(radio.set_lora_mod_params(&LORA_MOD_PARAMS)); | ||
| 106 | unwrap!(radio.set_lora_packet_params(&LORA_PACKET_PARAMS)); | ||
| 107 | unwrap!(radio.calibrate_image(CalibrateImage::ISM_863_870)); | ||
| 108 | unwrap!(radio.set_rf_frequency(&RF_FREQ)); | ||
| 109 | |||
| 110 | defmt::info!("Status: {:?}", unwrap!(radio.status())); | ||
| 111 | |||
| 112 | unwrap!(led2.set_low()); | ||
| 113 | |||
| 114 | loop { | ||
| 115 | pin.wait_for_rising_edge().await; | ||
| 116 | unwrap!(led3.set_high()); | ||
| 117 | unwrap!(radio.set_irq_cfg(&CfgIrq::new().irq_enable_all(Irq::TxDone))); | ||
| 118 | unwrap!(radio.write_buffer(TX_BUF_OFFSET, PING_DATA_BYTES)); | ||
| 119 | unwrap!(radio.set_tx(Timeout::DISABLED)); | ||
| 120 | |||
| 121 | InterruptFuture::new(&mut radio_irq).await; | ||
| 122 | let (_, irq_status) = unwrap!(radio.irq_status()); | ||
| 123 | if irq_status & Irq::TxDone.mask() != 0 { | ||
| 124 | defmt::info!("TX done"); | ||
| 125 | } | ||
| 126 | unwrap!(radio.clear_irq_status(irq_status)); | ||
| 127 | unwrap!(led3.set_low()); | ||
| 128 | } | ||
| 129 | } | ||
diff --git a/stm32-data b/stm32-data | |||
| Subproject bf50912000cd6c24ef5cb8cc7a0372a11645712 | Subproject 3fb217ad3eebe2d8808b8af4d04ce051c69ecb7 | ||
