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:
bors[bot] 2022-09-04 07:17:23 +00:00 committed by GitHub
commit 6264fe39a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 269 additions and 257 deletions

View File

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

View File

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

View File

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

View File

@ -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),
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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