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