diff options
| author | Timo Kröger <[email protected]> | 2022-06-25 07:01:31 +0200 |
|---|---|---|
| committer | Timo Kröger <[email protected]> | 2022-08-26 15:44:57 +0200 |
| commit | 8e8106ef555373f8b0f04fe1f67684efef435a94 (patch) | |
| tree | 7e53fc6734b38cb3821ffe2b9b19a5cd0f9dd2e3 /embassy-lora | |
| parent | 61c666212fa04571a74c27f649526459a376aa8a (diff) | |
lora: Improve IRQ handling
* Interrupt handler only triggers a waker:
Do the actual interrupt processing which involves SUBGHZ SPI coms in the task.
* Do not require a static state for the constructor.
* Remove unsafe from construcor.
Diffstat (limited to 'embassy-lora')
| -rw-r--r-- | embassy-lora/src/stm32wl/mod.rs | 122 |
1 files changed, 50 insertions, 72 deletions
diff --git a/embassy-lora/src/stm32wl/mod.rs b/embassy-lora/src/stm32wl/mod.rs index 7822d0153..5e1773d55 100644 --- a/embassy-lora/src/stm32wl/mod.rs +++ b/embassy-lora/src/stm32wl/mod.rs | |||
| @@ -1,18 +1,18 @@ | |||
| 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. |
| 2 | use core::future::Future; | 2 | use core::future::Future; |
| 3 | use core::mem::MaybeUninit; | 3 | use core::task::Poll; |
| 4 | 4 | ||
| 5 | use embassy_hal_common::{into_ref, PeripheralRef}; | 5 | use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; |
| 6 | use embassy_stm32::dma::NoDma; | 6 | use embassy_stm32::dma::NoDma; |
| 7 | use embassy_stm32::gpio::{AnyPin, Output}; | 7 | use embassy_stm32::gpio::{AnyPin, Output}; |
| 8 | use embassy_stm32::interrupt::{InterruptExt, SUBGHZ_RADIO}; | 8 | use embassy_stm32::interrupt::{Interrupt, InterruptExt, SUBGHZ_RADIO}; |
| 9 | use embassy_stm32::subghz::{ | 9 | use embassy_stm32::subghz::{ |
| 10 | CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, Irq, LoRaBandwidth, LoRaModParams, LoRaPacketParams, | 10 | CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, Irq, LoRaBandwidth, LoRaModParams, LoRaPacketParams, |
| 11 | LoRaSyncWord, Ocp, PaConfig, PaSel, PacketType, RampTime, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk, | 11 | LoRaSyncWord, Ocp, PaConfig, PaSel, PacketType, RampTime, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk, |
| 12 | Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams, | 12 | Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams, |
| 13 | }; | 13 | }; |
| 14 | use embassy_stm32::Peripheral; | 14 | use embassy_sync::waitqueue::AtomicWaker; |
| 15 | use embassy_sync::signal::Signal; | 15 | use futures::future::poll_fn; |
| 16 | use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig}; | 16 | use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig}; |
| 17 | use lorawan_device::async_device::Timings; | 17 | use lorawan_device::async_device::Timings; |
| 18 | 18 | ||
| @@ -28,65 +28,43 @@ pub enum State { | |||
| 28 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 28 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 29 | pub struct RadioError; | 29 | pub struct RadioError; |
| 30 | 30 | ||
| 31 | static IRQ: Signal<(Status, u16)> = Signal::new(); | 31 | static IRQ_WAKER: AtomicWaker = AtomicWaker::new(); |
| 32 | 32 | ||
| 33 | struct StateInner<'d> { | 33 | /// The radio peripheral keeping the radio state and owning the radio IRQ. |
| 34 | pub struct SubGhzRadio<'d, RS> { | ||
| 34 | radio: SubGhz<'d, NoDma, NoDma>, | 35 | radio: SubGhz<'d, NoDma, NoDma>, |
| 35 | switch: RadioSwitch<'d>, | 36 | switch: RS, |
| 37 | irq: PeripheralRef<'d, SUBGHZ_RADIO>, | ||
| 36 | } | 38 | } |
| 37 | 39 | ||
| 38 | /// External state storage for the radio state | 40 | #[derive(Default)] |
| 39 | pub struct SubGhzState<'a>(MaybeUninit<StateInner<'a>>); | 41 | #[non_exhaustive] |
| 40 | impl<'d> SubGhzState<'d> { | 42 | pub struct SubGhzRadioConfig { |
| 41 | pub const fn new() -> Self { | 43 | pub reg_mode: RegMode, |
| 42 | Self(MaybeUninit::uninit()) | 44 | pub calibrate_image: CalibrateImage, |
| 43 | } | ||
| 44 | } | 45 | } |
| 45 | 46 | ||
| 46 | /// The radio peripheral keeping the radio state and owning the radio IRQ. | 47 | impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> { |
| 47 | pub struct SubGhzRadio<'d> { | ||
| 48 | state: *mut StateInner<'d>, | ||
| 49 | _irq: PeripheralRef<'d, SUBGHZ_RADIO>, | ||
| 50 | } | ||
| 51 | |||
| 52 | impl<'d> SubGhzRadio<'d> { | ||
| 53 | /// Create a new instance of a SubGhz radio for LoRaWAN. | 48 | /// Create a new instance of a SubGhz radio for LoRaWAN. |
| 54 | /// | 49 | pub fn new( |
| 55 | /// # Safety | 50 | mut radio: SubGhz<'d, NoDma, NoDma>, |
| 56 | /// Do not leak self or futures | 51 | 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, | 52 | irq: impl Peripheral<P = SUBGHZ_RADIO> + 'd, |
| 62 | ) -> Self { | 53 | config: SubGhzRadioConfig, |
| 54 | ) -> Result<Self, RadioError> { | ||
| 63 | into_ref!(irq); | 55 | into_ref!(irq); |
| 64 | 56 | ||
| 65 | let mut inner = StateInner { radio, switch }; | 57 | radio.reset(); |
| 66 | inner.radio.reset(); | ||
| 67 | |||
| 68 | let state_ptr = state.0.as_mut_ptr(); | ||
| 69 | state_ptr.write(inner); | ||
| 70 | 58 | ||
| 71 | irq.disable(); | 59 | irq.disable(); |
| 72 | irq.set_handler(|p| { | 60 | irq.set_handler(|_| { |
| 73 | // This is safe because we only get interrupts when configured for, so | 61 | IRQ_WAKER.wake(); |
| 74 | // the radio will be awaiting on the signal at this point. If not, the ISR will | 62 | 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 | }); | 63 | }); |
| 79 | irq.set_handler_context(state_ptr as *mut ()); | ||
| 80 | irq.enable(); | ||
| 81 | 64 | ||
| 82 | Self { | 65 | Self { radio, switch, irq } |
| 83 | state: state_ptr, | ||
| 84 | _irq: irq, | ||
| 85 | } | ||
| 86 | } | 66 | } |
| 87 | } | ||
| 88 | 67 | ||
| 89 | impl<'d> StateInner<'d> { | ||
| 90 | /// Configure radio settings in preparation for TX or RX | 68 | /// Configure radio settings in preparation for TX or RX |
| 91 | pub(crate) fn configure(&mut self) -> Result<(), RadioError> { | 69 | pub(crate) fn configure(&mut self) -> Result<(), RadioError> { |
| 92 | trace!("Configuring STM32WL SUBGHZ radio"); | 70 | trace!("Configuring STM32WL SUBGHZ radio"); |
| @@ -151,8 +129,7 @@ impl<'d> StateInner<'d> { | |||
| 151 | self.radio.set_tx(Timeout::DISABLED)?; | 129 | self.radio.set_tx(Timeout::DISABLED)?; |
| 152 | 130 | ||
| 153 | loop { | 131 | loop { |
| 154 | let (_status, irq_status) = IRQ.wait().await; | 132 | let (_status, irq_status) = self.irq_wait().await; |
| 155 | IRQ.reset(); | ||
| 156 | 133 | ||
| 157 | if irq_status & Irq::TxDone.mask() != 0 { | 134 | if irq_status & Irq::TxDone.mask() != 0 { |
| 158 | let stats = self.radio.lora_stats()?; | 135 | let stats = self.radio.lora_stats()?; |
| @@ -214,8 +191,8 @@ impl<'d> StateInner<'d> { | |||
| 214 | trace!("RX started"); | 191 | trace!("RX started"); |
| 215 | 192 | ||
| 216 | loop { | 193 | loop { |
| 217 | let (status, irq_status) = IRQ.wait().await; | 194 | let (status, irq_status) = self.irq_wait().await; |
| 218 | IRQ.reset(); | 195 | |
| 219 | trace!("RX IRQ {:?}, {:?}", status, irq_status); | 196 | trace!("RX IRQ {:?}, {:?}", status, irq_status); |
| 220 | if irq_status & Irq::RxDone.mask() != 0 { | 197 | if irq_status & Irq::RxDone.mask() != 0 { |
| 221 | let (status, len, ptr) = self.radio.rx_buffer_status()?; | 198 | let (status, len, ptr) = self.radio.rx_buffer_status()?; |
| @@ -238,17 +215,24 @@ impl<'d> StateInner<'d> { | |||
| 238 | } | 215 | } |
| 239 | } | 216 | } |
| 240 | 217 | ||
| 241 | /// Read interrupt status and store in global signal | 218 | async fn irq_wait(&mut self) -> (Status, u16) { |
| 242 | fn on_interrupt(&mut self) { | 219 | poll_fn(|cx| { |
| 243 | let (status, irq_status) = self.radio.irq_status().expect("error getting irq status"); | 220 | self.irq.unpend(); |
| 244 | self.radio | 221 | self.irq.enable(); |
| 245 | .clear_irq_status(irq_status) | 222 | IRQ_WAKER.register(cx.waker()); |
| 246 | .expect("error clearing irq status"); | 223 | |
| 247 | if irq_status & Irq::PreambleDetected.mask() != 0 { | 224 | let (status, irq_status) = self.radio.irq_status().expect("error getting irq status"); |
| 248 | trace!("Preamble detected, ignoring"); | 225 | self.radio |
| 249 | } else { | 226 | .clear_irq_status(irq_status) |
| 250 | IRQ.signal((status, irq_status)); | 227 | .expect("error clearing irq status"); |
| 251 | } | 228 | trace!("IRQ status: {=u16:b}", irq_status); |
| 229 | if irq_status == 0 { | ||
| 230 | Poll::Pending | ||
| 231 | } else { | ||
| 232 | Poll::Ready((status, irq_status)) | ||
| 233 | } | ||
| 234 | }) | ||
| 235 | .await | ||
| 252 | } | 236 | } |
| 253 | } | 237 | } |
| 254 | 238 | ||
| @@ -257,18 +241,12 @@ impl PhyRxTx for SubGhzRadio<'static> { | |||
| 257 | 241 | ||
| 258 | type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm; | 242 | type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm; |
| 259 | fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> { | 243 | fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> { |
| 260 | async move { | 244 | async move { self.do_tx(config, buf).await } |
| 261 | let inner = unsafe { &mut *self.state }; | ||
| 262 | inner.do_tx(config, buf).await | ||
| 263 | } | ||
| 264 | } | 245 | } |
| 265 | 246 | ||
| 266 | type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm; | 247 | type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm; |
| 267 | fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> { | 248 | fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> { |
| 268 | async move { | 249 | async move { self.do_rx(config, buf).await } |
| 269 | let inner = unsafe { &mut *self.state }; | ||
| 270 | inner.do_rx(config, buf).await | ||
| 271 | } | ||
| 272 | } | 250 | } |
| 273 | } | 251 | } |
| 274 | 252 | ||
| @@ -278,7 +256,7 @@ impl From<embassy_stm32::spi::Error> for RadioError { | |||
| 278 | } | 256 | } |
| 279 | } | 257 | } |
| 280 | 258 | ||
| 281 | impl<'d> Timings for SubGhzRadio<'d> { | 259 | impl<'d, RS> Timings for SubGhzRadio<'d, RS> { |
| 282 | fn get_rx_window_offset_ms(&self) -> i32 { | 260 | fn get_rx_window_offset_ms(&self) -> i32 { |
| 283 | -200 | 261 | -200 |
| 284 | } | 262 | } |
