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:
Timo Kröger 2022-06-25 07:01:31 +02:00
parent 61c666212f
commit 8e8106ef55
2 changed files with 54 additions and 77 deletions

View file

@ -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
}

View file

@ -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();