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 <timokroeger93@gmail.com> Co-authored-by: Ulf Lilleengen <lulf@redhat.com>
This commit is contained in:
commit
6264fe39a5
@ -18,6 +18,7 @@ flavors = [
|
|||||||
sx127x = []
|
sx127x = []
|
||||||
stm32wl = ["embassy-stm32", "embassy-stm32/subghz"]
|
stm32wl = ["embassy-stm32", "embassy-stm32/subghz"]
|
||||||
time = []
|
time = []
|
||||||
|
defmt = ["dep:defmt", "lorawan/defmt", "lorawan-device/defmt"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
@ -34,5 +35,5 @@ futures = { version = "0.3.17", default-features = false, features = [ "async-aw
|
|||||||
embedded-hal = { version = "0.2", features = ["unproven"] }
|
embedded-hal = { version = "0.2", features = ["unproven"] }
|
||||||
bit_field = { version = "0.10" }
|
bit_field = { version = "0.10" }
|
||||||
|
|
||||||
lorawan-device = { version = "0.7.1", default-features = false, features = ["async"] }
|
lorawan-device = { version = "0.8.0", default-features = false, features = ["async"] }
|
||||||
lorawan = { version = "0.7.1", default-features = false }
|
lorawan = { version = "0.7.1", default-features = false }
|
||||||
|
@ -11,13 +11,35 @@ pub mod stm32wl;
|
|||||||
#[cfg(feature = "sx127x")]
|
#[cfg(feature = "sx127x")]
|
||||||
pub mod sx127x;
|
pub mod sx127x;
|
||||||
|
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
use embassy_time::{Duration, Instant, Timer};
|
||||||
|
|
||||||
/// A convenience timer to use with the LoRaWAN crate
|
/// A convenience timer to use with the LoRaWAN crate
|
||||||
pub struct LoraTimer;
|
#[cfg(feature = "time")]
|
||||||
|
pub struct LoraTimer {
|
||||||
|
start: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
impl LoraTimer {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { start: Instant::now() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
#[cfg(feature = "time")]
|
||||||
impl lorawan_device::async_device::radio::Timer for LoraTimer {
|
impl lorawan_device::async_device::radio::Timer for LoraTimer {
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.start = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
type AtFuture<'m> = impl core::future::Future<Output = ()> + 'm;
|
||||||
|
fn at<'m>(&'m mut self, millis: u64) -> Self::AtFuture<'m> {
|
||||||
|
Timer::at(self.start + Duration::from_millis(millis))
|
||||||
|
}
|
||||||
|
|
||||||
type DelayFuture<'m> = impl core::future::Future<Output = ()> + 'm;
|
type DelayFuture<'m> = impl core::future::Future<Output = ()> + 'm;
|
||||||
fn delay_ms<'m>(&'m mut self, millis: u64) -> Self::DelayFuture<'m> {
|
fn delay_ms<'m>(&'m mut self, millis: u64) -> Self::DelayFuture<'m> {
|
||||||
embassy_time::Timer::after(embassy_time::Duration::from_millis(millis))
|
Timer::after(Duration::from_millis(millis))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
//! A radio driver integration for the radio found on STM32WL family devices.
|
//! A radio driver integration for the radio found on STM32WL family devices.
|
||||||
use core::future::Future;
|
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::dma::NoDma;
|
||||||
use embassy_stm32::gpio::{AnyPin, Output};
|
use embassy_stm32::interrupt::{Interrupt, InterruptExt, SUBGHZ_RADIO};
|
||||||
use embassy_stm32::interrupt::{InterruptExt, SUBGHZ_RADIO};
|
|
||||||
use embassy_stm32::subghz::{
|
use embassy_stm32::subghz::{
|
||||||
CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, Irq, LoRaBandwidth, LoRaModParams, LoRaPacketParams,
|
CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, HseTrim, Irq, LoRaBandwidth, LoRaModParams,
|
||||||
LoRaSyncWord, Ocp, PaConfig, PaSel, PacketType, RampTime, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk,
|
LoRaPacketParams, LoRaSyncWord, Ocp, PaConfig, PacketType, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk,
|
||||||
Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams,
|
Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams,
|
||||||
};
|
};
|
||||||
use embassy_stm32::Peripheral;
|
use embassy_sync::waitqueue::AtomicWaker;
|
||||||
use embassy_sync::signal::Signal;
|
use futures::future::poll_fn;
|
||||||
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
|
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
|
||||||
use lorawan_device::async_device::Timings;
|
use lorawan_device::async_device::Timings;
|
||||||
|
|
||||||
@ -28,102 +27,52 @@ pub enum State {
|
|||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub struct RadioError;
|
pub struct RadioError;
|
||||||
|
|
||||||
static IRQ: Signal<(Status, u16)> = Signal::new();
|
static IRQ_WAKER: AtomicWaker = AtomicWaker::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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The radio peripheral keeping the radio state and owning the radio IRQ.
|
/// The radio peripheral keeping the radio state and owning the radio IRQ.
|
||||||
pub struct SubGhzRadio<'d> {
|
pub struct SubGhzRadio<'d, RS> {
|
||||||
state: *mut StateInner<'d>,
|
radio: SubGhz<'d, NoDma, NoDma>,
|
||||||
_irq: PeripheralRef<'d, SUBGHZ_RADIO>,
|
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,
|
||||||
|
pub pa_config: PaConfig,
|
||||||
|
pub tx_params: TxParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
|
||||||
/// Create a new instance of a SubGhz radio for LoRaWAN.
|
/// Create a new instance of a SubGhz radio for LoRaWAN.
|
||||||
///
|
pub fn new(
|
||||||
/// # Safety
|
mut radio: SubGhz<'d, NoDma, NoDma>,
|
||||||
/// Do not leak self or futures
|
switch: RS,
|
||||||
pub unsafe fn new(
|
|
||||||
state: &'d mut SubGhzState<'d>,
|
|
||||||
radio: SubGhz<'d, NoDma, NoDma>,
|
|
||||||
switch: RadioSwitch<'d>,
|
|
||||||
irq: impl Peripheral<P = SUBGHZ_RADIO> + 'd,
|
irq: impl Peripheral<P = SUBGHZ_RADIO> + 'd,
|
||||||
) -> Self {
|
config: SubGhzRadioConfig,
|
||||||
|
) -> Result<Self, RadioError> {
|
||||||
into_ref!(irq);
|
into_ref!(irq);
|
||||||
|
|
||||||
let mut inner = StateInner { radio, switch };
|
radio.reset();
|
||||||
inner.radio.reset();
|
|
||||||
|
|
||||||
let state_ptr = state.0.as_mut_ptr();
|
|
||||||
state_ptr.write(inner);
|
|
||||||
|
|
||||||
irq.disable();
|
irq.disable();
|
||||||
irq.set_handler(|p| {
|
irq.set_handler(|_| {
|
||||||
// This is safe because we only get interrupts when configured for, so
|
IRQ_WAKER.wake();
|
||||||
// the radio will be awaiting on the signal at this point. If not, the ISR will
|
unsafe { SUBGHZ_RADIO::steal().disable() };
|
||||||
// anyway only adjust the state in the IRQ signal state.
|
|
||||||
let state = &mut *(p as *mut StateInner<'d>);
|
|
||||||
state.on_interrupt();
|
|
||||||
});
|
});
|
||||||
irq.set_handler_context(state_ptr as *mut ());
|
|
||||||
irq.enable();
|
|
||||||
|
|
||||||
Self {
|
configure_radio(&mut radio, config)?;
|
||||||
state: state_ptr,
|
|
||||||
_irq: irq,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'d> StateInner<'d> {
|
Ok(Self { radio, switch, irq })
|
||||||
/// 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
|
/// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form
|
||||||
/// the upcoming RX window start.
|
/// the upcoming RX window start.
|
||||||
async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> {
|
async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> {
|
||||||
//trace!("TX Request: {}", config);
|
trace!("TX request: {}", config);
|
||||||
trace!("TX START");
|
self.switch.set_tx();
|
||||||
self.switch.set_tx_lp();
|
|
||||||
self.configure()?;
|
|
||||||
|
|
||||||
self.radio
|
self.radio
|
||||||
.set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?;
|
.set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?;
|
||||||
@ -139,34 +88,26 @@ impl<'d> StateInner<'d> {
|
|||||||
|
|
||||||
self.radio.set_lora_packet_params(&packet_params)?;
|
self.radio.set_lora_packet_params(&packet_params)?;
|
||||||
|
|
||||||
let irq_cfg = CfgIrq::new()
|
let irq_cfg = CfgIrq::new().irq_enable_all(Irq::TxDone).irq_enable_all(Irq::Timeout);
|
||||||
.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_irq_cfg(&irq_cfg)?;
|
||||||
|
|
||||||
self.radio.set_buffer_base_address(0, 0)?;
|
self.radio.set_buffer_base_address(0, 0)?;
|
||||||
self.radio.write_buffer(0, buf)?;
|
self.radio.write_buffer(0, buf)?;
|
||||||
|
|
||||||
self.radio.set_tx(Timeout::DISABLED)?;
|
// The maximum airtime for any LoRaWAN package is 2793.5ms.
|
||||||
|
// The value of 4000ms is copied from C driver and gives us a good safety margin.
|
||||||
|
self.radio.set_tx(Timeout::from_millis_sat(4000))?;
|
||||||
|
trace!("TX started");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (_status, irq_status) = IRQ.wait().await;
|
let (_status, irq_status) = self.irq_wait().await;
|
||||||
IRQ.reset();
|
|
||||||
|
|
||||||
if irq_status & Irq::TxDone.mask() != 0 {
|
if irq_status & Irq::TxDone.mask() != 0 {
|
||||||
let stats = self.radio.lora_stats()?;
|
trace!("TX done");
|
||||||
let (status, error_mask) = self.radio.op_error()?;
|
|
||||||
trace!(
|
|
||||||
"TX done. Stats: {:?}. OP error: {:?}, mask {:?}",
|
|
||||||
stats,
|
|
||||||
status,
|
|
||||||
error_mask
|
|
||||||
);
|
|
||||||
|
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
} else if irq_status & Irq::Timeout.mask() != 0 {
|
}
|
||||||
trace!("TX timeout");
|
|
||||||
|
if irq_status & Irq::Timeout.mask() != 0 {
|
||||||
return Err(RadioError);
|
return Err(RadioError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -174,10 +115,15 @@ impl<'d> StateInner<'d> {
|
|||||||
|
|
||||||
fn set_lora_mod_params(&mut self, config: RfConfig) -> Result<(), Error> {
|
fn set_lora_mod_params(&mut self, config: RfConfig) -> Result<(), Error> {
|
||||||
let mod_params = LoRaModParams::new()
|
let mod_params = LoRaModParams::new()
|
||||||
.set_sf(convert_spreading_factor(config.spreading_factor))
|
.set_sf(convert_spreading_factor(&config.spreading_factor))
|
||||||
.set_bw(convert_bandwidth(config.bandwidth))
|
.set_bw(convert_bandwidth(&config.bandwidth))
|
||||||
.set_cr(CodingRate::Cr45)
|
.set_cr(CodingRate::Cr45)
|
||||||
.set_ldro_en(true);
|
.set_ldro_en(matches!(
|
||||||
|
(config.spreading_factor, config.bandwidth),
|
||||||
|
(SpreadingFactor::_12, Bandwidth::_125KHz)
|
||||||
|
| (SpreadingFactor::_12, Bandwidth::_250KHz)
|
||||||
|
| (SpreadingFactor::_11, Bandwidth::_125KHz)
|
||||||
|
));
|
||||||
self.radio.set_lora_mod_params(&mod_params)
|
self.radio.set_lora_mod_params(&mod_params)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,10 +131,8 @@ impl<'d> StateInner<'d> {
|
|||||||
/// be able to hold a single LoRaWAN packet.
|
/// be able to hold a single LoRaWAN packet.
|
||||||
async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> {
|
async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> {
|
||||||
assert!(buf.len() >= 255);
|
assert!(buf.len() >= 255);
|
||||||
trace!("RX START");
|
trace!("RX request: {}", config);
|
||||||
// trace!("Starting RX: {}", config);
|
|
||||||
self.switch.set_rx();
|
self.switch.set_rx();
|
||||||
self.configure()?;
|
|
||||||
|
|
||||||
self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
|
self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
|
||||||
|
|
||||||
@ -198,77 +142,110 @@ impl<'d> StateInner<'d> {
|
|||||||
.set_preamble_len(8)
|
.set_preamble_len(8)
|
||||||
.set_header_type(HeaderType::Variable)
|
.set_header_type(HeaderType::Variable)
|
||||||
.set_payload_len(0xFF)
|
.set_payload_len(0xFF)
|
||||||
.set_crc_en(true)
|
.set_crc_en(false)
|
||||||
.set_invert_iq(true);
|
.set_invert_iq(true);
|
||||||
self.radio.set_lora_packet_params(&packet_params)?;
|
self.radio.set_lora_packet_params(&packet_params)?;
|
||||||
|
|
||||||
let irq_cfg = CfgIrq::new()
|
let irq_cfg = CfgIrq::new()
|
||||||
.irq_enable_all(Irq::RxDone)
|
.irq_enable_all(Irq::RxDone)
|
||||||
.irq_enable_all(Irq::PreambleDetected)
|
.irq_enable_all(Irq::PreambleDetected)
|
||||||
|
.irq_enable_all(Irq::HeaderValid)
|
||||||
.irq_enable_all(Irq::HeaderErr)
|
.irq_enable_all(Irq::HeaderErr)
|
||||||
.irq_enable_all(Irq::Timeout)
|
.irq_enable_all(Irq::Err)
|
||||||
.irq_enable_all(Irq::Err);
|
.irq_enable_all(Irq::Timeout);
|
||||||
self.radio.set_irq_cfg(&irq_cfg)?;
|
self.radio.set_irq_cfg(&irq_cfg)?;
|
||||||
|
|
||||||
|
self.radio.set_buffer_base_address(0, 0)?;
|
||||||
|
|
||||||
|
// NOTE: Upper layer handles timeout by cancelling the future
|
||||||
self.radio.set_rx(Timeout::DISABLED)?;
|
self.radio.set_rx(Timeout::DISABLED)?;
|
||||||
|
|
||||||
trace!("RX started");
|
trace!("RX started");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (status, irq_status) = IRQ.wait().await;
|
let (_status, irq_status) = self.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()?;
|
|
||||||
|
|
||||||
|
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 packet_status = self.radio.lora_packet_status()?;
|
||||||
let rssi = packet_status.rssi_pkt().to_integer();
|
let rssi = packet_status.rssi_pkt().to_integer();
|
||||||
let snr = packet_status.snr_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.read_buffer(ptr, &mut buf[..len as usize])?;
|
||||||
self.radio.set_standby(StandbyClk::Rc)?;
|
self.radio.set_standby(StandbyClk::Rc)?;
|
||||||
|
|
||||||
|
trace!("RX done: {=[u8]:#02X}", &mut buf[..len as usize]);
|
||||||
return Ok((len as usize, RxQuality::new(rssi, snr as i8)));
|
return Ok((len as usize, RxQuality::new(rssi, snr as i8)));
|
||||||
} else if irq_status & (Irq::Timeout.mask() | Irq::TxDone.mask()) != 0 {
|
}
|
||||||
|
|
||||||
|
if irq_status & Irq::Timeout.mask() != 0 {
|
||||||
return Err(RadioError);
|
return Err(RadioError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read interrupt status and store in global signal
|
async fn irq_wait(&mut self) -> (Status, u16) {
|
||||||
fn on_interrupt(&mut self) {
|
poll_fn(|cx| {
|
||||||
let (status, irq_status) = self.radio.irq_status().expect("error getting irq status");
|
self.irq.unpend();
|
||||||
self.radio
|
self.irq.enable();
|
||||||
.clear_irq_status(irq_status)
|
IRQ_WAKER.register(cx.waker());
|
||||||
.expect("error clearing irq status");
|
|
||||||
if irq_status & Irq::PreambleDetected.mask() != 0 {
|
let (status, irq_status) = self.radio.irq_status().expect("error getting irq status");
|
||||||
trace!("Preamble detected, ignoring");
|
self.radio
|
||||||
} else {
|
.clear_irq_status(irq_status)
|
||||||
IRQ.signal((status, irq_status));
|
.expect("error clearing irq status");
|
||||||
}
|
|
||||||
|
trace!("SUGHZ IRQ 0b{=u16:b}, {:?}", irq_status, status);
|
||||||
|
|
||||||
|
if irq_status == 0 {
|
||||||
|
Poll::Pending
|
||||||
|
} else {
|
||||||
|
Poll::Ready((status, irq_status))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PhyRxTx for SubGhzRadio<'static> {
|
fn configure_radio(radio: &mut SubGhz<'_, NoDma, NoDma>, config: SubGhzRadioConfig) -> Result<(), RadioError> {
|
||||||
|
trace!("Configuring STM32WL SUBGHZ radio");
|
||||||
|
|
||||||
|
radio.set_regulator_mode(config.reg_mode)?;
|
||||||
|
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(100)));
|
||||||
|
radio.set_tcxo_mode(&tcxo_mode)?;
|
||||||
|
// Reduce input capacitance as shown in Reference Manual "Figure 23. HSE32 TCXO control".
|
||||||
|
// The STM32CUBE C driver also does this.
|
||||||
|
radio.set_hse_in_trim(HseTrim::MIN)?;
|
||||||
|
|
||||||
|
// Re-calibrate everything after setting the TXCO config.
|
||||||
|
radio.calibrate(0x7F)?;
|
||||||
|
radio.calibrate_image(config.calibrate_image)?;
|
||||||
|
|
||||||
|
radio.set_pa_config(&config.pa_config)?;
|
||||||
|
radio.set_tx_params(&config.tx_params)?;
|
||||||
|
radio.set_pa_ocp(Ocp::Max140m)?;
|
||||||
|
|
||||||
|
radio.set_packet_type(PacketType::LoRa)?;
|
||||||
|
radio.set_lora_sync_word(LoRaSyncWord::Public)?;
|
||||||
|
|
||||||
|
trace!("Done initializing STM32WL SUBGHZ radio");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<RS: RadioSwitch> PhyRxTx for SubGhzRadio<'static, RS> {
|
||||||
type PhyError = RadioError;
|
type PhyError = RadioError;
|
||||||
|
|
||||||
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm;
|
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm where RS: 'm;
|
||||||
fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
|
fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
|
||||||
async move {
|
async move { self.do_tx(config, buf).await }
|
||||||
let inner = unsafe { &mut *self.state };
|
|
||||||
inner.do_tx(config, buf).await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm;
|
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm where RS: 'm;
|
||||||
fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
|
fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
|
||||||
async move {
|
async move { self.do_rx(config, buf).await }
|
||||||
let inner = unsafe { &mut *self.state };
|
|
||||||
inner.do_rx(config, buf).await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,48 +255,21 @@ 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 {
|
fn get_rx_window_offset_ms(&self) -> i32 {
|
||||||
-200
|
-500
|
||||||
}
|
}
|
||||||
fn get_rx_window_duration_ms(&self) -> u32 {
|
fn get_rx_window_duration_ms(&self) -> u32 {
|
||||||
800
|
3000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the radio switch found on STM32WL based boards, used to control the radio for transmission or reception.
|
pub trait RadioSwitch {
|
||||||
pub struct RadioSwitch<'d> {
|
fn set_rx(&mut self);
|
||||||
ctrl1: Output<'d, AnyPin>,
|
fn set_tx(&mut self);
|
||||||
ctrl2: Output<'d, AnyPin>,
|
|
||||||
ctrl3: Output<'d, AnyPin>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d> RadioSwitch<'d> {
|
fn convert_spreading_factor(sf: &SpreadingFactor) -> SF {
|
||||||
pub fn new(ctrl1: Output<'d, AnyPin>, ctrl2: Output<'d, AnyPin>, ctrl3: Output<'d, 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 {
|
match sf {
|
||||||
SpreadingFactor::_7 => SF::Sf7,
|
SpreadingFactor::_7 => SF::Sf7,
|
||||||
SpreadingFactor::_8 => SF::Sf8,
|
SpreadingFactor::_8 => SF::Sf8,
|
||||||
@ -330,7 +280,7 @@ fn convert_spreading_factor(sf: SpreadingFactor) -> SF {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_bandwidth(bw: Bandwidth) -> LoRaBandwidth {
|
fn convert_bandwidth(bw: &Bandwidth) -> LoRaBandwidth {
|
||||||
match bw {
|
match bw {
|
||||||
Bandwidth::_125KHz => LoRaBandwidth::Bw125,
|
Bandwidth::_125KHz => LoRaBandwidth::Bw125,
|
||||||
Bandwidth::_250KHz => LoRaBandwidth::Bw250,
|
Bandwidth::_250KHz => LoRaBandwidth::Bw250,
|
||||||
|
@ -202,54 +202,11 @@ impl Default for Config {
|
|||||||
|
|
||||||
pub(crate) unsafe fn init(config: Config) {
|
pub(crate) unsafe fn init(config: Config) {
|
||||||
let (sys_clk, sw, vos) = match config.mux {
|
let (sys_clk, sw, vos) = match config.mux {
|
||||||
ClockSrc::HSI16 => {
|
ClockSrc::HSI16 => (HSI_FREQ.0, 0x01, VoltageScale::Range2),
|
||||||
// Enable HSI16
|
ClockSrc::HSE32 => (HSE32_FREQ.0, 0x02, VoltageScale::Range1),
|
||||||
RCC.cr().write(|w| w.set_hsion(true));
|
ClockSrc::MSI(range) => (range.freq(), 0x00, range.vos()),
|
||||||
while !RCC.cr().read().hsirdy() {}
|
|
||||||
|
|
||||||
(HSI_FREQ.0, 0x01, VoltageScale::Range2)
|
|
||||||
}
|
|
||||||
ClockSrc::HSE32 => {
|
|
||||||
// Enable HSE32
|
|
||||||
RCC.cr().write(|w| {
|
|
||||||
w.set_hsebyppwr(true);
|
|
||||||
w.set_hseon(true);
|
|
||||||
});
|
|
||||||
while !RCC.cr().read().hserdy() {}
|
|
||||||
|
|
||||||
(HSE32_FREQ.0, 0x02, VoltageScale::Range1)
|
|
||||||
}
|
|
||||||
ClockSrc::MSI(range) => {
|
|
||||||
RCC.cr().write(|w| {
|
|
||||||
w.set_msirange(range.into());
|
|
||||||
w.set_msion(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
while !RCC.cr().read().msirdy() {}
|
|
||||||
|
|
||||||
(range.freq(), 0x00, range.vos())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
RCC.cfgr().modify(|w| {
|
|
||||||
w.set_sw(sw.into());
|
|
||||||
if config.ahb_pre == AHBPrescaler::NotDivided {
|
|
||||||
w.set_hpre(0);
|
|
||||||
} else {
|
|
||||||
w.set_hpre(config.ahb_pre.into());
|
|
||||||
}
|
|
||||||
w.set_ppre1(config.apb1_pre.into());
|
|
||||||
w.set_ppre2(config.apb2_pre.into());
|
|
||||||
});
|
|
||||||
|
|
||||||
RCC.extcfgr().modify(|w| {
|
|
||||||
if config.shd_ahb_pre == AHBPrescaler::NotDivided {
|
|
||||||
w.set_shdhpre(0);
|
|
||||||
} else {
|
|
||||||
w.set_shdhpre(config.shd_ahb_pre.into());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let ahb_freq: u32 = match config.ahb_pre {
|
let ahb_freq: u32 = match config.ahb_pre {
|
||||||
AHBPrescaler::NotDivided => sys_clk,
|
AHBPrescaler::NotDivided => sys_clk,
|
||||||
pre => {
|
pre => {
|
||||||
@ -288,16 +245,6 @@ pub(crate) unsafe fn init(config: Config) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let apb3_freq = shd_ahb_freq;
|
|
||||||
|
|
||||||
if config.enable_lsi {
|
|
||||||
let csr = RCC.csr().read();
|
|
||||||
if !csr.lsion() {
|
|
||||||
RCC.csr().modify(|w| w.set_lsion(true));
|
|
||||||
while !RCC.csr().read().lsirdy() {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust flash latency
|
// Adjust flash latency
|
||||||
let flash_clk_src_freq: u32 = shd_ahb_freq;
|
let flash_clk_src_freq: u32 = shd_ahb_freq;
|
||||||
let ws = match vos {
|
let ws = match vos {
|
||||||
@ -319,6 +266,61 @@ pub(crate) unsafe fn init(config: Config) {
|
|||||||
|
|
||||||
while FLASH.acr().read().latency() != ws {}
|
while FLASH.acr().read().latency() != ws {}
|
||||||
|
|
||||||
|
match config.mux {
|
||||||
|
ClockSrc::HSI16 => {
|
||||||
|
// Enable HSI16
|
||||||
|
RCC.cr().write(|w| w.set_hsion(true));
|
||||||
|
while !RCC.cr().read().hsirdy() {}
|
||||||
|
}
|
||||||
|
ClockSrc::HSE32 => {
|
||||||
|
// Enable HSE32
|
||||||
|
RCC.cr().write(|w| {
|
||||||
|
w.set_hsebyppwr(true);
|
||||||
|
w.set_hseon(true);
|
||||||
|
});
|
||||||
|
while !RCC.cr().read().hserdy() {}
|
||||||
|
}
|
||||||
|
ClockSrc::MSI(range) => {
|
||||||
|
let cr = RCC.cr().read();
|
||||||
|
assert!(!cr.msion() || cr.msirdy());
|
||||||
|
RCC.cr().write(|w| {
|
||||||
|
w.set_msirgsel(true);
|
||||||
|
w.set_msirange(range.into());
|
||||||
|
w.set_msion(true);
|
||||||
|
});
|
||||||
|
while !RCC.cr().read().msirdy() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RCC.extcfgr().modify(|w| {
|
||||||
|
if config.shd_ahb_pre == AHBPrescaler::NotDivided {
|
||||||
|
w.set_shdhpre(0);
|
||||||
|
} else {
|
||||||
|
w.set_shdhpre(config.shd_ahb_pre.into());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
RCC.cfgr().modify(|w| {
|
||||||
|
w.set_sw(sw.into());
|
||||||
|
if config.ahb_pre == AHBPrescaler::NotDivided {
|
||||||
|
w.set_hpre(0);
|
||||||
|
} else {
|
||||||
|
w.set_hpre(config.ahb_pre.into());
|
||||||
|
}
|
||||||
|
w.set_ppre1(config.apb1_pre.into());
|
||||||
|
w.set_ppre2(config.apb2_pre.into());
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: switch voltage range
|
||||||
|
|
||||||
|
if config.enable_lsi {
|
||||||
|
let csr = RCC.csr().read();
|
||||||
|
if !csr.lsion() {
|
||||||
|
RCC.csr().modify(|w| w.set_lsion(true));
|
||||||
|
while !RCC.csr().read().lsirdy() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
set_freqs(Clocks {
|
set_freqs(Clocks {
|
||||||
sys: Hertz(sys_clk),
|
sys: Hertz(sys_clk),
|
||||||
ahb1: Hertz(ahb_freq),
|
ahb1: Hertz(ahb_freq),
|
||||||
@ -326,7 +328,7 @@ pub(crate) unsafe fn init(config: Config) {
|
|||||||
ahb3: Hertz(shd_ahb_freq),
|
ahb3: Hertz(shd_ahb_freq),
|
||||||
apb1: Hertz(apb1_freq),
|
apb1: Hertz(apb1_freq),
|
||||||
apb2: Hertz(apb2_freq),
|
apb2: Hertz(apb2_freq),
|
||||||
apb3: Hertz(apb3_freq),
|
apb3: Hertz(shd_ahb_freq),
|
||||||
apb1_tim: Hertz(apb1_tim_freq),
|
apb1_tim: Hertz(apb1_tim_freq),
|
||||||
apb2_tim: Hertz(apb2_tim_freq),
|
apb2_tim: Hertz(apb2_tim_freq),
|
||||||
});
|
});
|
||||||
|
@ -179,6 +179,19 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Useful for on chip peripherals like SUBGHZ which are hardwired.
|
||||||
|
/// The bus can optionally be exposed externally with `Spi::new()` still.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn new_internal(
|
||||||
|
peri: impl Peripheral<P = T> + 'd,
|
||||||
|
txdma: impl Peripheral<P = Tx> + 'd,
|
||||||
|
rxdma: impl Peripheral<P = Rx> + 'd,
|
||||||
|
freq: Hertz,
|
||||||
|
config: Config,
|
||||||
|
) -> Self {
|
||||||
|
Self::new_inner(peri, None, None, None, txdma, rxdma, freq, config)
|
||||||
|
}
|
||||||
|
|
||||||
fn new_inner(
|
fn new_inner(
|
||||||
peri: impl Peripheral<P = T> + 'd,
|
peri: impl Peripheral<P = T> + 'd,
|
||||||
sck: Option<PeripheralRef<'d, AnyPin>>,
|
sck: Option<PeripheralRef<'d, AnyPin>>,
|
||||||
|
@ -81,7 +81,7 @@ pub use value_error::ValueError;
|
|||||||
use crate::dma::NoDma;
|
use crate::dma::NoDma;
|
||||||
use crate::peripherals::SUBGHZSPI;
|
use crate::peripherals::SUBGHZSPI;
|
||||||
use crate::rcc::sealed::RccPeripheral;
|
use crate::rcc::sealed::RccPeripheral;
|
||||||
use crate::spi::{BitOrder, Config as SpiConfig, MisoPin, MosiPin, SckPin, Spi, MODE_0};
|
use crate::spi::{BitOrder, Config as SpiConfig, Spi, MODE_0};
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
use crate::{pac, Peripheral};
|
use crate::{pac, Peripheral};
|
||||||
|
|
||||||
@ -212,9 +212,6 @@ impl<'d, Tx, Rx> SubGhz<'d, Tx, Rx> {
|
|||||||
/// clock.
|
/// clock.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
peri: impl Peripheral<P = SUBGHZSPI> + 'd,
|
peri: impl Peripheral<P = SUBGHZSPI> + 'd,
|
||||||
sck: impl Peripheral<P = impl SckPin<SUBGHZSPI>> + 'd,
|
|
||||||
mosi: impl Peripheral<P = impl MosiPin<SUBGHZSPI>> + 'd,
|
|
||||||
miso: impl Peripheral<P = impl MisoPin<SUBGHZSPI>> + 'd,
|
|
||||||
txdma: impl Peripheral<P = Tx> + 'd,
|
txdma: impl Peripheral<P = Tx> + 'd,
|
||||||
rxdma: impl Peripheral<P = Rx> + 'd,
|
rxdma: impl Peripheral<P = Rx> + 'd,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -227,7 +224,7 @@ impl<'d, Tx, Rx> SubGhz<'d, Tx, Rx> {
|
|||||||
let mut config = SpiConfig::default();
|
let mut config = SpiConfig::default();
|
||||||
config.mode = MODE_0;
|
config.mode = MODE_0;
|
||||||
config.bit_order = BitOrder::MsbFirst;
|
config.bit_order = BitOrder::MsbFirst;
|
||||||
let spi = Spi::new(peri, sck, mosi, miso, txdma, rxdma, clk, config);
|
let spi = Spi::new_internal(peri, txdma, rxdma, clk, config);
|
||||||
|
|
||||||
unsafe { wakeup() };
|
unsafe { wakeup() };
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de
|
|||||||
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "unstable-traits", "memory-x"] }
|
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "unstable-traits", "memory-x"] }
|
||||||
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx127x", "time", "defmt"], optional = true}
|
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx127x", "time", "defmt"], optional = true}
|
||||||
|
|
||||||
lorawan-device = { version = "0.7.1", default-features = false, features = ["async"], optional = true }
|
lorawan-device = { version = "0.8.0", default-features = false, features = ["async"], optional = true }
|
||||||
lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"], optional = true }
|
lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"], optional = true }
|
||||||
|
|
||||||
defmt = "0.3"
|
defmt = "0.3"
|
||||||
|
@ -47,7 +47,7 @@ async fn main(_spawner: Spawner) {
|
|||||||
let radio = Sx127xRadio::new(spi, cs, reset, ready_pin, DummySwitch).await.unwrap();
|
let radio = Sx127xRadio::new(spi, cs, reset, ready_pin, DummySwitch).await.unwrap();
|
||||||
|
|
||||||
let region = region::EU868::default().into();
|
let region = region::EU868::default().into();
|
||||||
let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer, Rng::new(p.RNG));
|
let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer::new(), Rng::new(p.RNG));
|
||||||
|
|
||||||
defmt::info!("Joining LoRaWAN network");
|
defmt::info!("Joining LoRaWAN network");
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["de
|
|||||||
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "subghz", "unstable-pac", "exti"] }
|
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "subghz", "unstable-pac", "exti"] }
|
||||||
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] }
|
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] }
|
||||||
|
|
||||||
lorawan-device = { version = "0.7.1", default-features = false, features = ["async"] }
|
lorawan-device = { version = "0.8.0", default-features = false, features = ["async"] }
|
||||||
lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"] }
|
lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"] }
|
||||||
|
|
||||||
defmt = "0.3"
|
defmt = "0.3"
|
||||||
|
@ -9,7 +9,7 @@ use embassy_executor::Spawner;
|
|||||||
use embassy_lora::stm32wl::*;
|
use embassy_lora::stm32wl::*;
|
||||||
use embassy_lora::LoraTimer;
|
use embassy_lora::LoraTimer;
|
||||||
use embassy_stm32::dma::NoDma;
|
use embassy_stm32::dma::NoDma;
|
||||||
use embassy_stm32::gpio::{Level, Output, Pin, Speed};
|
use embassy_stm32::gpio::{AnyPin, Level, Output, Pin, Speed};
|
||||||
use embassy_stm32::rng::Rng;
|
use embassy_stm32::rng::Rng;
|
||||||
use embassy_stm32::subghz::*;
|
use embassy_stm32::subghz::*;
|
||||||
use embassy_stm32::{interrupt, pac};
|
use embassy_stm32::{interrupt, pac};
|
||||||
@ -17,6 +17,32 @@ use lorawan::default_crypto::DefaultFactory as Crypto;
|
|||||||
use lorawan_device::async_device::{region, Device, JoinMode};
|
use lorawan_device::async_device::{region, Device, JoinMode};
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
|
struct RadioSwitch<'a> {
|
||||||
|
ctrl1: Output<'a, AnyPin>,
|
||||||
|
ctrl2: Output<'a, AnyPin>,
|
||||||
|
ctrl3: Output<'a, AnyPin>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RadioSwitch<'a> {
|
||||||
|
fn new(ctrl1: Output<'a, AnyPin>, ctrl2: Output<'a, AnyPin>, ctrl3: Output<'a, AnyPin>) -> Self {
|
||||||
|
Self { ctrl1, ctrl2, ctrl3 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> embassy_lora::stm32wl::RadioSwitch for RadioSwitch<'a> {
|
||||||
|
fn set_rx(&mut self) {
|
||||||
|
self.ctrl1.set_high();
|
||||||
|
self.ctrl2.set_low();
|
||||||
|
self.ctrl3.set_high();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_tx(&mut self) {
|
||||||
|
self.ctrl1.set_high();
|
||||||
|
self.ctrl2.set_high();
|
||||||
|
self.ctrl3.set_high();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[embassy_executor::main]
|
#[embassy_executor::main]
|
||||||
async fn main(_spawner: Spawner) {
|
async fn main(_spawner: Spawner) {
|
||||||
let mut config = embassy_stm32::Config::default();
|
let mut config = embassy_stm32::Config::default();
|
||||||
@ -31,18 +57,19 @@ async fn main(_spawner: Spawner) {
|
|||||||
let ctrl3 = Output::new(p.PC5.degrade(), Level::High, Speed::High);
|
let ctrl3 = Output::new(p.PC5.degrade(), Level::High, Speed::High);
|
||||||
let rfs = RadioSwitch::new(ctrl1, ctrl2, ctrl3);
|
let rfs = RadioSwitch::new(ctrl1, ctrl2, ctrl3);
|
||||||
|
|
||||||
let radio = SubGhz::new(p.SUBGHZSPI, p.PA5, p.PA7, p.PA6, NoDma, NoDma);
|
let radio = SubGhz::new(p.SUBGHZSPI, NoDma, NoDma);
|
||||||
|
|
||||||
let irq = interrupt::take!(SUBGHZ_RADIO);
|
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 mut radio_config = SubGhzRadioConfig::default();
|
||||||
|
radio_config.calibrate_image = CalibrateImage::ISM_863_870;
|
||||||
|
let radio = SubGhzRadio::new(radio, rfs, irq, radio_config).unwrap();
|
||||||
|
|
||||||
let mut region: region::Configuration = region::EU868::default().into();
|
let mut region: region::Configuration = region::EU868::default().into();
|
||||||
|
|
||||||
// NOTE: This is specific for TTN, as they have a special RX1 delay
|
// NOTE: This is specific for TTN, as they have a special RX1 delay
|
||||||
region.set_receive_delay1(5000);
|
region.set_receive_delay1(5000);
|
||||||
|
|
||||||
let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer, Rng::new(p.RNG));
|
let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer::new(), Rng::new(p.RNG));
|
||||||
|
|
||||||
// Depending on network, this might be part of JOIN
|
// Depending on network, this might be part of JOIN
|
||||||
device.set_datarate(region::DR::_0); // SF12
|
device.set_datarate(region::DR::_0); // SF12
|
||||||
|
@ -72,7 +72,7 @@ async fn main(_spawner: Spawner) {
|
|||||||
unsafe { interrupt::SUBGHZ_RADIO::steal() }.disable();
|
unsafe { interrupt::SUBGHZ_RADIO::steal() }.disable();
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut radio = SubGhz::new(p.SUBGHZSPI, p.PA5, p.PA7, p.PA6, NoDma, NoDma);
|
let mut radio = SubGhz::new(p.SUBGHZSPI, NoDma, NoDma);
|
||||||
|
|
||||||
defmt::info!("Radio ready for use");
|
defmt::info!("Radio ready for use");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user