embassy/embassy-lora/src/stm32wl/mod.rs
Daniel Bevenius 06a76cd7ce Extract setting of lora modulation params
This commit suggests extracting the lora modulation parameters into a
separate function which can then be called from both the do_tx, and
the do_rx functions.
2022-06-15 16:21:52 +02:00

339 lines
11 KiB
Rust

//! A radio driver integration for the radio found on STM32WL family devices.
use core::future::Future;
use core::mem::MaybeUninit;
use embassy::channel::signal::Signal;
use embassy_hal_common::unborrow;
use embassy_stm32::dma::NoDma;
use embassy_stm32::gpio::{AnyPin, Output};
use embassy_stm32::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::Unborrow;
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
use lorawan_device::async_device::Timings;
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
Idle,
Txing,
Rxing,
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RadioError;
static IRQ: Signal<(Status, u16)> = Signal::new();
struct StateInner<'a> {
radio: SubGhz<'a, NoDma, NoDma>,
switch: RadioSwitch<'a>,
}
/// External state storage for the radio state
pub struct SubGhzState<'a>(MaybeUninit<StateInner<'a>>);
impl<'a> SubGhzState<'a> {
pub const fn new() -> Self {
Self(MaybeUninit::uninit())
}
}
/// The radio peripheral keeping the radio state and owning the radio IRQ.
pub struct SubGhzRadio<'a> {
state: *mut StateInner<'a>,
_irq: SUBGHZ_RADIO,
}
impl<'a> SubGhzRadio<'a> {
/// Create a new instance of a SubGhz radio for LoRaWAN.
///
/// # Safety
/// Do not leak self or futures
pub unsafe fn new(
state: &'a mut SubGhzState<'a>,
radio: SubGhz<'a, NoDma, NoDma>,
switch: RadioSwitch<'a>,
irq: impl Unborrow<Target = SUBGHZ_RADIO>,
) -> Self {
unborrow!(irq);
let mut inner = StateInner { radio, switch };
inner.radio.reset();
let state_ptr = state.0.as_mut_ptr();
state_ptr.write(inner);
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<'a>);
state.on_interrupt();
});
irq.set_handler_context(state_ptr as *mut ());
irq.enable();
Self {
state: state_ptr,
_irq: irq,
}
}
}
impl<'a> StateInner<'a> {
/// Configure radio settings in preparation for TX or RX
pub(crate) fn configure(&mut self) -> Result<(), RadioError> {
trace!("Configuring STM32WL SUBGHZ radio");
self.radio.set_standby(StandbyClk::Rc)?;
let tcxo_mode = TcxoMode::new()
.set_txco_trim(TcxoTrim::Volts1pt7)
.set_timeout(Timeout::from_duration_sat(core::time::Duration::from_millis(40)));
self.radio.set_tcxo_mode(&tcxo_mode)?;
self.radio.set_regulator_mode(RegMode::Ldo)?;
self.radio.calibrate_image(CalibrateImage::ISM_863_870)?;
self.radio.set_buffer_base_address(0, 0)?;
self.radio
.set_pa_config(&PaConfig::new().set_pa_duty_cycle(0x1).set_hp_max(0x0).set_pa(PaSel::Lp))?;
self.radio.set_pa_ocp(Ocp::Max140m)?;
// let tx_params = TxParams::LP_14.set_ramp_time(RampTime::Micros40);
self.radio
.set_tx_params(&TxParams::new().set_ramp_time(RampTime::Micros40).set_power(0x0A))?;
self.radio.set_packet_type(PacketType::LoRa)?;
self.radio.set_lora_sync_word(LoRaSyncWord::Public)?;
trace!("Done initializing STM32WL SUBGHZ radio");
Ok(())
}
/// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form
/// the upcoming RX window start.
async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> {
//trace!("TX Request: {}", config);
trace!("TX START");
self.switch.set_tx_lp();
self.configure()?;
self.radio
.set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?;
self.set_lora_mod_params(config.rf)?;
let packet_params = LoRaPacketParams::new()
.set_preamble_len(8)
.set_header_type(HeaderType::Variable)
.set_payload_len(buf.len() as u8)
.set_crc_en(true)
.set_invert_iq(false);
self.radio.set_lora_packet_params(&packet_params)?;
let irq_cfg = CfgIrq::new()
.irq_enable_all(Irq::TxDone)
.irq_enable_all(Irq::RxDone)
.irq_enable_all(Irq::Timeout);
self.radio.set_irq_cfg(&irq_cfg)?;
self.radio.set_buffer_base_address(0, 0)?;
self.radio.write_buffer(0, buf)?;
self.radio.set_tx(Timeout::DISABLED)?;
loop {
let (_status, irq_status) = IRQ.wait().await;
IRQ.reset();
if irq_status & Irq::TxDone.mask() != 0 {
let stats = self.radio.lora_stats()?;
let (status, error_mask) = self.radio.op_error()?;
trace!(
"TX done. Stats: {:?}. OP error: {:?}, mask {:?}",
stats,
status,
error_mask
);
return Ok(0);
} else if irq_status & Irq::Timeout.mask() != 0 {
trace!("TX timeout");
return Err(RadioError);
}
}
}
fn set_lora_mod_params(&mut self, config: RfConfig) -> Result<(), Error> {
let mod_params = LoRaModParams::new()
.set_sf(convert_spreading_factor(config.spreading_factor))
.set_bw(convert_bandwidth(config.bandwidth))
.set_cr(CodingRate::Cr45)
.set_ldro_en(true);
self.radio.set_lora_mod_params(&mod_params)
}
/// Perform a radio receive operation with the radio config and receive buffer. The receive buffer must
/// be able to hold a single LoRaWAN packet.
async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> {
assert!(buf.len() >= 255);
trace!("RX START");
// trace!("Starting RX: {}", config);
self.switch.set_rx();
self.configure()?;
self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
self.set_lora_mod_params(config)?;
let packet_params = LoRaPacketParams::new()
.set_preamble_len(8)
.set_header_type(HeaderType::Variable)
.set_payload_len(0xFF)
.set_crc_en(true)
.set_invert_iq(true);
self.radio.set_lora_packet_params(&packet_params)?;
let irq_cfg = CfgIrq::new()
.irq_enable_all(Irq::RxDone)
.irq_enable_all(Irq::PreambleDetected)
.irq_enable_all(Irq::HeaderErr)
.irq_enable_all(Irq::Timeout)
.irq_enable_all(Irq::Err);
self.radio.set_irq_cfg(&irq_cfg)?;
self.radio.set_rx(Timeout::DISABLED)?;
trace!("RX started");
loop {
let (status, irq_status) = IRQ.wait().await;
IRQ.reset();
trace!("RX IRQ {:?}, {:?}", status, irq_status);
if irq_status & Irq::RxDone.mask() != 0 {
let (status, len, ptr) = self.radio.rx_buffer_status()?;
let packet_status = self.radio.lora_packet_status()?;
let rssi = packet_status.rssi_pkt().to_integer();
let snr = packet_status.snr_pkt().to_integer();
trace!(
"RX done. Received {} bytes. RX status: {:?}. Pkt status: {:?}",
len,
status.cmd(),
packet_status,
);
self.radio.read_buffer(ptr, &mut buf[..len as usize])?;
self.radio.set_standby(StandbyClk::Rc)?;
return Ok((len as usize, RxQuality::new(rssi, snr as i8)));
} else if irq_status & (Irq::Timeout.mask() | Irq::TxDone.mask()) != 0 {
return Err(RadioError);
}
}
}
/// 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));
}
}
}
impl PhyRxTx for SubGhzRadio<'static> {
type PhyError = RadioError;
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
}
}
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
}
}
}
impl<'a> From<embassy_stm32::spi::Error> for RadioError {
fn from(_: embassy_stm32::spi::Error) -> Self {
RadioError
}
}
impl<'a> Timings for SubGhzRadio<'a> {
fn get_rx_window_offset_ms(&self) -> i32 {
-200
}
fn get_rx_window_duration_ms(&self) -> u32 {
800
}
}
/// Represents the radio switch found on STM32WL based boards, used to control the radio for transmission or reception.
pub struct RadioSwitch<'a> {
ctrl1: Output<'a, AnyPin>,
ctrl2: Output<'a, AnyPin>,
ctrl3: Output<'a, AnyPin>,
}
impl<'a> RadioSwitch<'a> {
pub fn new(ctrl1: Output<'a, AnyPin>, ctrl2: Output<'a, AnyPin>, ctrl3: Output<'a, AnyPin>) -> Self {
Self { ctrl1, ctrl2, ctrl3 }
}
pub(crate) fn set_rx(&mut self) {
self.ctrl1.set_high();
self.ctrl2.set_low();
self.ctrl3.set_high();
}
pub(crate) fn set_tx_lp(&mut self) {
self.ctrl1.set_high();
self.ctrl2.set_high();
self.ctrl3.set_high();
}
#[allow(dead_code)]
pub(crate) fn set_tx_hp(&mut self) {
self.ctrl2.set_high();
self.ctrl1.set_low();
self.ctrl3.set_high();
}
}
fn convert_spreading_factor(sf: SpreadingFactor) -> SF {
match sf {
SpreadingFactor::_7 => SF::Sf7,
SpreadingFactor::_8 => SF::Sf8,
SpreadingFactor::_9 => SF::Sf9,
SpreadingFactor::_10 => SF::Sf10,
SpreadingFactor::_11 => SF::Sf11,
SpreadingFactor::_12 => SF::Sf12,
}
}
fn convert_bandwidth(bw: Bandwidth) -> LoRaBandwidth {
match bw {
Bandwidth::_125KHz => LoRaBandwidth::Bw125,
Bandwidth::_250KHz => LoRaBandwidth::Bw250,
Bandwidth::_500KHz => LoRaBandwidth::Bw500,
}
}