aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2022-09-04 07:17:23 +0000
committerGitHub <[email protected]>2022-09-04 07:17:23 +0000
commit6264fe39a563b77e5a8fd873e7d29841af3b3c73 (patch)
treeb0f17887591523166f3ed3f7eea4330743c7b692
parent7d5c1fcebf875a5584518b99a70e8de980fba2ae (diff)
parent6cdff72d6d2becb3f8a4659571fd5e6a339cbefa (diff)
Merge #839
839: Misc LoRaWAN improvements r=lulf a=timokroeger Trying too get `embassy-lora` running on a [LoRa-E5 Dev Board](https://wiki.seeedstudio.com/LoRa_E5_Dev_Board/). I can see the join message arriving in the The Things Network console but the device does not receive the accept message yet. Opening this PR anyway because I think there are some nice things to decouple the lora crate from the nucleo board. `@lulf` Could you test if this PR breaks your LoRa setup? Marking as draft for the time being. Co-authored-by: Timo Kröger <[email protected]> Co-authored-by: Ulf Lilleengen <[email protected]>
-rw-r--r--embassy-lora/Cargo.toml3
-rw-r--r--embassy-lora/src/lib.rs26
-rw-r--r--embassy-lora/src/stm32wl/mod.rs310
-rw-r--r--embassy-stm32/src/rcc/wl.rs116
-rw-r--r--embassy-stm32/src/spi/mod.rs13
-rw-r--r--embassy-stm32/src/subghz/mod.rs7
-rw-r--r--examples/stm32l0/Cargo.toml2
-rw-r--r--examples/stm32l0/src/bin/lorawan.rs2
-rw-r--r--examples/stm32wl/Cargo.toml2
-rw-r--r--examples/stm32wl/src/bin/lorawan.rs39
-rw-r--r--examples/stm32wl/src/bin/subghz.rs2
11 files changed, 267 insertions, 255 deletions
diff --git a/embassy-lora/Cargo.toml b/embassy-lora/Cargo.toml
index 9d5e7aed2..a80557a89 100644
--- a/embassy-lora/Cargo.toml
+++ b/embassy-lora/Cargo.toml
@@ -18,6 +18,7 @@ flavors = [
18sx127x = [] 18sx127x = []
19stm32wl = ["embassy-stm32", "embassy-stm32/subghz"] 19stm32wl = ["embassy-stm32", "embassy-stm32/subghz"]
20time = [] 20time = []
21defmt = ["dep:defmt", "lorawan/defmt", "lorawan-device/defmt"]
21 22
22[dependencies] 23[dependencies]
23 24
@@ -34,5 +35,5 @@ futures = { version = "0.3.17", default-features = false, features = [ "async-aw
34embedded-hal = { version = "0.2", features = ["unproven"] } 35embedded-hal = { version = "0.2", features = ["unproven"] }
35bit_field = { version = "0.10" } 36bit_field = { version = "0.10" }
36 37
37lorawan-device = { version = "0.7.1", default-features = false, features = ["async"] } 38lorawan-device = { version = "0.8.0", default-features = false, features = ["async"] }
38lorawan = { version = "0.7.1", default-features = false } 39lorawan = { version = "0.7.1", default-features = false }
diff --git a/embassy-lora/src/lib.rs b/embassy-lora/src/lib.rs
index 1b2dd45c2..2483dcb2e 100644
--- a/embassy-lora/src/lib.rs
+++ b/embassy-lora/src/lib.rs
@@ -11,13 +11,35 @@ pub mod stm32wl;
11#[cfg(feature = "sx127x")] 11#[cfg(feature = "sx127x")]
12pub mod sx127x; 12pub mod sx127x;
13 13
14#[cfg(feature = "time")]
15use embassy_time::{Duration, Instant, Timer};
16
14/// A convenience timer to use with the LoRaWAN crate 17/// A convenience timer to use with the LoRaWAN crate
15pub struct LoraTimer; 18#[cfg(feature = "time")]
19pub struct LoraTimer {
20 start: Instant,
21}
22
23#[cfg(feature = "time")]
24impl LoraTimer {
25 pub fn new() -> Self {
26 Self { start: Instant::now() }
27 }
28}
16 29
17#[cfg(feature = "time")] 30#[cfg(feature = "time")]
18impl lorawan_device::async_device::radio::Timer for LoraTimer { 31impl lorawan_device::async_device::radio::Timer for LoraTimer {
32 fn reset(&mut self) {
33 self.start = Instant::now();
34 }
35
36 type AtFuture<'m> = impl core::future::Future<Output = ()> + 'm;
37 fn at<'m>(&'m mut self, millis: u64) -> Self::AtFuture<'m> {
38 Timer::at(self.start + Duration::from_millis(millis))
39 }
40
19 type DelayFuture<'m> = impl core::future::Future<Output = ()> + 'm; 41 type DelayFuture<'m> = impl core::future::Future<Output = ()> + 'm;
20 fn delay_ms<'m>(&'m mut self, millis: u64) -> Self::DelayFuture<'m> { 42 fn delay_ms<'m>(&'m mut self, millis: u64) -> Self::DelayFuture<'m> {
21 embassy_time::Timer::after(embassy_time::Duration::from_millis(millis)) 43 Timer::after(Duration::from_millis(millis))
22 } 44 }
23} 45}
diff --git a/embassy-lora/src/stm32wl/mod.rs b/embassy-lora/src/stm32wl/mod.rs
index 7822d0153..4d11244b6 100644
--- a/embassy-lora/src/stm32wl/mod.rs
+++ b/embassy-lora/src/stm32wl/mod.rs
@@ -1,18 +1,17 @@
1//! A radio driver integration for the radio found on STM32WL family devices. 1//! A radio driver integration for the radio found on STM32WL family devices.
2use core::future::Future; 2use core::future::Future;
3use core::mem::MaybeUninit; 3use core::task::Poll;
4 4
5use embassy_hal_common::{into_ref, PeripheralRef}; 5use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
6use embassy_stm32::dma::NoDma; 6use embassy_stm32::dma::NoDma;
7use embassy_stm32::gpio::{AnyPin, Output}; 7use embassy_stm32::interrupt::{Interrupt, InterruptExt, SUBGHZ_RADIO};
8use embassy_stm32::interrupt::{InterruptExt, SUBGHZ_RADIO};
9use embassy_stm32::subghz::{ 8use embassy_stm32::subghz::{
10 CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, Irq, LoRaBandwidth, LoRaModParams, LoRaPacketParams, 9 CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, HseTrim, Irq, LoRaBandwidth, LoRaModParams,
11 LoRaSyncWord, Ocp, PaConfig, PaSel, PacketType, RampTime, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk, 10 LoRaPacketParams, LoRaSyncWord, Ocp, PaConfig, PacketType, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk,
12 Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams, 11 Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams,
13}; 12};
14use embassy_stm32::Peripheral; 13use embassy_sync::waitqueue::AtomicWaker;
15use embassy_sync::signal::Signal; 14use futures::future::poll_fn;
16use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig}; 15use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
17use lorawan_device::async_device::Timings; 16use lorawan_device::async_device::Timings;
18 17
@@ -28,102 +27,52 @@ pub enum State {
28#[cfg_attr(feature = "defmt", derive(defmt::Format))] 27#[cfg_attr(feature = "defmt", derive(defmt::Format))]
29pub struct RadioError; 28pub struct RadioError;
30 29
31static IRQ: Signal<(Status, u16)> = Signal::new(); 30static IRQ_WAKER: AtomicWaker = AtomicWaker::new();
32 31
33struct StateInner<'d> { 32/// The radio peripheral keeping the radio state and owning the radio IRQ.
33pub struct SubGhzRadio<'d, RS> {
34 radio: SubGhz<'d, NoDma, NoDma>, 34 radio: SubGhz<'d, NoDma, NoDma>,
35 switch: RadioSwitch<'d>, 35 switch: RS,
36} 36 irq: PeripheralRef<'d, SUBGHZ_RADIO>,
37
38/// External state storage for the radio state
39pub struct SubGhzState<'a>(MaybeUninit<StateInner<'a>>);
40impl<'d> SubGhzState<'d> {
41 pub const fn new() -> Self {
42 Self(MaybeUninit::uninit())
43 }
44} 37}
45 38
46/// The radio peripheral keeping the radio state and owning the radio IRQ. 39#[derive(Default)]
47pub struct SubGhzRadio<'d> { 40#[non_exhaustive]
48 state: *mut StateInner<'d>, 41pub struct SubGhzRadioConfig {
49 _irq: PeripheralRef<'d, SUBGHZ_RADIO>, 42 pub reg_mode: RegMode,
43 pub calibrate_image: CalibrateImage,
44 pub pa_config: PaConfig,
45 pub tx_params: TxParams,
50} 46}
51 47
52impl<'d> SubGhzRadio<'d> { 48impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
53 /// Create a new instance of a SubGhz radio for LoRaWAN. 49 /// Create a new instance of a SubGhz radio for LoRaWAN.
54 /// 50 pub fn new(
55 /// # Safety 51 mut radio: SubGhz<'d, NoDma, NoDma>,
56 /// Do not leak self or futures 52 switch: RS,
57 pub unsafe fn new(
58 state: &'d mut SubGhzState<'d>,
59 radio: SubGhz<'d, NoDma, NoDma>,
60 switch: RadioSwitch<'d>,
61 irq: impl Peripheral<P = SUBGHZ_RADIO> + 'd, 53 irq: impl Peripheral<P = SUBGHZ_RADIO> + 'd,
62 ) -> Self { 54 config: SubGhzRadioConfig,
55 ) -> Result<Self, RadioError> {
63 into_ref!(irq); 56 into_ref!(irq);
64 57
65 let mut inner = StateInner { radio, switch }; 58 radio.reset();
66 inner.radio.reset();
67
68 let state_ptr = state.0.as_mut_ptr();
69 state_ptr.write(inner);
70 59
71 irq.disable(); 60 irq.disable();
72 irq.set_handler(|p| { 61 irq.set_handler(|_| {
73 // This is safe because we only get interrupts when configured for, so 62 IRQ_WAKER.wake();
74 // the radio will be awaiting on the signal at this point. If not, the ISR will 63 unsafe { SUBGHZ_RADIO::steal().disable() };
75 // anyway only adjust the state in the IRQ signal state.
76 let state = &mut *(p as *mut StateInner<'d>);
77 state.on_interrupt();
78 }); 64 });
79 irq.set_handler_context(state_ptr as *mut ());
80 irq.enable();
81
82 Self {
83 state: state_ptr,
84 _irq: irq,
85 }
86 }
87}
88
89impl<'d> StateInner<'d> {
90 /// Configure radio settings in preparation for TX or RX
91 pub(crate) fn configure(&mut self) -> Result<(), RadioError> {
92 trace!("Configuring STM32WL SUBGHZ radio");
93 self.radio.set_standby(StandbyClk::Rc)?;
94 let tcxo_mode = TcxoMode::new()
95 .set_txco_trim(TcxoTrim::Volts1pt7)
96 .set_timeout(Timeout::from_duration_sat(core::time::Duration::from_millis(40)));
97
98 self.radio.set_tcxo_mode(&tcxo_mode)?;
99 self.radio.set_regulator_mode(RegMode::Ldo)?;
100
101 self.radio.calibrate_image(CalibrateImage::ISM_863_870)?;
102
103 self.radio.set_buffer_base_address(0, 0)?;
104 65
105 self.radio 66 configure_radio(&mut radio, config)?;
106 .set_pa_config(&PaConfig::new().set_pa_duty_cycle(0x1).set_hp_max(0x0).set_pa(PaSel::Lp))?;
107
108 self.radio.set_pa_ocp(Ocp::Max140m)?;
109
110 // let tx_params = TxParams::LP_14.set_ramp_time(RampTime::Micros40);
111 self.radio
112 .set_tx_params(&TxParams::new().set_ramp_time(RampTime::Micros40).set_power(0x0A))?;
113 67
114 self.radio.set_packet_type(PacketType::LoRa)?; 68 Ok(Self { radio, switch, irq })
115 self.radio.set_lora_sync_word(LoRaSyncWord::Public)?;
116 trace!("Done initializing STM32WL SUBGHZ radio");
117 Ok(())
118 } 69 }
119 70
120 /// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form 71 /// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form
121 /// the upcoming RX window start. 72 /// the upcoming RX window start.
122 async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> { 73 async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> {
123 //trace!("TX Request: {}", config); 74 trace!("TX request: {}", config);
124 trace!("TX START"); 75 self.switch.set_tx();
125 self.switch.set_tx_lp();
126 self.configure()?;
127 76
128 self.radio 77 self.radio
129 .set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?; 78 .set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?;
@@ -139,34 +88,26 @@ impl<'d> StateInner<'d> {
139 88
140 self.radio.set_lora_packet_params(&packet_params)?; 89 self.radio.set_lora_packet_params(&packet_params)?;
141 90
142 let irq_cfg = CfgIrq::new() 91 let irq_cfg = CfgIrq::new().irq_enable_all(Irq::TxDone).irq_enable_all(Irq::Timeout);
143 .irq_enable_all(Irq::TxDone)
144 .irq_enable_all(Irq::RxDone)
145 .irq_enable_all(Irq::Timeout);
146 self.radio.set_irq_cfg(&irq_cfg)?; 92 self.radio.set_irq_cfg(&irq_cfg)?;
147 93
148 self.radio.set_buffer_base_address(0, 0)?; 94 self.radio.set_buffer_base_address(0, 0)?;
149 self.radio.write_buffer(0, buf)?; 95 self.radio.write_buffer(0, buf)?;
150 96
151 self.radio.set_tx(Timeout::DISABLED)?; 97 // The maximum airtime for any LoRaWAN package is 2793.5ms.
98 // The value of 4000ms is copied from C driver and gives us a good safety margin.
99 self.radio.set_tx(Timeout::from_millis_sat(4000))?;
100 trace!("TX started");
152 101
153 loop { 102 loop {
154 let (_status, irq_status) = IRQ.wait().await; 103 let (_status, irq_status) = self.irq_wait().await;
155 IRQ.reset();
156 104
157 if irq_status & Irq::TxDone.mask() != 0 { 105 if irq_status & Irq::TxDone.mask() != 0 {
158 let stats = self.radio.lora_stats()?; 106 trace!("TX done");
159 let (status, error_mask) = self.radio.op_error()?;
160 trace!(
161 "TX done. Stats: {:?}. OP error: {:?}, mask {:?}",
162 stats,
163 status,
164 error_mask
165 );
166
167 return Ok(0); 107 return Ok(0);
168 } else if irq_status & Irq::Timeout.mask() != 0 { 108 }
169 trace!("TX timeout"); 109
110 if irq_status & Irq::Timeout.mask() != 0 {
170 return Err(RadioError); 111 return Err(RadioError);
171 } 112 }
172 } 113 }
@@ -174,10 +115,15 @@ impl<'d> StateInner<'d> {
174 115
175 fn set_lora_mod_params(&mut self, config: RfConfig) -> Result<(), Error> { 116 fn set_lora_mod_params(&mut self, config: RfConfig) -> Result<(), Error> {
176 let mod_params = LoRaModParams::new() 117 let mod_params = LoRaModParams::new()
177 .set_sf(convert_spreading_factor(config.spreading_factor)) 118 .set_sf(convert_spreading_factor(&config.spreading_factor))
178 .set_bw(convert_bandwidth(config.bandwidth)) 119 .set_bw(convert_bandwidth(&config.bandwidth))
179 .set_cr(CodingRate::Cr45) 120 .set_cr(CodingRate::Cr45)
180 .set_ldro_en(true); 121 .set_ldro_en(matches!(
122 (config.spreading_factor, config.bandwidth),
123 (SpreadingFactor::_12, Bandwidth::_125KHz)
124 | (SpreadingFactor::_12, Bandwidth::_250KHz)
125 | (SpreadingFactor::_11, Bandwidth::_125KHz)
126 ));
181 self.radio.set_lora_mod_params(&mod_params) 127 self.radio.set_lora_mod_params(&mod_params)
182 } 128 }
183 129
@@ -185,10 +131,8 @@ impl<'d> StateInner<'d> {
185 /// be able to hold a single LoRaWAN packet. 131 /// be able to hold a single LoRaWAN packet.
186 async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> { 132 async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> {
187 assert!(buf.len() >= 255); 133 assert!(buf.len() >= 255);
188 trace!("RX START"); 134 trace!("RX request: {}", config);
189 // trace!("Starting RX: {}", config);
190 self.switch.set_rx(); 135 self.switch.set_rx();
191 self.configure()?;
192 136
193 self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?; 137 self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
194 138
@@ -198,77 +142,110 @@ impl<'d> StateInner<'d> {
198 .set_preamble_len(8) 142 .set_preamble_len(8)
199 .set_header_type(HeaderType::Variable) 143 .set_header_type(HeaderType::Variable)
200 .set_payload_len(0xFF) 144 .set_payload_len(0xFF)
201 .set_crc_en(true) 145 .set_crc_en(false)
202 .set_invert_iq(true); 146 .set_invert_iq(true);
203 self.radio.set_lora_packet_params(&packet_params)?; 147 self.radio.set_lora_packet_params(&packet_params)?;
204 148
205 let irq_cfg = CfgIrq::new() 149 let irq_cfg = CfgIrq::new()
206 .irq_enable_all(Irq::RxDone) 150 .irq_enable_all(Irq::RxDone)
207 .irq_enable_all(Irq::PreambleDetected) 151 .irq_enable_all(Irq::PreambleDetected)
152 .irq_enable_all(Irq::HeaderValid)
208 .irq_enable_all(Irq::HeaderErr) 153 .irq_enable_all(Irq::HeaderErr)
209 .irq_enable_all(Irq::Timeout) 154 .irq_enable_all(Irq::Err)
210 .irq_enable_all(Irq::Err); 155 .irq_enable_all(Irq::Timeout);
211 self.radio.set_irq_cfg(&irq_cfg)?; 156 self.radio.set_irq_cfg(&irq_cfg)?;
212 157
158 self.radio.set_buffer_base_address(0, 0)?;
159
160 // NOTE: Upper layer handles timeout by cancelling the future
213 self.radio.set_rx(Timeout::DISABLED)?; 161 self.radio.set_rx(Timeout::DISABLED)?;
162
214 trace!("RX started"); 163 trace!("RX started");
215 164
216 loop { 165 loop {
217 let (status, irq_status) = IRQ.wait().await; 166 let (_status, irq_status) = self.irq_wait().await;
218 IRQ.reset();
219 trace!("RX IRQ {:?}, {:?}", status, irq_status);
220 if irq_status & Irq::RxDone.mask() != 0 {
221 let (status, len, ptr) = self.radio.rx_buffer_status()?;
222 167
168 if irq_status & Irq::RxDone.mask() != 0 {
169 let (_status, len, ptr) = self.radio.rx_buffer_status()?;
223 let packet_status = self.radio.lora_packet_status()?; 170 let packet_status = self.radio.lora_packet_status()?;
224 let rssi = packet_status.rssi_pkt().to_integer(); 171 let rssi = packet_status.rssi_pkt().to_integer();
225 let snr = packet_status.snr_pkt().to_integer(); 172 let snr = packet_status.snr_pkt().to_integer();
226 trace!(
227 "RX done. Received {} bytes. RX status: {:?}. Pkt status: {:?}",
228 len,
229 status.cmd(),
230 packet_status,
231 );
232 self.radio.read_buffer(ptr, &mut buf[..len as usize])?; 173 self.radio.read_buffer(ptr, &mut buf[..len as usize])?;
233 self.radio.set_standby(StandbyClk::Rc)?; 174 self.radio.set_standby(StandbyClk::Rc)?;
175
176 trace!("RX done: {=[u8]:#02X}", &mut buf[..len as usize]);
234 return Ok((len as usize, RxQuality::new(rssi, snr as i8))); 177 return Ok((len as usize, RxQuality::new(rssi, snr as i8)));
235 } else if irq_status & (Irq::Timeout.mask() | Irq::TxDone.mask()) != 0 { 178 }
179
180 if irq_status & Irq::Timeout.mask() != 0 {
236 return Err(RadioError); 181 return Err(RadioError);
237 } 182 }
238 } 183 }
239 } 184 }
240 185
241 /// Read interrupt status and store in global signal 186 async fn irq_wait(&mut self) -> (Status, u16) {
242 fn on_interrupt(&mut self) { 187 poll_fn(|cx| {
243 let (status, irq_status) = self.radio.irq_status().expect("error getting irq status"); 188 self.irq.unpend();
244 self.radio 189 self.irq.enable();
245 .clear_irq_status(irq_status) 190 IRQ_WAKER.register(cx.waker());
246 .expect("error clearing irq status"); 191
247 if irq_status & Irq::PreambleDetected.mask() != 0 { 192 let (status, irq_status) = self.radio.irq_status().expect("error getting irq status");
248 trace!("Preamble detected, ignoring"); 193 self.radio
249 } else { 194 .clear_irq_status(irq_status)
250 IRQ.signal((status, irq_status)); 195 .expect("error clearing irq status");
251 } 196
197 trace!("SUGHZ IRQ 0b{=u16:b}, {:?}", irq_status, status);
198
199 if irq_status == 0 {
200 Poll::Pending
201 } else {
202 Poll::Ready((status, irq_status))
203 }
204 })
205 .await
252 } 206 }
253} 207}
254 208
255impl PhyRxTx for SubGhzRadio<'static> { 209fn configure_radio(radio: &mut SubGhz<'_, NoDma, NoDma>, config: SubGhzRadioConfig) -> Result<(), RadioError> {
210 trace!("Configuring STM32WL SUBGHZ radio");
211
212 radio.set_regulator_mode(config.reg_mode)?;
213 radio.set_standby(StandbyClk::Rc)?;
214
215 let tcxo_mode = TcxoMode::new()
216 .set_txco_trim(TcxoTrim::Volts1pt7)
217 .set_timeout(Timeout::from_duration_sat(core::time::Duration::from_millis(100)));
218 radio.set_tcxo_mode(&tcxo_mode)?;
219 // Reduce input capacitance as shown in Reference Manual "Figure 23. HSE32 TCXO control".
220 // The STM32CUBE C driver also does this.
221 radio.set_hse_in_trim(HseTrim::MIN)?;
222
223 // Re-calibrate everything after setting the TXCO config.
224 radio.calibrate(0x7F)?;
225 radio.calibrate_image(config.calibrate_image)?;
226
227 radio.set_pa_config(&config.pa_config)?;
228 radio.set_tx_params(&config.tx_params)?;
229 radio.set_pa_ocp(Ocp::Max140m)?;
230
231 radio.set_packet_type(PacketType::LoRa)?;
232 radio.set_lora_sync_word(LoRaSyncWord::Public)?;
233
234 trace!("Done initializing STM32WL SUBGHZ radio");
235 Ok(())
236}
237
238impl<RS: RadioSwitch> PhyRxTx for SubGhzRadio<'static, RS> {
256 type PhyError = RadioError; 239 type PhyError = RadioError;
257 240
258 type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm; 241 type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm where RS: 'm;
259 fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> { 242 fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
260 async move { 243 async move { self.do_tx(config, buf).await }
261 let inner = unsafe { &mut *self.state };
262 inner.do_tx(config, buf).await
263 }
264 } 244 }
265 245
266 type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm; 246 type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm where RS: 'm;
267 fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> { 247 fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
268 async move { 248 async move { self.do_rx(config, buf).await }
269 let inner = unsafe { &mut *self.state };
270 inner.do_rx(config, buf).await
271 }
272 } 249 }
273} 250}
274 251
@@ -278,48 +255,21 @@ impl From<embassy_stm32::spi::Error> for RadioError {
278 } 255 }
279} 256}
280 257
281impl<'d> Timings for SubGhzRadio<'d> { 258impl<'d, RS> Timings for SubGhzRadio<'d, RS> {
282 fn get_rx_window_offset_ms(&self) -> i32 { 259 fn get_rx_window_offset_ms(&self) -> i32 {
283 -200 260 -500
284 } 261 }
285 fn get_rx_window_duration_ms(&self) -> u32 { 262 fn get_rx_window_duration_ms(&self) -> u32 {
286 800 263 3000
287 } 264 }
288} 265}
289 266
290/// Represents the radio switch found on STM32WL based boards, used to control the radio for transmission or reception. 267pub trait RadioSwitch {
291pub struct RadioSwitch<'d> { 268 fn set_rx(&mut self);
292 ctrl1: Output<'d, AnyPin>, 269 fn set_tx(&mut self);
293 ctrl2: Output<'d, AnyPin>,
294 ctrl3: Output<'d, AnyPin>,
295}
296
297impl<'d> RadioSwitch<'d> {
298 pub fn new(ctrl1: Output<'d, AnyPin>, ctrl2: Output<'d, AnyPin>, ctrl3: Output<'d, AnyPin>) -> Self {
299 Self { ctrl1, ctrl2, ctrl3 }
300 }
301
302 pub(crate) fn set_rx(&mut self) {
303 self.ctrl1.set_high();
304 self.ctrl2.set_low();
305 self.ctrl3.set_high();
306 }
307
308 pub(crate) fn set_tx_lp(&mut self) {
309 self.ctrl1.set_high();
310 self.ctrl2.set_high();
311 self.ctrl3.set_high();
312 }
313
314 #[allow(dead_code)]
315 pub(crate) fn set_tx_hp(&mut self) {
316 self.ctrl2.set_high();
317 self.ctrl1.set_low();
318 self.ctrl3.set_high();
319 }
320} 270}
321 271
322fn convert_spreading_factor(sf: SpreadingFactor) -> SF { 272fn convert_spreading_factor(sf: &SpreadingFactor) -> SF {
323 match sf { 273 match sf {
324 SpreadingFactor::_7 => SF::Sf7, 274 SpreadingFactor::_7 => SF::Sf7,
325 SpreadingFactor::_8 => SF::Sf8, 275 SpreadingFactor::_8 => SF::Sf8,
@@ -330,7 +280,7 @@ fn convert_spreading_factor(sf: SpreadingFactor) -> SF {
330 } 280 }
331} 281}
332 282
333fn convert_bandwidth(bw: Bandwidth) -> LoRaBandwidth { 283fn convert_bandwidth(bw: &Bandwidth) -> LoRaBandwidth {
334 match bw { 284 match bw {
335 Bandwidth::_125KHz => LoRaBandwidth::Bw125, 285 Bandwidth::_125KHz => LoRaBandwidth::Bw125,
336 Bandwidth::_250KHz => LoRaBandwidth::Bw250, 286 Bandwidth::_250KHz => LoRaBandwidth::Bw250,
diff --git a/embassy-stm32/src/rcc/wl.rs b/embassy-stm32/src/rcc/wl.rs
index 69c192c67..347674918 100644
--- a/embassy-stm32/src/rcc/wl.rs
+++ b/embassy-stm32/src/rcc/wl.rs
@@ -202,54 +202,11 @@ impl Default for Config {
202 202
203pub(crate) unsafe fn init(config: Config) { 203pub(crate) unsafe fn init(config: Config) {
204 let (sys_clk, sw, vos) = match config.mux { 204 let (sys_clk, sw, vos) = match config.mux {
205 ClockSrc::HSI16 => { 205 ClockSrc::HSI16 => (HSI_FREQ.0, 0x01, VoltageScale::Range2),
206 // Enable HSI16 206 ClockSrc::HSE32 => (HSE32_FREQ.0, 0x02, VoltageScale::Range1),
207 RCC.cr().write(|w| w.set_hsion(true)); 207 ClockSrc::MSI(range) => (range.freq(), 0x00, range.vos()),
208 while !RCC.cr().read().hsirdy() {}
209
210 (HSI_FREQ.0, 0x01, VoltageScale::Range2)
211 }
212 ClockSrc::HSE32 => {
213 // Enable HSE32
214 RCC.cr().write(|w| {
215 w.set_hsebyppwr(true);
216 w.set_hseon(true);
217 });
218 while !RCC.cr().read().hserdy() {}
219
220 (HSE32_FREQ.0, 0x02, VoltageScale::Range1)
221 }
222 ClockSrc::MSI(range) => {
223 RCC.cr().write(|w| {
224 w.set_msirange(range.into());
225 w.set_msion(true);
226 });
227
228 while !RCC.cr().read().msirdy() {}
229
230 (range.freq(), 0x00, range.vos())
231 }
232 }; 208 };
233 209
234 RCC.cfgr().modify(|w| {
235 w.set_sw(sw.into());
236 if config.ahb_pre == AHBPrescaler::NotDivided {
237 w.set_hpre(0);
238 } else {
239 w.set_hpre(config.ahb_pre.into());
240 }
241 w.set_ppre1(config.apb1_pre.into());
242 w.set_ppre2(config.apb2_pre.into());
243 });
244
245 RCC.extcfgr().modify(|w| {
246 if config.shd_ahb_pre == AHBPrescaler::NotDivided {
247 w.set_shdhpre(0);
248 } else {
249 w.set_shdhpre(config.shd_ahb_pre.into());
250 }
251 });
252
253 let ahb_freq: u32 = match config.ahb_pre { 210 let ahb_freq: u32 = match config.ahb_pre {
254 AHBPrescaler::NotDivided => sys_clk, 211 AHBPrescaler::NotDivided => sys_clk,
255 pre => { 212 pre => {
@@ -288,16 +245,6 @@ pub(crate) unsafe fn init(config: Config) {
288 } 245 }
289 }; 246 };
290 247
291 let apb3_freq = shd_ahb_freq;
292
293 if config.enable_lsi {
294 let csr = RCC.csr().read();
295 if !csr.lsion() {
296 RCC.csr().modify(|w| w.set_lsion(true));
297 while !RCC.csr().read().lsirdy() {}
298 }
299 }
300
301 // Adjust flash latency 248 // Adjust flash latency
302 let flash_clk_src_freq: u32 = shd_ahb_freq; 249 let flash_clk_src_freq: u32 = shd_ahb_freq;
303 let ws = match vos { 250 let ws = match vos {
@@ -319,6 +266,61 @@ pub(crate) unsafe fn init(config: Config) {
319 266
320 while FLASH.acr().read().latency() != ws {} 267 while FLASH.acr().read().latency() != ws {}
321 268
269 match config.mux {
270 ClockSrc::HSI16 => {
271 // Enable HSI16
272 RCC.cr().write(|w| w.set_hsion(true));
273 while !RCC.cr().read().hsirdy() {}
274 }
275 ClockSrc::HSE32 => {
276 // Enable HSE32
277 RCC.cr().write(|w| {
278 w.set_hsebyppwr(true);
279 w.set_hseon(true);
280 });
281 while !RCC.cr().read().hserdy() {}
282 }
283 ClockSrc::MSI(range) => {
284 let cr = RCC.cr().read();
285 assert!(!cr.msion() || cr.msirdy());
286 RCC.cr().write(|w| {
287 w.set_msirgsel(true);
288 w.set_msirange(range.into());
289 w.set_msion(true);
290 });
291 while !RCC.cr().read().msirdy() {}
292 }
293 }
294
295 RCC.extcfgr().modify(|w| {
296 if config.shd_ahb_pre == AHBPrescaler::NotDivided {
297 w.set_shdhpre(0);
298 } else {
299 w.set_shdhpre(config.shd_ahb_pre.into());
300 }
301 });
302
303 RCC.cfgr().modify(|w| {
304 w.set_sw(sw.into());
305 if config.ahb_pre == AHBPrescaler::NotDivided {
306 w.set_hpre(0);
307 } else {
308 w.set_hpre(config.ahb_pre.into());
309 }
310 w.set_ppre1(config.apb1_pre.into());
311 w.set_ppre2(config.apb2_pre.into());
312 });
313
314 // TODO: switch voltage range
315
316 if config.enable_lsi {
317 let csr = RCC.csr().read();
318 if !csr.lsion() {
319 RCC.csr().modify(|w| w.set_lsion(true));
320 while !RCC.csr().read().lsirdy() {}
321 }
322 }
323
322 set_freqs(Clocks { 324 set_freqs(Clocks {
323 sys: Hertz(sys_clk), 325 sys: Hertz(sys_clk),
324 ahb1: Hertz(ahb_freq), 326 ahb1: Hertz(ahb_freq),
@@ -326,7 +328,7 @@ pub(crate) unsafe fn init(config: Config) {
326 ahb3: Hertz(shd_ahb_freq), 328 ahb3: Hertz(shd_ahb_freq),
327 apb1: Hertz(apb1_freq), 329 apb1: Hertz(apb1_freq),
328 apb2: Hertz(apb2_freq), 330 apb2: Hertz(apb2_freq),
329 apb3: Hertz(apb3_freq), 331 apb3: Hertz(shd_ahb_freq),
330 apb1_tim: Hertz(apb1_tim_freq), 332 apb1_tim: Hertz(apb1_tim_freq),
331 apb2_tim: Hertz(apb2_tim_freq), 333 apb2_tim: Hertz(apb2_tim_freq),
332 }); 334 });
diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs
index acc29d87e..02e6020b0 100644
--- a/embassy-stm32/src/spi/mod.rs
+++ b/embassy-stm32/src/spi/mod.rs
@@ -179,6 +179,19 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
179 ) 179 )
180 } 180 }
181 181
182 /// Useful for on chip peripherals like SUBGHZ which are hardwired.
183 /// The bus can optionally be exposed externally with `Spi::new()` still.
184 #[allow(dead_code)]
185 pub(crate) fn new_internal(
186 peri: impl Peripheral<P = T> + 'd,
187 txdma: impl Peripheral<P = Tx> + 'd,
188 rxdma: impl Peripheral<P = Rx> + 'd,
189 freq: Hertz,
190 config: Config,
191 ) -> Self {
192 Self::new_inner(peri, None, None, None, txdma, rxdma, freq, config)
193 }
194
182 fn new_inner( 195 fn new_inner(
183 peri: impl Peripheral<P = T> + 'd, 196 peri: impl Peripheral<P = T> + 'd,
184 sck: Option<PeripheralRef<'d, AnyPin>>, 197 sck: Option<PeripheralRef<'d, AnyPin>>,
diff --git a/embassy-stm32/src/subghz/mod.rs b/embassy-stm32/src/subghz/mod.rs
index a74f9a6d5..33398fa1d 100644
--- a/embassy-stm32/src/subghz/mod.rs
+++ b/embassy-stm32/src/subghz/mod.rs
@@ -81,7 +81,7 @@ pub use value_error::ValueError;
81use crate::dma::NoDma; 81use crate::dma::NoDma;
82use crate::peripherals::SUBGHZSPI; 82use crate::peripherals::SUBGHZSPI;
83use crate::rcc::sealed::RccPeripheral; 83use crate::rcc::sealed::RccPeripheral;
84use crate::spi::{BitOrder, Config as SpiConfig, MisoPin, MosiPin, SckPin, Spi, MODE_0}; 84use crate::spi::{BitOrder, Config as SpiConfig, Spi, MODE_0};
85use crate::time::Hertz; 85use crate::time::Hertz;
86use crate::{pac, Peripheral}; 86use crate::{pac, Peripheral};
87 87
@@ -212,9 +212,6 @@ impl<'d, Tx, Rx> SubGhz<'d, Tx, Rx> {
212 /// clock. 212 /// clock.
213 pub fn new( 213 pub fn new(
214 peri: impl Peripheral<P = SUBGHZSPI> + 'd, 214 peri: impl Peripheral<P = SUBGHZSPI> + 'd,
215 sck: impl Peripheral<P = impl SckPin<SUBGHZSPI>> + 'd,
216 mosi: impl Peripheral<P = impl MosiPin<SUBGHZSPI>> + 'd,
217 miso: impl Peripheral<P = impl MisoPin<SUBGHZSPI>> + 'd,
218 txdma: impl Peripheral<P = Tx> + 'd, 215 txdma: impl Peripheral<P = Tx> + 'd,
219 rxdma: impl Peripheral<P = Rx> + 'd, 216 rxdma: impl Peripheral<P = Rx> + 'd,
220 ) -> Self { 217 ) -> Self {
@@ -227,7 +224,7 @@ impl<'d, Tx, Rx> SubGhz<'d, Tx, Rx> {
227 let mut config = SpiConfig::default(); 224 let mut config = SpiConfig::default();
228 config.mode = MODE_0; 225 config.mode = MODE_0;
229 config.bit_order = BitOrder::MsbFirst; 226 config.bit_order = BitOrder::MsbFirst;
230 let spi = Spi::new(peri, sck, mosi, miso, txdma, rxdma, clk, config); 227 let spi = Spi::new_internal(peri, txdma, rxdma, clk, config);
231 228
232 unsafe { wakeup() }; 229 unsafe { wakeup() };
233 230
diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml
index 11751a21d..6358fe865 100644
--- a/examples/stm32l0/Cargo.toml
+++ b/examples/stm32l0/Cargo.toml
@@ -14,7 +14,7 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de
14embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "unstable-traits", "memory-x"] } 14embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "unstable-traits", "memory-x"] }
15embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx127x", "time", "defmt"], optional = true} 15embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx127x", "time", "defmt"], optional = true}
16 16
17lorawan-device = { version = "0.7.1", default-features = false, features = ["async"], optional = true } 17lorawan-device = { version = "0.8.0", default-features = false, features = ["async"], optional = true }
18lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"], optional = true } 18lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"], optional = true }
19 19
20defmt = "0.3" 20defmt = "0.3"
diff --git a/examples/stm32l0/src/bin/lorawan.rs b/examples/stm32l0/src/bin/lorawan.rs
index 303558b96..00ff67f3f 100644
--- a/examples/stm32l0/src/bin/lorawan.rs
+++ b/examples/stm32l0/src/bin/lorawan.rs
@@ -47,7 +47,7 @@ async fn main(_spawner: Spawner) {
47 let radio = Sx127xRadio::new(spi, cs, reset, ready_pin, DummySwitch).await.unwrap(); 47 let radio = Sx127xRadio::new(spi, cs, reset, ready_pin, DummySwitch).await.unwrap();
48 48
49 let region = region::EU868::default().into(); 49 let region = region::EU868::default().into();
50 let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer, Rng::new(p.RNG)); 50 let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer::new(), Rng::new(p.RNG));
51 51
52 defmt::info!("Joining LoRaWAN network"); 52 defmt::info!("Joining LoRaWAN network");
53 53
diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml
index 5f6679f4b..e2e7d4078 100644
--- a/examples/stm32wl/Cargo.toml
+++ b/examples/stm32wl/Cargo.toml
@@ -10,7 +10,7 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de
10embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "subghz", "unstable-pac", "exti"] } 10embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "subghz", "unstable-pac", "exti"] }
11embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] } 11embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] }
12 12
13lorawan-device = { version = "0.7.1", default-features = false, features = ["async"] } 13lorawan-device = { version = "0.8.0", default-features = false, features = ["async"] }
14lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"] } 14lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"] }
15 15
16defmt = "0.3" 16defmt = "0.3"
diff --git a/examples/stm32wl/src/bin/lorawan.rs b/examples/stm32wl/src/bin/lorawan.rs
index 7e8a8946d..9143e64da 100644
--- a/examples/stm32wl/src/bin/lorawan.rs
+++ b/examples/stm32wl/src/bin/lorawan.rs
@@ -9,7 +9,7 @@ use embassy_executor::Spawner;
9use embassy_lora::stm32wl::*; 9use embassy_lora::stm32wl::*;
10use embassy_lora::LoraTimer; 10use embassy_lora::LoraTimer;
11use embassy_stm32::dma::NoDma; 11use embassy_stm32::dma::NoDma;
12use embassy_stm32::gpio::{Level, Output, Pin, Speed}; 12use embassy_stm32::gpio::{AnyPin, Level, Output, Pin, Speed};
13use embassy_stm32::rng::Rng; 13use embassy_stm32::rng::Rng;
14use embassy_stm32::subghz::*; 14use embassy_stm32::subghz::*;
15use embassy_stm32::{interrupt, pac}; 15use embassy_stm32::{interrupt, pac};
@@ -17,6 +17,32 @@ use lorawan::default_crypto::DefaultFactory as Crypto;
17use lorawan_device::async_device::{region, Device, JoinMode}; 17use lorawan_device::async_device::{region, Device, JoinMode};
18use {defmt_rtt as _, panic_probe as _}; 18use {defmt_rtt as _, panic_probe as _};
19 19
20struct RadioSwitch<'a> {
21 ctrl1: Output<'a, AnyPin>,
22 ctrl2: Output<'a, AnyPin>,
23 ctrl3: Output<'a, AnyPin>,
24}
25
26impl<'a> RadioSwitch<'a> {
27 fn new(ctrl1: Output<'a, AnyPin>, ctrl2: Output<'a, AnyPin>, ctrl3: Output<'a, AnyPin>) -> Self {
28 Self { ctrl1, ctrl2, ctrl3 }
29 }
30}
31
32impl<'a> embassy_lora::stm32wl::RadioSwitch for RadioSwitch<'a> {
33 fn set_rx(&mut self) {
34 self.ctrl1.set_high();
35 self.ctrl2.set_low();
36 self.ctrl3.set_high();
37 }
38
39 fn set_tx(&mut self) {
40 self.ctrl1.set_high();
41 self.ctrl2.set_high();
42 self.ctrl3.set_high();
43 }
44}
45
20#[embassy_executor::main] 46#[embassy_executor::main]
21async fn main(_spawner: Spawner) { 47async fn main(_spawner: Spawner) {
22 let mut config = embassy_stm32::Config::default(); 48 let mut config = embassy_stm32::Config::default();
@@ -31,18 +57,19 @@ async fn main(_spawner: Spawner) {
31 let ctrl3 = Output::new(p.PC5.degrade(), Level::High, Speed::High); 57 let ctrl3 = Output::new(p.PC5.degrade(), Level::High, Speed::High);
32 let rfs = RadioSwitch::new(ctrl1, ctrl2, ctrl3); 58 let rfs = RadioSwitch::new(ctrl1, ctrl2, ctrl3);
33 59
34 let radio = SubGhz::new(p.SUBGHZSPI, p.PA5, p.PA7, p.PA6, NoDma, NoDma); 60 let radio = SubGhz::new(p.SUBGHZSPI, NoDma, NoDma);
35
36 let irq = interrupt::take!(SUBGHZ_RADIO); 61 let irq = interrupt::take!(SUBGHZ_RADIO);
37 static mut RADIO_STATE: SubGhzState<'static> = SubGhzState::new(); 62
38 let radio = unsafe { SubGhzRadio::new(&mut RADIO_STATE, radio, rfs, irq) }; 63 let mut radio_config = SubGhzRadioConfig::default();
64 radio_config.calibrate_image = CalibrateImage::ISM_863_870;
65 let radio = SubGhzRadio::new(radio, rfs, irq, radio_config).unwrap();
39 66
40 let mut region: region::Configuration = region::EU868::default().into(); 67 let mut region: region::Configuration = region::EU868::default().into();
41 68
42 // NOTE: This is specific for TTN, as they have a special RX1 delay 69 // NOTE: This is specific for TTN, as they have a special RX1 delay
43 region.set_receive_delay1(5000); 70 region.set_receive_delay1(5000);
44 71
45 let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer, Rng::new(p.RNG)); 72 let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer::new(), Rng::new(p.RNG));
46 73
47 // Depending on network, this might be part of JOIN 74 // Depending on network, this might be part of JOIN
48 device.set_datarate(region::DR::_0); // SF12 75 device.set_datarate(region::DR::_0); // SF12
diff --git a/examples/stm32wl/src/bin/subghz.rs b/examples/stm32wl/src/bin/subghz.rs
index c5e9bb597..8f674d796 100644
--- a/examples/stm32wl/src/bin/subghz.rs
+++ b/examples/stm32wl/src/bin/subghz.rs
@@ -72,7 +72,7 @@ async fn main(_spawner: Spawner) {
72 unsafe { interrupt::SUBGHZ_RADIO::steal() }.disable(); 72 unsafe { interrupt::SUBGHZ_RADIO::steal() }.disable();
73 }); 73 });
74 74
75 let mut radio = SubGhz::new(p.SUBGHZSPI, p.PA5, p.PA7, p.PA6, NoDma, NoDma); 75 let mut radio = SubGhz::new(p.SUBGHZSPI, NoDma, NoDma);
76 76
77 defmt::info!("Radio ready for use"); 77 defmt::info!("Radio ready for use");
78 78