aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2021-08-31 14:32:48 +0200
committerUlf Lilleengen <[email protected]>2021-09-02 10:39:56 +0200
commit7ad6280e6575fc400f84caa5c26eb3ba9770877f (patch)
tree4a888ee84f7c5c59a713e08e53d41fdd6a21f020
parentdb3cb02032fd6b861b2c39a0a354767cc72af1df (diff)
Add HAL for SubGhz peripheral for STM32 WL series
Based on the HAL from stm32wl, the peripheral driver has been modified to fit into embassy, using the embassy APIs, providing operation of the radio peripheral. The initial version does not offer any async APIs, but the example shows how the radio IRQ can be used to perform async TX of the radio.
-rw-r--r--embassy-hal-common/Cargo.toml1
-rw-r--r--embassy-hal-common/src/lib.rs1
-rw-r--r--embassy-hal-common/src/ratio.rs128
-rw-r--r--embassy-stm32/Cargo.toml1
-rw-r--r--embassy-stm32/src/lib.rs3
-rw-r--r--embassy-stm32/src/pwr/mod.rs1
-rw-r--r--embassy-stm32/src/rcc/wl5x/mod.rs16
-rw-r--r--embassy-stm32/src/spi/mod.rs1
-rw-r--r--embassy-stm32/src/spi/v2.rs3
-rw-r--r--embassy-stm32/src/subghz/bit_sync.rs160
-rw-r--r--embassy-stm32/src/subghz/cad_params.rs230
-rw-r--r--embassy-stm32/src/subghz/calibrate.rs122
-rw-r--r--embassy-stm32/src/subghz/fallback_mode.rs37
-rw-r--r--embassy-stm32/src/subghz/hse_trim.rs107
-rw-r--r--embassy-stm32/src/subghz/irq.rs292
-rw-r--r--embassy-stm32/src/subghz/lora_sync_word.rs20
-rw-r--r--embassy-stm32/src/subghz/mod.rs1679
-rw-r--r--embassy-stm32/src/subghz/mod_params.rs996
-rw-r--r--embassy-stm32/src/subghz/ocp.rs14
-rw-r--r--embassy-stm32/src/subghz/op_error.rs48
-rw-r--r--embassy-stm32/src/subghz/pa_config.rs161
-rw-r--r--embassy-stm32/src/subghz/packet_params.rs537
-rw-r--r--embassy-stm32/src/subghz/packet_status.rs279
-rw-r--r--embassy-stm32/src/subghz/packet_type.rs44
-rw-r--r--embassy-stm32/src/subghz/pkt_ctrl.rs247
-rw-r--r--embassy-stm32/src/subghz/pmode.rs27
-rw-r--r--embassy-stm32/src/subghz/pwr_ctrl.rs160
-rw-r--r--embassy-stm32/src/subghz/reg_mode.rs18
-rw-r--r--embassy-stm32/src/subghz/rf_frequency.rs138
-rw-r--r--embassy-stm32/src/subghz/rx_timeout_stop.rs21
-rw-r--r--embassy-stm32/src/subghz/sleep_cfg.rs109
-rw-r--r--embassy-stm32/src/subghz/smps.rs45
-rw-r--r--embassy-stm32/src/subghz/standby_clk.rs20
-rw-r--r--embassy-stm32/src/subghz/stats.rs184
-rw-r--r--embassy-stm32/src/subghz/status.rs202
-rw-r--r--embassy-stm32/src/subghz/tcxo_mode.rs170
-rw-r--r--embassy-stm32/src/subghz/timeout.rs469
-rw-r--r--embassy-stm32/src/subghz/tx_params.rs166
-rw-r--r--embassy-stm32/src/subghz/value_error.rs129
-rw-r--r--examples/stm32wl55/Cargo.toml2
-rw-r--r--examples/stm32wl55/src/bin/subghz.rs129
m---------stm32-data0
42 files changed, 7109 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 }
18log = { version = "0.4.11", optional = true } 18log = { version = "0.4.11", optional = true }
19cortex-m = "0.7.1" 19cortex-m = "0.7.1"
20usb-device = "0.2.7" 20usb-device = "0.2.7"
21num-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..d2f6daba5 100644
--- a/embassy-hal-common/src/lib.rs
+++ b/embassy-hal-common/src/lib.rs
@@ -8,6 +8,7 @@ mod macros;
8pub mod peripheral; 8pub mod peripheral;
9pub mod ring_buffer; 9pub mod ring_buffer;
10pub mod usb; 10pub mod usb;
11pub mod ratio;
11 12
12/// Low power blocking wait loop using WFE/SEV. 13/// Low power blocking wait loop using WFE/SEV.
13pub fn low_power_wait_until(mut condition: impl FnMut() -> bool) { 14pub fn low_power_wait_until(mut condition: impl FnMut() -> bool) {
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 @@
1use core::ops::{Add, Div, Mul};
2use 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))]
7pub struct Ratio<T> {
8 /// Numerator.
9 numer: T,
10 /// Denominator.
11 denom: T,
12}
13
14impl<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
34impl<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
42impl<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
52impl<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
62impl<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
72macro_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
90impl_from_for_float!(u8);
91impl_from_for_float!(u16);
92impl_from_for_float!(u32);
93impl_from_for_float!(u64);
94impl_from_for_float!(u128);
95impl_from_for_float!(i8);
96impl_from_for_float!(i16);
97impl_from_for_float!(i32);
98impl_from_for_float!(i64);
99impl_from_for_float!(i128);
100
101impl<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)]
108mod 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 = [ ]
44sdmmc-rs = ["embedded-sdmmc"] 44sdmmc-rs = ["embedded-sdmmc"]
45net = ["embassy-net", "vcell"] 45net = ["embassy-net", "vcell"]
46memory-x = ["stm32-metapac/memory-x"] 46memory-x = ["stm32-metapac/memory-x"]
47subghz = []
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/lib.rs b/embassy-stm32/src/lib.rs
index 503d10f57..e0e77a59d 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -51,6 +51,9 @@ pub mod spi;
51#[cfg(usart)] 51#[cfg(usart)]
52pub mod usart; 52pub mod usart;
53 53
54#[cfg(feature = "subghz")]
55pub mod subghz;
56
54// This must go last, so that it sees all the impl_foo! macros defined earlier. 57// This must go last, so that it sees all the impl_foo! macros defined earlier.
55mod generated { 58mod generated {
56 59
diff --git a/embassy-stm32/src/pwr/mod.rs b/embassy-stm32/src/pwr/mod.rs
index 1bb104bd3..bc167b016 100644
--- a/embassy-stm32/src/pwr/mod.rs
+++ b/embassy-stm32/src/pwr/mod.rs
@@ -1,5 +1,6 @@
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")]
3mod _version; 4mod _version;
4 5
5pub use _version::*; 6pub use _version::*;
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::*;
2use crate::pac; 2use crate::pac;
3use crate::peripherals::{self, RCC}; 3use crate::peripherals::{self, RCC};
4use crate::rcc::{get_freqs, set_freqs, Clocks}; 4use crate::rcc::{get_freqs, set_freqs, Clocks};
5use crate::time::Hertz;
6use crate::time::U32Ext; 5use crate::time::U32Ext;
7use core::marker::PhantomData; 6use core::marker::PhantomData;
8use embassy::util::Unborrow; 7use embassy::util::Unborrow;
@@ -16,10 +15,12 @@ use embassy_hal_common::unborrow;
16/// HSI speed 15/// HSI speed
17pub const HSI_FREQ: u32 = 16_000_000; 16pub const HSI_FREQ: u32 = 16_000_000;
18 17
18pub 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)]
21pub enum ClockSrc { 22pub 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
10use crate::gpio::Pin; 10use crate::gpio::Pin;
11 11
12#[derive(Debug)]
12#[cfg_attr(feature = "defmt", derive(defmt::Format))] 13#[cfg_attr(feature = "defmt", derive(defmt::Format))]
13pub enum Error { 14pub 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))]
9pub struct BitSync {
10 val: u8,
11}
12
13impl 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
150impl From<BitSync> for u8 {
151 fn from(bs: BitSync) -> Self {
152 bs.val
153 }
154}
155
156impl 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 @@
1use 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)]
9pub 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)]
28pub 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))]
66pub struct CadParams {
67 buf: [u8; 8],
68}
69
70impl 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
226impl 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))]
8pub struct CalibrateImage(pub(crate) u8, pub(crate) u8);
9
10impl 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
72impl 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)]
86pub 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
103impl 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)]
9pub 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
18impl From<FallbackMode> for u8 {
19 fn from(fm: FallbackMode) -> Self {
20 fm as u8
21 }
22}
23
24impl 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 @@
1use 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))]
11pub struct HseTrim {
12 val: u8,
13}
14
15impl 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
97impl From<HseTrim> for u8 {
98 fn from(ht: HseTrim) -> Self {
99 ht.val
100 }
101}
102
103impl 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))]
6pub 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
23impl 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))]
41pub 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
96impl 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))]
117pub struct CfgIrq {
118 buf: [u8; 9],
119}
120
121impl 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
288impl 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))]
6pub enum LoRaSyncWord {
7 /// LoRa private network.
8 Private,
9 /// LoRa public network.
10 Public,
11}
12
13impl 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..b1ed078fd
--- /dev/null
+++ b/embassy-stm32/src/subghz/mod.rs
@@ -0,0 +1,1679 @@
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
13mod bit_sync;
14mod cad_params;
15mod calibrate;
16mod fallback_mode;
17mod hse_trim;
18mod irq;
19mod lora_sync_word;
20mod mod_params;
21mod ocp;
22mod op_error;
23mod pa_config;
24mod packet_params;
25mod packet_status;
26mod packet_type;
27mod pkt_ctrl;
28mod pmode;
29mod pwr_ctrl;
30mod reg_mode;
31mod rf_frequency;
32mod rx_timeout_stop;
33mod sleep_cfg;
34mod smps;
35mod standby_clk;
36mod stats;
37mod status;
38mod tcxo_mode;
39mod timeout;
40mod tx_params;
41mod value_error;
42
43pub use bit_sync::BitSync;
44pub use cad_params::{CadParams, ExitMode, NbCadSymbol};
45pub use calibrate::{Calibrate, CalibrateImage};
46pub use fallback_mode::FallbackMode;
47pub use hse_trim::HseTrim;
48pub use irq::{CfgIrq, Irq, IrqLine};
49pub use lora_sync_word::LoRaSyncWord;
50pub use mod_params::BpskModParams;
51pub use mod_params::{CodingRate, LoRaBandwidth, LoRaModParams, SpreadingFactor};
52pub use mod_params::{FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape};
53pub use ocp::Ocp;
54pub use op_error::OpError;
55pub use pa_config::{PaConfig, PaSel};
56pub use packet_params::{
57 AddrComp, BpskPacketParams, CrcType, GenericPacketParams, HeaderType, LoRaPacketParams,
58 PreambleDetection,
59};
60pub use packet_status::{FskPacketStatus, LoRaPacketStatus};
61pub use packet_type::PacketType;
62pub use pkt_ctrl::{InfSeqSel, PktCtrl};
63pub use pmode::PMode;
64pub use pwr_ctrl::{CurrentLim, PwrCtrl};
65pub use reg_mode::RegMode;
66pub use rf_frequency::RfFreq;
67pub use rx_timeout_stop::RxTimeoutStop;
68pub use sleep_cfg::{SleepCfg, Startup};
69pub use smps::SmpsDrv;
70pub use standby_clk::StandbyClk;
71pub use stats::{FskStats, LoRaStats, Stats};
72pub use status::{CmdStatus, Status, StatusMode};
73pub use tcxo_mode::{TcxoMode, TcxoTrim};
74pub use timeout::Timeout;
75pub use tx_params::{RampTime, TxParams};
76pub use value_error::ValueError;
77
78use embassy_hal_common::ratio::Ratio;
79
80use 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};
88use embassy::util::Unborrow;
89use embedded_hal::{
90 blocking::spi::{Transfer, Write},
91 spi::MODE_0,
92};
93
94/// Passthrough for SPI errors (for now)
95pub type Error = crate::spi::Error;
96
97struct Nss {
98 _priv: (),
99}
100
101impl 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
128impl 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]
145unsafe 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]
164fn 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/// Returns `true` if the radio is busy or NSS is low.
172///
173/// See RM0461 Rev 4 section 5.3 page 181 "Radio busy management" for more
174/// details.
175#[inline]
176fn rfbusyms() -> bool {
177 let pwr = pac::PWR;
178 unsafe { pwr.sr2().read().rfbusyms() == pac::pwr::vals::Rfbusyms::BUSY }
179}
180
181/// Sub-GHz radio peripheral
182pub struct SubGhz<'d, Tx, Rx> {
183 spi: Spi<'d, SUBGHZSPI, Tx, Rx>,
184}
185
186impl<'d, Tx, Rx> SubGhz<'d, Tx, Rx> {
187 fn pulse_radio_reset() {
188 let rcc = pac::RCC;
189 unsafe {
190 rcc.csr().modify(|w| w.set_rfrst(true));
191 rcc.csr().modify(|w| w.set_rfrst(false));
192 }
193 }
194
195 // TODO: This should be replaced with async handling based on IRQ
196 fn poll_not_busy(&self) {
197 let mut count: u32 = 1_000_000;
198 while rfbusys() {
199 count -= 1;
200 if count == 0 {
201 let pwr = pac::PWR;
202 unsafe {
203 panic!(
204 "rfbusys timeout pwr.sr2=0x{:X} pwr.subghzspicr=0x{:X} pwr.cr1=0x{:X}",
205 pwr.sr2().read().0,
206 pwr.subghzspicr().read().0,
207 pwr.cr1().read().0
208 );
209 }
210 }
211 }
212 }
213
214 /// Create a new sub-GHz radio driver from a peripheral.
215 ///
216 /// This will reset the radio and the SPI bus, and enable the peripheral
217 /// clock.
218 pub fn new(
219 peri: impl Unborrow<Target = SUBGHZSPI> + 'd,
220 sck: impl Unborrow<Target = impl SckPin<SUBGHZSPI>>,
221 mosi: impl Unborrow<Target = impl MosiPin<SUBGHZSPI>>,
222 miso: impl Unborrow<Target = impl MisoPin<SUBGHZSPI>>,
223 txdma: impl Unborrow<Target = Tx>,
224 rxdma: impl Unborrow<Target = Rx>,
225 ) -> Self {
226 Self::pulse_radio_reset();
227
228 // see RM0453 rev 1 section 7.2.13 page 291
229 // The SUBGHZSPI_SCK frequency is obtained by PCLK3 divided by two.
230 // The SUBGHZSPI_SCK clock maximum speed must not exceed 16 MHz.
231 let clk = Hertz(core::cmp::min(SUBGHZSPI::frequency().0 / 2, 16_000_000));
232 let mut config = SpiConfig::default();
233 config.mode = MODE_0;
234 config.byte_order = ByteOrder::MsbFirst;
235 let spi = Spi::new(peri, sck, mosi, miso, txdma, rxdma, clk, config);
236
237 unsafe { wakeup() };
238
239 SubGhz { spi }
240 }
241}
242
243impl<'d> SubGhz<'d, NoDma, NoDma> {
244 fn read(&mut self, opcode: OpCode, data: &mut [u8]) -> Result<(), Error> {
245 self.poll_not_busy();
246 {
247 let _nss: Nss = Nss::new();
248 self.spi.write(&[opcode as u8])?;
249 self.spi.transfer(data)?;
250 }
251 self.poll_not_busy();
252 Ok(())
253 }
254
255 /// Read one byte from the sub-Ghz radio.
256 fn read_1(&mut self, opcode: OpCode) -> Result<u8, Error> {
257 let mut buf: [u8; 1] = [0; 1];
258 self.read(opcode, &mut buf)?;
259 Ok(buf[0])
260 }
261
262 /// Read a fixed number of bytes from the sub-Ghz radio.
263 fn read_n<const N: usize>(&mut self, opcode: OpCode) -> Result<[u8; N], Error> {
264 let mut buf: [u8; N] = [0; N];
265 self.read(opcode, &mut buf)?;
266 Ok(buf)
267 }
268
269 fn write(&mut self, data: &[u8]) -> Result<(), Error> {
270 self.poll_not_busy();
271 {
272 let _nss: Nss = Nss::new();
273 self.spi.write(data)?;
274 }
275 self.poll_not_busy();
276 Ok(())
277 }
278
279 pub fn write_buffer(&mut self, offset: u8, data: &[u8]) -> Result<(), Error> {
280 self.poll_not_busy();
281 {
282 let _nss: Nss = Nss::new();
283 self.spi.write(&[OpCode::WriteBuffer as u8, offset])?;
284 self.spi.write(data)?;
285 }
286 self.poll_not_busy();
287
288 Ok(())
289 }
290
291 /// Read the radio buffer at the given offset.
292 ///
293 /// The offset and length of a received packet is provided by
294 /// [`rx_buffer_status`](Self::rx_buffer_status).
295 pub fn read_buffer(&mut self, offset: u8, buf: &mut [u8]) -> Result<Status, Error> {
296 let mut status_buf: [u8; 1] = [0];
297
298 self.poll_not_busy();
299 {
300 let _nss: Nss = Nss::new();
301 self.spi.write(&[OpCode::ReadBuffer as u8, offset])?;
302 self.spi.transfer(&mut status_buf)?;
303 self.spi.transfer(buf)?;
304 }
305 self.poll_not_busy();
306
307 Ok(status_buf[0].into())
308 }
309}
310
311// helper to pack register writes into a single buffer to avoid multiple DMA
312// transfers
313macro_rules! wr_reg {
314 [$reg:ident, $($data:expr),+] => {
315 &[
316 OpCode::WriteRegister as u8,
317 Register::$reg.address().to_be_bytes()[0],
318 Register::$reg.address().to_be_bytes()[1],
319 $($data),+
320 ]
321 };
322}
323
324// 5.8.2
325/// Register access
326impl<'d> SubGhz<'d, NoDma, NoDma> {
327 // register write with variable length data
328 fn write_register(&mut self, register: Register, data: &[u8]) -> Result<(), Error> {
329 let addr: [u8; 2] = register.address().to_be_bytes();
330
331 self.poll_not_busy();
332 {
333 let _nss: Nss = Nss::new();
334 self.spi
335 .write(&[OpCode::WriteRegister as u8, addr[0], addr[1]])?;
336 self.spi.write(data)?;
337 }
338 self.poll_not_busy();
339
340 Ok(())
341 }
342
343 /// Set the LoRa bit synchronization.
344 pub fn set_bit_sync(&mut self, bs: BitSync) -> Result<(), Error> {
345 self.write(wr_reg![GBSYNC, bs.as_bits()])
346 }
347
348 /// Set the generic packet control register.
349 pub fn set_pkt_ctrl(&mut self, pkt_ctrl: PktCtrl) -> Result<(), Error> {
350 self.write(wr_reg![GPKTCTL1A, pkt_ctrl.as_bits()])
351 }
352
353 /// Set the initial value for generic packet whitening.
354 ///
355 /// This sets the first 8 bits, the 9th bit is set with
356 /// [`set_pkt_ctrl`](Self::set_pkt_ctrl).
357 pub fn set_init_whitening(&mut self, init: u8) -> Result<(), Error> {
358 self.write(wr_reg![GWHITEINIRL, init])
359 }
360
361 /// Set the initial value for generic packet CRC polynomial.
362 ///
363 /// # Example
364 ///
365 /// ```no_run
366 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
367 /// sg.set_crc_polynomial(0x1D0F)?;
368 /// # Ok::<(), embassy_stm32::subghz::Error>(())
369 /// ```
370 pub fn set_crc_polynomial(&mut self, polynomial: u16) -> Result<(), Error> {
371 let bytes: [u8; 2] = polynomial.to_be_bytes();
372 self.write(wr_reg![GCRCINIRH, bytes[0], bytes[1]])
373 }
374
375 /// Set the generic packet CRC polynomial.
376 ///
377 /// # Example
378 ///
379 /// ```no_run
380 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
381 /// sg.set_initial_crc_polynomial(0x1021)?;
382 /// # Ok::<(), embassy_stm32::subghz::Error>(())
383 /// ```
384 pub fn set_initial_crc_polynomial(&mut self, polynomial: u16) -> Result<(), Error> {
385 let bytes: [u8; 2] = polynomial.to_be_bytes();
386 self.write(wr_reg![GCRCPOLRH, bytes[0], bytes[1]])
387 }
388
389 /// Set the synchronization word registers.
390 ///
391 /// # Example
392 ///
393 /// ```no_run
394 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
395 /// const SYNC_WORD: [u8; 8] = [0x79, 0x80, 0x0C, 0xC0, 0x29, 0x95, 0xF8, 0x4A];
396 ///
397 /// sg.set_sync_word(&SYNC_WORD)?;
398 /// # Ok::<(), embassy_stm32::subghz::Error>(())
399 /// ```
400 pub fn set_sync_word(&mut self, sync_word: &[u8; 8]) -> Result<(), Error> {
401 self.write_register(Register::GSYNC7, sync_word)
402 }
403
404 /// Set the LoRa synchronization word registers.
405 ///
406 /// # Example
407 ///
408 /// ```no_run
409 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
410 /// use embassy_stm32::subghz::{LoRaSyncWord, PacketType};
411 ///
412 /// sg.set_packet_type(PacketType::LoRa)?;
413 /// sg.set_lora_sync_word(LoRaSyncWord::Public)?;
414 /// # Ok::<(), embassy_stm32::subghz::Error>(())
415 /// ```
416 pub fn set_lora_sync_word(&mut self, sync_word: LoRaSyncWord) -> Result<(), Error> {
417 let bytes: [u8; 2] = sync_word.bytes();
418 self.write(wr_reg![LSYNCH, bytes[0], bytes[1]])
419 }
420
421 /// Set the RX gain control.
422 ///
423 /// # Example
424 ///
425 /// ```no_run
426 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
427 /// use embassy_stm32::subghz::PMode;
428 ///
429 /// sg.set_rx_gain(PMode::Boost)?;
430 /// # Ok::<(), embassy_stm32::subghz::Error>(())
431 /// ```
432 pub fn set_rx_gain(&mut self, pmode: PMode) -> Result<(), Error> {
433 self.write(wr_reg![RXGAINC, pmode as u8])
434 }
435
436 /// Set the power amplifier over current protection.
437 ///
438 /// # Example
439 ///
440 /// Maximum 60mA for LP PA mode.
441 ///
442 /// ```no_run
443 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
444 /// use embassy_stm32::subghz::Ocp;
445 ///
446 /// sg.set_pa_ocp(Ocp::Max60m)?;
447 /// # Ok::<(), embassy_stm32::subghz::Error>(())
448 /// ```
449 ///
450 /// Maximum 60mA for HP PA mode.
451 ///
452 /// ```no_run
453 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
454 /// use embassy_stm32::subghz::Ocp;
455 ///
456 /// sg.set_pa_ocp(Ocp::Max140m)?;
457 /// # Ok::<(), embassy_stm32::subghz::Error>(())
458 /// ```
459 pub fn set_pa_ocp(&mut self, ocp: Ocp) -> Result<(), Error> {
460 self.write(wr_reg![PAOCP, ocp as u8])
461 }
462
463 /// Set the HSE32 crystal OSC_IN load capaitor trimming.
464 ///
465 /// # Example
466 ///
467 /// Set the trim to the lowest value.
468 ///
469 /// ```no_run
470 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
471 /// use embassy_stm32::subghz::HseTrim;
472 ///
473 /// sg.set_hse_in_trim(HseTrim::MIN)?;
474 /// # Ok::<(), embassy_stm32::subghz::Error>(())
475 /// ```
476 pub fn set_hse_in_trim(&mut self, trim: HseTrim) -> Result<(), Error> {
477 self.write(wr_reg![HSEINTRIM, trim.into()])
478 }
479
480 /// Set the HSE32 crystal OSC_OUT load capaitor trimming.
481 ///
482 /// # Example
483 ///
484 /// Set the trim to the lowest value.
485 ///
486 /// ```no_run
487 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
488 /// use embassy_stm32::subghz::HseTrim;
489 ///
490 /// sg.set_hse_out_trim(HseTrim::MIN)?;
491 /// # Ok::<(), embassy_stm32::subghz::Error>(())
492 /// ```
493 pub fn set_hse_out_trim(&mut self, trim: HseTrim) -> Result<(), Error> {
494 self.write(wr_reg![HSEOUTTRIM, trim.into()])
495 }
496
497 /// Set the SMPS clock detection enabled.
498 ///
499 /// SMPS clock detection must be enabled fore enabling the SMPS.
500 pub fn set_smps_clock_det_en(&mut self, en: bool) -> Result<(), Error> {
501 self.write(wr_reg![SMPSC0, (en as u8) << 6])
502 }
503
504 /// Set the power current limiting.
505 pub fn set_pwr_ctrl(&mut self, pwr_ctrl: PwrCtrl) -> Result<(), Error> {
506 self.write(wr_reg![PC, pwr_ctrl.as_bits()])
507 }
508
509 /// Set the maximum SMPS drive capability.
510 pub fn set_smps_drv(&mut self, drv: SmpsDrv) -> Result<(), Error> {
511 self.write(wr_reg![SMPSC2, (drv as u8) << 1])
512 }
513}
514
515// 5.8.3
516/// Operating mode commands
517impl<'d> SubGhz<'d, NoDma, NoDma> {
518 /// Put the radio into sleep mode.
519 ///
520 /// This command is only accepted in standby mode.
521 /// The cfg argument allows some optional functions to be maintained
522 /// in sleep mode.
523 ///
524 /// # Safety
525 ///
526 /// 1. After the `set_sleep` command, the sub-GHz radio NSS must not go low
527 /// for 500 μs.
528 /// No reason is provided, the reference manual (RM0453 rev 2) simply
529 /// says "you must".
530 /// 2. The radio cannot be used while in sleep mode.
531 /// 3. The radio must be woken up with [`wakeup`] before resuming use.
532 ///
533 /// # Example
534 ///
535 /// Put the radio into sleep mode.
536 ///
537 /// ```no_run
538 /// # let dp = unsafe { embassy_stm32::pac::Peripherals::steal() };
539 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
540 /// use embassy_stm32::{
541 /// subghz::{wakeup, SleepCfg, StandbyClk},
542 /// };
543 ///
544 /// sg.set_standby(StandbyClk::Rc)?;
545 /// unsafe { sg.set_sleep(SleepCfg::default())? };
546 /// embassy::time::Timer::after(embassy::time::Duration::from_micros(500)).await;
547 /// unsafe { wakeup() };
548 /// # Ok::<(), embassy_stm32::subghz::Error>(())
549 /// ```
550 pub unsafe fn set_sleep(&mut self, cfg: SleepCfg) -> Result<(), Error> {
551 self.write(&[OpCode::SetSleep as u8, u8::from(cfg)])
552 }
553
554 /// Put the radio into standby mode.
555 ///
556 /// # Examples
557 ///
558 /// Put the radio into standby mode using the RC 13MHz clock.
559 ///
560 /// ```no_run
561 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
562 /// use embassy_stm32::subghz::StandbyClk;
563 ///
564 /// sg.set_standby(StandbyClk::Rc)?;
565 /// # Ok::<(), embassy_stm32::subghz::Error>(())
566 /// ```
567 ///
568 /// Put the radio into standby mode using the HSE32 clock.
569 ///
570 /// ```no_run
571 /// # let mut dp = unsafe { embassy_stm32::pac::Peripherals::steal() };
572 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
573 /// use embassy_stm32::subghz::StandbyClk;
574 ///
575 /// dp.RCC
576 /// .cr
577 /// .modify(|_, w| w.hseon().enabled().hsebyppwr().vddtcxo());
578 /// while dp.RCC.cr.read().hserdy().is_not_ready() {}
579 ///
580 /// sg.set_standby(StandbyClk::Hse)?;
581 /// # Ok::<(), embassy_stm32::subghz::Error>(())
582 /// ```
583 pub fn set_standby(&mut self, standby_clk: StandbyClk) -> Result<(), Error> {
584 self.write(&[OpCode::SetStandby as u8, u8::from(standby_clk)])
585 }
586
587 /// Put the subghz radio into frequency synthesis mode.
588 ///
589 /// The RF-PLL frequency must be set with [`set_rf_frequency`] before using
590 /// this command.
591 ///
592 /// Check the datasheet for more information, this is a test command but
593 /// I honestly do not see any use for it. Please update this description
594 /// if you know more than I do.
595 ///
596 /// # Example
597 ///
598 /// ```no_run
599 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
600 /// use embassy_stm32::subghz::RfFreq;
601 ///
602 /// sg.set_rf_frequency(&RfFreq::from_frequency(915_000_000))?;
603 /// sg.set_fs()?;
604 /// # Ok::<(), embassy_stm32::subghz::Error>(())
605 /// ```
606 ///
607 /// [`set_rf_frequency`]: crate::subghz::SubGhz::set_rf_frequency
608 pub fn set_fs(&mut self) -> Result<(), Error> {
609 self.write(&[OpCode::SetFs.into()])
610 }
611
612 /// Set the sub-GHz radio in TX mode.
613 ///
614 /// # Example
615 ///
616 /// Transmit with no timeout.
617 ///
618 /// ```no_run
619 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
620 /// use embassy_stm32::subghz::Timeout;
621 ///
622 /// sg.set_tx(Timeout::DISABLED)?;
623 /// # Ok::<(), embassy_stm32::subghz::Error>(())
624 /// ```
625 pub fn set_tx(&mut self, timeout: Timeout) -> Result<(), Error> {
626 let tobits: u32 = timeout.into_bits();
627 self.write(&[
628 OpCode::SetTx.into(),
629 (tobits >> 16) as u8,
630 (tobits >> 8) as u8,
631 tobits as u8,
632 ])
633 }
634
635 /// Set the sub-GHz radio in RX mode.
636 ///
637 /// # Example
638 ///
639 /// Receive with a 1 second timeout.
640 ///
641 /// ```no_run
642 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
643 /// use core::time::Duration;
644 /// use embassy_stm32::subghz::Timeout;
645 ///
646 /// sg.set_rx(Timeout::from_duration_sat(Duration::from_secs(1)))?;
647 /// # Ok::<(), embassy_stm32::subghz::Error>(())
648 /// ```
649 pub fn set_rx(&mut self, timeout: Timeout) -> Result<(), Error> {
650 let tobits: u32 = timeout.into_bits();
651 self.write(&[
652 OpCode::SetRx.into(),
653 (tobits >> 16) as u8,
654 (tobits >> 8) as u8,
655 tobits as u8,
656 ])
657 }
658
659 /// Allows selection of the receiver event which stops the RX timeout timer.
660 ///
661 /// # Example
662 ///
663 /// Set the RX timeout timer to stop on preamble detection.
664 ///
665 /// ```no_run
666 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
667 /// use embassy_stm32::subghz::RxTimeoutStop;
668 ///
669 /// sg.set_rx_timeout_stop(RxTimeoutStop::Preamble)?;
670 /// # Ok::<(), embassy_stm32::subghz::Error>(())
671 /// ```
672 pub fn set_rx_timeout_stop(&mut self, rx_timeout_stop: RxTimeoutStop) -> Result<(), Error> {
673 self.write(&[
674 OpCode::SetStopRxTimerOnPreamble.into(),
675 rx_timeout_stop.into(),
676 ])
677 }
678
679 /// Put the radio in non-continuous RX mode.
680 ///
681 /// This command must be sent in Standby mode.
682 /// This command is only functional with FSK and LoRa packet type.
683 ///
684 /// The following steps are performed:
685 /// 1. Save sub-GHz radio configuration.
686 /// 2. Enter Receive mode and listen for a preamble for the specified `rx_period`.
687 /// 3. Upon the detection of a preamble, the `rx_period` timeout is stopped
688 /// and restarted with the value 2 x `rx_period` + `sleep_period`.
689 /// During this new period, the sub-GHz radio looks for the detection of
690 /// a synchronization word when in (G)FSK modulation mode,
691 /// or a header when in LoRa modulation mode.
692 /// 4. If no packet is received during the listen period defined by
693 /// 2 x `rx_period` + `sleep_period`, the sleep mode is entered for a
694 /// duration of `sleep_period`. At the end of the receive period,
695 /// the sub-GHz radio takes some time to save the context before starting
696 /// the sleep period.
697 /// 5. After the sleep period, a new listening period is automatically
698 /// started. The sub-GHz radio restores the sub-GHz radio configuration
699 /// and continuous with step 2.
700 ///
701 /// The listening mode is terminated in one of the following cases:
702 /// * if a packet is received during the listening period: the sub-GHz radio
703 /// issues a [`RxDone`] interrupt and enters standby mode.
704 /// * if [`set_standby`] is sent during the listening period or after the
705 /// sub-GHz has been requested to exit sleep mode by sub-GHz radio SPI NSS
706 ///
707 /// # Example
708 ///
709 /// ```no_run
710 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
711 /// use core::time::Duration;
712 /// use embassy_stm32::subghz::{StandbyClk, Timeout};
713 ///
714 /// const RX_PERIOD: Timeout = Timeout::from_duration_sat(Duration::from_millis(100));
715 /// const SLEEP_PERIOD: Timeout = Timeout::from_duration_sat(Duration::from_secs(1));
716 ///
717 /// sg.set_standby(StandbyClk::Rc)?;
718 /// sg.set_rx_duty_cycle(RX_PERIOD, SLEEP_PERIOD)?;
719 /// # Ok::<(), embassy_stm32::subghz::Error>(())
720 /// ```
721 ///
722 /// [`RxDone`]: crate::subghz::Irq::RxDone
723 /// [`set_rf_frequency`]: crate::subghz::SubGhz::set_rf_frequency
724 /// [`set_standby`]: crate::subghz::SubGhz::set_standby
725 pub fn set_rx_duty_cycle(
726 &mut self,
727 rx_period: Timeout,
728 sleep_period: Timeout,
729 ) -> Result<(), Error> {
730 let rx_period_bits: u32 = rx_period.into_bits();
731 let sleep_period_bits: u32 = sleep_period.into_bits();
732 self.write(&[
733 OpCode::SetRxDutyCycle.into(),
734 (rx_period_bits >> 16) as u8,
735 (rx_period_bits >> 8) as u8,
736 rx_period_bits as u8,
737 (sleep_period_bits >> 16) as u8,
738 (sleep_period_bits >> 8) as u8,
739 sleep_period_bits as u8,
740 ])
741 }
742
743 /// Channel Activity Detection (CAD) with LoRa packets.
744 ///
745 /// The channel activity detection (CAD) is a specific LoRa operation mode,
746 /// where the sub-GHz radio searches for a LoRa radio signal.
747 /// After the search is completed, the Standby mode is automatically
748 /// entered, CAD is done and IRQ is generated.
749 /// When a LoRa radio signal is detected, the CAD detected IRQ is also
750 /// generated.
751 ///
752 /// The length of the search must be configured with [`set_cad_params`]
753 /// prior to calling `set_cad`.
754 ///
755 /// # Example
756 ///
757 /// ```no_run
758 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
759 /// use core::time::Duration;
760 /// use embassy_stm32::subghz::{CadParams, ExitMode, NbCadSymbol, StandbyClk, Timeout};
761 ///
762 /// const RX_PERIOD: Timeout = Timeout::from_duration_sat(Duration::from_millis(100));
763 /// const SLEEP_PERIOD: Timeout = Timeout::from_duration_sat(Duration::from_secs(1));
764 /// const CAD_PARAMS: CadParams = CadParams::new()
765 /// .set_num_symbol(NbCadSymbol::S4)
766 /// .set_det_peak(0x18)
767 /// .set_det_min(0x10)
768 /// .set_exit_mode(ExitMode::Standby);
769 ///
770 /// sg.set_standby(StandbyClk::Rc)?;
771 /// sg.set_cad_params(&CAD_PARAMS)?;
772 /// sg.set_cad()?;
773 /// # Ok::<(), embassy_stm32::subghz::Error>(())
774 /// ```
775 ///
776 /// [`set_cad_params`]: crate::subghz::SubGhz::set_cad_params
777 pub fn set_cad(&mut self) -> Result<(), Error> {
778 self.write(&[OpCode::SetCad.into()])
779 }
780
781 /// Generate a continuous transmit tone at the RF-PLL frequency.
782 ///
783 /// The sub-GHz radio remains in continuous transmit tone mode until a mode
784 /// configuration command is received.
785 ///
786 /// # Example
787 ///
788 /// ```no_run
789 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
790 /// sg.set_tx_continuous_wave()?;
791 /// # Ok::<(), embassy_stm32::subghz::Error>(())
792 /// ```
793 pub fn set_tx_continuous_wave(&mut self) -> Result<(), Error> {
794 self.write(&[OpCode::SetTxContinuousWave as u8])
795 }
796
797 /// Generate an infinite preamble at the RF-PLL frequency.
798 ///
799 /// The preamble is an alternating 0s and 1s sequence in generic (G)FSK and
800 /// (G)MSK modulations.
801 /// The preamble is symbol 0 in LoRa modulation.
802 /// The sub-GHz radio remains in infinite preamble mode until a mode
803 /// configuration command is received.
804 ///
805 /// # Example
806 ///
807 /// ```no_run
808 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
809 /// sg.set_tx_continuous_preamble()?;
810 /// # Ok::<(), embassy_stm32::subghz::Error>(())
811 /// ```
812 pub fn set_tx_continuous_preamble(&mut self) -> Result<(), Error> {
813 self.write(&[OpCode::SetTxContinuousPreamble as u8])
814 }
815}
816
817// 5.8.4
818/// Radio configuration commands
819impl<'d> SubGhz<'d, NoDma, NoDma> {
820 /// Set the packet type (modulation scheme).
821 ///
822 /// # Examples
823 ///
824 /// FSK (frequency shift keying):
825 ///
826 /// ```no_run
827 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
828 /// use embassy_stm32::subghz::PacketType;
829 ///
830 /// sg.set_packet_type(PacketType::Fsk)?;
831 /// # Ok::<(), embassy_stm32::subghz::Error>(())
832 /// ```
833 ///
834 /// LoRa (long range):
835 ///
836 /// ```no_run
837 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
838 /// use embassy_stm32::subghz::PacketType;
839 ///
840 /// sg.set_packet_type(PacketType::LoRa)?;
841 /// # Ok::<(), embassy_stm32::subghz::Error>(())
842 /// ```
843 ///
844 /// BPSK (binary phase shift keying):
845 ///
846 /// ```no_run
847 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
848 /// use embassy_stm32::subghz::PacketType;
849 ///
850 /// sg.set_packet_type(PacketType::Bpsk)?;
851 /// # Ok::<(), embassy_stm32::subghz::Error>(())
852 /// ```
853 ///
854 /// MSK (minimum shift keying):
855 ///
856 /// ```no_run
857 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
858 /// use embassy_stm32::subghz::PacketType;
859 ///
860 /// sg.set_packet_type(PacketType::Msk)?;
861 /// # Ok::<(), embassy_stm32::subghz::Error>(())
862 /// ```
863 pub fn set_packet_type(&mut self, packet_type: PacketType) -> Result<(), Error> {
864 self.write(&[OpCode::SetPacketType as u8, packet_type as u8])
865 }
866
867 /// Get the packet type.
868 ///
869 /// # Example
870 ///
871 /// ```no_run
872 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
873 /// use embassy_stm32::subghz::PacketType;
874 ///
875 /// sg.set_packet_type(PacketType::LoRa)?;
876 /// assert_eq!(sg.packet_type()?, Ok(PacketType::LoRa));
877 /// # Ok::<(), embassy_stm32::subghz::Error>(())
878 /// ```
879 pub fn packet_type(&mut self) -> Result<Result<PacketType, u8>, Error> {
880 let pkt_type: [u8; 2] = self.read_n(OpCode::GetPacketType)?;
881 Ok(PacketType::from_raw(pkt_type[1]))
882 }
883
884 /// Set the radio carrier frequency.
885 ///
886 /// # Example
887 ///
888 /// Set the frequency to 915MHz (Australia and North America).
889 ///
890 /// ```no_run
891 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
892 /// use embassy_stm32::subghz::RfFreq;
893 ///
894 /// sg.set_rf_frequency(&RfFreq::F915)?;
895 /// # Ok::<(), embassy_stm32::subghz::Error>(())
896 /// ```
897 pub fn set_rf_frequency(&mut self, freq: &RfFreq) -> Result<(), Error> {
898 self.write(freq.as_slice())
899 }
900
901 /// Set the transmit output power and the PA ramp-up time.
902 ///
903 /// # Example
904 ///
905 /// Set the output power to +10 dBm (low power mode) and a ramp up time of
906 /// 40 microseconds.
907 ///
908 /// ```no_run
909 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
910 /// use embassy_stm32::subghz::{PaConfig, PaSel, RampTime, TxParams};
911 ///
912 /// const TX_PARAMS: TxParams = TxParams::new()
913 /// .set_ramp_time(RampTime::Micros40)
914 /// .set_power(0x0D);
915 /// const PA_CONFIG: PaConfig = PaConfig::new()
916 /// .set_pa(PaSel::Lp)
917 /// .set_pa_duty_cycle(0x1)
918 /// .set_hp_max(0x0);
919 ///
920 /// sg.set_pa_config(&PA_CONFIG)?;
921 /// sg.set_tx_params(&TX_PARAMS)?;
922 /// # Ok::<(), embassy_stm32::subghz::Error>(())
923 /// ```
924 pub fn set_tx_params(&mut self, params: &TxParams) -> Result<(), Error> {
925 self.write(params.as_slice())
926 }
927
928 /// Power amplifier configuation.
929 ///
930 /// Used to customize the maximum output power and efficiency.
931 ///
932 /// # Example
933 ///
934 /// Set the output power to +22 dBm (high power mode) and a ramp up time of
935 /// 200 microseconds.
936 ///
937 /// ```no_run
938 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
939 /// use embassy_stm32::subghz::{PaConfig, PaSel, RampTime, TxParams};
940 ///
941 /// const TX_PARAMS: TxParams = TxParams::new()
942 /// .set_ramp_time(RampTime::Micros200)
943 /// .set_power(0x16);
944 /// const PA_CONFIG: PaConfig = PaConfig::new()
945 /// .set_pa(PaSel::Hp)
946 /// .set_pa_duty_cycle(0x4)
947 /// .set_hp_max(0x7);
948 ///
949 /// sg.set_pa_config(&PA_CONFIG)?;
950 /// sg.set_tx_params(&TX_PARAMS)?;
951 /// # Ok::<(), embassy_stm32::subghz::Error>(())
952 /// ```
953 pub fn set_pa_config(&mut self, pa_config: &PaConfig) -> Result<(), Error> {
954 self.write(pa_config.as_slice())
955 }
956
957 /// Operating mode to enter after a successful packet transmission or
958 /// packet reception.
959 ///
960 /// # Example
961 ///
962 /// Set the fallback mode to standby mode.
963 ///
964 /// ```no_run
965 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
966 /// use embassy_stm32::subghz::FallbackMode;
967 ///
968 /// sg.set_tx_rx_fallback_mode(FallbackMode::Standby)?;
969 /// # Ok::<(), embassy_stm32::subghz::Error>(())
970 /// ```
971 pub fn set_tx_rx_fallback_mode(&mut self, fm: FallbackMode) -> Result<(), Error> {
972 self.write(&[OpCode::SetTxRxFallbackMode as u8, fm as u8])
973 }
974
975 /// Set channel activity detection (CAD) parameters.
976 ///
977 /// # Example
978 ///
979 /// ```no_run
980 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
981 /// use core::time::Duration;
982 /// use embassy_stm32::subghz::{CadParams, ExitMode, NbCadSymbol, StandbyClk, Timeout};
983 ///
984 /// const RX_PERIOD: Timeout = Timeout::from_duration_sat(Duration::from_millis(100));
985 /// const SLEEP_PERIOD: Timeout = Timeout::from_duration_sat(Duration::from_secs(1));
986 /// const CAD_PARAMS: CadParams = CadParams::new()
987 /// .set_num_symbol(NbCadSymbol::S4)
988 /// .set_det_peak(0x18)
989 /// .set_det_min(0x10)
990 /// .set_exit_mode(ExitMode::Standby);
991 ///
992 /// sg.set_standby(StandbyClk::Rc)?;
993 /// sg.set_cad_params(&CAD_PARAMS)?;
994 /// sg.set_cad()?;
995 /// # Ok::<(), embassy_stm32::subghz::Error>(())
996 /// ```
997 pub fn set_cad_params(&mut self, params: &CadParams) -> Result<(), Error> {
998 self.write(params.as_slice())
999 }
1000
1001 /// Set the data buffer base address for the packet handling in TX and RX.
1002 ///
1003 /// There is a 256B TX buffer and a 256B RX buffer.
1004 /// These buffers are not memory mapped, they are accessed via the
1005 /// [`read_buffer`] and [`write_buffer`] methods.
1006 ///
1007 /// # Example
1008 ///
1009 /// Set the TX and RX buffer base to the start.
1010 ///
1011 /// ```no_run
1012 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1013 /// sg.set_buffer_base_address(0, 0)?;
1014 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1015 /// ```
1016 ///
1017 /// [`read_buffer`]: SubGhz::read_buffer
1018 /// [`write_buffer`]: SubGhz::write_buffer
1019 pub fn set_buffer_base_address(&mut self, tx: u8, rx: u8) -> Result<(), Error> {
1020 self.write(&[OpCode::SetBufferBaseAddress as u8, tx, rx])
1021 }
1022
1023 /// Set the (G)FSK modulation parameters.
1024 ///
1025 /// # Example
1026 ///
1027 /// ```no_run
1028 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1029 /// use embassy_stm32::subghz::{
1030 /// FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape, PacketType,
1031 /// };
1032 ///
1033 /// const BITRATE: FskBitrate = FskBitrate::from_bps(32_000);
1034 /// const PULSE_SHAPE: FskPulseShape = FskPulseShape::Bt03;
1035 /// const BW: FskBandwidth = FskBandwidth::Bw9;
1036 /// const FDEV: FskFdev = FskFdev::from_hertz(31_250);
1037 ///
1038 /// const MOD_PARAMS: FskModParams = FskModParams::new()
1039 /// .set_bitrate(BITRATE)
1040 /// .set_pulse_shape(PULSE_SHAPE)
1041 /// .set_bandwidth(BW)
1042 /// .set_fdev(FDEV);
1043 ///
1044 /// sg.set_packet_type(PacketType::Fsk)?;
1045 /// sg.set_fsk_mod_params(&MOD_PARAMS)?;
1046 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1047 /// ```
1048 pub fn set_fsk_mod_params(&mut self, params: &FskModParams) -> Result<(), Error> {
1049 self.write(params.as_slice())
1050 }
1051
1052 /// Set the LoRa modulation parameters.
1053 ///
1054 /// # Example
1055 ///
1056 /// ```no_run
1057 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1058 /// use embassy_stm32::subghz::{
1059 /// CodingRate, LoRaBandwidth, LoRaModParams, PacketType, SpreadingFactor,
1060 /// };
1061 ///
1062 /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new()
1063 /// .set_sf(SpreadingFactor::Sf7)
1064 /// .set_bw(LoRaBandwidth::Bw125)
1065 /// .set_cr(CodingRate::Cr45)
1066 /// .set_ldro_en(false);
1067 ///
1068 /// sg.set_packet_type(PacketType::LoRa)?;
1069 /// sg.set_lora_mod_params(&MOD_PARAMS)?;
1070 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1071 /// ```
1072 pub fn set_lora_mod_params(&mut self, params: &LoRaModParams) -> Result<(), Error> {
1073 self.write(params.as_slice())
1074 }
1075
1076 /// Set the BPSK modulation parameters.
1077 ///
1078 /// # Example
1079 ///
1080 /// ```no_run
1081 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1082 /// use embassy_stm32::subghz::{BpskModParams, FskBitrate, PacketType};
1083 ///
1084 /// const MOD_PARAMS: BpskModParams = BpskModParams::new().set_bitrate(FskBitrate::from_bps(600));
1085 ///
1086 /// sg.set_packet_type(PacketType::Bpsk)?;
1087 /// sg.set_bpsk_mod_params(&MOD_PARAMS)?;
1088 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1089 /// ```
1090 pub fn set_bpsk_mod_params(&mut self, params: &BpskModParams) -> Result<(), Error> {
1091 self.write(params.as_slice())
1092 }
1093
1094 /// Set the generic (FSK) packet parameters.
1095 ///
1096 /// # Example
1097 ///
1098 /// ```no_run
1099 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1100 /// use embassy_stm32::subghz::{
1101 /// AddrComp, CrcType, GenericPacketParams, HeaderType, PacketType, PreambleDetection,
1102 /// };
1103 ///
1104 /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new()
1105 /// .set_preamble_len(8)
1106 /// .set_preamble_detection(PreambleDetection::Disabled)
1107 /// .set_sync_word_len(2)
1108 /// .set_addr_comp(AddrComp::Disabled)
1109 /// .set_header_type(HeaderType::Fixed)
1110 /// .set_payload_len(128)
1111 /// .set_crc_type(CrcType::Byte2)
1112 /// .set_whitening_enable(true);
1113 ///
1114 /// sg.set_packet_type(PacketType::Fsk)?;
1115 /// sg.set_packet_params(&PKT_PARAMS)?;
1116 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1117 /// ```
1118 pub fn set_packet_params(&mut self, params: &GenericPacketParams) -> Result<(), Error> {
1119 self.write(params.as_slice())
1120 }
1121
1122 /// Set the BPSK packet parameters.
1123 ///
1124 /// # Example
1125 ///
1126 /// ```no_run
1127 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1128 /// use embassy_stm32::subghz::{BpskPacketParams, PacketType};
1129 ///
1130 /// sg.set_packet_type(PacketType::Bpsk)?;
1131 /// sg.set_bpsk_packet_params(&BpskPacketParams::new().set_payload_len(64))?;
1132 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1133 /// ```
1134 pub fn set_bpsk_packet_params(&mut self, params: &BpskPacketParams) -> Result<(), Error> {
1135 self.write(params.as_slice())
1136 }
1137
1138 /// Set the LoRa packet parameters.
1139 ///
1140 /// # Example
1141 ///
1142 /// ```no_run
1143 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1144 /// use embassy_stm32::subghz::{HeaderType, LoRaPacketParams, PacketType};
1145 ///
1146 /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new()
1147 /// .set_preamble_len(5 * 8)
1148 /// .set_header_type(HeaderType::Fixed)
1149 /// .set_payload_len(64)
1150 /// .set_crc_en(true)
1151 /// .set_invert_iq(true);
1152 ///
1153 /// sg.set_packet_type(PacketType::LoRa)?;
1154 /// sg.set_lora_packet_params(&PKT_PARAMS)?;
1155 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1156 /// ```
1157 pub fn set_lora_packet_params(&mut self, params: &LoRaPacketParams) -> Result<(), Error> {
1158 self.write(params.as_slice())
1159 }
1160
1161 /// Set the number of LoRa symbols to be received before starting the
1162 /// reception of a LoRa packet.
1163 ///
1164 /// Packet reception is started after `n` + 1 symbols are detected.
1165 ///
1166 /// # Example
1167 ///
1168 /// Start reception after a single LoRa word is detected
1169 ///
1170 /// ```no_run
1171 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1172 ///
1173 /// // ... setup the radio for LoRa RX
1174 ///
1175 /// sg.set_lora_symb_timeout(0)?;
1176 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1177 /// ```
1178 pub fn set_lora_symb_timeout(&mut self, n: u8) -> Result<(), Error> {
1179 self.write(&[OpCode::SetLoRaSymbTimeout.into(), n])
1180 }
1181}
1182
1183// 5.8.5
1184/// Communication status and information commands
1185impl<'d> SubGhz<'d, NoDma, NoDma> {
1186 /// Get the radio status.
1187 ///
1188 /// The hardware (or documentation) appears to have many bugs where this
1189 /// will return reserved values.
1190 /// See this thread in the ST community for details: [link]
1191 ///
1192 /// ```no_run
1193 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1194 /// use embassy_stm32::subghz::Status;
1195 ///
1196 /// let status: Status = sg.status()?;
1197 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1198 /// ```
1199 ///
1200 /// [link]: https://community.st.com/s/question/0D53W00000hR9GQSA0/stm32wl55-getstatus-command-returns-reserved-cmdstatus
1201 pub fn status(&mut self) -> Result<Status, Error> {
1202 Ok(self.read_1(OpCode::GetStatus)?.into())
1203 }
1204
1205 /// Get the RX buffer status.
1206 ///
1207 /// The return tuple is (status, payload_length, buffer_pointer).
1208 ///
1209 /// # Example
1210 ///
1211 /// ```no_run
1212 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1213 /// use embassy_stm32::subghz::{CmdStatus, Timeout};
1214 ///
1215 /// sg.set_rx(Timeout::DISABLED)?;
1216 /// loop {
1217 /// let (status, len, ptr) = sg.rx_buffer_status()?;
1218 ///
1219 /// if status.cmd() == Ok(CmdStatus::Avaliable) {
1220 /// let mut buf: [u8; 256] = [0; 256];
1221 /// let data: &mut [u8] = &mut buf[..usize::from(len)];
1222 /// sg.read_buffer(ptr, data)?;
1223 /// // ... do things with the data
1224 /// break;
1225 /// }
1226 /// }
1227 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1228 /// ```
1229 pub fn rx_buffer_status(&mut self) -> Result<(Status, u8, u8), Error> {
1230 let data: [u8; 3] = self.read_n(OpCode::GetRxBufferStatus)?;
1231 Ok((data[0].into(), data[1], data[2]))
1232 }
1233
1234 /// Returns information on the last received (G)FSK packet.
1235 ///
1236 /// # Example
1237 ///
1238 /// ```no_run
1239 /// # use std::fmt::Write;
1240 /// # let mut uart = String::new();
1241 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1242 /// use embassy_stm32::subghz::{CmdStatus, Timeout};
1243 ///
1244 /// sg.set_rx(Timeout::DISABLED)?;
1245 /// loop {
1246 /// let pkt_status = sg.fsk_packet_status()?;
1247 ///
1248 /// if pkt_status.status().cmd() == Ok(CmdStatus::Avaliable) {
1249 /// let rssi = pkt_status.rssi_avg();
1250 /// writeln!(&mut uart, "Avg RSSI: {} dBm", rssi);
1251 /// break;
1252 /// }
1253 /// }
1254 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1255 /// ```
1256 pub fn fsk_packet_status(&mut self) -> Result<FskPacketStatus, Error> {
1257 Ok(FskPacketStatus::from(self.read_n(OpCode::GetPacketStatus)?))
1258 }
1259
1260 /// Returns information on the last received LoRa packet.
1261 ///
1262 /// # Example
1263 ///
1264 /// ```no_run
1265 /// # use std::fmt::Write;
1266 /// # let mut uart = String::new();
1267 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1268 /// use embassy_stm32::subghz::{CmdStatus, Timeout};
1269 ///
1270 /// sg.set_rx(Timeout::DISABLED)?;
1271 /// loop {
1272 /// let pkt_status = sg.lora_packet_status()?;
1273 ///
1274 /// if pkt_status.status().cmd() == Ok(CmdStatus::Avaliable) {
1275 /// let snr = pkt_status.snr_pkt();
1276 /// writeln!(&mut uart, "SNR: {} dB", snr);
1277 /// break;
1278 /// }
1279 /// }
1280 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1281 /// ```
1282 pub fn lora_packet_status(&mut self) -> Result<LoRaPacketStatus, Error> {
1283 Ok(LoRaPacketStatus::from(
1284 self.read_n(OpCode::GetPacketStatus)?,
1285 ))
1286 }
1287
1288 /// Get the instantaneous signal strength during packet reception.
1289 ///
1290 /// The units are in dbm.
1291 ///
1292 /// # Example
1293 ///
1294 /// Log the instantaneous signal strength to UART.
1295 ///
1296 /// ```no_run
1297 /// # use std::fmt::Write;
1298 /// # let mut uart = String::new();
1299 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1300 /// use embassy_stm32::subghz::{CmdStatus, Timeout};
1301 ///
1302 /// sg.set_rx(Timeout::DISABLED)?;
1303 /// let (_, rssi) = sg.rssi_inst()?;
1304 /// writeln!(&mut uart, "RSSI: {} dBm", rssi);
1305 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1306 /// ```
1307 pub fn rssi_inst(&mut self) -> Result<(Status, Ratio<i16>), Error> {
1308 let data: [u8; 2] = self.read_n(OpCode::GetRssiInst)?;
1309 let status: Status = data[0].into();
1310 let rssi: Ratio<i16> = Ratio::new_raw(i16::from(data[1]), -2);
1311
1312 Ok((status, rssi))
1313 }
1314
1315 /// (G)FSK packet stats.
1316 ///
1317 /// # Example
1318 ///
1319 /// ```no_run
1320 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1321 /// use embassy_stm32::subghz::{FskStats, Stats};
1322 ///
1323 /// let stats: Stats<FskStats> = sg.fsk_stats()?;
1324 /// // ... use stats
1325 /// sg.reset_stats()?;
1326 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1327 /// ```
1328 pub fn fsk_stats(&mut self) -> Result<Stats<FskStats>, Error> {
1329 let data: [u8; 7] = self.read_n(OpCode::GetStats)?;
1330 Ok(Stats::from_raw_fsk(data))
1331 }
1332
1333 /// LoRa packet stats.
1334 ///
1335 /// # Example
1336 ///
1337 /// ```no_run
1338 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1339 /// use embassy_stm32::subghz::{LoRaStats, Stats};
1340 ///
1341 /// let stats: Stats<LoRaStats> = sg.lora_stats()?;
1342 /// // ... use stats
1343 /// sg.reset_stats()?;
1344 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1345 /// ```
1346 pub fn lora_stats(&mut self) -> Result<Stats<LoRaStats>, Error> {
1347 let data: [u8; 7] = self.read_n(OpCode::GetStats)?;
1348 Ok(Stats::from_raw_lora(data))
1349 }
1350
1351 /// Reset the stats as reported in [`lora_stats`] and [`fsk_stats`].
1352 ///
1353 /// # Example
1354 ///
1355 /// ```no_run
1356 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1357 ///
1358 /// sg.reset_stats()?;
1359 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1360 /// ```
1361 ///
1362 /// [`lora_stats`]: crate::subghz::SubGhz::lora_stats
1363 /// [`fsk_stats`]: crate::subghz::SubGhz::fsk_stats
1364 pub fn reset_stats(&mut self) -> Result<(), Error> {
1365 const RESET_STATS: [u8; 7] = [0x00; 7];
1366 self.write(&RESET_STATS)
1367 }
1368}
1369
1370// 5.8.6
1371/// IRQ commands
1372impl<'d> SubGhz<'d, NoDma, NoDma> {
1373 /// Set the interrupt configuration.
1374 ///
1375 /// # Example
1376 ///
1377 /// Enable TX and timeout interrupts.
1378 ///
1379 /// ```no_run
1380 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1381 /// use embassy_stm32::subghz::{CfgIrq, Irq};
1382 ///
1383 /// const IRQ_CFG: CfgIrq = CfgIrq::new()
1384 /// .irq_enable_all(Irq::TxDone)
1385 /// .irq_enable_all(Irq::Timeout);
1386 /// sg.set_irq_cfg(&IRQ_CFG)?;
1387 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1388 /// ```
1389 pub fn set_irq_cfg(&mut self, cfg: &CfgIrq) -> Result<(), Error> {
1390 self.write(cfg.as_slice())
1391 }
1392
1393 /// Get the IRQ status.
1394 ///
1395 /// # Example
1396 ///
1397 /// Wait for TX to complete or timeout.
1398 ///
1399 /// ```no_run
1400 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1401 /// use embassy_stm32::subghz::Irq;
1402 ///
1403 /// loop {
1404 /// let (_, irq_status) = sg.irq_status()?;
1405 /// sg.clear_irq_status(irq_status)?;
1406 /// if irq_status & Irq::TxDone.mask() != 0 {
1407 /// // handle TX done
1408 /// break;
1409 /// }
1410 /// if irq_status & Irq::Timeout.mask() != 0 {
1411 /// // handle timeout
1412 /// break;
1413 /// }
1414 /// }
1415 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1416 /// ```
1417 pub fn irq_status(&mut self) -> Result<(Status, u16), Error> {
1418 let data: [u8; 3] = self.read_n(OpCode::GetIrqStatus)?;
1419 let irq_status: u16 = u16::from_be_bytes([data[1], data[2]]);
1420 Ok((data[0].into(), irq_status))
1421 }
1422
1423 /// Clear the IRQ status.
1424 ///
1425 /// # Example
1426 ///
1427 /// Clear the [`TxDone`] and [`RxDone`] interrupts.
1428 ///
1429 /// ```no_run
1430 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1431 /// use embassy_stm32::subghz::Irq;
1432 ///
1433 /// sg.clear_irq_status(Irq::TxDone.mask() | Irq::RxDone.mask())?;
1434 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1435 /// ```
1436 ///
1437 /// [`TxDone`]: crate::subghz::Irq::TxDone
1438 /// [`RxDone`]: crate::subghz::Irq::RxDone
1439 pub fn clear_irq_status(&mut self, mask: u16) -> Result<(), Error> {
1440 self.write(&[OpCode::ClrIrqStatus as u8, (mask >> 8) as u8, mask as u8])
1441 }
1442}
1443
1444// 5.8.7
1445/// Miscellaneous commands
1446impl<'d> SubGhz<'d, NoDma, NoDma> {
1447 /// Calibrate one or several blocks at any time when in standby mode.
1448 ///
1449 /// The blocks to calibrate are defined by `cal` argument.
1450 /// When the calibration is ongoing, BUSY is set.
1451 /// A falling edge on BUSY indicates the end of all enabled calibrations.
1452 ///
1453 /// This function will not poll for BUSY.
1454 ///
1455 /// # Example
1456 ///
1457 /// Calibrate the RC 13 MHz and PLL.
1458 ///
1459 /// ```no_run
1460 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1461 /// use embassy_stm32::subghz::{Calibrate, StandbyClk, SubGhz};
1462 ///
1463 /// sg.set_standby(StandbyClk::Rc)?;
1464 /// sg.calibrate(Calibrate::Rc13M.mask() | Calibrate::Pll.mask())?;
1465 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1466 /// ```
1467 pub fn calibrate(&mut self, cal: u8) -> Result<(), Error> {
1468 // bit 7 is reserved and must be kept at reset value.
1469 self.write(&[OpCode::Calibrate as u8, cal & 0x7F])
1470 }
1471
1472 /// Calibrate the image at the given frequencies.
1473 ///
1474 /// Requires the radio to be in standby mode.
1475 ///
1476 /// # Example
1477 ///
1478 /// Calibrate the image for the 430 - 440 MHz ISM band.
1479 ///
1480 /// ```no_run
1481 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1482 /// use embassy_stm32::subghz::{CalibrateImage, StandbyClk};
1483 ///
1484 /// sg.set_standby(StandbyClk::Rc)?;
1485 /// sg.calibrate_image(CalibrateImage::ISM_430_440)?;
1486 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1487 /// ```
1488 pub fn calibrate_image(&mut self, cal: CalibrateImage) -> Result<(), Error> {
1489 self.write(&[OpCode::CalibrateImage as u8, cal.0, cal.1])
1490 }
1491
1492 /// Set the radio power supply.
1493 ///
1494 /// # Examples
1495 ///
1496 /// Use the linear dropout regulator (LDO):
1497 ///
1498 /// ```no_run
1499 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1500 /// use embassy_stm32::subghz::RegMode;
1501 ///
1502 /// sg.set_regulator_mode(RegMode::Ldo)?;
1503 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1504 /// ```
1505 ///
1506 /// Use the switch mode power supply (SPMS):
1507 ///
1508 /// ```no_run
1509 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1510 /// use embassy_stm32::subghz::RegMode;
1511 ///
1512 /// sg.set_regulator_mode(RegMode::Smps)?;
1513 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1514 /// ```
1515 pub fn set_regulator_mode(&mut self, reg_mode: RegMode) -> Result<(), Error> {
1516 self.write(&[OpCode::SetRegulatorMode as u8, reg_mode as u8])
1517 }
1518
1519 /// Get the radio operational errors.
1520 ///
1521 /// # Example
1522 ///
1523 /// ```no_run
1524 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1525 /// use embassy_stm32::subghz::OpError;
1526 ///
1527 /// let (status, error_mask) = sg.op_error()?;
1528 /// if error_mask & OpError::PllLockError.mask() != 0 {
1529 /// // ... handle PLL lock error
1530 /// }
1531 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1532 /// ```
1533 pub fn op_error(&mut self) -> Result<(Status, u16), Error> {
1534 let data: [u8; 3] = self.read_n(OpCode::GetError)?;
1535 Ok((data[0].into(), u16::from_le_bytes([data[1], data[2]])))
1536 }
1537
1538 /// Clear all errors as reported by [`op_error`].
1539 ///
1540 /// # Example
1541 ///
1542 /// ```no_run
1543 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1544 /// use embassy_stm32::subghz::OpError;
1545 ///
1546 /// let (status, error_mask) = sg.op_error()?;
1547 /// // ignore all errors
1548 /// if error_mask != 0 {
1549 /// sg.clear_error()?;
1550 /// }
1551 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1552 /// ```
1553 ///
1554 /// [`op_error`]: crate::subghz::SubGhz::op_error
1555 pub fn clear_error(&mut self) -> Result<(), Error> {
1556 self.write(&[OpCode::ClrError as u8, 0x00])
1557 }
1558}
1559
1560// 5.8.8
1561/// Set TCXO mode command
1562impl<'d> SubGhz<'d, NoDma, NoDma> {
1563 /// Set the TCXO trim and HSE32 ready timeout.
1564 ///
1565 /// # Example
1566 ///
1567 /// Setup the TCXO with 1.7V trim and a 10ms timeout.
1568 ///
1569 /// ```no_run
1570 /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...);
1571 /// use embassy_stm32::subghz::{TcxoMode, TcxoTrim, Timeout};
1572 ///
1573 /// const TCXO_MODE: TcxoMode = TcxoMode::new()
1574 /// .set_txco_trim(TcxoTrim::Volts1pt7)
1575 /// .set_timeout(Timeout::from_millis_sat(10));
1576 /// sg.set_tcxo_mode(&TCXO_MODE)?;
1577 /// # Ok::<(), embassy_stm32::subghz::Error>(())
1578 /// ```
1579 pub fn set_tcxo_mode(&mut self, tcxo_mode: &TcxoMode) -> Result<(), Error> {
1580 self.write(tcxo_mode.as_slice())
1581 }
1582}
1583
1584/// sub-GHz radio opcodes.
1585///
1586/// See Table 41 "Sub-GHz radio SPI commands overview"
1587#[repr(u8)]
1588#[derive(Debug, Clone, Copy)]
1589#[allow(dead_code)]
1590pub(crate) enum OpCode {
1591 Calibrate = 0x89,
1592 CalibrateImage = 0x98,
1593 CfgDioIrq = 0x08,
1594 ClrError = 0x07,
1595 ClrIrqStatus = 0x02,
1596 GetError = 0x17,
1597 GetIrqStatus = 0x12,
1598 GetPacketStatus = 0x14,
1599 GetPacketType = 0x11,
1600 GetRssiInst = 0x15,
1601 GetRxBufferStatus = 0x13,
1602 GetStats = 0x10,
1603 GetStatus = 0xC0,
1604 ReadBuffer = 0x1E,
1605 RegRegister = 0x1D,
1606 ResetStats = 0x00,
1607 SetBufferBaseAddress = 0x8F,
1608 SetCad = 0xC5,
1609 SetCadParams = 0x88,
1610 SetFs = 0xC1,
1611 SetLoRaSymbTimeout = 0xA0,
1612 SetModulationParams = 0x8B,
1613 SetPacketParams = 0x8C,
1614 SetPacketType = 0x8A,
1615 SetPaConfig = 0x95,
1616 SetRegulatorMode = 0x96,
1617 SetRfFrequency = 0x86,
1618 SetRx = 0x82,
1619 SetRxDutyCycle = 0x94,
1620 SetSleep = 0x84,
1621 SetStandby = 0x80,
1622 SetStopRxTimerOnPreamble = 0x9F,
1623 SetTcxoMode = 0x97,
1624 SetTx = 0x83,
1625 SetTxContinuousPreamble = 0xD2,
1626 SetTxContinuousWave = 0xD1,
1627 SetTxParams = 0x8E,
1628 SetTxRxFallbackMode = 0x93,
1629 WriteBuffer = 0x0E,
1630 WriteRegister = 0x0D,
1631}
1632
1633impl From<OpCode> for u8 {
1634 fn from(opcode: OpCode) -> Self {
1635 opcode as u8
1636 }
1637}
1638
1639#[repr(u16)]
1640#[allow(clippy::upper_case_acronyms)]
1641pub(crate) enum Register {
1642 /// Generic bit synchronization.
1643 GBSYNC = 0x06AC,
1644 /// Generic packet control.
1645 GPKTCTL1A = 0x06B8,
1646 /// Generic whitening.
1647 GWHITEINIRL = 0x06B9,
1648 /// Generic CRC initial.
1649 GCRCINIRH = 0x06BC,
1650 /// Generic CRC polynomial.
1651 GCRCPOLRH = 0x06BE,
1652 /// Generic synchronization word 7.
1653 GSYNC7 = 0x06C0,
1654 /// LoRa synchronization word MSB.
1655 LSYNCH = 0x0740,
1656 /// LoRa synchronization word LSB.
1657 #[allow(dead_code)]
1658 LSYNCL = 0x0741,
1659 /// Receiver gain control.
1660 RXGAINC = 0x08AC,
1661 /// PA over current protection.
1662 PAOCP = 0x08E7,
1663 /// HSE32 OSC_IN capacitor trim.
1664 HSEINTRIM = 0x0911,
1665 /// HSE32 OSC_OUT capacitor trim.
1666 HSEOUTTRIM = 0x0912,
1667 /// SMPS control 0.
1668 SMPSC0 = 0x0916,
1669 /// Power control.
1670 PC = 0x091A,
1671 /// SMPS control 2.
1672 SMPSC2 = 0x0923,
1673}
1674
1675impl Register {
1676 pub const fn address(self) -> u16 {
1677 self as u16
1678 }
1679}
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))]
4pub 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
49impl 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
165impl Ord for FskBandwidth {
166 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
167 self.hertz().cmp(&other.hertz())
168 }
169}
170
171impl 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))]
180pub 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)]
196pub struct FskBitrate {
197 bits: u32,
198}
199
200impl 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
273impl Ord for FskBitrate {
274 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
275 self.as_bps().cmp(&other.as_bps())
276 }
277}
278
279impl 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))]
288pub struct FskFdev {
289 bits: u32,
290}
291
292impl 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))]
354pub struct FskModParams {
355 buf: [u8; 9],
356}
357
358impl 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
592impl 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)]
604pub 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
623impl 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)]
635pub 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
658impl 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
693impl Ord for LoRaBandwidth {
694 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
695 self.hertz().cmp(&other.hertz())
696 }
697}
698
699impl 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)]
711pub 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
728pub struct LoRaModParams {
729 buf: [u8; 5],
730}
731
732impl 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
841impl 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))]
853pub struct BpskModParams {
854 buf: [u8; 5],
855}
856
857impl 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
917impl Default for BpskModParams {
918 fn default() -> Self {
919 Self::new()
920 }
921}
922
923#[cfg(test)]
924mod 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)]
9pub 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)]
9pub 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
28impl 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))]
8pub struct PaConfig {
9 buf: [u8; 5],
10}
11
12impl 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
112impl 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)]
123pub enum PaSel {
124 /// High power amplifier.
125 Hp = 0b0,
126 /// Low power amplifier.
127 Lp = 0b1,
128}
129
130impl PartialOrd for PaSel {
131 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
132 Some(self.cmp(other))
133 }
134}
135
136impl 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
146impl Default for PaSel {
147 fn default() -> Self {
148 PaSel::Lp
149 }
150}
151
152#[cfg(test)]
153mod 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))]
5pub 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))]
22pub 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))]
37pub 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
44impl 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))]
64pub 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))]
82pub struct GenericPacketParams {
83 buf: [u8; 10],
84}
85
86impl 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
306impl 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)]
316pub struct LoRaPacketParams {
317 buf: [u8; 7],
318}
319
320impl 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
465impl 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))]
476pub struct BpskPacketParams {
477 buf: [u8; 2],
478}
479
480impl 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
533impl 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 @@
1use embassy_hal_common::ratio::Ratio;
2
3use 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)]
11pub struct FskPacketStatus {
12 buf: [u8; 4],
13}
14
15impl From<[u8; 4]> for FskPacketStatus {
16 fn from(buf: [u8; 4]) -> Self {
17 FskPacketStatus { buf }
18 }
19}
20
21impl 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")]
115impl 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
147impl 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)]
171pub struct LoRaPacketStatus {
172 buf: [u8; 4],
173}
174
175impl From<[u8; 4]> for LoRaPacketStatus {
176 fn from(buf: [u8; 4]) -> Self {
177 LoRaPacketStatus { buf }
178 }
179}
180
181impl 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")]
252impl 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
270impl 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))]
9pub 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
20impl 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))]
6pub 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
17impl 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))]
28pub struct PktCtrl {
29 val: u8,
30}
31
32impl 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
237impl From<PktCtrl> for u8 {
238 fn from(pc: PktCtrl) -> Self {
239 pc.val
240 }
241}
242
243impl 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))]
9pub 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)]
7pub 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
18impl 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
41impl 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))]
52pub struct PwrCtrl {
53 val: u8,
54}
55
56impl 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
150impl From<PwrCtrl> for u8 {
151 fn from(bs: PwrCtrl) -> Self {
152 bs.val
153 }
154}
155
156impl 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)]
5pub 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
14impl 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))]
8pub struct RfFreq {
9 buf: [u8; 5],
10}
11
12impl 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)]
126mod 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)]
9pub 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
17impl 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)]
7pub 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
20impl 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))]
33pub struct SleepCfg(u8);
34
35impl 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
99impl From<SleepCfg> for u8 {
100 fn from(sc: SleepCfg) -> Self {
101 sc.0
102 }
103}
104
105impl 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)]
7pub 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
18impl 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
41impl 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)]
9pub enum StandbyClk {
10 /// RC 13 MHz used in standby mode.
11 Rc = 0b0,
12 /// HSE32 used in standby mode.
13 Hse = 0b1,
14}
15
16impl 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 @@
1use crate::subghz::status::Status;
2
3#[derive(Debug, PartialEq, Eq, Clone, Copy)]
4#[cfg_attr(feature = "defmt", derive(defmt::Format))]
5pub struct LoRaStats;
6
7impl 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))]
15pub struct FskStats;
16
17impl 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))]
31pub 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
39impl<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
97impl 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
128impl 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
159impl 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)]
171mod 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))]
10pub 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
23impl 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))]
60pub 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
86impl 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)]
120pub struct Status(u8);
121
122impl From<u8> for Status {
123 fn from(x: u8) -> Self {
124 Status(x)
125 }
126}
127impl From<Status> for u8 {
128 fn from(x: Status) -> Self {
129 x.0
130 }
131}
132
133impl 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
183impl 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")]
193impl 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 @@
1use 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)]
12pub 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
31impl 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
46impl 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))]
84pub struct TcxoMode {
85 buf: [u8; 5],
86}
87
88impl 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
166impl 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 @@
1use core::time::Duration;
2
3use crate::subghz::value_error::ValueError;
4
5const 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))]
28pub struct Timeout {
29 bits: u32,
30}
31
32impl 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
403impl From<Timeout> for Duration {
404 fn from(to: Timeout) -> Self {
405 to.as_duration()
406 }
407}
408
409impl From<Timeout> for [u8; 3] {
410 fn from(to: Timeout) -> Self {
411 to.as_bytes()
412 }
413}
414
415impl 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)]
422mod 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)]
7pub 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
26impl From<RampTime> for u8 {
27 fn from(rt: RampTime) -> Self {
28 rt as u8
29 }
30}
31
32impl 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
47impl 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))]
66pub struct TxParams {
67 buf: [u8; 3],
68}
69
70impl 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
162impl 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))]
8pub struct ValueError<T> {
9 value: T,
10 limit: T,
11 over: bool,
12}
13
14impl<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
118impl<T> core::fmt::Display for ValueError<T>
119where
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]
20embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-trace"] } 20embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-trace"] }
21embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] } 21embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] }
22embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "defmt-trace", "stm32wl55jc_cm4", "time-driver-tim2", "memory-x"] } 22embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "defmt-trace", "stm32wl55jc_cm4", "time-driver-tim2", "memory-x", "subghz"] }
23embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" } 23embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" }
24 24
25defmt = "0.2.0" 25defmt = "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"]
9mod example_common;
10
11use embassy::{traits::gpio::WaitForRisingEdge, util::InterruptFuture};
12use 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};
21use embedded_hal::digital::v2::OutputPin;
22use example_common::unwrap;
23
24const PING_DATA: &str = "PING";
25const DATA_LEN: u8 = PING_DATA.len() as u8;
26const PING_DATA_BYTES: &[u8] = PING_DATA.as_bytes();
27const PREAMBLE_LEN: u16 = 5 * 8;
28
29const RF_FREQ: RfFreq = RfFreq::from_frequency(867_500_000);
30
31const SYNC_WORD: [u8; 8] = [0x79, 0x80, 0x0C, 0xC0, 0x29, 0x95, 0xF8, 0x4A];
32const SYNC_WORD_LEN: u8 = SYNC_WORD.len() as u8;
33const SYNC_WORD_LEN_BITS: u8 = SYNC_WORD_LEN * 8;
34
35const TX_BUF_OFFSET: u8 = 128;
36const RX_BUF_OFFSET: u8 = 0;
37const 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
44const 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"
52const PA_CONFIG: PaConfig = PaConfig::new()
53 .set_pa_duty_cycle(0x1)
54 .set_hp_max(0x0)
55 .set_pa(PaSel::Lp);
56
57const 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
63const TX_PARAMS: TxParams = TxParams::new()
64 .set_power(0x0D)
65 .set_ramp_time(RampTime::Micros40);
66
67fn 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()")]
74async 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