feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+96
View File
@@ -0,0 +1,96 @@
use defmt::trace;
use embedded_hal_async::spi::{Operation, SpiDevice};
use crate::mod_params::RadioError::{self, SPI};
use crate::mod_traits::InterfaceVariant;
pub(crate) struct SpiInterface<SPI, IV> {
pub(crate) spi: SPI,
pub(crate) iv: IV,
}
impl<SPI, IV> SpiInterface<SPI, IV>
where
SPI: SpiDevice<u8>,
IV: InterfaceVariant,
{
pub fn new(spi: SPI, iv: IV) -> Self {
Self { spi, iv }
}
// Write a buffer to the radio.
pub async fn write(&mut self, write_buffer: &[u8], is_sleep_command: bool) -> Result<(), RadioError> {
self.spi.write(write_buffer).await.map_err(|_| SPI)?;
trace!("write: {=[u8]:02x}", write_buffer);
if !is_sleep_command {
self.iv.wait_on_busy().await?;
}
Ok(())
}
// Write
pub async fn write_with_payload(
&mut self,
write_buffer: &[u8],
payload: &[u8],
is_sleep_command: bool,
) -> Result<(), RadioError> {
let mut ops = [Operation::Write(write_buffer), Operation::Write(payload)];
self.spi.transaction(&mut ops).await.map_err(|_| SPI)?;
trace!("write_buf: {=[u8]:02x} -> {=[u8]:02x}", write_buffer, payload);
if !is_sleep_command {
self.iv.wait_on_busy().await?;
}
Ok(())
}
// Request a read, filling the provided buffer.
pub async fn read(&mut self, write_buffer: &[u8], read_buffer: &mut [u8]) -> Result<(), RadioError> {
{
let mut ops = [Operation::Write(write_buffer), Operation::Read(read_buffer)];
self.spi.transaction(&mut ops).await.map_err(|_| SPI)?;
}
self.iv.wait_on_busy().await?;
trace!(
"read: addr={=[u8]:02x}, len={}, data={=[u8]:02x}",
write_buffer,
read_buffer.len(),
read_buffer
);
Ok(())
}
// Request a read with status, filling the provided buffer and returning the status.
pub async fn read_with_status(&mut self, write_buffer: &[u8], read_buffer: &mut [u8]) -> Result<u8, RadioError> {
let mut status = [0u8];
{
let mut ops = [
Operation::Write(write_buffer),
Operation::Read(&mut status),
Operation::Read(read_buffer),
];
self.spi.transaction(&mut ops).await.map_err(|_| SPI)?;
}
self.iv.wait_on_busy().await?;
trace!(
"read: addr={=[u8]:02x}, len={}, status={:02x}, buf={=[u8]:02x}",
write_buffer,
read_buffer.len(),
status[0],
read_buffer
);
Ok(status[0])
}
}
+173
View File
@@ -0,0 +1,173 @@
use embedded_hal::digital::OutputPin;
use embedded_hal_async::delay::DelayNs;
use embedded_hal_async::digital::Wait;
use crate::mod_params::RadioError;
use crate::mod_params::RadioError::*;
use crate::mod_traits::InterfaceVariant;
/// Base for the InterfaceVariant implementation for the Sx127x combination
pub struct GenericSx127xInterfaceVariant<CTRL, WAIT> {
reset: CTRL,
irq: WAIT,
rf_switch_rx: Option<CTRL>,
rf_switch_tx: Option<CTRL>,
}
impl<CTRL, WAIT> GenericSx127xInterfaceVariant<CTRL, WAIT>
where
CTRL: OutputPin,
WAIT: Wait,
{
/// Create an InterfaceVariant instance for an stm32l0/sx1276 combination
pub fn new(
reset: CTRL,
irq: WAIT,
rf_switch_rx: Option<CTRL>,
rf_switch_tx: Option<CTRL>,
) -> Result<Self, RadioError> {
Ok(Self {
reset,
irq,
rf_switch_rx,
rf_switch_tx,
})
}
}
impl<CTRL, WAIT> InterfaceVariant for GenericSx127xInterfaceVariant<CTRL, WAIT>
where
CTRL: OutputPin,
WAIT: Wait,
{
async fn reset(&mut self, delay: &mut impl DelayNs) -> Result<(), RadioError> {
delay.delay_ms(10).await;
self.reset.set_low().map_err(|_| Reset)?;
delay.delay_ms(10).await;
self.reset.set_high().map_err(|_| Reset)?;
delay.delay_ms(10).await;
Ok(())
}
async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
Ok(())
}
async fn await_irq(&mut self) -> Result<(), RadioError> {
self.irq.wait_for_high().await.map_err(|_| Irq)
}
async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_tx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
None => (),
};
match &mut self.rf_switch_rx {
Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
None => Ok(()),
}
}
async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_rx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
None => (),
};
match &mut self.rf_switch_tx {
Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
None => Ok(()),
}
}
async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_rx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
None => (),
};
match &mut self.rf_switch_tx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
None => Ok(()),
}
}
}
/// Base for the InterfaceVariant implementation for a generic Sx126x LoRa board
pub struct GenericSx126xInterfaceVariant<CTRL, WAIT> {
reset: CTRL,
dio1: WAIT,
busy: WAIT,
rf_switch_rx: Option<CTRL>,
rf_switch_tx: Option<CTRL>,
}
impl<CTRL, WAIT> GenericSx126xInterfaceVariant<CTRL, WAIT>
where
CTRL: OutputPin,
WAIT: Wait,
{
/// Create an InterfaceVariant instance for an nrf52840/sx1262 combination
pub fn new(
reset: CTRL,
dio1: WAIT,
busy: WAIT,
rf_switch_rx: Option<CTRL>,
rf_switch_tx: Option<CTRL>,
) -> Result<Self, RadioError> {
Ok(Self {
reset,
dio1,
busy,
rf_switch_rx,
rf_switch_tx,
})
}
}
impl<CTRL, WAIT> InterfaceVariant for GenericSx126xInterfaceVariant<CTRL, WAIT>
where
CTRL: OutputPin,
WAIT: Wait,
{
async fn reset(&mut self, delay: &mut impl DelayNs) -> Result<(), RadioError> {
delay.delay_ms(10).await;
self.reset.set_low().map_err(|_| Reset)?;
delay.delay_ms(20).await;
self.reset.set_high().map_err(|_| Reset)?;
delay.delay_ms(10).await;
Ok(())
}
async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
self.busy.wait_for_low().await.map_err(|_| Busy)
}
async fn await_irq(&mut self) -> Result<(), RadioError> {
self.dio1.wait_for_high().await.map_err(|_| DIO1)?;
Ok(())
}
async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_tx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
None => (),
};
match &mut self.rf_switch_rx {
Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
None => Ok(()),
}
}
async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_rx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
None => (),
};
match &mut self.rf_switch_tx {
Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
None => Ok(()),
}
}
async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
match &mut self.rf_switch_rx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
None => (),
};
match &mut self.rf_switch_tx {
Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
None => Ok(()),
}
}
}
+365
View File
@@ -0,0 +1,365 @@
#![no_std]
#![allow(async_fn_in_trait)]
#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#[cfg(feature = "lorawan-radio")]
#[cfg_attr(docsrs, doc(cfg(feature = "lorawan-radio")))]
/// Provides an implementation of the async LoRaWAN device trait.
pub mod lorawan_radio;
/// The read/write interface between an embedded framework/MCU combination and a LoRa chip
pub(crate) mod interface;
/// InterfaceVariant implementations using `embedded-hal`.
pub mod iv;
/// Parameters used across the lora-phy crate to support various use cases
pub mod mod_params;
/// Traits implemented externally or internally to support control of LoRa chips
pub mod mod_traits;
/// Specific implementation to support Semtech Sx126x chips
pub mod sx126x;
/// Specific implementation to support Semtech Sx127x chips
pub mod sx127x;
pub use crate::mod_params::RxMode;
pub use embedded_hal_async::delay::DelayNs;
use interface::*;
use mod_params::*;
use mod_traits::*;
/// Provides the physical layer API to support LoRa chips
pub struct LoRa<RK, DLY>
where
RK: RadioKind,
DLY: DelayNs,
{
radio_kind: RK,
delay: DLY,
radio_mode: RadioMode,
enable_public_network: bool,
cold_start: bool,
calibrate_image: bool,
}
impl<RK, DLY> LoRa<RK, DLY>
where
RK: RadioKind,
DLY: DelayNs,
{
/// Build and return a new instance of the LoRa physical layer API to control an initialized LoRa radio
pub async fn new(radio_kind: RK, enable_public_network: bool, delay: DLY) -> Result<Self, RadioError> {
let mut lora = Self {
radio_kind,
delay,
radio_mode: RadioMode::Sleep,
enable_public_network,
cold_start: true,
calibrate_image: true,
};
lora.init().await?;
Ok(lora)
}
/// Wait for an IRQ event to occur
pub async fn wait_for_irq(&mut self) -> Result<(), RadioError> {
self.radio_kind.await_irq().await
}
/// Process an IRQ event and return the new state of the radio
pub async fn process_irq_event(&mut self) -> Result<Option<IrqState>, RadioError> {
self.radio_kind.process_irq_event(self.radio_mode, None, false).await
}
/// Create modulation parameters for a communication channel
pub fn create_modulation_params(
&mut self,
spreading_factor: SpreadingFactor,
bandwidth: Bandwidth,
coding_rate: CodingRate,
frequency_in_hz: u32,
) -> Result<ModulationParams, RadioError> {
self.radio_kind
.create_modulation_params(spreading_factor, bandwidth, coding_rate, frequency_in_hz)
}
/// Create packet parameters for a send operation on a communication channel
pub fn create_tx_packet_params(
&mut self,
preamble_length: u16,
implicit_header: bool,
crc_on: bool,
iq_inverted: bool,
modulation_params: &ModulationParams,
) -> Result<PacketParams, RadioError> {
self.radio_kind.create_packet_params(
preamble_length,
implicit_header,
0,
crc_on,
iq_inverted,
modulation_params,
)
}
/// Create packet parameters for a receive operation on a communication channel
pub fn create_rx_packet_params(
&mut self,
preamble_length: u16,
implicit_header: bool,
max_payload_length: u8,
crc_on: bool,
iq_inverted: bool,
modulation_params: &ModulationParams,
) -> Result<PacketParams, RadioError> {
self.radio_kind.create_packet_params(
preamble_length,
implicit_header,
max_payload_length,
crc_on,
iq_inverted,
modulation_params,
)
}
/// Initialize a Semtech chip as the radio for LoRa physical layer communications
pub async fn init(&mut self) -> Result<(), RadioError> {
self.cold_start = true;
self.radio_kind.reset(&mut self.delay).await?;
self.radio_kind.ensure_ready(self.radio_mode).await?;
self.radio_kind.set_standby().await?;
self.radio_mode = RadioMode::Standby;
self.do_cold_start().await
}
async fn do_cold_start(&mut self) -> Result<(), RadioError> {
self.radio_kind.init_lora(self.enable_public_network).await?;
self.radio_kind.set_tx_power_and_ramp_time(0, None, false).await?;
self.radio_kind.set_irq_params(Some(self.radio_mode)).await?;
self.cold_start = false;
self.calibrate_image = true;
Ok(())
}
/// Place the LoRa physical layer in standby mode
pub async fn enter_standby(&mut self) -> Result<(), RadioError> {
self.radio_kind.set_standby().await
}
/// Place the LoRa physical layer in low power mode, specifying cold or warm start (if the Semtech chip supports it)
pub async fn sleep(&mut self, warm_start_if_possible: bool) -> Result<(), RadioError> {
if self.radio_mode != RadioMode::Sleep {
self.radio_kind.ensure_ready(self.radio_mode).await?;
self.radio_kind
.set_sleep(warm_start_if_possible, &mut self.delay)
.await?;
if !warm_start_if_possible {
self.cold_start = true;
}
self.radio_mode = RadioMode::Sleep;
}
Ok(())
}
/// Prepare the Semtech chip for a send operation
pub async fn prepare_for_tx(
&mut self,
mdltn_params: &ModulationParams,
tx_pkt_params: &mut PacketParams,
output_power: i32,
buffer: &[u8],
) -> Result<(), RadioError> {
self.prepare_modem(mdltn_params).await?;
self.radio_kind.set_modulation_params(mdltn_params).await?;
self.radio_kind
.set_tx_power_and_ramp_time(output_power, Some(mdltn_params), true)
.await?;
self.radio_kind.ensure_ready(self.radio_mode).await?;
if self.radio_mode != RadioMode::Standby {
self.radio_kind.set_standby().await?;
self.radio_mode = RadioMode::Standby;
}
tx_pkt_params.set_payload_length(buffer.len())?;
self.radio_kind.set_packet_params(tx_pkt_params).await?;
self.radio_kind.set_channel(mdltn_params.frequency_in_hz).await?;
self.radio_kind.set_payload(buffer).await?;
self.radio_mode = RadioMode::Transmit;
self.radio_kind.set_irq_params(Some(self.radio_mode)).await?;
Ok(())
}
/// Execute a send operation
pub async fn tx(&mut self) -> Result<(), RadioError> {
if let RadioMode::Transmit = self.radio_mode {
self.radio_kind.do_tx().await?;
loop {
self.wait_for_irq().await?;
match self.radio_kind.process_irq_event(self.radio_mode, None, true).await {
Ok(Some(IrqState::Done | IrqState::PreambleReceived)) => {
self.radio_mode = RadioMode::Standby;
return Ok(());
}
Ok(None) => continue,
Err(err) => {
self.radio_kind.ensure_ready(self.radio_mode).await?;
self.radio_kind.set_standby().await?;
self.radio_mode = RadioMode::Standby;
return Err(err);
}
}
}
} else {
Err(RadioError::InvalidRadioMode)
}
}
/// Prepare radio to receive a frame in either single or continuous packet mode.
/// Notes:
/// * sx126x SetRx(0 < timeout < MAX) will listen util LoRa packet header is detected,
/// therefore we only use 0 (Single Mode) and MAX (continuous) values.
/// TODO: Find a way to express timeout for sx126x, allowing waiting for packet upto 262s
/// TODO: Allow DutyCycle as well?
pub async fn prepare_for_rx(
&mut self,
listen_mode: RxMode,
mdltn_params: &ModulationParams,
rx_pkt_params: &PacketParams,
) -> Result<(), RadioError> {
defmt::trace!("RX mode: {}", listen_mode);
self.prepare_modem(mdltn_params).await?;
self.radio_kind.set_modulation_params(mdltn_params).await?;
self.radio_kind.set_packet_params(rx_pkt_params).await?;
self.radio_kind.set_channel(mdltn_params.frequency_in_hz).await?;
self.radio_mode = listen_mode.into();
self.radio_kind.set_irq_params(Some(self.radio_mode)).await?;
Ok(())
}
/// Start receiving and wait for result
pub async fn rx(
&mut self,
packet_params: &PacketParams,
receiving_buffer: &mut [u8],
) -> Result<(u8, PacketStatus), RadioError> {
if let RadioMode::Receive(listen_mode) = self.radio_mode {
self.radio_kind.do_rx(listen_mode).await?;
loop {
self.wait_for_irq().await?;
match self.radio_kind.process_irq_event(self.radio_mode, None, true).await {
Ok(Some(actual_state)) => match actual_state {
IrqState::PreambleReceived => continue,
IrqState::Done => {
let received_len = self.radio_kind.get_rx_payload(packet_params, receiving_buffer).await?;
let rx_pkt_status = self.radio_kind.get_rx_packet_status().await?;
return Ok((received_len, rx_pkt_status));
}
},
Ok(None) => continue,
Err(err) => {
// if in rx continuous mode, allow the caller to determine whether to keep receiving
if self.radio_mode != RadioMode::Receive(RxMode::Continuous) {
self.radio_kind.ensure_ready(self.radio_mode).await?;
self.radio_kind.set_standby().await?;
self.radio_mode = RadioMode::Standby;
}
return Err(err);
}
}
}
} else {
Err(RadioError::InvalidRadioMode)
}
}
/// Prepare the Semtech chip for a channel activity detection operation
pub async fn prepare_for_cad(&mut self, mdltn_params: &ModulationParams) -> Result<(), RadioError> {
self.prepare_modem(mdltn_params).await?;
self.radio_kind.set_modulation_params(mdltn_params).await?;
self.radio_kind.set_channel(mdltn_params.frequency_in_hz).await?;
self.radio_mode = RadioMode::ChannelActivityDetection;
self.radio_kind.set_irq_params(Some(self.radio_mode)).await?;
Ok(())
}
/// Start channel activity detection operation and return the result
pub async fn cad(&mut self, mdltn_params: &ModulationParams) -> Result<bool, RadioError> {
if self.radio_mode == RadioMode::ChannelActivityDetection {
self.radio_kind.do_cad(mdltn_params).await?;
self.wait_for_irq().await?;
let mut cad_activity_detected = false;
match self
.radio_kind
.process_irq_event(self.radio_mode, Some(&mut cad_activity_detected), true)
.await
{
Ok(Some(IrqState::Done)) => Ok(cad_activity_detected),
Err(err) => {
self.radio_kind.ensure_ready(self.radio_mode).await?;
self.radio_kind.set_standby().await?;
self.radio_mode = RadioMode::Standby;
Err(err)
}
Ok(_) => unreachable!(),
}
} else {
Err(RadioError::InvalidRadioMode)
}
}
/// Place radio in continuous wave mode, generally for regulatory testing
///
/// SemTech app note AN1200.26 “Semtech LoRa FCC 15.247 Guidance” covers usage.
///
/// Presumes that init() is called before this function
pub async fn continuous_wave(
&mut self,
mdltn_params: &ModulationParams,
output_power: i32,
) -> Result<(), RadioError> {
self.prepare_modem(mdltn_params).await?;
let tx_pkt_params = self
.radio_kind
.create_packet_params(0, false, 16, false, false, mdltn_params)?;
self.radio_kind.set_packet_params(&tx_pkt_params).await?;
self.radio_kind.set_modulation_params(mdltn_params).await?;
self.radio_kind
.set_tx_power_and_ramp_time(output_power, Some(mdltn_params), true)
.await?;
self.radio_kind.ensure_ready(self.radio_mode).await?;
if self.radio_mode != RadioMode::Standby {
self.radio_kind.set_standby().await?;
self.radio_mode = RadioMode::Standby;
}
self.radio_kind.set_channel(mdltn_params.frequency_in_hz).await?;
self.radio_mode = RadioMode::Transmit;
self.radio_kind.set_irq_params(Some(self.radio_mode)).await?;
self.radio_kind.set_tx_continuous_wave_mode().await
}
async fn prepare_modem(&mut self, mdltn_params: &ModulationParams) -> Result<(), RadioError> {
self.radio_kind.ensure_ready(self.radio_mode).await?;
if self.radio_mode != RadioMode::Standby {
self.radio_kind.set_standby().await?;
self.radio_mode = RadioMode::Standby;
}
if self.cold_start {
self.do_cold_start().await?;
}
if self.calibrate_image {
self.radio_kind.calibrate_image(mdltn_params.frequency_in_hz).await?;
self.calibrate_image = false;
}
Ok(())
}
}
+177
View File
@@ -0,0 +1,177 @@
#![allow(missing_docs)]
use super::mod_params::{PacketParams, RadioError};
use super::mod_traits::RadioKind;
use super::{DelayNs, LoRa, RxMode};
use lora_modulation::BaseBandModulationParams;
use lorawan_device::async_device::{
radio::{PhyRxTx, RxConfig, RxMode as LorawanRxMode, RxQuality, RxStatus, TxConfig},
Timings,
};
const DEFAULT_RX_WINDOW_LEAD_TIME: u32 = 50;
/// LoRa radio using the physical layer API in the external lora-phy crate.
///
/// The const generic P is the max power the radio may be instructed to transmit at. The const
/// generic G is the antenna gain and board loss in dBi.
pub struct LorawanRadio<RK, DLY, const P: u8, const G: i8 = 0>
where
RK: RadioKind,
DLY: DelayNs,
{
pub(crate) lora: LoRa<RK, DLY>,
rx_pkt_params: Option<PacketParams>,
rx_window_lead_time: u32,
rx_window_buffer: u32,
}
impl<RK, DLY, const P: u8, const G: i8> From<LoRa<RK, DLY>> for LorawanRadio<RK, DLY, P, G>
where
RK: RadioKind,
DLY: DelayNs,
{
fn from(lora: LoRa<RK, DLY>) -> Self {
Self {
lora,
rx_pkt_params: None,
rx_window_lead_time: DEFAULT_RX_WINDOW_LEAD_TIME,
rx_window_buffer: DEFAULT_RX_WINDOW_LEAD_TIME,
}
}
}
impl<RK, DLY, const P: u8, const G: i8> LorawanRadio<RK, DLY, P, G>
where
RK: RadioKind,
DLY: DelayNs,
{
pub fn set_rx_window_lead_time(&mut self, lt: u32) {
self.rx_window_lead_time = lt;
}
pub fn set_rx_window_buffer(&mut self, buffer: u32) {
self.rx_window_buffer = buffer;
}
}
/// Provide the timing values for boards supported by the external lora-phy crate
impl<RK, DLY, const P: u8, const G: i8> Timings for LorawanRadio<RK, DLY, P, G>
where
RK: RadioKind,
DLY: DelayNs,
{
fn get_rx_window_buffer(&self) -> u32 {
self.rx_window_lead_time
}
fn get_rx_window_lead_time_ms(&self) -> u32 {
self.rx_window_lead_time
}
}
#[derive(Debug, defmt::Format)]
pub enum Error {
Radio(RadioError),
NoRxParams,
}
impl From<RadioError> for Error {
fn from(err: RadioError) -> Self {
Error::Radio(err)
}
}
/// Provide the LoRa physical layer rx/tx interface for boards supported by the external lora-phy
/// crate
impl<RK, DLY, const P: u8, const G: i8> PhyRxTx for LorawanRadio<RK, DLY, P, G>
where
RK: RadioKind,
DLY: DelayNs,
{
type PhyError = Error;
const ANTENNA_GAIN: i8 = G;
const MAX_RADIO_POWER: u8 = P;
async fn tx(&mut self, config: TxConfig, buffer: &[u8]) -> Result<u32, Self::PhyError> {
let mdltn_params = self.lora.create_modulation_params(
config.rf.bb.sf,
config.rf.bb.bw,
config.rf.bb.cr,
config.rf.frequency,
)?;
let mut tx_pkt_params = self
.lora
.create_tx_packet_params(8, false, true, false, &mdltn_params)?;
self.lora
.prepare_for_tx(&mdltn_params, &mut tx_pkt_params, config.pw.into(), buffer)
.await?;
self.lora.tx().await?;
Ok(0)
}
async fn setup_rx(&mut self, config: RxConfig) -> Result<(), Self::PhyError> {
let mdltn_params = self.lora.create_modulation_params(
config.rf.bb.sf,
config.rf.bb.bw,
config.rf.bb.cr,
config.rf.frequency,
)?;
let rx_pkt_params = self
.lora
.create_rx_packet_params(8, false, 255, true, true, &mdltn_params)?;
self.lora
.prepare_for_rx(RxMode::from(config.mode, config.rf.bb), &mdltn_params, &rx_pkt_params)
.await?;
self.rx_pkt_params = Some(rx_pkt_params);
Ok(())
}
async fn rx_single(&mut self, buf: &mut [u8]) -> Result<RxStatus, Self::PhyError> {
if let Some(rx_params) = &self.rx_pkt_params {
match self.lora.rx(rx_params, buf).await {
Ok((len, q)) => Ok(RxStatus::Rx(len as usize, RxQuality::new(q.rssi, q.snr as i8))),
Err(RadioError::ReceiveTimeout) => Ok(RxStatus::RxTimeout),
Err(err) => Err(err.into()),
}
} else {
Err(Error::NoRxParams)
}
}
async fn rx_continuous(&mut self, receiving_buffer: &mut [u8]) -> Result<(usize, RxQuality), Self::PhyError> {
if let Some(rx_params) = &self.rx_pkt_params {
match self.lora.rx(rx_params, receiving_buffer).await {
Ok((received_len, rx_pkt_status)) => {
Ok((
received_len as usize,
RxQuality::new(rx_pkt_status.rssi, rx_pkt_status.snr as i8), // downcast snr
))
}
Err(err) => Err(err.into()),
}
} else {
Err(Error::NoRxParams)
}
}
async fn low_power(&mut self) -> Result<(), Self::PhyError> {
self.lora.sleep(false).await.map_err(|e| e.into())
}
}
impl RxMode {
fn from(mode: LorawanRxMode, bb: BaseBandModulationParams) -> Self {
match mode {
LorawanRxMode::Continuous => RxMode::Continuous,
LorawanRxMode::Single { ms } => {
// Since both sx126x and sx127x have a preamble-based timeout, we translate
// the additional millisecond delay into symbols and add it to the amount of preamble symbols.
const PREAMBLE_SYMBOLS: u16 = 13; // 12.25
let num_symbols = PREAMBLE_SYMBOLS + bb.delay_in_symbols(ms);
RxMode::Single(num_symbols)
}
}
}
}
+114
View File
@@ -0,0 +1,114 @@
pub use lora_modulation::{Bandwidth, CodingRate, SpreadingFactor};
/// Errors types reported during LoRa physical layer processing
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, defmt::Format, PartialEq)]
#[allow(missing_docs)]
pub enum RadioError {
SPI,
Reset,
RfSwitchRx,
RfSwitchTx,
Busy,
Irq,
DIO1,
InvalidConfiguration,
InvalidRadioMode,
OpError(u8),
InvalidBaseAddress(usize, usize),
PayloadSizeUnexpected(usize),
PayloadSizeMismatch(usize, usize),
UnavailableSpreadingFactor,
UnavailableBandwidth,
InvalidBandwidthForFrequency,
InvalidSF6ExplicitHeaderRequest,
InvalidOutputPowerForFrequency,
TransmitTimeout,
ReceiveTimeout,
DutyCycleUnsupported,
RngUnsupported,
}
/// Status for a received packet
#[derive(Clone, Copy)]
#[allow(missing_docs)]
pub struct PacketStatus {
pub rssi: i16,
pub snr: i16,
}
/// The state of the radio
#[derive(Clone, Copy, defmt::Format, PartialEq)]
pub enum RadioMode {
/// Sleep mode
Sleep,
/// Standby mode
Standby,
/// Frequency synthesis mode
FrequencySynthesis,
/// Transmit (TX) mode
Transmit,
/// Receive (RX) mode
Receive(RxMode),
/// Channel activity detection (CAD) mode
ChannelActivityDetection,
}
impl From<RxMode> for RadioMode {
fn from(rx_mode: RxMode) -> Self {
RadioMode::Receive(rx_mode)
}
}
/// Listening mode for LoRaWAN packet detection/reception
#[derive(Clone, Copy, defmt::Format, PartialEq)]
pub enum RxMode {
/// Single shot Rx Mode to listen until packet preamble is detected or RxTimeout occurs.
/// The device will stay in RX Mode until a packet is received.
/// Preamble length as symbols is configured via following registers:
/// sx126x: uses `SetLoRaSymbNumTimeout(0 < n < 255)` + `SetStopRxTimerOnPreamble(1)`
/// sx127x: uses `RegSymbTimeout (4 < n < 1023)`
// TODO: Single mode with time-based timeout is available on sx126x, but not sx127x
Single(u16),
/// Continuous Rx mode to listen for incoming packets continuously
Continuous,
/// Receive in Duty Cycle mode (NB! Not supported on sx127x)
DutyCycle(DutyCycleParams),
}
/// Modulation parameters for a send and/or receive communication channel
pub struct ModulationParams {
pub(crate) spreading_factor: SpreadingFactor,
pub(crate) bandwidth: Bandwidth,
pub(crate) coding_rate: CodingRate,
pub(crate) low_data_rate_optimize: u8,
pub(crate) frequency_in_hz: u32,
}
/// Packet parameters for a send or receive communication channel
pub struct PacketParams {
pub(crate) preamble_length: u16, // number of LoRa symbols in the preamble
pub(crate) implicit_header: bool, // if the header is explicit, it will be transmitted in the LoRa packet, but is not transmitted if the header is implicit (known fixed length)
pub(crate) payload_length: u8,
pub(crate) crc_on: bool,
pub(crate) iq_inverted: bool,
}
impl PacketParams {
pub(crate) fn set_payload_length(&mut self, payload_length: usize) -> Result<(), RadioError> {
if payload_length > 255 {
return Err(RadioError::PayloadSizeUnexpected(payload_length));
}
self.payload_length = payload_length as u8;
Ok(())
}
}
/// Receive duty cycle parameters
#[derive(Clone, Copy, defmt::Format, PartialEq)]
pub struct DutyCycleParams {
/// receive interval
pub rx_time: u32,
/// sleep interval
pub sleep_time: u32,
}
+113
View File
@@ -0,0 +1,113 @@
use embedded_hal_async::delay::DelayNs;
use crate::mod_params::*;
/// Functions implemented for an embedded framework for an MCU/LoRa chip combination
/// to allow this crate to control the LoRa chip.
pub trait InterfaceVariant {
/// Reset the LoRa chip
async fn reset(&mut self, delay: &mut impl DelayNs) -> Result<(), RadioError>;
/// Wait for the LoRa chip to become available for an operation
async fn wait_on_busy(&mut self) -> Result<(), RadioError>;
/// Wait for the LoRa chip to indicate an event has occurred
async fn await_irq(&mut self) -> Result<(), RadioError>;
/// Enable an antenna used for receive operations, disabling other antennas
async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError>;
/// Enable an antenna used for send operations, disabling other antennas
async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError>;
/// Disable all antennas
async fn disable_rf_switch(&mut self) -> Result<(), RadioError>;
}
/// Specifies an IRQ processing state to run the loop to
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum IrqState {
/// Runs the loop until after the preamble has been received
PreambleReceived,
/// Runs the loop until the operation is fully complete
Done,
}
/// Functions implemented for a specific kind of LoRa chip, called internally by the outward facing
/// LoRa physical layer API
pub trait RadioKind {
/// Initialize lora radio
async fn init_lora(&mut self, is_public_network: bool) -> Result<(), RadioError>;
/// Create modulation parameters specific to the LoRa chip kind and type
fn create_modulation_params(
&self,
spreading_factor: SpreadingFactor,
bandwidth: Bandwidth,
coding_rate: CodingRate,
frequency_in_hz: u32,
) -> Result<ModulationParams, RadioError>;
/// Create packet parameters specific to the LoRa chip kind and type
fn create_packet_params(
&self,
preamble_length: u16,
implicit_header: bool,
payload_length: u8,
crc_on: bool,
iq_inverted: bool,
modulation_params: &ModulationParams,
) -> Result<PacketParams, RadioError>;
/// Reset the loRa chip
async fn reset(&mut self, delay: &mut impl DelayNs) -> Result<(), RadioError>;
/// Ensure the LoRa chip is in the appropriate state to allow operation requests
async fn ensure_ready(&mut self, mode: RadioMode) -> Result<(), RadioError>;
/// Place the LoRa chip in standby mode
async fn set_standby(&mut self) -> Result<(), RadioError>;
/// Place the LoRa chip in power-saving mode
async fn set_sleep(&mut self, warm_start_if_possible: bool, delay: &mut impl DelayNs) -> Result<(), RadioError>;
/// Set the LoRa chip send and receive buffer base addresses
async fn set_tx_rx_buffer_base_address(
&mut self,
tx_base_addr: usize,
rx_base_addr: usize,
) -> Result<(), RadioError>;
/// Perform any necessary LoRa chip power setup prior to a send operation
async fn set_tx_power_and_ramp_time(
&mut self,
output_power: i32,
mdltn_params: Option<&ModulationParams>,
is_tx_prep: bool,
) -> Result<(), RadioError>;
/// Set the LoRa chip modulation parameters prior to using a communication channel
async fn set_modulation_params(&mut self, mdltn_params: &ModulationParams) -> Result<(), RadioError>;
/// Set the LoRa chip packet parameters prior to sending or receiving packets
async fn set_packet_params(&mut self, pkt_params: &PacketParams) -> Result<(), RadioError>;
/// Set the LoRa chip to support a given communication channel frequency
async fn calibrate_image(&mut self, frequency_in_hz: u32) -> Result<(), RadioError>;
/// Set the frequency for a communication channel
async fn set_channel(&mut self, frequency_in_hz: u32) -> Result<(), RadioError>;
/// Set a payload for a subsequent send operation
async fn set_payload(&mut self, payload: &[u8]) -> Result<(), RadioError>;
/// Perform a transmit operation
async fn do_tx(&mut self) -> Result<(), RadioError>;
/// Set up to perform a receive operation (single-shot, continuous, or duty cycle)
async fn do_rx(&mut self, rx_mode: RxMode) -> Result<(), RadioError>;
/// Get an available packet made available as the result of a receive operation
async fn get_rx_payload(
&mut self,
rx_pkt_params: &PacketParams,
receiving_buffer: &mut [u8],
) -> Result<u8, RadioError>;
/// Get the RSSI and SNR for the packet made available as the result of a receive operation
async fn get_rx_packet_status(&mut self) -> Result<PacketStatus, RadioError>;
/// Perform a channel activity detection operation
async fn do_cad(&mut self, mdltn_params: &ModulationParams) -> Result<(), RadioError>;
/// Set the LoRa chip to provide notification of specific events based on radio state
async fn set_irq_params(&mut self, radio_mode: Option<RadioMode>) -> Result<(), RadioError>;
/// Set the LoRa chip into the TxContinuousWave mode
async fn set_tx_continuous_wave_mode(&mut self) -> Result<(), RadioError>;
/// Await for an IRQ event. This is droppable and thus safe to use in a select branch.
async fn await_irq(&mut self) -> Result<(), RadioError>;
/// Process LoRa radio IRQs
async fn process_irq_event(
&mut self,
radio_mode: RadioMode,
cad_activity_detected: Option<&mut bool>,
clear_interrupts: bool,
) -> Result<Option<IrqState>, RadioError>;
}
+969
View File
@@ -0,0 +1,969 @@
mod radio_kind_params;
use defmt::debug;
use embedded_hal_async::delay::DelayNs;
use embedded_hal_async::spi::*;
pub use radio_kind_params::TcxoCtrlVoltage;
use radio_kind_params::*;
use crate::mod_params::*;
use crate::mod_traits::IrqState;
use crate::{InterfaceVariant, RadioKind, SpiInterface};
mod variant;
pub use variant::*;
// Syncwords for public and private networks
const LORA_MAC_PUBLIC_SYNCWORD: u16 = 0x3444; // corresponds to sx127x 0x34
const LORA_MAC_PRIVATE_SYNCWORD: u16 = 0x1424; // corresponds to sx127x 0x12
// Maximum number of registers that can be added to the retention list
const MAX_NUMBER_REGS_IN_RETENTION: u8 = 4;
// Internal frequency of the radio
const SX126X_XTAL_FREQ: u32 = 32000000;
// Scaling factor used to perform fixed-point operations
const SX126X_PLL_STEP_SHIFT_AMOUNT: u32 = 14;
// PLL step - scaled with SX126X_PLL_STEP_SHIFT_AMOUNT
const SX126X_PLL_STEP_SCALED: u32 = SX126X_XTAL_FREQ >> (25 - SX126X_PLL_STEP_SHIFT_AMOUNT);
// Maximum value for parameter symbNum
const SX126X_MAX_LORA_SYMB_NUM_TIMEOUT: u8 = 248;
// Time required for the TCXO to wakeup [ms].
const BRD_TCXO_WAKEUP_TIME: u32 = 10;
// SetRx timeout argument for enabling continuous mode
const RX_CONTINUOUS_TIMEOUT: u32 = 0xffffff;
/// Power amplifier selection
#[repr(u8)]
pub enum DeviceSel {
/// Low power, power amplifier, used by sx1261
LowPowerPA = 1,
/// High power, power amplifier, used by sx1262
HighPowerPA = 0,
}
/// Configuration for SX126x-based boards
pub struct Config<C: Sx126xVariant + Sized> {
/// LoRa chip variant on this board
pub chip: C,
/// Board is using TCXO (once enabled DIO3 cannot be used as IRQ)
pub tcxo_ctrl: Option<TcxoCtrlVoltage>,
/// Whether board is using optional DCDC in addition to LDO
pub use_dcdc: bool,
/// Whether to boost receive
pub rx_boost: bool,
}
/// Base for the RadioKind implementation for the LoRa chip kind and board type
pub struct Sx126x<SPI, IV, C: Sx126xVariant + Sized> {
intf: SpiInterface<SPI, IV>,
config: Config<C>,
}
impl<SPI, IV, C> Sx126x<SPI, IV, C>
where
SPI: SpiDevice<u8>,
IV: InterfaceVariant,
C: Sx126xVariant,
{
/// Create an instance of the RadioKind implementation for the LoRa chip kind and board type
pub fn new(spi: SPI, iv: IV, config: Config<C>) -> Self {
let intf = SpiInterface::new(spi, iv);
Self { intf, config }
}
// Utility functions
async fn add_register_to_retention_list(&mut self, register: Register) -> Result<(), RadioError> {
let mut buffer = [0x00u8; (1 + (2 * MAX_NUMBER_REGS_IN_RETENTION)) as usize];
// Read the address and registers already added to the list
self.intf
.read(
&[
OpCode::ReadRegister.value(),
Register::RetentionList.addr1(),
Register::RetentionList.addr2(),
0x00u8,
],
&mut buffer,
)
.await?;
let number_of_registers = buffer[0];
for i in 0..number_of_registers {
if register.addr1() == buffer[(1 + (2 * i)) as usize] && register.addr2() == buffer[(2 + (2 * i)) as usize]
{
return Ok(()); // register already in list
}
}
if number_of_registers < MAX_NUMBER_REGS_IN_RETENTION {
buffer[0] += 1; // increment number of registers
buffer[(1 + (2 * number_of_registers)) as usize] = register.addr1();
buffer[(2 + (2 * number_of_registers)) as usize] = register.addr2();
let register = [
OpCode::WriteRegister.value(),
Register::RetentionList.addr1(),
Register::RetentionList.addr2(),
];
self.intf.write_with_payload(&register, &buffer, false).await
} else {
Err(RadioError::InvalidConfiguration)
}
}
async fn update_retention_list(&mut self) -> Result<(), RadioError> {
self.add_register_to_retention_list(Register::RxGain).await?;
self.add_register_to_retention_list(Register::TxModulation).await
}
// Set the number of symbols the radio will wait to detect a reception
async fn set_lora_symbol_num_timeout(&mut self, symbol_num: u16) -> Result<(), RadioError> {
let mut exp = 0u8;
let mut mant = ((symbol_num.min(SX126X_MAX_LORA_SYMB_NUM_TIMEOUT.into()) + 1) >> 1) as u8;
while mant > 31 {
mant = (mant + 3) >> 2;
exp += 1;
}
let val: u8 = mant << ((2 * exp) + 1);
self.intf
.write(&[OpCode::SetLoRaSymbTimeout.value(), val], false)
.await?;
if symbol_num > 0 {
let val = exp + (mant << 3);
let buf = [
OpCode::WriteRegister.value(),
Register::SynchTimeout.addr1(),
Register::SynchTimeout.addr2(),
val,
];
self.intf.write(&buf, false).await?;
}
Ok(())
}
async fn set_pa_config(&mut self, pa_duty_cycle: u8, hp_max: u8, device_sel: DeviceSel) -> Result<(), RadioError> {
const PA_LUT_RESERVED: u8 = 0x01;
let op_code_and_pa_config = [
OpCode::SetPAConfig.value(),
pa_duty_cycle,
hp_max,
device_sel as u8,
PA_LUT_RESERVED,
];
self.intf.write(&op_code_and_pa_config, false).await
}
fn timeout_1(timeout: u32) -> u8 {
((timeout >> 16) & 0xFF) as u8
}
fn timeout_2(timeout: u32) -> u8 {
((timeout >> 8) & 0xFF) as u8
}
fn timeout_3(timeout: u32) -> u8 {
(timeout & 0xFF) as u8
}
fn convert_freq_in_hz_to_pll_step(freq_in_hz: u32) -> u32 {
// Get integer and fractional parts of the frequency computed with a PLL step scaled value
let steps_int = freq_in_hz / SX126X_PLL_STEP_SCALED;
let steps_frac = freq_in_hz - (steps_int * SX126X_PLL_STEP_SCALED);
(steps_int << SX126X_PLL_STEP_SHIFT_AMOUNT)
+ (((steps_frac << SX126X_PLL_STEP_SHIFT_AMOUNT) + (SX126X_PLL_STEP_SCALED >> 1)) / SX126X_PLL_STEP_SCALED)
}
}
impl<SPI, IV, C> RadioKind for Sx126x<SPI, IV, C>
where
SPI: SpiDevice<u8>,
IV: InterfaceVariant,
C: Sx126xVariant,
{
async fn init_lora(&mut self, is_public_network: bool) -> Result<(), RadioError> {
// DC-DC regulator setup (default is LDO)
if self.config.use_dcdc {
let reg_data = [OpCode::SetRegulatorMode.value(), RegulatorMode::UseDCDC.value()];
self.intf.write(&reg_data, false).await?;
}
// DIO2 acting as RF Switch (default is DIO2 as IRQ)
if self.config.chip.use_dio2_as_rfswitch() {
let cmd = [
OpCode::SetDIO2AsRfSwitchCtrl.value(),
self.config.chip.use_dio2_as_rfswitch() as u8,
];
self.intf.write(&cmd, false).await?;
}
// DIO3 acting as TCXO controller (default is DIO3 as IRQ)
if let Some(voltage) = self.config.tcxo_ctrl {
// When TCXO is used, XOSC_START_ERR flag is raised at POR or at
// wake-up from Sleep mode in cold-start condition. This is an
// expected behaviour since chip is not yet aware of being clocked
// by TCXO and therefore this should be initially cleared manually.
let mut buf = [0u8; 2];
let _ = self
.intf
.read_with_status(&[OpCode::ClearDeviceErrors.value()], &mut buf)
.await?;
// Each unit is 15.625uS (which is 1/64th ms)
let timeout = BRD_TCXO_WAKEUP_TIME << 6;
let op_code_and_tcxo_control = [
OpCode::SetTCXOMode.value(),
voltage.value() & 0x07,
Self::timeout_1(timeout),
Self::timeout_2(timeout),
Self::timeout_3(timeout),
];
self.intf.write(&op_code_and_tcxo_control, false).await?;
// Re-run calibration now that chip knows that it's running from TCXO
self.intf
.write(&[OpCode::Calibrate.value(), 0b0111_1111], false)
.await?;
self.intf.iv.wait_on_busy().await?;
}
// Enable LoRa packet engine...
self.intf
.write(&[OpCode::SetPacketType.value(), PacketType::LoRa.value()], false)
.await?;
let word = match is_public_network {
true => u16::to_be_bytes(LORA_MAC_PUBLIC_SYNCWORD),
false => u16::to_be_bytes(LORA_MAC_PRIVATE_SYNCWORD),
};
// ...and network syncword
let lora_syncword_set = [
OpCode::WriteRegister.value(),
Register::LoRaSyncword.addr1(),
Register::LoRaSyncword.addr2(),
word[0],
word[1],
];
self.intf.write(&lora_syncword_set, false).await?;
self.set_tx_rx_buffer_base_address(0, 0).await?;
// Update register list to support warm starts from sleep mode
self.update_retention_list().await?;
Ok(())
}
fn create_modulation_params(
&self,
spreading_factor: SpreadingFactor,
bandwidth: Bandwidth,
coding_rate: CodingRate,
frequency_in_hz: u32,
) -> Result<ModulationParams, RadioError> {
// Parameter validation
spreading_factor_value(spreading_factor)?;
bandwidth_value(bandwidth)?;
coding_rate_value(coding_rate)?;
if ((bandwidth == Bandwidth::_250KHz) || (bandwidth == Bandwidth::_500KHz)) && (frequency_in_hz < 400_000_000) {
return Err(RadioError::InvalidBandwidthForFrequency);
}
let mut low_data_rate_optimize = 0x00u8;
if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12))
&& (bandwidth == Bandwidth::_125KHz))
|| ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz))
{
low_data_rate_optimize = 0x01u8;
}
Ok(ModulationParams {
spreading_factor,
bandwidth,
coding_rate,
low_data_rate_optimize,
frequency_in_hz,
})
}
fn create_packet_params(
&self,
mut preamble_length: u16,
implicit_header: bool,
payload_length: u8,
crc_on: bool,
iq_inverted: bool,
modulation_params: &ModulationParams,
) -> Result<PacketParams, RadioError> {
if ((modulation_params.spreading_factor == SpreadingFactor::_5)
|| (modulation_params.spreading_factor == SpreadingFactor::_6))
&& (preamble_length < 12)
{
preamble_length = 12;
}
Ok(PacketParams {
preamble_length,
implicit_header,
payload_length,
crc_on,
iq_inverted,
})
}
async fn reset(&mut self, delay: &mut impl DelayNs) -> Result<(), RadioError> {
self.intf.iv.reset(delay).await
}
// Wakeup the radio if it is in Sleep or ReceiveDutyCycle mode; otherwise, ensure it is not busy.
async fn ensure_ready(&mut self, mode: RadioMode) -> Result<(), RadioError> {
match mode {
RadioMode::Sleep | RadioMode::Receive(RxMode::DutyCycle(_)) => {
let op_code_and_null = [OpCode::GetStatus.value(), 0x00u8];
self.intf.write(&op_code_and_null, false).await?;
}
_ => self.intf.iv.wait_on_busy().await?,
}
Ok(())
}
// Use standby mode RC (not XOSC).
async fn set_standby(&mut self) -> Result<(), RadioError> {
let op_code_and_standby_mode = [OpCode::SetStandby.value(), StandbyMode::RC.value()];
self.intf.write(&op_code_and_standby_mode, false).await?;
self.intf.iv.disable_rf_switch().await
}
async fn set_sleep(&mut self, warm_start_if_possible: bool, delay: &mut impl DelayNs) -> Result<(), RadioError> {
self.intf.iv.disable_rf_switch().await?;
let sleep_params = SleepParams {
wakeup_rtc: false,
reset: false,
warm_start: warm_start_if_possible,
};
let op_code_and_sleep_params = [OpCode::SetSleep.value(), sleep_params.value()];
self.intf.write(&op_code_and_sleep_params, true).await?;
delay.delay_ms(2).await;
Ok(())
}
async fn set_tx_rx_buffer_base_address(
&mut self,
tx_base_addr: usize,
rx_base_addr: usize,
) -> Result<(), RadioError> {
if tx_base_addr > 255 || rx_base_addr > 255 {
return Err(RadioError::InvalidBaseAddress(tx_base_addr, rx_base_addr));
}
let op_code_and_base_addrs = [
OpCode::SetBufferBaseAddress.value(),
tx_base_addr as u8,
rx_base_addr as u8,
];
self.intf.write(&op_code_and_base_addrs, false).await
}
// Set parameters associated with power for a send operation. Currently, over current protection (OCP) uses the default set automatically after set_pa_config()
// output_power desired RF output power (dBm)
// mdltn_params needed for a power vs channel frequency validation
// tx_boosted_if_possible not pertinent for sx126x
// is_tx_prep indicates which ramp up time to use
async fn set_tx_power_and_ramp_time(
&mut self,
output_power: i32,
mdltn_params: Option<&ModulationParams>,
is_tx_prep: bool,
) -> Result<(), RadioError> {
let tx_params_power;
let ramp_time = match is_tx_prep {
true => RampTime::Ramp40Us, // for instance, prior to TX or CAD
false => RampTime::Ramp200Us, // for instance, on initialization
};
match self.config.chip.get_device_sel() {
DeviceSel::LowPowerPA => {
const LOW_POWER_MIN: i32 = -17;
const LOW_POWER_MAX: i32 = 15;
// Clamp power between [-17, 15] dBm
let txp = output_power.clamp(LOW_POWER_MIN, LOW_POWER_MAX);
if txp == 15 {
if let Some(m_p) = mdltn_params {
if m_p.frequency_in_hz < 400_000_000 {
return Err(RadioError::InvalidOutputPowerForFrequency);
}
}
}
// For SX1261:
// if f < 400 MHz, paDutyCycle should not be higher than 0x04,
// if f > 400 Mhz, paDutyCycle should not be higher than 0x07.
// From Table 13-21: PA Operating Modes with Optimal Settings
match txp {
LOW_POWER_MAX => {
self.set_pa_config(0x06, 0x00, DeviceSel::LowPowerPA).await?;
tx_params_power = 14;
}
11..=14 => {
self.set_pa_config(0x04, 0x00, DeviceSel::LowPowerPA).await?;
tx_params_power = txp as u8;
}
// 10 and less
LOW_POWER_MIN..=10 => {
self.set_pa_config(0x01, 0x00, DeviceSel::LowPowerPA).await?;
// table indicates 10 dBm => txp = 13, therefore we add 3 to values below 10
tx_params_power = txp as u8 + 3;
}
_ => unreachable!("Invalid output power value for low power PA!"),
}
}
DeviceSel::HighPowerPA => {
const HIGH_POWER_MIN: i32 = -9;
const HIGH_POWER_MAX: i32 = 22;
// Clamp power between [-9, 22] dBm
let txp = output_power.clamp(HIGH_POWER_MIN, HIGH_POWER_MAX);
// Provide better resistance of the SX1262 Tx to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2)
let mut tx_clamp_cfg = [0x00u8];
self.intf
.read(
&[
OpCode::ReadRegister.value(),
Register::TxClampCfg.addr1(),
Register::TxClampCfg.addr2(),
0x00u8,
],
&mut tx_clamp_cfg,
)
.await?;
tx_clamp_cfg[0] |= 0x0F << 1;
let register_and_tx_clamp_cfg = [
OpCode::WriteRegister.value(),
Register::TxClampCfg.addr1(),
Register::TxClampCfg.addr2(),
tx_clamp_cfg[0],
];
self.intf.write(&register_and_tx_clamp_cfg, false).await?;
// From Table 13-21: PA Operating Modes with Optimal Settings
match txp {
21..=HIGH_POWER_MAX => {
self.set_pa_config(0x04, 0x07, DeviceSel::HighPowerPA).await?;
tx_params_power = 22;
}
18..=20 => {
self.set_pa_config(0x03, 0x05, DeviceSel::HighPowerPA).await?;
// table indicates 20 dBm => txp = 22, therefore we add 2 to this range
tx_params_power = txp as u8 + 2;
}
15..=17 => {
self.set_pa_config(0x02, 0x03, DeviceSel::HighPowerPA).await?;
// table indicates 17 dBm => txp = 22, therefore we add 5 to this range
tx_params_power = txp as u8 + 5;
}
HIGH_POWER_MIN..=14 => {
self.set_pa_config(0x02, 0x02, DeviceSel::HighPowerPA).await?;
// table indicates 14 dBm => txp = 22, therefore we add 8 to this range
tx_params_power = txp as u8 + 8;
}
_ => {
unreachable!("Invalid output power value for high power PA!")
}
}
}
}
let op_code_and_tx_params = [OpCode::SetTxParams.value(), tx_params_power, ramp_time.value()];
self.intf.write(&op_code_and_tx_params, false).await
}
async fn set_modulation_params(&mut self, mdltn_params: &ModulationParams) -> Result<(), RadioError> {
let spreading_factor_val = spreading_factor_value(mdltn_params.spreading_factor)?;
let bandwidth_val = bandwidth_value(mdltn_params.bandwidth)?;
let coding_rate_val = coding_rate_value(mdltn_params.coding_rate)?;
debug!(
"sf = {}, bw = {}, cr = {}",
spreading_factor_val, bandwidth_val, coding_rate_val
);
let op_code_and_mod_params = [
OpCode::SetModulationParams.value(),
spreading_factor_val,
bandwidth_val,
coding_rate_val,
mdltn_params.low_data_rate_optimize,
];
self.intf.write(&op_code_and_mod_params, false).await?;
// Handle modulation quality with the 500 kHz LoRa bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1)
let mut tx_mod = [0x00u8];
self.intf
.read(
&[
OpCode::ReadRegister.value(),
Register::TxModulation.addr1(),
Register::TxModulation.addr2(),
0x00u8,
],
&mut tx_mod,
)
.await?;
if mdltn_params.bandwidth == Bandwidth::_500KHz {
let register_and_tx_mod_update = [
OpCode::WriteRegister.value(),
Register::TxModulation.addr1(),
Register::TxModulation.addr2(),
tx_mod[0] & (!(1 << 2)),
];
self.intf.write(&register_and_tx_mod_update, false).await
} else {
let register_and_tx_mod_update = [
OpCode::WriteRegister.value(),
Register::TxModulation.addr1(),
Register::TxModulation.addr2(),
tx_mod[0] | (1 << 2),
];
self.intf.write(&register_and_tx_mod_update, false).await
}
}
async fn set_packet_params(&mut self, pkt_params: &PacketParams) -> Result<(), RadioError> {
let op_code_and_pkt_params = [
OpCode::SetPacketParams.value(),
((pkt_params.preamble_length >> 8) & 0xFF) as u8,
(pkt_params.preamble_length & 0xFF) as u8,
pkt_params.implicit_header as u8,
pkt_params.payload_length,
pkt_params.crc_on as u8,
pkt_params.iq_inverted as u8,
];
self.intf.write(&op_code_and_pkt_params, false).await?;
// Optimize Inverted IQ Operation, otherwise packet loss with longer packets might occur.
let mut iq_polarity = [0x00u8];
self.intf
.read(
&[
OpCode::ReadRegister.value(),
Register::IQPolarity.addr1(),
Register::IQPolarity.addr2(),
0x00u8,
],
&mut iq_polarity,
)
.await?;
let reg = if pkt_params.iq_inverted {
iq_polarity[0] & (!(1 << 2))
} else {
iq_polarity[0] | (1 << 2)
};
let op = [
OpCode::WriteRegister.value(),
Register::IQPolarity.addr1(),
Register::IQPolarity.addr2(),
reg,
];
self.intf.write(&op, false).await?;
Ok(())
}
// Calibrate the image rejection based on the given frequency
async fn calibrate_image(&mut self, frequency_in_hz: u32) -> Result<(), RadioError> {
let mut cal_freq = [0x00u8, 0x00u8];
if frequency_in_hz > 900000000 {
cal_freq[0] = 0xE1;
cal_freq[1] = 0xE9;
} else if frequency_in_hz > 850000000 {
cal_freq[0] = 0xD7;
cal_freq[1] = 0xDB;
} else if frequency_in_hz > 770000000 {
cal_freq[0] = 0xC1;
cal_freq[1] = 0xC5;
} else if frequency_in_hz > 460000000 {
cal_freq[0] = 0x75;
cal_freq[1] = 0x81;
} else if frequency_in_hz > 425000000 {
cal_freq[0] = 0x6B;
cal_freq[1] = 0x6F;
}
let op_code_and_cal_freq = [OpCode::CalibrateImage.value(), cal_freq[0], cal_freq[1]];
self.intf.write(&op_code_and_cal_freq, false).await
}
async fn set_channel(&mut self, frequency_in_hz: u32) -> Result<(), RadioError> {
debug!("channel = {}", frequency_in_hz);
let freq_in_pll_steps = Self::convert_freq_in_hz_to_pll_step(frequency_in_hz);
let op_code_and_pll_steps = [
OpCode::SetRFFrequency.value(),
((freq_in_pll_steps >> 24) & 0xFF) as u8,
((freq_in_pll_steps >> 16) & 0xFF) as u8,
((freq_in_pll_steps >> 8) & 0xFF) as u8,
(freq_in_pll_steps & 0xFF) as u8,
];
self.intf.write(&op_code_and_pll_steps, false).await
}
async fn set_payload(&mut self, payload: &[u8]) -> Result<(), RadioError> {
let op_code_and_offset = [OpCode::WriteBuffer.value(), 0x00u8];
self.intf.write_with_payload(&op_code_and_offset, payload, false).await
}
async fn do_tx(&mut self) -> Result<(), RadioError> {
self.intf.iv.enable_rf_switch_tx().await?;
// Disable timeout
let cmd = [
OpCode::SetTx.value(),
Self::timeout_1(0),
Self::timeout_2(0),
Self::timeout_3(0),
];
self.intf.write(&cmd, false).await
}
async fn do_rx(&mut self, rx_mode: RxMode) -> Result<(), RadioError> {
self.intf.iv.enable_rf_switch_rx().await?;
// Stop the Rx timer on preamble detection
let op_code_and_true_flag = [OpCode::SetStopRxTimerOnPreamble.value(), 0x01u8];
self.intf.write(&op_code_and_true_flag, false).await?;
let num_symbols = match rx_mode {
RxMode::DutyCycle(_) | RxMode::Continuous => 0,
RxMode::Single(n) => n,
};
self.set_lora_symbol_num_timeout(num_symbols).await?;
let rx_gain = if self.config.rx_boost { 0x96 } else { 0x94 };
let register_and_rx_gain = [
OpCode::WriteRegister.value(),
Register::RxGain.addr1(),
Register::RxGain.addr2(),
rx_gain,
];
self.intf.write(&register_and_rx_gain, false).await?;
match rx_mode {
RxMode::DutyCycle(args) => {
let op = [
OpCode::SetRxDutyCycle.value(),
Self::timeout_1(args.rx_time),
Self::timeout_2(args.rx_time),
Self::timeout_3(args.rx_time),
Self::timeout_1(args.sleep_time),
Self::timeout_2(args.sleep_time),
Self::timeout_3(args.sleep_time),
];
self.intf.write(&op, false).await
}
RxMode::Single(_) => {
let op = [
OpCode::SetRx.value(),
Self::timeout_1(0),
Self::timeout_2(0),
Self::timeout_3(0),
];
self.intf.write(&op, false).await
}
RxMode::Continuous => {
let op = [
OpCode::SetRx.value(),
Self::timeout_1(RX_CONTINUOUS_TIMEOUT),
Self::timeout_2(RX_CONTINUOUS_TIMEOUT),
Self::timeout_3(RX_CONTINUOUS_TIMEOUT),
];
self.intf.write(&op, false).await
}
}
}
async fn get_rx_payload(
&mut self,
rx_pkt_params: &PacketParams,
receiving_buffer: &mut [u8],
) -> Result<u8, RadioError> {
let op_code = [OpCode::GetRxBufferStatus.value()];
let mut rx_buffer_status = [0x00u8; 2];
let read_status = self.intf.read_with_status(&op_code, &mut rx_buffer_status).await?;
if OpStatusErrorMask::is_error(read_status) {
return Err(RadioError::OpError(read_status));
}
let mut payload_length_buffer = [0x00u8];
if rx_pkt_params.implicit_header {
self.intf
.read(
&[
OpCode::ReadRegister.value(),
Register::PayloadLength.addr1(),
Register::PayloadLength.addr2(),
0x00u8,
],
&mut payload_length_buffer,
)
.await?;
} else {
payload_length_buffer[0] = rx_buffer_status[0];
}
let payload_length = payload_length_buffer[0];
let offset = rx_buffer_status[1];
if (payload_length as usize) > receiving_buffer.len() {
Err(RadioError::PayloadSizeMismatch(
payload_length as usize,
receiving_buffer.len(),
))
} else {
self.intf
.read(
&[OpCode::ReadBuffer.value(), offset, 0x00u8],
&mut receiving_buffer[..payload_length as usize],
)
.await?;
Ok(payload_length)
}
}
async fn get_rx_packet_status(&mut self) -> Result<PacketStatus, RadioError> {
let op_code = [OpCode::GetPacketStatus.value()];
let mut pkt_status = [0x00u8; 3];
let read_status = self.intf.read_with_status(&op_code, &mut pkt_status).await?;
if OpStatusErrorMask::is_error(read_status) {
return Err(RadioError::OpError(read_status));
}
// check this ???
let rssi = ((-(pkt_status[0] as i32)) >> 1) as i16;
let snr = (((pkt_status[1] as i8) + 2) >> 2) as i16;
let _signal_rssi = ((-(pkt_status[2] as i32)) >> 1) as i16; // unused currently
Ok(PacketStatus { rssi, snr })
}
async fn do_cad(&mut self, mdltn_params: &ModulationParams) -> Result<(), RadioError> {
self.intf.iv.enable_rf_switch_rx().await?;
let mut rx_gain_final = 0x94u8;
// if Rx boosted, set max LNA gain, increase current by ~2mA for around ~3dB in sensitivity
if self.config.rx_boost {
rx_gain_final = 0x96u8;
}
let register_and_rx_gain = [
OpCode::WriteRegister.value(),
Register::RxGain.addr1(),
Register::RxGain.addr2(),
rx_gain_final,
];
self.intf.write(&register_and_rx_gain, false).await?;
// See:
// https://lora-developers.semtech.com/documentation/tech-papers-and-guides/channel-activity-detection-ensuring-your-lora-packets-are-sent/how-to-ensure-your-lora-packets-are-sent-properly
// for default values used here.
let spreading_factor_val = spreading_factor_value(mdltn_params.spreading_factor)?;
let op_code_and_cad_params = [
OpCode::SetCADParams.value(),
CADSymbols::_8.value(), // number of symbols for detection
spreading_factor_val + 13u8, // limit for detection of SNR peak
10u8, // minimum symbol recognition
0x00u8, // CAD exit mode without listen-before-send or subsequent receive processing
0x00u8, // no timeout
0x00u8,
0x00u8,
];
self.intf.write(&op_code_and_cad_params, false).await?;
let op_code_for_set_cad = [OpCode::SetCAD.value()];
self.intf.write(&op_code_for_set_cad, false).await
}
// Set the IRQ mask and DIO masks
async fn set_irq_params(&mut self, radio_mode: Option<RadioMode>) -> Result<(), RadioError> {
let mut irq_mask: u16 = IrqMask::None.value();
let mut dio1_mask: u16 = IrqMask::None.value();
let dio2_mask: u16 = IrqMask::None.value();
let dio3_mask: u16 = IrqMask::None.value();
match radio_mode {
Some(RadioMode::Standby) => {
irq_mask = IrqMask::All.value();
dio1_mask = IrqMask::All.value();
}
Some(RadioMode::Transmit) => {
irq_mask = IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value();
dio1_mask = IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value();
}
Some(RadioMode::Receive(_)) => {
irq_mask = IrqMask::All.value();
dio1_mask = IrqMask::All.value();
}
Some(RadioMode::ChannelActivityDetection) => {
irq_mask = IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value();
dio1_mask = IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value();
}
_ => {}
}
let op_code_and_masks = [
OpCode::CfgDIOIrq.value(),
((irq_mask >> 8) & 0x00FF) as u8,
(irq_mask & 0x00FF) as u8,
((dio1_mask >> 8) & 0x00FF) as u8,
(dio1_mask & 0x00FF) as u8,
((dio2_mask >> 8) & 0x00FF) as u8,
(dio2_mask & 0x00FF) as u8,
((dio3_mask >> 8) & 0x00FF) as u8,
(dio3_mask & 0x00FF) as u8,
];
self.intf.write(&op_code_and_masks, false).await
}
async fn set_tx_continuous_wave_mode(&mut self) -> Result<(), RadioError> {
self.intf.iv.enable_rf_switch_tx().await?;
let op_code = [OpCode::SetTxContinuousWave.value()];
self.intf.write(&op_code, false).await
}
async fn await_irq(&mut self) -> Result<(), RadioError> {
self.intf.iv.await_irq().await
}
/// Process the radio IRQ. Log unexpected interrupts. Packets from other
/// devices can cause unexpected interrupts.
///
/// NB! Do not await this future in a select branch as interrupting it
/// mid-flow could cause radio lock up.
async fn process_irq_event(
&mut self,
radio_mode: RadioMode,
cad_activity_detected: Option<&mut bool>,
clear_interrupts: bool,
) -> Result<Option<IrqState>, RadioError> {
let op_code = [OpCode::GetIrqStatus.value()];
let mut irq_status = [0x00u8, 0x00u8];
// Assuming intf.read_with_status is an existing async method that reads the IRQ status.
let read_status = self.intf.read_with_status(&op_code, &mut irq_status).await?;
let irq_flags = ((irq_status[0] as u16) << 8) | (irq_status[1] as u16);
if clear_interrupts && irq_flags != 0 {
let op_code_and_irq_status = [OpCode::ClrIrqStatus.value(), irq_status[0], irq_status[1]];
self.intf.write(&op_code_and_irq_status, false).await?;
}
if OpStatusErrorMask::is_error(read_status) {
debug!(
"process_irq read status error = 0x{:x} in radio mode {}",
read_status, radio_mode
);
}
debug!(
"process_irq satisfied: irq_flags = 0x{:x} in radio mode {}",
irq_flags, radio_mode
);
if (irq_flags & IrqMask::HeaderValid.value()) == IrqMask::HeaderValid.value() {
debug!("HeaderValid in radio mode {}", radio_mode);
}
if (irq_flags & IrqMask::PreambleDetected.value()) == IrqMask::PreambleDetected.value() {
debug!("PreambleDetected in radio mode {}", radio_mode);
}
if (irq_flags & IrqMask::SyncwordValid.value()) == IrqMask::SyncwordValid.value() {
debug!("SyncwordValid in radio mode {}", radio_mode);
}
match radio_mode {
RadioMode::Transmit => {
if (irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value() {
return Ok(Some(IrqState::Done));
}
if (irq_flags & IrqMask::RxTxTimeout.value()) == IrqMask::RxTxTimeout.value() {
return Err(RadioError::TransmitTimeout);
}
}
RadioMode::Receive(rx_mode) => {
if (irq_flags & IrqMask::HeaderError.value()) == IrqMask::HeaderError.value() {
debug!("HeaderError in radio mode {}", radio_mode);
}
if (irq_flags & IrqMask::CRCError.value()) == IrqMask::CRCError.value() {
debug!("CRCError in radio mode {}", radio_mode);
}
if (irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value() {
debug!("RxDone in radio mode {}", radio_mode);
if rx_mode != RxMode::Continuous {
// implicit header mode timeout behavior (see DS_SX1261-2_V1.2 datasheet chapter 15.3)
let register_and_clear = [
OpCode::WriteRegister.value(),
Register::RTCCtrl.addr1(),
Register::RTCCtrl.addr2(),
0x00u8,
];
self.intf.write(&register_and_clear, false).await?;
let mut evt_clr = [0x00u8];
self.intf
.read(
&[
OpCode::ReadRegister.value(),
Register::EvtClr.addr1(),
Register::EvtClr.addr2(),
0x00u8,
],
&mut evt_clr,
)
.await?;
evt_clr[0] |= 1 << 1;
let register_and_evt_clear = [
OpCode::WriteRegister.value(),
Register::EvtClr.addr1(),
Register::EvtClr.addr2(),
evt_clr[0],
];
self.intf.write(&register_and_evt_clear, false).await?;
}
return Ok(Some(IrqState::Done));
}
if IrqMask::PreambleDetected.is_set_in(irq_flags) || IrqMask::HeaderValid.is_set_in(irq_flags) {
return Ok(Some(IrqState::PreambleReceived));
}
if (irq_flags & IrqMask::RxTxTimeout.value()) == IrqMask::RxTxTimeout.value() {
return Err(RadioError::ReceiveTimeout);
}
}
RadioMode::ChannelActivityDetection => {
if (irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value() {
if let Some(detected) = cad_activity_detected {
*detected =
(irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value();
}
return Ok(Some(IrqState::Done));
}
}
RadioMode::Sleep | RadioMode::Standby => {
defmt::warn!("IRQ during sleep/standby?");
}
RadioMode::FrequencySynthesis => todo!(),
}
// If none of the specific conditions are met, return None to indicate no IRQ state change.
Ok(None)
}
}
#[cfg(test)]
mod tests {
// use super::*;
#[test]
// -17 (0xEF) to +14 (0x0E) dBm by step of 1 dB if low power PA is selected
// -9 (0xF7) to +22 (0x16) dBm by step of 1 dB if high power PA is selected
fn power_level_negative_value_conversion() {
let mut i32_val: i32 = -17;
assert_eq!(i32_val as u8, 0xefu8);
i32_val = -9;
assert_eq!(i32_val as u8, 0xf7u8);
}
}
+304
View File
@@ -0,0 +1,304 @@
use crate::mod_params::*;
#[derive(Clone, Copy, PartialEq)]
#[allow(dead_code)]
#[allow(clippy::upper_case_acronyms)]
pub enum PacketType {
GFSK = 0x00,
LoRa = 0x01,
None = 0x0F,
}
impl PacketType {
pub const fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
pub enum IrqMask {
None = 0x0000,
TxDone = 0x0001,
RxDone = 0x0002,
PreambleDetected = 0x0004,
SyncwordValid = 0x0008,
HeaderValid = 0x0010,
HeaderError = 0x0020,
CRCError = 0x0040,
CADDone = 0x0080,
CADActivityDetected = 0x0100,
RxTxTimeout = 0x0200,
All = 0xFFFF,
}
impl IrqMask {
pub fn value(self) -> u16 {
self as u16
}
pub fn is_set_in(self, mask: u16) -> bool {
self.value() & mask == self.value()
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
#[allow(clippy::upper_case_acronyms)]
pub enum Register {
PacketParams = 0x0704, // packet configuration
PayloadLength = 0x0702, // payload size
/// Number of symbols given as SX126X_REG_LR_SYNCH_TIMEOUT[7:3] * 2 ^ (2*SX126X_REG_LR_SYNCH_TIMEOUT[2:0] + 1)
/// Info from SDK (not present in user manual).
SynchTimeout = 0x0706,
Syncword = 0x06C0, // Syncword values
LoRaSyncword = 0x0740, // LoRa Syncword value
GeneratedRandomNumber = 0x0819, //32-bit generated random number
AnaLNA = 0x08E2, // disable the LNA
AnaMixer = 0x08E5, // disable the mixer
RxGain = 0x08AC, // RX gain (0x94: power saving, 0x96: rx boosted)
XTATrim = 0x0911, // device internal trimming capacitor
OCP = 0x08E7, // over current protection max value
RetentionList = 0x029F, // retention list
/// Inverted IQ operation optimization - possible packet loss for longer packets
/// DS.SX1261-2.W.APP Rev.2.1 - Chapter 15.4
IQPolarity = 0x0736,
TxModulation = 0x0889, // modulation quality with 500 kHz LoRa Bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1)
TxClampCfg = 0x08D8, // better resistance to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2)
RTCCtrl = 0x0902, // RTC control
EvtClr = 0x0944, // event clear
}
impl Register {
pub fn addr1(self) -> u8 {
((self as u16 & 0xFF00) >> 8) as u8
}
pub fn addr2(self) -> u8 {
(self as u16 & 0x00FF) as u8
}
}
#[derive(Clone, Copy, PartialEq)]
#[allow(dead_code)]
pub enum OpCode {
GetStatus = 0xC0,
WriteRegister = 0x0D,
ReadRegister = 0x1D,
WriteBuffer = 0x0E,
ReadBuffer = 0x1E,
SetSleep = 0x84,
SetStandby = 0x80,
SetFS = 0xC1,
SetTx = 0x83,
SetRx = 0x82,
SetRxDutyCycle = 0x94,
SetCAD = 0xC5,
SetTxContinuousWave = 0xD1,
SetTxContinuousPremable = 0xD2,
SetPacketType = 0x8A,
GetPacketType = 0x11,
SetRFFrequency = 0x86,
SetTxParams = 0x8E,
SetPAConfig = 0x95,
SetCADParams = 0x88,
SetBufferBaseAddress = 0x8F,
SetModulationParams = 0x8B,
SetPacketParams = 0x8C,
GetRxBufferStatus = 0x13,
GetPacketStatus = 0x14,
GetRSSIInst = 0x15,
GetStats = 0x10,
ResetStats = 0x00,
CfgDIOIrq = 0x08,
GetIrqStatus = 0x12,
ClrIrqStatus = 0x02,
Calibrate = 0x89,
CalibrateImage = 0x98,
SetRegulatorMode = 0x96,
GetDeviceErrors = 0x17,
ClearDeviceErrors = 0x07,
SetTCXOMode = 0x97,
SetTxFallbackMode = 0x93,
SetDIO2AsRfSwitchCtrl = 0x9d,
SetStopRxTimerOnPreamble = 0x9F,
SetLoRaSymbTimeout = 0xA0,
}
impl OpCode {
pub fn value(self) -> u8 {
self as u8
}
}
// See RM0453 Reference manual STM32WL5x advanced Arm®-based 32-bit MCUs with sub-GHz radio solution, section 5.8.5
#[derive(Clone, Copy, PartialEq)]
pub enum OpStatusErrorMask {
Timeout = (0x03 << 1),
ProcessingError = (0x04 << 1),
ExecutionError = (0x05 << 1),
}
impl OpStatusErrorMask {
pub fn is_error(status: u8) -> bool {
let error_flags = status & 0x0e;
OpStatusErrorMask::Timeout as u8 == error_flags
|| OpStatusErrorMask::ProcessingError as u8 == error_flags
|| OpStatusErrorMask::ExecutionError as u8 == error_flags
}
}
#[derive(Clone, Copy)]
pub struct SleepParams {
pub wakeup_rtc: bool, // get out of sleep mode if wakeup signal received from RTC
pub reset: bool,
pub warm_start: bool,
}
impl SleepParams {
pub fn value(self) -> u8 {
((self.warm_start as u8) << 2) | ((self.reset as u8) << 1) | (self.wakeup_rtc as u8)
}
}
#[derive(Clone, Copy, PartialEq)]
#[allow(dead_code)]
#[allow(clippy::upper_case_acronyms)]
pub enum StandbyMode {
RC = 0x00,
XOSC = 0x01,
}
impl StandbyMode {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub enum RegulatorMode {
UseLDO = 0x00,
UseDCDC = 0x01,
}
impl RegulatorMode {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub struct CalibrationParams {
pub rc64k_enable: bool, // calibrate RC64K clock
pub rc13m_enable: bool, // calibrate RC13M clock
pub pll_enable: bool, // calibrate PLL
pub adc_pulse_enable: bool, // calibrate ADC Pulse
pub adc_bulkn_enable: bool, // calibrate ADC bulkN
pub adc_bulkp_enable: bool, // calibrate ADC bulkP
pub img_enable: bool,
}
#[allow(dead_code)]
impl CalibrationParams {
pub fn value(self) -> u8 {
((self.img_enable as u8) << 6)
| ((self.adc_bulkp_enable as u8) << 5)
| ((self.adc_bulkn_enable as u8) << 4)
| ((self.adc_pulse_enable as u8) << 3)
| ((self.pll_enable as u8) << 2)
| ((self.rc13m_enable as u8) << 1)
| (self.rc64k_enable as u8)
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy)]
pub enum TcxoCtrlVoltage {
Ctrl1V6 = 0x00,
Ctrl1V7 = 0x01,
Ctrl1V8 = 0x02,
Ctrl2V2 = 0x03,
Ctrl2V4 = 0x04,
Ctrl2V7 = 0x05,
Ctrl3V0 = 0x06,
Ctrl3V3 = 0x07,
}
#[allow(missing_docs)]
impl TcxoCtrlVoltage {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
#[allow(clippy::enum_variant_names)]
pub enum RampTime {
Ramp10Us = 0x00,
Ramp20Us = 0x01,
Ramp40Us = 0x02,
Ramp80Us = 0x03,
Ramp200Us = 0x04,
Ramp800Us = 0x05,
Ramp1700Us = 0x06,
Ramp3400Us = 0x07,
}
impl RampTime {
pub fn value(self) -> u8 {
self as u8
}
}
pub fn spreading_factor_value(spreading_factor: SpreadingFactor) -> Result<u8, RadioError> {
match spreading_factor {
SpreadingFactor::_5 => Ok(0x05),
SpreadingFactor::_6 => Ok(0x06),
SpreadingFactor::_7 => Ok(0x07),
SpreadingFactor::_8 => Ok(0x08),
SpreadingFactor::_9 => Ok(0x09),
SpreadingFactor::_10 => Ok(0x0A),
SpreadingFactor::_11 => Ok(0x0B),
SpreadingFactor::_12 => Ok(0x0C),
}
}
pub fn bandwidth_value(bandwidth: Bandwidth) -> Result<u8, RadioError> {
match bandwidth {
Bandwidth::_7KHz => Ok(0x00),
Bandwidth::_10KHz => Ok(0x08),
Bandwidth::_15KHz => Ok(0x01),
Bandwidth::_20KHz => Ok(0x09),
Bandwidth::_31KHz => Ok(0x02),
Bandwidth::_41KHz => Ok(0x0a),
Bandwidth::_62KHz => Ok(0x03),
Bandwidth::_125KHz => Ok(0x04),
Bandwidth::_250KHz => Ok(0x05),
Bandwidth::_500KHz => Ok(0x06),
}
}
pub fn coding_rate_value(coding_rate: CodingRate) -> Result<u8, RadioError> {
match coding_rate {
CodingRate::_4_5 => Ok(0x01),
CodingRate::_4_6 => Ok(0x02),
CodingRate::_4_7 => Ok(0x03),
CodingRate::_4_8 => Ok(0x04),
}
}
#[derive(Clone, Copy)]
pub enum CADSymbols {
_1 = 0x00,
_2 = 0x01,
_4 = 0x02,
_8 = 0x03,
_16 = 0x04,
}
impl CADSymbols {
pub fn value(self) -> u8 {
self as u8
}
}
+47
View File
@@ -0,0 +1,47 @@
use super::DeviceSel;
/// Implement this trait on your custom variant or use provided impls
pub trait Sx126xVariant {
/// whether to use high or low power PA
fn get_device_sel(&self) -> DeviceSel;
/// use dio2 as rf switch output
fn use_dio2_as_rfswitch(&self) -> bool {
true
}
}
/// Sx1261 uses only LowPowerPA
pub struct Sx1261;
impl Sx126xVariant for Sx1261 {
fn get_device_sel(&self) -> super::DeviceSel {
super::DeviceSel::LowPowerPA
}
}
/// Sx1262 uses only HighPowerPA
pub struct Sx1262;
impl Sx126xVariant for Sx1262 {
fn get_device_sel(&self) -> super::DeviceSel {
super::DeviceSel::HighPowerPA
}
}
/// Stm32wl variant.
pub struct Stm32wl {
/// select which output to use. (Switching is not supported)
pub use_high_power_pa: bool,
}
impl Sx126xVariant for Stm32wl {
fn get_device_sel(&self) -> super::DeviceSel {
if self.use_high_power_pa {
DeviceSel::HighPowerPA
} else {
DeviceSel::LowPowerPA
}
}
fn use_dio2_as_rfswitch(&self) -> bool {
false
}
}
+541
View File
@@ -0,0 +1,541 @@
mod radio_kind_params;
mod sx1272;
pub use sx1272::Sx1272;
mod sx1276;
pub use sx1276::Sx1276;
use defmt::debug;
use embedded_hal_async::delay::DelayNs;
use embedded_hal_async::spi::*;
use radio_kind_params::*;
use crate::mod_params::*;
use crate::mod_traits::IrqState;
use crate::{InterfaceVariant, RadioKind, SpiInterface};
// Syncwords for public and private networks
const LORA_MAC_PUBLIC_SYNCWORD: u8 = 0x34; // corresponds to sx126x 0x3444
const LORA_MAC_PRIVATE_SYNCWORD: u8 = 0x12; // corresponds to sx126x 0x1424
// TCXO flag
const TCXO_FOR_OSCILLATOR: u8 = 0x10u8;
// Frequency synthesizer step for frequency calculation (Hz)
const FREQUENCY_SYNTHESIZER_STEP: f64 = 61.03515625; // FXOSC (32 MHz) * 1000000 (Hz/MHz) / 524288 (2^19)
// Limits for preamble detection window in single reception mode
const SX127X_MIN_LORA_SYMB_NUM_TIMEOUT: u16 = 4;
const SX127X_MAX_LORA_SYMB_NUM_TIMEOUT: u16 = 1023;
// Constant values need to compute the RSSI value
const SX1272_RSSI_OFFSET: i16 = -139;
const SX1276_RSSI_OFFSET_LF: i16 = -164;
const SX1276_RSSI_OFFSET_HF: i16 = -157;
const SX1276_RF_MID_BAND_THRESH: u32 = 525_000_000;
/// Configuration for SX127x-based boards
pub struct Config<C: Sx127xVariant> {
/// LoRa chip used on specific board
pub chip: C,
/// Whether board is using crystal oscillator or external clock
pub tcxo_used: bool,
/// Whether to use PA_BOOST for transmit instead of RFO (sx1272) or RFO_LF (sx1276).
/// NB! Depends on board layout.
pub tx_boost: bool,
/// Whether to boost receive
pub rx_boost: bool,
}
/// Base for the RadioKind implementation for the LoRa chip kind and board type
pub struct Sx127x<SPI, IV, C: Sx127xVariant + Sized> {
intf: SpiInterface<SPI, IV>,
config: Config<C>,
}
impl<SPI, IV, C> Sx127x<SPI, IV, C>
where
SPI: SpiDevice<u8>,
IV: InterfaceVariant,
C: Sx127xVariant,
{
/// Create an instance of the RadioKind implementation for the LoRa chip kind and board type
pub fn new(spi: SPI, iv: IV, config: Config<C>) -> Self {
let intf = SpiInterface::new(spi, iv);
Self { intf, config }
}
// Utility functions
async fn write_register(&mut self, register: Register, value: u8) -> Result<(), RadioError> {
let write_buffer = [register.write_addr(), value];
self.intf.write(&write_buffer, false).await
}
async fn read_register(&mut self, register: Register) -> Result<u8, RadioError> {
let write_buffer = [register.read_addr()];
let mut read_buffer = [0x00u8];
self.intf.read(&write_buffer, &mut read_buffer).await?;
Ok(read_buffer[0])
}
async fn read_buffer(&mut self, register: Register, buf: &mut [u8]) -> Result<(), RadioError> {
self.intf.read(&[register.read_addr()], buf).await
}
async fn write_buffer(&mut self, register: Register, buf: &[u8]) -> Result<(), RadioError> {
self.intf.write_with_payload(&[register.write_addr()], buf, false).await
}
// Set the number of symbols the radio will wait to detect a reception (up to 1023 symbols)
async fn set_lora_symbol_num_timeout(&mut self, symbol_num: u16) -> Result<(), RadioError> {
let val = symbol_num.min(SX127X_MAX_LORA_SYMB_NUM_TIMEOUT);
let symbol_num_msb = ((val >> 8) & 0x03) as u8;
let symbol_num_lsb = (val & 0xff) as u8;
let mut config_2 = self.read_register(Register::RegModemConfig2).await?;
config_2 = (config_2 & 0xfcu8) | symbol_num_msb;
self.write_register(Register::RegModemConfig2, config_2).await?;
self.write_register(Register::RegSymbTimeoutLsb, symbol_num_lsb).await
}
// Set the over current protection (mA) on the radio
async fn set_ocp(&mut self, ocp_trim: OcpTrim) -> Result<(), RadioError> {
self.write_register(Register::RegOcp, ocp_trim.value()).await
}
}
impl<SPI, IV, C> RadioKind for Sx127x<SPI, IV, C>
where
SPI: SpiDevice<u8>,
IV: InterfaceVariant,
C: Sx127xVariant,
{
async fn init_lora(&mut self, is_public_network: bool) -> Result<(), RadioError> {
if self.config.tcxo_used {
self.write_register(C::reg_txco(), TCXO_FOR_OSCILLATOR).await?;
}
let syncword = if is_public_network {
LORA_MAC_PUBLIC_SYNCWORD
} else {
LORA_MAC_PRIVATE_SYNCWORD
};
self.write_register(Register::RegSyncWord, syncword).await?;
self.set_tx_rx_buffer_base_address(0, 0).await?;
Ok(())
}
fn create_modulation_params(
&self,
spreading_factor: SpreadingFactor,
bandwidth: Bandwidth,
coding_rate: CodingRate,
frequency_in_hz: u32,
) -> Result<ModulationParams, RadioError> {
// Parameter validation
spreading_factor_value(spreading_factor)?;
coding_rate_value(coding_rate)?;
C::bandwidth_value(bandwidth)?;
if ((bandwidth == Bandwidth::_250KHz) || (bandwidth == Bandwidth::_500KHz)) && (frequency_in_hz < 400_000_000) {
return Err(RadioError::InvalidBandwidthForFrequency);
}
// Section 4.1.1.5 and 4.1.1.6
let bw_in_hz = u32::from(bandwidth);
let symbol_duration = 1000 / (bw_in_hz / (0x01u32 << spreading_factor_value(spreading_factor)?));
let mut low_data_rate_optimize = 0x00u8;
if symbol_duration > 16 {
low_data_rate_optimize = 0x01u8
}
Ok(ModulationParams {
spreading_factor,
bandwidth,
coding_rate,
low_data_rate_optimize,
frequency_in_hz,
})
}
fn create_packet_params(
&self,
preamble_length: u16,
implicit_header: bool,
payload_length: u8,
crc_on: bool,
iq_inverted: bool,
modulation_params: &ModulationParams,
) -> Result<PacketParams, RadioError> {
// Parameter validation
if (modulation_params.spreading_factor == SpreadingFactor::_6) && !implicit_header {
return Err(RadioError::InvalidSF6ExplicitHeaderRequest);
}
Ok(PacketParams {
preamble_length,
implicit_header,
payload_length,
crc_on,
iq_inverted,
})
}
async fn reset(&mut self, delay: &mut impl DelayNs) -> Result<(), RadioError> {
self.intf.iv.reset(delay).await?;
self.set_sleep(false, delay).await?; // ensure sleep mode is entered so that the LoRa mode bit is set
Ok(())
}
async fn ensure_ready(&mut self, _mode: RadioMode) -> Result<(), RadioError> {
Ok(())
}
async fn set_standby(&mut self) -> Result<(), RadioError> {
self.write_register(Register::RegOpMode, LoRaMode::Standby.value())
.await?;
self.intf.iv.disable_rf_switch().await
}
async fn set_sleep(&mut self, _warm_start_if_possible: bool, _delay: &mut impl DelayNs) -> Result<(), RadioError> {
// Warm start is unavailable for sx127x
self.intf.iv.disable_rf_switch().await?;
let buf = [Register::RegOpMode.write_addr(), LoRaMode::Sleep.value()];
// NB! Switching to sleep mode is "sleep" command...
self.intf.write(&buf, true).await?;
Ok(())
}
async fn set_tx_rx_buffer_base_address(
&mut self,
tx_base_addr: usize,
rx_base_addr: usize,
) -> Result<(), RadioError> {
if tx_base_addr > 255 || rx_base_addr > 255 {
return Err(RadioError::InvalidBaseAddress(tx_base_addr, rx_base_addr));
}
self.write_register(Register::RegFifoTxBaseAddr, 0x00u8).await?;
self.write_register(Register::RegFifoRxBaseAddr, 0x00u8).await
}
// Set parameters associated with power for a send operation.
// p_out desired RF output power (dBm)
// mdltn_params needed for a power vs channel frequency validation
// tx_boosted_if_possible determine if transmit boost is requested
// is_tx_prep indicates which ramp up time to use
async fn set_tx_power_and_ramp_time(
&mut self,
p_out: i32,
_mdltn_params: Option<&ModulationParams>,
is_tx_prep: bool,
) -> Result<(), RadioError> {
debug!("tx power = {}", p_out);
// Configure tx power and boost
C::set_tx_power(self, p_out, self.config.tx_boost).await?;
let ramp_time = match is_tx_prep {
true => RampTime::Ramp40Us, // for instance, prior to TX or CAD
false => RampTime::Ramp250Us, // for instance, on initialization
};
let val = C::ramp_value(ramp_time);
self.write_register(Register::RegPaRamp, val).await
}
async fn set_modulation_params(&mut self, mdltn_params: &ModulationParams) -> Result<(), RadioError> {
let sf_val = spreading_factor_value(mdltn_params.spreading_factor)?;
let bw_val = C::bandwidth_value(mdltn_params.bandwidth)?;
let coding_rate_denominator_val = coding_rate_denominator_value(mdltn_params.coding_rate)?;
debug!(
"sf = {}, bw = {}, cr_denom = {}",
sf_val, bw_val, coding_rate_denominator_val
);
// Configure LoRa optimization (0x31) and detection threshold registers (0x37)
let (opt, thr) = match mdltn_params.spreading_factor {
SpreadingFactor::_6 => (0x05, 0x0c),
_ => (0x03, 0x0a),
};
let reg_val = self.read_register(Register::RegDetectionOptimize).await?;
// Keep reserved bits [6:3] for RegDetectOptimize
let val = (reg_val & 0b0111_1000) | opt;
self.write_register(Register::RegDetectionOptimize, val).await?;
self.write_register(Register::RegDetectionThreshold, thr).await?;
// Spreading Factor, Bandwidth, codingrate, ldro
C::set_modulation_params(self, mdltn_params).await?;
Ok(())
}
async fn set_packet_params(&mut self, pkt_params: &PacketParams) -> Result<(), RadioError> {
self.write_register(
Register::RegPreambleMsb,
((pkt_params.preamble_length >> 8) & 0x00ff) as u8,
)
.await?;
self.write_register(Register::RegPreambleLsb, (pkt_params.preamble_length & 0x00ff) as u8)
.await?;
C::set_packet_params(self, pkt_params).await?;
// IQ inversion:
// RegInvertiq - [0x33]
// [6] - InvertIQRX
// [5:1] - Reserved: 0x13
// [0] - InvertIQTX
// RegInvertiq2 - [0x3b]
// Set to 0x19 when RX, otherwise set 0x1d
let (iq1, iq2) = match pkt_params.iq_inverted {
true => (1 << 6, 0x19),
false => (1 << 0, 0x1d),
};
// Keep reserved value for InvertIq as well
self.write_register(Register::RegInvertiq, (0x13 << 1) | iq1).await?;
self.write_register(Register::RegInvertiq2, iq2).await?;
Ok(())
}
// Calibrate the image rejection based on the given frequency
async fn calibrate_image(&mut self, _frequency_in_hz: u32) -> Result<(), RadioError> {
// An automatic process, but can set bit ImageCalStart in RegImageCal, when the device is in Standby mode.
Ok(())
}
async fn set_channel(&mut self, frequency_in_hz: u32) -> Result<(), RadioError> {
debug!("channel = {}", frequency_in_hz);
let frf = (frequency_in_hz as f64 / FREQUENCY_SYNTHESIZER_STEP) as u32;
self.write_register(Register::RegFrfMsb, ((frf & 0x00FF0000) >> 16) as u8)
.await?;
self.write_register(Register::RegFrfMid, ((frf & 0x0000FF00) >> 8) as u8)
.await?;
self.write_register(Register::RegFrfLsb, (frf & 0x000000FF) as u8).await
}
async fn set_payload(&mut self, payload: &[u8]) -> Result<(), RadioError> {
self.write_register(Register::RegFifoAddrPtr, 0x00u8).await?;
self.write_register(Register::RegPayloadLength, 0x00u8).await?;
self.write_buffer(Register::RegFifo, payload).await?;
self.write_register(Register::RegPayloadLength, payload.len() as u8)
.await
}
async fn do_tx(&mut self) -> Result<(), RadioError> {
self.intf.iv.enable_rf_switch_tx().await?;
self.write_register(Register::RegOpMode, LoRaMode::Tx.value()).await
}
async fn do_rx(&mut self, rx_mode: RxMode) -> Result<(), RadioError> {
let (num_symbols, mode) = match rx_mode {
RxMode::DutyCycle(_) => Err(RadioError::DutyCycleUnsupported),
RxMode::Single(ns) => Ok((ns.max(SX127X_MIN_LORA_SYMB_NUM_TIMEOUT), LoRaMode::RxSingle)),
RxMode::Continuous => Ok((0, LoRaMode::RxContinuous)),
}?;
self.intf.iv.enable_rf_switch_rx().await?;
self.set_lora_symbol_num_timeout(num_symbols).await?;
let lna_gain = if self.config.rx_boost {
LnaGain::G1.boosted_value()
} else {
LnaGain::G1.value()
};
self.write_register(Register::RegLna, lna_gain).await?;
self.write_register(Register::RegFifoAddrPtr, 0x00u8).await?;
self.write_register(Register::RegPayloadLength, 0xffu8).await?;
self.write_register(Register::RegOpMode, mode.value()).await
}
async fn get_rx_payload(
&mut self,
_rx_pkt_params: &PacketParams,
receiving_buffer: &mut [u8],
) -> Result<u8, RadioError> {
let payload_length = self.read_register(Register::RegRxNbBytes).await?;
if (payload_length as usize) > receiving_buffer.len() {
return Err(RadioError::PayloadSizeMismatch(
payload_length as usize,
receiving_buffer.len(),
));
}
let fifo_addr = self.read_register(Register::RegFifoRxCurrentAddr).await?;
self.write_register(Register::RegFifoAddrPtr, fifo_addr).await?;
self.read_buffer(Register::RegFifo, &mut receiving_buffer[0..payload_length as usize])
.await?;
self.write_register(Register::RegFifoAddrPtr, 0x00u8).await?;
Ok(payload_length)
}
async fn get_rx_packet_status(&mut self) -> Result<PacketStatus, RadioError> {
let snr = {
let packet_snr = self.read_register(Register::RegPktSnrValue).await?;
packet_snr as i8 as i16 / 4
};
let rssi = {
let packet_rssi = self.read_register(Register::RegPktRssiValue).await?;
let rssi_offset = C::rssi_offset(self).await?;
if snr >= 0 {
rssi_offset + (packet_rssi as f32 * 16.0 / 15.0) as i16
} else {
rssi_offset + (packet_rssi as i16) + snr
}
};
Ok(PacketStatus { rssi, snr })
}
async fn do_cad(&mut self, _mdltn_params: &ModulationParams) -> Result<(), RadioError> {
self.intf.iv.enable_rf_switch_rx().await?;
let mut lna_gain_final = LnaGain::G1.value();
if self.config.rx_boost {
lna_gain_final = LnaGain::G1.boosted_value();
}
self.write_register(Register::RegLna, lna_gain_final).await?;
self.write_register(Register::RegOpMode, LoRaMode::Cad.value()).await
}
// Set the IRQ mask to disable unwanted interrupts,
// enable interrupts on DIO pins (sx127x has multiple),
// and allow interrupts.
async fn set_irq_params(&mut self, radio_mode: Option<RadioMode>) -> Result<(), RadioError> {
match radio_mode {
Some(RadioMode::Transmit) => {
self.write_register(
Register::RegIrqFlagsMask,
IrqMask::All.value() ^ IrqMask::TxDone.value(),
)
.await?;
let mut dio_mapping_1 = self.read_register(Register::RegDioMapping1).await?;
dio_mapping_1 = (dio_mapping_1 & DioMapping1Dio0::Mask.value()) | DioMapping1Dio0::TxDone.value();
self.write_register(Register::RegDioMapping1, dio_mapping_1).await?;
self.write_register(Register::RegIrqFlags, 0x00u8).await?;
}
Some(RadioMode::Receive(_)) => {
self.write_register(
Register::RegIrqFlagsMask,
IrqMask::All.value()
^ (IrqMask::RxDone.value()
| IrqMask::RxTimeout.value()
| IrqMask::CRCError.value()
| IrqMask::HeaderValid.value()),
)
.await?;
// HeaderValid and CRCError are mutually exclusive when attempting to
// trigger DIO-based interrupt, so our approach is to trigger HeaderValid
// as this is required for preamble detection.
// TODO: RxTimeout should be configured on DIO1
let dio_mapping_1 = self.read_register(Register::RegDioMapping1).await?;
let val = (dio_mapping_1 & DioMapping1Dio0::Mask.value() & DioMapping1Dio3::Mask.value())
| (DioMapping1Dio0::RxDone.value() | DioMapping1Dio3::ValidHeader.value());
self.write_register(Register::RegDioMapping1, val).await?;
self.write_register(Register::RegIrqFlags, 0x00u8).await?;
}
Some(RadioMode::ChannelActivityDetection) => {
self.write_register(
Register::RegIrqFlagsMask,
IrqMask::All.value() ^ (IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value()),
)
.await?;
let mut dio_mapping_1 = self.read_register(Register::RegDioMapping1).await?;
dio_mapping_1 = (dio_mapping_1 & DioMapping1Dio0::Mask.value()) | DioMapping1Dio0::CadDone.value();
self.write_register(Register::RegDioMapping1, dio_mapping_1).await?;
self.write_register(Register::RegIrqFlags, 0x00u8).await?;
}
_ => {
self.write_register(Register::RegIrqFlagsMask, IrqMask::All.value())
.await?;
let mut dio_mapping_1 = self.read_register(Register::RegDioMapping1).await?;
dio_mapping_1 = (dio_mapping_1 & DioMapping1Dio0::Mask.value()) | DioMapping1Dio0::Other.value();
self.write_register(Register::RegDioMapping1, dio_mapping_1).await?;
self.write_register(Register::RegIrqFlags, 0xffu8).await?;
}
}
Ok(())
}
async fn await_irq(&mut self) -> Result<(), RadioError> {
self.intf.iv.await_irq().await
}
/// Process the radio IRQ. Log unexpected interrupts. Packets from other
/// devices can cause unexpected interrupts.
///
/// NB! Do not await this future in a select branch as interrupting it
/// mid-flow could cause radio lock up.
async fn process_irq_event(
&mut self,
radio_mode: RadioMode,
cad_activity_detected: Option<&mut bool>,
clear_interrupts: bool,
) -> Result<Option<IrqState>, RadioError> {
let irq_flags = self.read_register(Register::RegIrqFlags).await?;
if clear_interrupts {
self.write_register(Register::RegIrqFlags, 0xffu8).await?; // clear all interrupts
}
match radio_mode {
RadioMode::Transmit => {
if (irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value() {
debug!("TxDone in radio mode {}", radio_mode);
return Ok(Some(IrqState::Done));
}
}
RadioMode::Receive(RxMode::Continuous) | RadioMode::Receive(RxMode::Single(_)) => {
if (irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value() {
debug!("RxDone in radio mode {}", radio_mode);
return Ok(Some(IrqState::Done));
}
if (irq_flags & IrqMask::RxTimeout.value()) == IrqMask::RxTimeout.value() {
debug!("RxTimeout in radio mode {}", radio_mode);
return Err(RadioError::ReceiveTimeout);
}
if IrqMask::HeaderValid.is_set_in(irq_flags) {
debug!("HeaderValid in radio mode {}", radio_mode);
return Ok(Some(IrqState::PreambleReceived));
}
}
RadioMode::ChannelActivityDetection => {
if (irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value() {
debug!("CADDone in radio mode {}", radio_mode);
// TODO: don't like how we mutate the cad_activity_detected parameter
if cad_activity_detected.is_some() {
// Check if the CAD (Channel Activity Detection) Activity Detected flag is set in irq_flags and then update the reference
*(cad_activity_detected.unwrap()) =
(irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value();
}
return Ok(Some(IrqState::Done));
}
}
RadioMode::Sleep | RadioMode::Standby => {
defmt::warn!("IRQ during sleep/standby?");
}
RadioMode::FrequencySynthesis => todo!(),
RadioMode::Receive(RxMode::DutyCycle(_)) => todo!(),
}
// If no specific IRQ condition is met, return None
Ok(None)
}
/// Set the LoRa chip into the TxContinuousWave mode
async fn set_tx_continuous_wave_mode(&mut self) -> Result<(), RadioError> {
C::set_tx_continuous_wave_mode(self).await
}
}
+341
View File
@@ -0,0 +1,341 @@
use crate::mod_params::*;
use crate::mod_traits::InterfaceVariant;
use crate::sx127x::Sx127x;
use embedded_hal_async::spi::SpiDevice;
pub trait Sx127xVariant {
fn bandwidth_value(bw: Bandwidth) -> Result<u8, RadioError>;
fn reg_txco() -> Register;
async fn set_tx_power<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
p_out: i32,
tx_boost: bool,
) -> Result<(), RadioError>
where
Self: Sized;
fn ramp_value(ramp_time: RampTime) -> u8;
async fn set_modulation_params<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
mdltn_params: &ModulationParams,
) -> Result<(), RadioError>
where
Self: Sized;
async fn set_packet_params<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
pkt_params: &PacketParams,
) -> Result<(), RadioError>
where
Self: Sized;
async fn rssi_offset<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
) -> Result<i16, RadioError>
where
Self: Sized;
async fn set_tx_continuous_wave_mode<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
) -> Result<(), RadioError>
where
Self: Sized;
}
/// Internal sx127x LoRa modes (signified by most significant bit flag)
#[derive(Clone, Copy)]
pub enum LoRaMode {
Sleep = 0x00,
Standby = 0x01,
Tx = 0x03,
RxContinuous = 0x05,
RxSingle = 0x06,
Cad = 0x07,
}
impl LoRaMode {
/// Mode value, including LoRa flag
pub fn value(self) -> u8 {
(self as u8) | 0x80u8
}
}
// IRQ mapping for sx127x chips:
// DIO0 - RxDone, TxDone, CadDone
// DIO1 - RxTimeout, FhssChangeChannel, CadDetected
// DIO2 - 3x FhssChangeChannel
// DIO3 - CadDone, ValidHeader, PayloadCrcError
// DIO4 - CadDetected, *PllLock, *PllLock
// DIO5 - *ModeReady, *ClkOut, *ClkOut
#[allow(dead_code)]
pub enum DioMapping1Dio0 {
RxDone = 0x00,
TxDone = 0x40,
CadDone = 0x80,
Other = 0xc0,
Mask = 0x3f,
}
impl DioMapping1Dio0 {
pub fn value(self) -> u8 {
self as u8
}
}
#[allow(dead_code)]
pub enum DioMapping1Dio1 {
RxTimeOut = 0b00 << 2,
FhssChangeChannel = 0b01 << 2,
CadDetected = 0b10 << 2,
Other = 0b11 << 2,
Mask = 0xf3,
}
#[allow(dead_code)]
impl DioMapping1Dio1 {
pub fn value(self) -> u8 {
self as u8
}
}
#[allow(dead_code)]
pub enum DioMapping1Dio3 {
CadDone = 0,
ValidHeader = 0b01,
PayloadCrcError = 0b10,
Other = 0b11,
Mask = 0xfc,
}
impl DioMapping1Dio3 {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub enum IrqMask {
None = 0x00,
CADActivityDetected = 0x01,
FhssChangedChannel = 0x02,
CADDone = 0x04,
TxDone = 0x08,
HeaderValid = 0x10,
CRCError = 0x20,
RxDone = 0x40,
RxTimeout = 0x80,
All = 0xFF,
}
impl IrqMask {
pub fn value(self) -> u8 {
self as u8
}
pub fn is_set_in(self, mask: u8) -> bool {
self.value() & mask == self.value()
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub enum Register {
RegFifo = 0x00,
RegOpMode = 0x01,
RegFrfMsb = 0x06,
RegFrfMid = 0x07,
RegFrfLsb = 0x08,
RegPaConfig = 0x09,
RegPaRamp = 0x0a,
RegOcp = 0x0b,
RegLna = 0x0c,
RegFifoAddrPtr = 0x0d,
RegFifoTxBaseAddr = 0x0e,
RegFifoRxBaseAddr = 0x0f,
RegFifoRxCurrentAddr = 0x10,
RegIrqFlagsMask = 0x11,
RegIrqFlags = 0x12,
RegRxNbBytes = 0x13,
RegPktSnrValue = 0x19,
RegModemStat = 0x18,
RegPktRssiValue = 0x1a,
RegModemConfig1 = 0x1d,
RegModemConfig2 = 0x1e,
RegSymbTimeoutLsb = 0x1f,
RegPreambleMsb = 0x20,
RegPreambleLsb = 0x21,
RegPayloadLength = 0x22,
RegMaxPayloadLength = 0x23,
RegModemConfig3 = 0x26,
RegFreqErrorMsb = 0x28,
RegFreqErrorMid = 0x29,
RegFreqErrorLsb = 0x2a,
RegRssiWideband = 0x2c,
RegDetectionOptimize = 0x31,
RegInvertiq = 0x33,
RegDetectionThreshold = 0x37,
RegSyncWord = 0x39,
RegInvertiq2 = 0x3b,
RegDioMapping1 = 0x40,
RegVersion = 0x42,
RegPaDacSX1272 = 0x5a,
RegPaDacSX1276 = 0x4d,
RegTcxoSX1276 = 0x4b,
RegTcxoSX1272 = 0x58,
}
impl Register {
pub fn read_addr(self) -> u8 {
(self as u8) & 0x7f
}
pub fn write_addr(self) -> u8 {
(self as u8) | 0x80
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub enum RampTime {
Ramp3_4Ms = 0x00,
Ramp2Ms = 0x01,
Ramp1Ms = 0x02,
Ramp500Us = 0x03,
Ramp250Us = 0x04,
Ramp125Us = 0x05,
Ramp100Us = 0x06,
Ramp62Us = 0x07,
Ramp50Us = 0x08,
Ramp40Us = 0x09,
Ramp31Us = 0x0a,
Ramp25Us = 0x0b,
Ramp20Us = 0x0c,
Ramp15Us = 0x0d,
Ramp12Us = 0x0e,
Ramp10Us = 0x0f,
}
impl RampTime {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub enum LnaGain {
G1 = 0x20, // maximum gain (default)
G2 = 0x40,
G3 = 0x60,
G4 = 0x80,
G5 = 0xa0,
G6 = 0xc0, // minumum gain
}
impl LnaGain {
pub fn value(self) -> u8 {
self as u8
}
pub fn boosted_value(self) -> u8 {
(self as u8) | 0x03u8
}
}
/// PA DAC configuration - sx1276+
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub enum PaDac {
_20DbmOn = 0x87,
_20DbmOff = 0x84,
}
impl PaDac {
pub fn value(self) -> u8 {
self as u8
}
}
/// PA configuration - sx1276+
#[derive(Clone, Copy)]
#[allow(dead_code)]
pub enum PaConfig {
PaBoost = 0x80,
MaxPower7NoPaBoost = 0x70,
}
impl PaConfig {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
#[allow(dead_code)]
#[allow(clippy::enum_variant_names)]
pub enum OcpTrim {
_45Ma = 0x00,
_50Ma = 0x01,
_55Ma = 0x02,
_60Ma = 0x03,
_65Ma = 0x04,
_70Ma = 0x05,
_75Ma = 0x06,
_80Ma = 0x07,
_85Ma = 0x08,
_90Ma = 0x09,
_95Ma = 0x0a,
_100Ma = 0x0b,
_105Ma = 0x0c,
_110Ma = 0x0d,
_115Ma = 0x0e,
_120Ma = 0x0f,
_130Ma = 0x10,
_140Ma = 0x11,
_150Ma = 0x12,
_160Ma = 0x13,
_170Ma = 0x14,
_180Ma = 0x15,
_190Ma = 0x16,
_200Ma = 0x17,
_210Ma = 0x18,
_220Ma = 0x19,
_230Ma = 0x1a,
_240Ma = 0x1b,
}
impl OcpTrim {
pub fn value(self) -> u8 {
(self as u8) | 0x20u8 // value plus OCP on flag
}
}
pub fn spreading_factor_value(spreading_factor: SpreadingFactor) -> Result<u8, RadioError> {
match spreading_factor {
SpreadingFactor::_5 => Err(RadioError::UnavailableSpreadingFactor),
SpreadingFactor::_6 => Ok(0x06),
SpreadingFactor::_7 => Ok(0x07),
SpreadingFactor::_8 => Ok(0x08),
SpreadingFactor::_9 => Ok(0x09),
SpreadingFactor::_10 => Ok(0x0A),
SpreadingFactor::_11 => Ok(0x0B),
SpreadingFactor::_12 => Ok(0x0C),
}
}
#[allow(dead_code)]
pub fn coding_rate_value(coding_rate: CodingRate) -> Result<u8, RadioError> {
match coding_rate {
CodingRate::_4_5 => Ok(0x01),
CodingRate::_4_6 => Ok(0x02),
CodingRate::_4_7 => Ok(0x03),
CodingRate::_4_8 => Ok(0x04),
}
}
pub fn coding_rate_denominator_value(coding_rate: CodingRate) -> Result<u8, RadioError> {
match coding_rate {
CodingRate::_4_5 => Ok(0x05),
CodingRate::_4_6 => Ok(0x06),
CodingRate::_4_7 => Ok(0x07),
CodingRate::_4_8 => Ok(0x08),
}
}
+122
View File
@@ -0,0 +1,122 @@
use crate::mod_params::{ModulationParams, PacketParams, RadioError};
use crate::mod_traits::InterfaceVariant;
use crate::sx127x::radio_kind_params::{coding_rate_value, spreading_factor_value, RampTime, Register, Sx127xVariant};
use crate::sx127x::{Sx127x, SX1272_RSSI_OFFSET};
use embedded_hal_async::spi::SpiDevice;
use lora_modulation::Bandwidth;
/// Sx1272 implements the Sx127xVariant trait
pub struct Sx1272;
impl Sx127xVariant for Sx1272 {
fn bandwidth_value(bw: Bandwidth) -> Result<u8, RadioError> {
match bw {
Bandwidth::_125KHz => Ok(0x00),
Bandwidth::_250KHz => Ok(0x01),
Bandwidth::_500KHz => Ok(0x02),
_ => Err(RadioError::UnavailableBandwidth),
}
}
fn reg_txco() -> Register {
Register::RegTcxoSX1272
}
async fn set_tx_power<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
p_out: i32,
tx_boost: bool,
) -> Result<(), RadioError> {
// SX1272 has two output pins:
// 1) RFO: (-1 to +14 dBm)
// 2) PA_BOOST: (+2 to +17 dBm and +5 to 20 +dBm)
// RegPaConfig - 0x32
// [7] - PaSelect (0: RFO, 1: PA_BOOST)
// [6:4] - Unused: 0
// [3:0] - Output power in dB steps
// RegPaDac - 0x5a (SX1272)
// [7:3] - Reserved (0x10 as default)
// [2:0] - PaDac: 0x04 default, 0x07 - enable +20 dBm on PA_BOOST
// TODO: Shall we also touch OCP settings?
if tx_boost {
// Deal with two ranges, +17dBm enables extra boost
if p_out > 17 {
// PA_BOOST out: +5 .. +20 dBm
let val = (p_out.clamp(5, 20) - 5) as u8 & 0x0f;
radio.write_register(Register::RegPaConfig, (1 << 7) | val).await?;
radio.write_register(Register::RegPaDacSX1272, 0x87).await?;
} else {
// PA_BOOST out: +2 .. +17 dBm
let val = (p_out.clamp(2, 17) - 2) as u8 & 0x0f;
radio.write_register(Register::RegPaConfig, (1 << 7) | val).await?;
radio.write_register(Register::RegPaDacSX1272, 0x84).await?;
}
} else {
// RFO out: -1 to +14 dBm
let val = (p_out.clamp(-1, 14) + 1) as u8 & 0x0f;
radio.write_register(Register::RegPaConfig, val).await?;
radio.write_register(Register::RegPaDacSX1272, 0x84).await?;
}
Ok(())
}
fn ramp_value(ramp_time: RampTime) -> u8 {
// Sx1272 - default: 0x19
// [4]: LowPnTxPllOff - use higher power, lower phase noise PLL
// only when the transmitter is used (default: 1)
// 0 - Standard PLL used in Rx mode, Lower PN PLL in Tx
// 1 - Standard PLL used in both Tx and Rx modes
ramp_time.value() | (1 << 4)
}
async fn set_modulation_params<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
mdltn_params: &ModulationParams,
) -> Result<(), RadioError> {
let bw_val = Self::bandwidth_value(mdltn_params.bandwidth)?;
let sf_val = spreading_factor_value(mdltn_params.spreading_factor)?;
let cfg1 = radio.read_register(Register::RegModemConfig1).await?;
let ldro = mdltn_params.low_data_rate_optimize;
let cr_val = coding_rate_value(mdltn_params.coding_rate)?;
let val = (cfg1 & 0b110) | (bw_val << 6) | (cr_val << 3) | ldro;
radio.write_register(Register::RegModemConfig1, val).await?;
let cfg2 = radio.read_register(Register::RegModemConfig2).await?;
let val = (cfg2 & 0b1111) | (sf_val << 4);
radio.write_register(Register::RegModemConfig2, val).await?;
Ok(())
}
async fn set_packet_params<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
pkt_params: &PacketParams,
) -> Result<(), RadioError>
where
Self: Sized,
{
let modemcfg1 = radio.read_register(Register::RegModemConfig1).await?;
let hdr = pkt_params.implicit_header as u8;
let crc = pkt_params.crc_on as u8;
let cfg1 = (modemcfg1 & 0b1111_1001) | (hdr << 2) | (crc << 1);
radio.write_register(Register::RegModemConfig1, cfg1).await?;
Ok(())
}
async fn rssi_offset<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
_: &mut Sx127x<SPI, IV, Self>,
) -> Result<i16, RadioError> {
Ok(SX1272_RSSI_OFFSET)
}
async fn set_tx_continuous_wave_mode<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
_: &mut Sx127x<SPI, IV, Self>,
) -> Result<(), RadioError> {
todo!()
}
}
+170
View File
@@ -0,0 +1,170 @@
use crate::mod_params::{ModulationParams, PacketParams, RadioError};
use crate::mod_traits::InterfaceVariant;
use crate::sx127x::radio_kind_params::{
coding_rate_denominator_value, spreading_factor_value, OcpTrim, PaConfig, PaDac, RampTime, Register, Sx127xVariant,
};
use crate::sx127x::{
Sx127x, FREQUENCY_SYNTHESIZER_STEP, SX1276_RF_MID_BAND_THRESH, SX1276_RSSI_OFFSET_HF, SX1276_RSSI_OFFSET_LF,
};
use embedded_hal_async::spi::SpiDevice;
use lora_modulation::Bandwidth;
/// Sx1276 implements the Sx127xVariant trait
pub struct Sx1276;
impl Sx127xVariant for Sx1276 {
fn bandwidth_value(bw: Bandwidth) -> Result<u8, RadioError> {
match bw {
Bandwidth::_7KHz => Ok(0x00),
Bandwidth::_10KHz => Ok(0x01),
Bandwidth::_15KHz => Ok(0x02),
Bandwidth::_20KHz => Ok(0x03),
Bandwidth::_31KHz => Ok(0x04),
Bandwidth::_41KHz => Ok(0x05),
Bandwidth::_62KHz => Ok(0x06),
Bandwidth::_125KHz => Ok(0x07),
Bandwidth::_250KHz => Ok(0x08),
Bandwidth::_500KHz => Ok(0x09),
}
}
fn reg_txco() -> Register {
Register::RegTcxoSX1276
}
async fn set_tx_power<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
p_out: i32,
tx_boost: bool,
) -> Result<(), RadioError> {
let pa_reg = Register::RegPaDacSX1276;
if tx_boost {
// Output via PA_BOOST: [2, 20] dBm
let txp = p_out.clamp(2, 20);
// Pout=17-(15-OutputPower)
let output_power: i32 = txp - 2;
if txp > 17 {
radio.write_register(pa_reg, PaDac::_20DbmOn.value()).await?;
radio.set_ocp(OcpTrim::_240Ma).await?;
} else {
radio.write_register(pa_reg, PaDac::_20DbmOff.value()).await?;
radio.set_ocp(OcpTrim::_100Ma).await?;
}
radio
.write_register(Register::RegPaConfig, PaConfig::PaBoost.value() | (output_power as u8))
.await?;
} else {
// Clamp output: [-4, 14] dBm
let txp = p_out.clamp(-4, 14);
// Pmax=10.8+0.6*MaxPower, where MaxPower is set below as 7 and therefore Pmax is 15
// Pout=Pmax-(15-OutputPower)
let output_power: i32 = txp;
radio.write_register(pa_reg, PaDac::_20DbmOff.value()).await?;
radio.set_ocp(OcpTrim::_100Ma).await?;
radio
.write_register(
Register::RegPaConfig,
PaConfig::MaxPower7NoPaBoost.value() | (output_power as u8),
)
.await?;
}
Ok(())
}
fn ramp_value(ramp_time: RampTime) -> u8 {
// Sx1276 - default: 0x09
// [4]: reserved (0x00)
ramp_time as u8
}
async fn set_modulation_params<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
mdltn_params: &ModulationParams,
) -> Result<(), RadioError> {
let bw_val = Self::bandwidth_value(mdltn_params.bandwidth)?;
let sf_val = spreading_factor_value(mdltn_params.spreading_factor)?;
let coding_rate_denominator_val = coding_rate_denominator_value(mdltn_params.coding_rate)?;
let mut config_2 = radio.read_register(Register::RegModemConfig2).await?;
config_2 = (config_2 & 0x0fu8) | ((sf_val << 4) & 0xf0u8);
radio.write_register(Register::RegModemConfig2, config_2).await?;
let mut config_1 = radio.read_register(Register::RegModemConfig1).await?;
config_1 = (config_1 & 0x0fu8) | (bw_val << 4);
radio.write_register(Register::RegModemConfig1, config_1).await?;
let cr = coding_rate_denominator_val - 4;
config_1 = radio.read_register(Register::RegModemConfig1).await?;
config_1 = (config_1 & 0xf1u8) | (cr << 1);
radio.write_register(Register::RegModemConfig1, config_1).await?;
let mut ldro_agc_auto_flags = 0x00u8; // LDRO and AGC Auto both off
if mdltn_params.low_data_rate_optimize != 0 {
ldro_agc_auto_flags = 0x08u8; // LDRO on and AGC Auto off
}
let mut config_3 = radio.read_register(Register::RegModemConfig3).await?;
config_3 = (config_3 & 0xf3u8) | ldro_agc_auto_flags;
radio.write_register(Register::RegModemConfig3, config_3).await
}
async fn set_packet_params<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
pkt_params: &PacketParams,
) -> Result<(), RadioError> {
let mut config_1 = radio.read_register(Register::RegModemConfig1).await?;
if pkt_params.implicit_header {
config_1 |= 0x01u8;
} else {
config_1 &= 0xfeu8;
}
radio.write_register(Register::RegModemConfig1, config_1).await?;
let mut config_2 = radio.read_register(Register::RegModemConfig2).await?;
if pkt_params.crc_on {
config_2 |= 0x04u8;
} else {
config_2 &= 0xfbu8;
}
radio.write_register(Register::RegModemConfig2, config_2).await?;
Ok(())
}
async fn rssi_offset<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
) -> Result<i16, RadioError> {
let frequency_in_hz = {
let msb = radio.read_register(Register::RegFrfMsb).await? as u32;
let mid = radio.read_register(Register::RegFrfMid).await? as u32;
let lsb = radio.read_register(Register::RegFrfLsb).await? as u32;
let frf = (msb << 16) + (mid << 8) + lsb;
(frf as f64 * FREQUENCY_SYNTHESIZER_STEP) as u32
};
if frequency_in_hz > SX1276_RF_MID_BAND_THRESH {
Ok(SX1276_RSSI_OFFSET_HF)
} else {
Ok(SX1276_RSSI_OFFSET_LF)
}
}
async fn set_tx_continuous_wave_mode<SPI: SpiDevice<u8>, IV: InterfaceVariant>(
radio: &mut Sx127x<SPI, IV, Self>,
) -> Result<(), RadioError> {
radio.intf.iv.enable_rf_switch_rx().await?;
let pa_config = radio.read_register(Register::RegPaConfig).await?;
let new_pa_config = pa_config | 0b1000_0000;
radio.write_register(Register::RegPaConfig, new_pa_config).await?;
radio.write_register(Register::RegOpMode, 0b1100_0011).await?;
let modem_config = radio.read_register(Register::RegModemConfig2).await?;
let new_modem_config = modem_config | 0b0000_1000;
radio
.write_register(Register::RegModemConfig2, new_modem_config)
.await?;
Ok(())
}
}