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.
This commit is contained in:
parent
61c666212f
commit
8e8106ef55
2 changed files with 54 additions and 77 deletions
|
@ -1,18 +1,18 @@
|
|||
//! A radio driver integration for the radio found on STM32WL family devices.
|
||||
use core::future::Future;
|
||||
use core::mem::MaybeUninit;
|
||||
use core::task::Poll;
|
||||
|
||||
use embassy_hal_common::{into_ref, PeripheralRef};
|
||||
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
|
||||
use embassy_stm32::dma::NoDma;
|
||||
use embassy_stm32::gpio::{AnyPin, Output};
|
||||
use embassy_stm32::interrupt::{InterruptExt, SUBGHZ_RADIO};
|
||||
use embassy_stm32::interrupt::{Interrupt, InterruptExt, SUBGHZ_RADIO};
|
||||
use embassy_stm32::subghz::{
|
||||
CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, Irq, LoRaBandwidth, LoRaModParams, LoRaPacketParams,
|
||||
LoRaSyncWord, Ocp, PaConfig, PaSel, PacketType, RampTime, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk,
|
||||
Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams,
|
||||
};
|
||||
use embassy_stm32::Peripheral;
|
||||
use embassy_sync::signal::Signal;
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
use futures::future::poll_fn;
|
||||
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
|
||||
use lorawan_device::async_device::Timings;
|
||||
|
||||
|
@ -28,65 +28,43 @@ pub enum State {
|
|||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct RadioError;
|
||||
|
||||
static IRQ: Signal<(Status, u16)> = Signal::new();
|
||||
|
||||
struct StateInner<'d> {
|
||||
radio: SubGhz<'d, NoDma, NoDma>,
|
||||
switch: RadioSwitch<'d>,
|
||||
}
|
||||
|
||||
/// External state storage for the radio state
|
||||
pub struct SubGhzState<'a>(MaybeUninit<StateInner<'a>>);
|
||||
impl<'d> SubGhzState<'d> {
|
||||
pub const fn new() -> Self {
|
||||
Self(MaybeUninit::uninit())
|
||||
}
|
||||
}
|
||||
static IRQ_WAKER: AtomicWaker = AtomicWaker::new();
|
||||
|
||||
/// The radio peripheral keeping the radio state and owning the radio IRQ.
|
||||
pub struct SubGhzRadio<'d> {
|
||||
state: *mut StateInner<'d>,
|
||||
_irq: PeripheralRef<'d, SUBGHZ_RADIO>,
|
||||
pub struct SubGhzRadio<'d, RS> {
|
||||
radio: SubGhz<'d, NoDma, NoDma>,
|
||||
switch: RS,
|
||||
irq: PeripheralRef<'d, SUBGHZ_RADIO>,
|
||||
}
|
||||
|
||||
impl<'d> SubGhzRadio<'d> {
|
||||
#[derive(Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct SubGhzRadioConfig {
|
||||
pub reg_mode: RegMode,
|
||||
pub calibrate_image: CalibrateImage,
|
||||
}
|
||||
|
||||
impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
|
||||
/// Create a new instance of a SubGhz radio for LoRaWAN.
|
||||
///
|
||||
/// # Safety
|
||||
/// Do not leak self or futures
|
||||
pub unsafe fn new(
|
||||
state: &'d mut SubGhzState<'d>,
|
||||
radio: SubGhz<'d, NoDma, NoDma>,
|
||||
switch: RadioSwitch<'d>,
|
||||
pub fn new(
|
||||
mut radio: SubGhz<'d, NoDma, NoDma>,
|
||||
switch: RS,
|
||||
irq: impl Peripheral<P = SUBGHZ_RADIO> + 'd,
|
||||
) -> Self {
|
||||
config: SubGhzRadioConfig,
|
||||
) -> Result<Self, RadioError> {
|
||||
into_ref!(irq);
|
||||
|
||||
let mut inner = StateInner { radio, switch };
|
||||
inner.radio.reset();
|
||||
|
||||
let state_ptr = state.0.as_mut_ptr();
|
||||
state_ptr.write(inner);
|
||||
radio.reset();
|
||||
|
||||
irq.disable();
|
||||
irq.set_handler(|p| {
|
||||
// This is safe because we only get interrupts when configured for, so
|
||||
// the radio will be awaiting on the signal at this point. If not, the ISR will
|
||||
// anyway only adjust the state in the IRQ signal state.
|
||||
let state = &mut *(p as *mut StateInner<'d>);
|
||||
state.on_interrupt();
|
||||
irq.set_handler(|_| {
|
||||
IRQ_WAKER.wake();
|
||||
unsafe { SUBGHZ_RADIO::steal().disable() };
|
||||
});
|
||||
irq.set_handler_context(state_ptr as *mut ());
|
||||
irq.enable();
|
||||
|
||||
Self {
|
||||
state: state_ptr,
|
||||
_irq: irq,
|
||||
}
|
||||
Self { radio, switch, irq }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> StateInner<'d> {
|
||||
/// Configure radio settings in preparation for TX or RX
|
||||
pub(crate) fn configure(&mut self) -> Result<(), RadioError> {
|
||||
trace!("Configuring STM32WL SUBGHZ radio");
|
||||
|
@ -151,8 +129,7 @@ impl<'d> StateInner<'d> {
|
|||
self.radio.set_tx(Timeout::DISABLED)?;
|
||||
|
||||
loop {
|
||||
let (_status, irq_status) = IRQ.wait().await;
|
||||
IRQ.reset();
|
||||
let (_status, irq_status) = self.irq_wait().await;
|
||||
|
||||
if irq_status & Irq::TxDone.mask() != 0 {
|
||||
let stats = self.radio.lora_stats()?;
|
||||
|
@ -214,8 +191,8 @@ impl<'d> StateInner<'d> {
|
|||
trace!("RX started");
|
||||
|
||||
loop {
|
||||
let (status, irq_status) = IRQ.wait().await;
|
||||
IRQ.reset();
|
||||
let (status, irq_status) = self.irq_wait().await;
|
||||
|
||||
trace!("RX IRQ {:?}, {:?}", status, irq_status);
|
||||
if irq_status & Irq::RxDone.mask() != 0 {
|
||||
let (status, len, ptr) = self.radio.rx_buffer_status()?;
|
||||
|
@ -238,17 +215,24 @@ impl<'d> StateInner<'d> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Read interrupt status and store in global signal
|
||||
fn on_interrupt(&mut self) {
|
||||
let (status, irq_status) = self.radio.irq_status().expect("error getting irq status");
|
||||
self.radio
|
||||
.clear_irq_status(irq_status)
|
||||
.expect("error clearing irq status");
|
||||
if irq_status & Irq::PreambleDetected.mask() != 0 {
|
||||
trace!("Preamble detected, ignoring");
|
||||
} else {
|
||||
IRQ.signal((status, irq_status));
|
||||
}
|
||||
async fn irq_wait(&mut self) -> (Status, u16) {
|
||||
poll_fn(|cx| {
|
||||
self.irq.unpend();
|
||||
self.irq.enable();
|
||||
IRQ_WAKER.register(cx.waker());
|
||||
|
||||
let (status, irq_status) = self.radio.irq_status().expect("error getting irq status");
|
||||
self.radio
|
||||
.clear_irq_status(irq_status)
|
||||
.expect("error clearing irq status");
|
||||
trace!("IRQ status: {=u16:b}", irq_status);
|
||||
if irq_status == 0 {
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready((status, irq_status))
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -257,18 +241,12 @@ impl PhyRxTx for SubGhzRadio<'static> {
|
|||
|
||||
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm;
|
||||
fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
|
||||
async move {
|
||||
let inner = unsafe { &mut *self.state };
|
||||
inner.do_tx(config, buf).await
|
||||
}
|
||||
async move { self.do_tx(config, buf).await }
|
||||
}
|
||||
|
||||
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm;
|
||||
fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
|
||||
async move {
|
||||
let inner = unsafe { &mut *self.state };
|
||||
inner.do_rx(config, buf).await
|
||||
}
|
||||
async move { self.do_rx(config, buf).await }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,7 +256,7 @@ impl From<embassy_stm32::spi::Error> for RadioError {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'d> Timings for SubGhzRadio<'d> {
|
||||
impl<'d, RS> Timings for SubGhzRadio<'d, RS> {
|
||||
fn get_rx_window_offset_ms(&self) -> i32 {
|
||||
-200
|
||||
}
|
||||
|
|
|
@ -32,10 +32,9 @@ async fn main(_spawner: Spawner) {
|
|||
let rfs = RadioSwitch::new(ctrl1, ctrl2, ctrl3);
|
||||
|
||||
let radio = SubGhz::new(p.SUBGHZSPI, NoDma, NoDma);
|
||||
|
||||
let irq = interrupt::take!(SUBGHZ_RADIO);
|
||||
static mut RADIO_STATE: SubGhzState<'static> = SubGhzState::new();
|
||||
let radio = unsafe { SubGhzRadio::new(&mut RADIO_STATE, radio, rfs, irq) };
|
||||
|
||||
let radio = SubGhzRadio::new(radio, rfs, irq);
|
||||
|
||||
let mut region: region::Configuration = region::EU868::default().into();
|
||||
|
||||
|
|
Loading…
Reference in a new issue