1407: Remove legacy LoRa drivers r=Dirbaio a=ceekdee

Remove legacy LoRa drivers and associated configuration.

Co-authored-by: ceekdee <taigatensor@gmail.com>
Co-authored-by: Chuck Davis <taigatensor@gmail.com>
This commit is contained in:
bors[bot] 2023-04-30 19:36:36 +00:00 committed by GitHub
commit ce04b732d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 12 additions and 9712 deletions

View file

@ -35,7 +35,7 @@ The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stac
The <a href="https://github.com/embassy-rs/nrf-softdevice">nrf-softdevice</a> crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers.
- **LoRa** -
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX126x and SX127x transceivers.
<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking.
- **USB** -
<a href="https://docs.embassy.dev/embassy-usb/">embassy-usb</a> implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own.

View file

@ -7,22 +7,13 @@ license = "MIT OR Apache-2.0"
[package.metadata.embassy_docs]
src_base = "https://github.com/embassy-rs/embassy/blob/embassy-lora-v$VERSION/embassy-lora/src/"
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/"
features = ["time", "defmt"]
flavors = [
{ name = "sx126x", target = "thumbv7em-none-eabihf", features = ["sx126x"] },
{ name = "sx127x", target = "thumbv7em-none-eabihf", features = ["sx127x"] },
{ name = "stm32wl", target = "thumbv7em-none-eabihf", features = ["stm32wl", "embassy-stm32?/stm32wl55jc-cm4", "embassy-stm32?/time-driver-any"] },
]
[lib]
features = ["stm32wl", "time", "defmt"]
target = "thumbv7em-none-eabi"
[features]
sx126x = []
sx127x = []
stm32wl = ["dep:embassy-stm32"]
time = []
defmt = ["dep:defmt", "lorawan/defmt", "lorawan-device/defmt"]
external-lora-phy = ["dep:lora-phy"]
defmt = ["dep:defmt", "lorawan-device/defmt"]
[dependencies]
@ -39,6 +30,5 @@ futures = { version = "0.3.17", default-features = false, features = [ "async-aw
embedded-hal = { version = "0.2", features = ["unproven"] }
bit_field = { version = "0.10" }
lora-phy = { version = "1", optional = true }
lora-phy = { version = "1" }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] }
lorawan = { version = "0.7.3", default-features = false }

View file

@ -1,23 +1,12 @@
#![no_std]
#![feature(async_fn_in_trait, impl_trait_projections)]
#![allow(incomplete_features)]
//! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device
//! crate's async LoRaWAN MAC implementation.
//! embassy-lora holds LoRa-specific functionality.
pub(crate) mod fmt;
#[cfg(feature = "external-lora-phy")]
/// interface variants required by the external lora crate
pub mod iv;
#[cfg(feature = "stm32wl")]
#[deprecated(note = "use the external LoRa physical layer crate - https://crates.io/crates/lora-phy")]
pub mod stm32wl;
#[cfg(feature = "sx126x")]
#[deprecated(note = "use the external LoRa physical layer crate - https://crates.io/crates/lora-phy")]
pub mod sx126x;
#[cfg(feature = "sx127x")]
#[deprecated(note = "use the external LoRa physical layer crate - https://crates.io/crates/lora-phy")]
pub mod sx127x;
/// interface variants required by the external lora physical layer crate (lora-phy)
pub mod iv;
#[cfg(feature = "time")]
use embassy_time::{Duration, Instant, Timer};

View file

@ -1,291 +0,0 @@
//! A radio driver integration for the radio found on STM32WL family devices.
#![allow(deprecated)]
use core::future::poll_fn;
use core::task::Poll;
use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
use embassy_stm32::dma::NoDma;
use embassy_stm32::interrupt::{Interrupt, InterruptExt, SUBGHZ_RADIO};
use embassy_stm32::subghz::{
CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, HseTrim, Irq, LoRaBandwidth, LoRaModParams,
LoRaPacketParams, LoRaSyncWord, Ocp, PaConfig, PacketType, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk,
Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams,
};
use embassy_sync::waitqueue::AtomicWaker;
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
use lorawan_device::async_device::Timings;
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
Idle,
Txing,
Rxing,
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RadioError;
static IRQ_WAKER: AtomicWaker = AtomicWaker::new();
/// The radio peripheral keeping the radio state and owning the radio IRQ.
pub struct SubGhzRadio<'d, RS> {
radio: SubGhz<'d, NoDma, NoDma>,
switch: RS,
irq: PeripheralRef<'d, SUBGHZ_RADIO>,
}
#[derive(Default)]
#[non_exhaustive]
pub struct SubGhzRadioConfig {
pub reg_mode: RegMode,
pub calibrate_image: CalibrateImage,
pub pa_config: PaConfig,
pub tx_params: TxParams,
}
impl<'d, RS: RadioSwitch> SubGhzRadio<'d, RS> {
/// Create a new instance of a SubGhz radio for LoRaWAN.
pub fn new(
mut radio: SubGhz<'d, NoDma, NoDma>,
switch: RS,
irq: impl Peripheral<P = SUBGHZ_RADIO> + 'd,
config: SubGhzRadioConfig,
) -> Result<Self, RadioError> {
into_ref!(irq);
radio.reset();
irq.disable();
irq.set_handler(|_| {
IRQ_WAKER.wake();
unsafe { SUBGHZ_RADIO::steal().disable() };
});
configure_radio(&mut radio, config)?;
Ok(Self { radio, switch, irq })
}
/// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form
/// the upcoming RX window start.
async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> {
trace!("TX request: {:?}", config);
self.switch.set_tx();
self.radio
.set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?;
self.set_lora_mod_params(config.rf)?;
let packet_params = LoRaPacketParams::new()
.set_preamble_len(8)
.set_header_type(HeaderType::Variable)
.set_payload_len(buf.len() as u8)
.set_crc_en(true)
.set_invert_iq(false);
self.radio.set_lora_packet_params(&packet_params)?;
let irq_cfg = CfgIrq::new().irq_enable_all(Irq::TxDone).irq_enable_all(Irq::Timeout);
self.radio.set_irq_cfg(&irq_cfg)?;
self.radio.set_buffer_base_address(0, 0)?;
self.radio.write_buffer(0, buf)?;
// The maximum airtime for any LoRaWAN package is 2793.5ms.
// The value of 4000ms is copied from C driver and gives us a good safety margin.
self.radio.set_tx(Timeout::from_millis_sat(4000))?;
trace!("TX started");
loop {
let (_status, irq_status) = self.irq_wait().await;
if irq_status & Irq::TxDone.mask() != 0 {
trace!("TX done");
return Ok(0);
}
if irq_status & Irq::Timeout.mask() != 0 {
return Err(RadioError);
}
}
}
fn set_lora_mod_params(&mut self, config: RfConfig) -> Result<(), Error> {
let mod_params = LoRaModParams::new()
.set_sf(convert_spreading_factor(&config.spreading_factor))
.set_bw(convert_bandwidth(&config.bandwidth))
.set_cr(CodingRate::Cr45)
.set_ldro_en(matches!(
(config.spreading_factor, config.bandwidth),
(SpreadingFactor::_12, Bandwidth::_125KHz)
| (SpreadingFactor::_12, Bandwidth::_250KHz)
| (SpreadingFactor::_11, Bandwidth::_125KHz)
));
self.radio.set_lora_mod_params(&mod_params)
}
/// Perform a radio receive operation with the radio config and receive buffer. The receive buffer must
/// be able to hold a single LoRaWAN packet.
async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> {
assert!(buf.len() >= 255);
trace!("RX request: {:?}", config);
self.switch.set_rx();
self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
self.set_lora_mod_params(config)?;
let packet_params = LoRaPacketParams::new()
.set_preamble_len(8)
.set_header_type(HeaderType::Variable)
.set_payload_len(0xFF)
.set_crc_en(false)
.set_invert_iq(true);
self.radio.set_lora_packet_params(&packet_params)?;
let irq_cfg = CfgIrq::new()
.irq_enable_all(Irq::RxDone)
.irq_enable_all(Irq::PreambleDetected)
.irq_enable_all(Irq::HeaderValid)
.irq_enable_all(Irq::HeaderErr)
.irq_enable_all(Irq::Err)
.irq_enable_all(Irq::Timeout);
self.radio.set_irq_cfg(&irq_cfg)?;
self.radio.set_buffer_base_address(0, 0)?;
// NOTE: Upper layer handles timeout by cancelling the future
self.radio.set_rx(Timeout::DISABLED)?;
trace!("RX started");
loop {
let (_status, irq_status) = self.irq_wait().await;
if irq_status & Irq::RxDone.mask() != 0 {
let (_status, len, ptr) = self.radio.rx_buffer_status()?;
let packet_status = self.radio.lora_packet_status()?;
let rssi = packet_status.rssi_pkt().to_integer();
let snr = packet_status.snr_pkt().to_integer();
self.radio.read_buffer(ptr, &mut buf[..len as usize])?;
self.radio.set_standby(StandbyClk::Rc)?;
#[cfg(feature = "defmt")]
trace!("RX done: {=[u8]:#02X}", &mut buf[..len as usize]);
#[cfg(feature = "log")]
trace!("RX done: {:02x?}", &mut buf[..len as usize]);
return Ok((len as usize, RxQuality::new(rssi, snr as i8)));
}
if irq_status & Irq::Timeout.mask() != 0 {
return Err(RadioError);
}
}
}
async fn irq_wait(&mut self) -> (Status, u16) {
poll_fn(|cx| {
self.irq.unpend();
self.irq.enable();
IRQ_WAKER.register(cx.waker());
let (status, irq_status) = self.radio.irq_status().expect("error getting irq status");
self.radio
.clear_irq_status(irq_status)
.expect("error clearing irq status");
trace!("SUGHZ IRQ 0b{:016b}, {:?}", irq_status, status);
if irq_status == 0 {
Poll::Pending
} else {
Poll::Ready((status, irq_status))
}
})
.await
}
}
fn configure_radio(radio: &mut SubGhz<'_, NoDma, NoDma>, config: SubGhzRadioConfig) -> Result<(), RadioError> {
trace!("Configuring STM32WL SUBGHZ radio");
radio.set_regulator_mode(config.reg_mode)?;
radio.set_standby(StandbyClk::Rc)?;
let tcxo_mode = TcxoMode::new()
.set_txco_trim(TcxoTrim::Volts1pt7)
.set_timeout(Timeout::from_duration_sat(core::time::Duration::from_millis(100)));
radio.set_tcxo_mode(&tcxo_mode)?;
// Reduce input capacitance as shown in Reference Manual "Figure 23. HSE32 TCXO control".
// The STM32CUBE C driver also does this.
radio.set_hse_in_trim(HseTrim::MIN)?;
// Re-calibrate everything after setting the TXCO config.
radio.calibrate(0x7F)?;
radio.calibrate_image(config.calibrate_image)?;
radio.set_pa_config(&config.pa_config)?;
radio.set_tx_params(&config.tx_params)?;
radio.set_pa_ocp(Ocp::Max140m)?;
radio.set_packet_type(PacketType::LoRa)?;
radio.set_lora_sync_word(LoRaSyncWord::Public)?;
trace!("Done initializing STM32WL SUBGHZ radio");
Ok(())
}
impl<'d, RS: RadioSwitch> PhyRxTx for SubGhzRadio<'d, RS> {
type PhyError = RadioError;
async fn tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, Self::PhyError> {
self.do_tx(config, buf).await
}
async fn rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), Self::PhyError> {
self.do_rx(config, buf).await
}
}
impl From<embassy_stm32::spi::Error> for RadioError {
fn from(_: embassy_stm32::spi::Error) -> Self {
RadioError
}
}
impl<'d, RS> Timings for SubGhzRadio<'d, RS> {
fn get_rx_window_offset_ms(&self) -> i32 {
-3
}
fn get_rx_window_duration_ms(&self) -> u32 {
1003
}
}
pub trait RadioSwitch {
fn set_rx(&mut self);
fn set_tx(&mut self);
}
fn convert_spreading_factor(sf: &SpreadingFactor) -> SF {
match sf {
SpreadingFactor::_7 => SF::Sf7,
SpreadingFactor::_8 => SF::Sf8,
SpreadingFactor::_9 => SF::Sf9,
SpreadingFactor::_10 => SF::Sf10,
SpreadingFactor::_11 => SF::Sf11,
SpreadingFactor::_12 => SF::Sf12,
}
}
fn convert_bandwidth(bw: &Bandwidth) -> LoRaBandwidth {
match bw {
Bandwidth::_125KHz => LoRaBandwidth::Bw125,
Bandwidth::_250KHz => LoRaBandwidth::Bw250,
Bandwidth::_500KHz => LoRaBandwidth::Bw500,
}
}

View file

@ -1,137 +0,0 @@
use defmt::Format;
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::*;
use lorawan_device::async_device::radio::{PhyRxTx, RfConfig, RxQuality, TxConfig};
use lorawan_device::async_device::Timings;
mod sx126x_lora;
use sx126x_lora::LoRa;
use self::sx126x_lora::mod_params::RadioError;
/// Semtech Sx126x LoRa peripheral
pub struct Sx126xRadio<SPI, CTRL, WAIT, BUS>
where
SPI: SpiBus<u8, Error = BUS> + 'static,
CTRL: OutputPin + 'static,
WAIT: Wait + 'static,
BUS: Error + Format + 'static,
{
pub lora: LoRa<SPI, CTRL, WAIT>,
}
impl<SPI, CTRL, WAIT, BUS> Sx126xRadio<SPI, CTRL, WAIT, BUS>
where
SPI: SpiBus<u8, Error = BUS> + 'static,
CTRL: OutputPin + 'static,
WAIT: Wait + 'static,
BUS: Error + Format + 'static,
{
pub async fn new(
spi: SPI,
cs: CTRL,
reset: CTRL,
antenna_rx: CTRL,
antenna_tx: CTRL,
dio1: WAIT,
busy: WAIT,
enable_public_network: bool,
) -> Result<Self, RadioError<BUS>> {
let mut lora = LoRa::new(spi, cs, reset, antenna_rx, antenna_tx, dio1, busy);
lora.init().await?;
lora.set_lora_modem(enable_public_network).await?;
Ok(Self { lora })
}
}
impl<SPI, CTRL, WAIT, BUS> Timings for Sx126xRadio<SPI, CTRL, WAIT, BUS>
where
SPI: SpiBus<u8, Error = BUS> + 'static,
CTRL: OutputPin + 'static,
WAIT: Wait + 'static,
BUS: Error + Format + 'static,
{
fn get_rx_window_offset_ms(&self) -> i32 {
-50
}
fn get_rx_window_duration_ms(&self) -> u32 {
1050
}
}
impl<SPI, CTRL, WAIT, BUS> PhyRxTx for Sx126xRadio<SPI, CTRL, WAIT, BUS>
where
SPI: SpiBus<u8, Error = BUS> + 'static,
CTRL: OutputPin + 'static,
WAIT: Wait + 'static,
BUS: Error + Format + 'static,
{
type PhyError = RadioError<BUS>;
async fn tx(&mut self, config: TxConfig, buffer: &[u8]) -> Result<u32, Self::PhyError> {
trace!("TX START");
self.lora
.set_tx_config(
config.pw,
config.rf.spreading_factor.into(),
config.rf.bandwidth.into(),
config.rf.coding_rate.into(),
8,
false,
true,
false,
0,
false,
)
.await?;
self.lora.set_max_payload_length(buffer.len() as u8).await?;
self.lora.set_channel(config.rf.frequency).await?;
self.lora.send(buffer, 0xffffff).await?;
self.lora.process_irq(None, None, None).await?;
trace!("TX DONE");
return Ok(0);
}
async fn rx(
&mut self,
config: RfConfig,
receiving_buffer: &mut [u8],
) -> Result<(usize, RxQuality), Self::PhyError> {
trace!("RX START");
self.lora
.set_rx_config(
config.spreading_factor.into(),
config.bandwidth.into(),
config.coding_rate.into(),
8,
4,
false,
0u8,
true,
false,
0,
true,
true,
)
.await?;
self.lora.set_max_payload_length(receiving_buffer.len() as u8).await?;
self.lora.set_channel(config.frequency).await?;
self.lora.rx(90 * 1000).await?;
let mut received_len = 0u8;
self.lora
.process_irq(Some(receiving_buffer), Some(&mut received_len), None)
.await?;
trace!("RX DONE");
let packet_status = self.lora.get_latest_packet_status();
let mut rssi = 0i16;
let mut snr = 0i8;
if packet_status.is_some() {
rssi = packet_status.unwrap().rssi as i16;
snr = packet_status.unwrap().snr;
}
Ok((received_len as usize, RxQuality::new(rssi, snr)))
}
}

View file

@ -1,256 +0,0 @@
use embassy_time::{Duration, Timer};
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::SpiBus;
use super::mod_params::RadioError::*;
use super::mod_params::*;
use super::LoRa;
// Defines the time required for the TCXO to wakeup [ms].
const BRD_TCXO_WAKEUP_TIME: u32 = 10;
// Provides board-specific functionality for Semtech SX126x-based boards.
impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
where
SPI: SpiBus<u8, Error = BUS>,
CTRL: OutputPin,
WAIT: Wait,
{
// De-initialize the radio I/Os pins interface. Useful when going into MCU low power modes.
pub(super) async fn brd_io_deinit(&mut self) -> Result<(), RadioError<BUS>> {
Ok(()) // no operation currently
}
// Initialize the TCXO power pin
pub(super) async fn brd_io_tcxo_init(&mut self) -> Result<(), RadioError<BUS>> {
let timeout = self.brd_get_board_tcxo_wakeup_time() << 6;
self.sub_set_dio3_as_tcxo_ctrl(TcxoCtrlVoltage::Ctrl1V7, timeout)
.await?;
Ok(())
}
// Initialize RF switch control pins
pub(super) async fn brd_io_rf_switch_init(&mut self) -> Result<(), RadioError<BUS>> {
self.sub_set_dio2_as_rf_switch_ctrl(true).await?;
Ok(())
}
// Initialize the radio debug pins
pub(super) async fn brd_io_dbg_init(&mut self) -> Result<(), RadioError<BUS>> {
Ok(()) // no operation currently
}
// Hardware reset of the radio
pub(super) async fn brd_reset(&mut self) -> Result<(), RadioError<BUS>> {
Timer::after(Duration::from_millis(10)).await;
self.reset.set_low().map_err(|_| Reset)?;
Timer::after(Duration::from_millis(20)).await;
self.reset.set_high().map_err(|_| Reset)?;
Timer::after(Duration::from_millis(10)).await;
Ok(())
}
// Wait while the busy pin is high
pub(super) async fn brd_wait_on_busy(&mut self) -> Result<(), RadioError<BUS>> {
self.busy.wait_for_low().await.map_err(|_| Busy)?;
Ok(())
}
// Wake up the radio
pub(super) async fn brd_wakeup(&mut self) -> Result<(), RadioError<BUS>> {
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[OpCode::GetStatus.value()]).await.map_err(SPI)?;
self.spi.write(&[0x00]).await.map_err(SPI)?;
self.cs.set_high().map_err(|_| CS)?;
self.brd_wait_on_busy().await?;
self.brd_set_operating_mode(RadioMode::StandbyRC);
Ok(())
}
// Send a command that writes data to the radio
pub(super) async fn brd_write_command(&mut self, op_code: OpCode, buffer: &[u8]) -> Result<(), RadioError<BUS>> {
self.sub_check_device_ready().await?;
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[op_code.value()]).await.map_err(SPI)?;
self.spi.write(buffer).await.map_err(SPI)?;
self.cs.set_high().map_err(|_| CS)?;
if op_code != OpCode::SetSleep {
self.brd_wait_on_busy().await?;
}
Ok(())
}
// Send a command that reads data from the radio, filling the provided buffer and returning a status
pub(super) async fn brd_read_command(&mut self, op_code: OpCode, buffer: &mut [u8]) -> Result<u8, RadioError<BUS>> {
let mut status = [0u8];
let mut input = [0u8];
self.sub_check_device_ready().await?;
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[op_code.value()]).await.map_err(SPI)?;
self.spi.transfer(&mut status, &[0x00]).await.map_err(SPI)?;
for i in 0..buffer.len() {
self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?;
buffer[i] = input[0];
}
self.cs.set_high().map_err(|_| CS)?;
self.brd_wait_on_busy().await?;
Ok(status[0])
}
// Write one or more bytes of data to the radio memory
pub(super) async fn brd_write_registers(
&mut self,
start_register: Register,
buffer: &[u8],
) -> Result<(), RadioError<BUS>> {
self.sub_check_device_ready().await?;
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[OpCode::WriteRegister.value()]).await.map_err(SPI)?;
self.spi
.write(&[
((start_register.addr() & 0xFF00) >> 8) as u8,
(start_register.addr() & 0x00FF) as u8,
])
.await
.map_err(SPI)?;
self.spi.write(buffer).await.map_err(SPI)?;
self.cs.set_high().map_err(|_| CS)?;
self.brd_wait_on_busy().await?;
Ok(())
}
// Read one or more bytes of data from the radio memory
pub(super) async fn brd_read_registers(
&mut self,
start_register: Register,
buffer: &mut [u8],
) -> Result<(), RadioError<BUS>> {
let mut input = [0u8];
self.sub_check_device_ready().await?;
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[OpCode::ReadRegister.value()]).await.map_err(SPI)?;
self.spi
.write(&[
((start_register.addr() & 0xFF00) >> 8) as u8,
(start_register.addr() & 0x00FF) as u8,
0x00u8,
])
.await
.map_err(SPI)?;
for i in 0..buffer.len() {
self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?;
buffer[i] = input[0];
}
self.cs.set_high().map_err(|_| CS)?;
self.brd_wait_on_busy().await?;
Ok(())
}
// Write data to the buffer holding the payload in the radio
pub(super) async fn brd_write_buffer(&mut self, offset: u8, buffer: &[u8]) -> Result<(), RadioError<BUS>> {
self.sub_check_device_ready().await?;
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[OpCode::WriteBuffer.value()]).await.map_err(SPI)?;
self.spi.write(&[offset]).await.map_err(SPI)?;
self.spi.write(buffer).await.map_err(SPI)?;
self.cs.set_high().map_err(|_| CS)?;
self.brd_wait_on_busy().await?;
Ok(())
}
// Read data from the buffer holding the payload in the radio
pub(super) async fn brd_read_buffer(&mut self, offset: u8, buffer: &mut [u8]) -> Result<(), RadioError<BUS>> {
let mut input = [0u8];
self.sub_check_device_ready().await?;
self.cs.set_low().map_err(|_| CS)?;
self.spi.write(&[OpCode::ReadBuffer.value()]).await.map_err(SPI)?;
self.spi.write(&[offset]).await.map_err(SPI)?;
self.spi.write(&[0x00]).await.map_err(SPI)?;
for i in 0..buffer.len() {
self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?;
buffer[i] = input[0];
}
self.cs.set_high().map_err(|_| CS)?;
self.brd_wait_on_busy().await?;
Ok(())
}
// Set the radio output power
pub(super) async fn brd_set_rf_tx_power(&mut self, power: i8) -> Result<(), RadioError<BUS>> {
self.sub_set_tx_params(power, RampTime::Ramp40Us).await?;
Ok(())
}
// Get the radio type
pub(super) fn brd_get_radio_type(&mut self) -> RadioType {
RadioType::SX1262
}
// Quiesce the antenna(s).
pub(super) fn brd_ant_sleep(&mut self) -> Result<(), RadioError<BUS>> {
self.antenna_tx.set_low().map_err(|_| AntTx)?;
self.antenna_rx.set_low().map_err(|_| AntRx)?;
Ok(())
}
// Prepare the antenna(s) for a receive operation
pub(super) fn brd_ant_set_rx(&mut self) -> Result<(), RadioError<BUS>> {
self.antenna_tx.set_low().map_err(|_| AntTx)?;
self.antenna_rx.set_high().map_err(|_| AntRx)?;
Ok(())
}
// Prepare the antenna(s) for a send operation
pub(super) fn brd_ant_set_tx(&mut self) -> Result<(), RadioError<BUS>> {
self.antenna_rx.set_low().map_err(|_| AntRx)?;
self.antenna_tx.set_high().map_err(|_| AntTx)?;
Ok(())
}
// Check if the given RF frequency is supported by the hardware
pub(super) async fn brd_check_rf_frequency(&mut self, _frequency: u32) -> Result<bool, RadioError<BUS>> {
Ok(true)
}
// Get the duration required for the TCXO to wakeup [ms].
pub(super) fn brd_get_board_tcxo_wakeup_time(&mut self) -> u32 {
BRD_TCXO_WAKEUP_TIME
}
/* Get current state of the DIO1 pin - not currently needed if waiting on DIO1 instead of using an IRQ process
pub(super) async fn brd_get_dio1_pin_state(
&mut self,
) -> Result<u32, RadioError<BUS>> {
Ok(0)
}
*/
// Get the current radio operatiing mode
pub(super) fn brd_get_operating_mode(&mut self) -> RadioMode {
self.operating_mode
}
// Set/Update the current radio operating mode This function is only required to reflect the current radio operating mode when processing interrupts.
pub(super) fn brd_set_operating_mode(&mut self, mode: RadioMode) {
self.operating_mode = mode;
}
}

View file

@ -1,732 +0,0 @@
#![allow(dead_code)]
use embassy_time::{Duration, Timer};
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::SpiBus;
mod board_specific;
pub mod mod_params;
mod subroutine;
use mod_params::RadioError::*;
use mod_params::*;
// Syncwords for public and private networks
const LORA_MAC_PUBLIC_SYNCWORD: u16 = 0x3444;
const LORA_MAC_PRIVATE_SYNCWORD: u16 = 0x1424;
// Maximum number of registers that can be added to the retention list
const MAX_NUMBER_REGS_IN_RETENTION: u8 = 4;
// Possible LoRa bandwidths
const LORA_BANDWIDTHS: [Bandwidth; 3] = [Bandwidth::_125KHz, Bandwidth::_250KHz, Bandwidth::_500KHz];
// Radio complete wakeup time with margin for temperature compensation [ms]
const RADIO_WAKEUP_TIME: u32 = 3;
/// Provides high-level access to Semtech SX126x-based boards
pub struct LoRa<SPI, CTRL, WAIT> {
spi: SPI,
cs: CTRL,
reset: CTRL,
antenna_rx: CTRL,
antenna_tx: CTRL,
dio1: WAIT,
busy: WAIT,
operating_mode: RadioMode,
rx_continuous: bool,
max_payload_length: u8,
modulation_params: Option<ModulationParams>,
packet_type: PacketType,
packet_params: Option<PacketParams>,
packet_status: Option<PacketStatus>,
image_calibrated: bool,
frequency_error: u32,
}
impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
where
SPI: SpiBus<u8, Error = BUS>,
CTRL: OutputPin,
WAIT: Wait,
{
/// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time ()
pub fn new(spi: SPI, cs: CTRL, reset: CTRL, antenna_rx: CTRL, antenna_tx: CTRL, dio1: WAIT, busy: WAIT) -> Self {
Self {
spi,
cs,
reset,
antenna_rx,
antenna_tx,
dio1,
busy,
operating_mode: RadioMode::Sleep,
rx_continuous: false,
max_payload_length: 0xFFu8,
modulation_params: None,
packet_type: PacketType::LoRa,
packet_params: None,
packet_status: None,
image_calibrated: false,
frequency_error: 0u32, // where is volatile FrequencyError modified ???
}
}
/// Initialize the radio
pub async fn init(&mut self) -> Result<(), RadioError<BUS>> {
self.sub_init().await?;
self.sub_set_standby(StandbyMode::RC).await?;
self.sub_set_regulator_mode(RegulatorMode::UseDCDC).await?;
self.sub_set_buffer_base_address(0x00u8, 0x00u8).await?;
self.sub_set_tx_params(0i8, RampTime::Ramp200Us).await?;
self.sub_set_dio_irq_params(
IrqMask::All.value(),
IrqMask::All.value(),
IrqMask::None.value(),
IrqMask::None.value(),
)
.await?;
self.add_register_to_retention_list(Register::RxGain.addr()).await?;
self.add_register_to_retention_list(Register::TxModulation.addr())
.await?;
Ok(())
}
/// Return current radio state
pub fn get_status(&mut self) -> RadioState {
match self.brd_get_operating_mode() {
RadioMode::Transmit => RadioState::TxRunning,
RadioMode::Receive => RadioState::RxRunning,
RadioMode::ChannelActivityDetection => RadioState::ChannelActivityDetecting,
_ => RadioState::Idle,
}
}
/// Configure the radio for LoRa (FSK support should be provided in a separate driver, if desired)
pub async fn set_lora_modem(&mut self, enable_public_network: bool) -> Result<(), RadioError<BUS>> {
self.sub_set_packet_type(PacketType::LoRa).await?;
if enable_public_network {
self.brd_write_registers(
Register::LoRaSyncword,
&[
((LORA_MAC_PUBLIC_SYNCWORD >> 8) & 0xFF) as u8,
(LORA_MAC_PUBLIC_SYNCWORD & 0xFF) as u8,
],
)
.await?;
} else {
self.brd_write_registers(
Register::LoRaSyncword,
&[
((LORA_MAC_PRIVATE_SYNCWORD >> 8) & 0xFF) as u8,
(LORA_MAC_PRIVATE_SYNCWORD & 0xFF) as u8,
],
)
.await?;
}
Ok(())
}
/// Sets the channel frequency
pub async fn set_channel(&mut self, frequency: u32) -> Result<(), RadioError<BUS>> {
self.sub_set_rf_frequency(frequency).await?;
Ok(())
}
/* Checks if the channel is free for the given time. This is currently not implemented until a substitute
for switching to the FSK modem is found.
pub async fn is_channel_free(&mut self, frequency: u32, rxBandwidth: u32, rssiThresh: i16, maxCarrierSenseTime: u32) -> bool;
*/
/// Generate a 32 bit random value based on the RSSI readings, after disabling all interrupts. Ensure set_lora_modem() is called befrorehand.
/// After calling this function either set_rx_config() or set_tx_config() must be called.
pub async fn get_random_value(&mut self) -> Result<u32, RadioError<BUS>> {
self.sub_set_dio_irq_params(
IrqMask::None.value(),
IrqMask::None.value(),
IrqMask::None.value(),
IrqMask::None.value(),
)
.await?;
let result = self.sub_get_random().await?;
Ok(result)
}
/// Set the reception parameters for the LoRa modem (only). Ensure set_lora_modem() is called befrorehand.
/// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol]
/// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
/// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
/// preamble_length length in symbols (the hardware adds 4 more symbols)
/// symb_timeout RxSingle timeout value in symbols
/// fixed_len fixed length packets [0: variable, 1: fixed]
/// payload_len payload length when fixed length is used
/// crc_on [0: OFF, 1: ON]
/// freq_hop_on intra-packet frequency hopping [0: OFF, 1: ON]
/// hop_period number of symbols between each hop
/// iq_inverted invert IQ signals [0: not inverted, 1: inverted]
/// rx_continuous reception mode [false: single mode, true: continuous mode]
pub async fn set_rx_config(
&mut self,
spreading_factor: SpreadingFactor,
bandwidth: Bandwidth,
coding_rate: CodingRate,
preamble_length: u16,
symb_timeout: u16,
fixed_len: bool,
payload_len: u8,
crc_on: bool,
_freq_hop_on: bool,
_hop_period: u8,
iq_inverted: bool,
rx_continuous: bool,
) -> Result<(), RadioError<BUS>> {
let mut symb_timeout_final = symb_timeout;
self.rx_continuous = rx_continuous;
if self.rx_continuous {
symb_timeout_final = 0;
}
if fixed_len {
self.max_payload_length = payload_len;
} else {
self.max_payload_length = 0xFFu8;
}
self.sub_set_stop_rx_timer_on_preamble_detect(false).await?;
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;
}
let modulation_params = ModulationParams {
spreading_factor: spreading_factor,
bandwidth: bandwidth,
coding_rate: coding_rate,
low_data_rate_optimize: low_data_rate_optimize,
};
let mut preamble_length_final = preamble_length;
if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6))
&& (preamble_length < 12)
{
preamble_length_final = 12;
}
let packet_params = PacketParams {
preamble_length: preamble_length_final,
implicit_header: fixed_len,
payload_length: self.max_payload_length,
crc_on: crc_on,
iq_inverted: iq_inverted,
};
self.modulation_params = Some(modulation_params);
self.packet_params = Some(packet_params);
self.standby().await?;
self.sub_set_modulation_params().await?;
self.sub_set_packet_params().await?;
self.sub_set_lora_symb_num_timeout(symb_timeout_final).await?;
// Optimize the Inverted IQ Operation (see DS_SX1261-2_V1.2 datasheet chapter 15.4)
let mut iq_polarity = [0x00u8];
self.brd_read_registers(Register::IQPolarity, &mut iq_polarity).await?;
if iq_inverted {
self.brd_write_registers(Register::IQPolarity, &[iq_polarity[0] & (!(1 << 2))])
.await?;
} else {
self.brd_write_registers(Register::IQPolarity, &[iq_polarity[0] | (1 << 2)])
.await?;
}
Ok(())
}
/// Set the transmission parameters for the LoRa modem (only).
/// power output power [dBm]
/// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol]
/// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
/// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
/// preamble_length length in symbols (the hardware adds 4 more symbols)
/// fixed_len fixed length packets [0: variable, 1: fixed]
/// crc_on [0: OFF, 1: ON]
/// freq_hop_on intra-packet frequency hopping [0: OFF, 1: ON]
/// hop_period number of symbols between each hop
/// iq_inverted invert IQ signals [0: not inverted, 1: inverted]
pub async fn set_tx_config(
&mut self,
power: i8,
spreading_factor: SpreadingFactor,
bandwidth: Bandwidth,
coding_rate: CodingRate,
preamble_length: u16,
fixed_len: bool,
crc_on: bool,
_freq_hop_on: bool,
_hop_period: u8,
iq_inverted: bool,
) -> Result<(), RadioError<BUS>> {
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;
}
let modulation_params = ModulationParams {
spreading_factor: spreading_factor,
bandwidth: bandwidth,
coding_rate: coding_rate,
low_data_rate_optimize: low_data_rate_optimize,
};
let mut preamble_length_final = preamble_length;
if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6))
&& (preamble_length < 12)
{
preamble_length_final = 12;
}
let packet_params = PacketParams {
preamble_length: preamble_length_final,
implicit_header: fixed_len,
payload_length: self.max_payload_length,
crc_on: crc_on,
iq_inverted: iq_inverted,
};
self.modulation_params = Some(modulation_params);
self.packet_params = Some(packet_params);
self.standby().await?;
self.sub_set_modulation_params().await?;
self.sub_set_packet_params().await?;
// Handle modulation quality with the 500 kHz LoRa bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1)
let mut tx_modulation = [0x00u8];
self.brd_read_registers(Register::TxModulation, &mut tx_modulation)
.await?;
if bandwidth == Bandwidth::_500KHz {
self.brd_write_registers(Register::TxModulation, &[tx_modulation[0] & (!(1 << 2))])
.await?;
} else {
self.brd_write_registers(Register::TxModulation, &[tx_modulation[0] | (1 << 2)])
.await?;
}
self.brd_set_rf_tx_power(power).await?;
Ok(())
}
/// Check if the given RF frequency is supported by the hardware [true: supported, false: unsupported]
pub async fn check_rf_frequency(&mut self, frequency: u32) -> Result<bool, RadioError<BUS>> {
Ok(self.brd_check_rf_frequency(frequency).await?)
}
/// Computes the packet time on air in ms for the given payload for a LoRa modem (can only be called once set_rx_config or set_tx_config have been called)
/// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol]
/// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved]
/// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
/// preamble_length length in symbols (the hardware adds 4 more symbols)
/// fixed_len fixed length packets [0: variable, 1: fixed]
/// payload_len sets payload length when fixed length is used
/// crc_on [0: OFF, 1: ON]
pub fn get_time_on_air(
&mut self,
spreading_factor: SpreadingFactor,
bandwidth: Bandwidth,
coding_rate: CodingRate,
preamble_length: u16,
fixed_len: bool,
payload_len: u8,
crc_on: bool,
) -> Result<u32, RadioError<BUS>> {
let numerator = 1000
* Self::get_lora_time_on_air_numerator(
spreading_factor,
bandwidth,
coding_rate,
preamble_length,
fixed_len,
payload_len,
crc_on,
);
let denominator = bandwidth.value_in_hz();
if denominator == 0 {
Err(RadioError::InvalidBandwidth)
} else {
Ok((numerator + denominator - 1) / denominator)
}
}
/// Send the buffer of the given size. Prepares the packet to be sent and sets the radio in transmission [timeout in ms]
pub async fn send(&mut self, buffer: &[u8], timeout: u32) -> Result<(), RadioError<BUS>> {
if self.packet_params.is_some() {
self.sub_set_dio_irq_params(
IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value(),
IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value(),
IrqMask::None.value(),
IrqMask::None.value(),
)
.await?;
let mut packet_params = self.packet_params.as_mut().unwrap();
packet_params.payload_length = buffer.len() as u8;
self.sub_set_packet_params().await?;
self.sub_send_payload(buffer, timeout).await?;
Ok(())
} else {
Err(RadioError::PacketParamsMissing)
}
}
/// Set the radio in sleep mode
pub async fn sleep(&mut self) -> Result<(), RadioError<BUS>> {
self.sub_set_sleep(SleepParams {
wakeup_rtc: false,
reset: false,
warm_start: true,
})
.await?;
Timer::after(Duration::from_millis(2)).await;
Ok(())
}
/// Set the radio in standby mode
pub async fn standby(&mut self) -> Result<(), RadioError<BUS>> {
self.sub_set_standby(StandbyMode::RC).await?;
Ok(())
}
/// Set the radio in reception mode for the given duration [0: continuous, others: timeout (ms)]
pub async fn rx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
self.sub_set_dio_irq_params(
IrqMask::All.value(),
IrqMask::All.value(),
IrqMask::None.value(),
IrqMask::None.value(),
)
.await?;
if self.rx_continuous {
self.sub_set_rx(0xFFFFFF).await?;
} else {
self.sub_set_rx(timeout << 6).await?;
}
Ok(())
}
/// Start a Channel Activity Detection
pub async fn start_cad(&mut self) -> Result<(), RadioError<BUS>> {
self.sub_set_dio_irq_params(
IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value(),
IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value(),
IrqMask::None.value(),
IrqMask::None.value(),
)
.await?;
self.sub_set_cad().await?;
Ok(())
}
/// Sets the radio in continuous wave transmission mode
/// frequency channel RF frequency
/// power output power [dBm]
/// timeout transmission mode timeout [s]
pub async fn set_tx_continuous_wave(
&mut self,
frequency: u32,
power: i8,
_timeout: u16,
) -> Result<(), RadioError<BUS>> {
self.sub_set_rf_frequency(frequency).await?;
self.brd_set_rf_tx_power(power).await?;
self.sub_set_tx_continuous_wave().await?;
Ok(())
}
/// Read the current RSSI value for the LoRa modem (only) [dBm]
pub async fn get_rssi(&mut self) -> Result<i16, RadioError<BUS>> {
let value = self.sub_get_rssi_inst().await?;
Ok(value as i16)
}
/// Write one or more radio registers with a buffer of a given size, starting at the first register address
pub async fn write_registers_from_buffer(
&mut self,
start_register: Register,
buffer: &[u8],
) -> Result<(), RadioError<BUS>> {
self.brd_write_registers(start_register, buffer).await?;
Ok(())
}
/// Read one or more radio registers into a buffer of a given size, starting at the first register address
pub async fn read_registers_into_buffer(
&mut self,
start_register: Register,
buffer: &mut [u8],
) -> Result<(), RadioError<BUS>> {
self.brd_read_registers(start_register, buffer).await?;
Ok(())
}
/// Set the maximum payload length (in bytes) for a LoRa modem (only).
pub async fn set_max_payload_length(&mut self, max: u8) -> Result<(), RadioError<BUS>> {
if self.packet_params.is_some() {
let packet_params = self.packet_params.as_mut().unwrap();
self.max_payload_length = max;
packet_params.payload_length = max;
self.sub_set_packet_params().await?;
Ok(())
} else {
Err(RadioError::PacketParamsMissing)
}
}
/// Get the time required for the board plus radio to get out of sleep [ms]
pub fn get_wakeup_time(&mut self) -> u32 {
self.brd_get_board_tcxo_wakeup_time() + RADIO_WAKEUP_TIME
}
/// Process the radio irq
pub async fn process_irq(
&mut self,
receiving_buffer: Option<&mut [u8]>,
received_len: Option<&mut u8>,
cad_activity_detected: Option<&mut bool>,
) -> Result<(), RadioError<BUS>> {
loop {
trace!("process_irq loop entered");
let de = self.sub_get_device_errors().await?;
trace!("device_errors: rc_64khz_calibration = {}, rc_13mhz_calibration = {}, pll_calibration = {}, adc_calibration = {}, image_calibration = {}, xosc_start = {}, pll_lock = {}, pa_ramp = {}",
de.rc_64khz_calibration, de.rc_13mhz_calibration, de.pll_calibration, de.adc_calibration, de.image_calibration, de.xosc_start, de.pll_lock, de.pa_ramp);
let st = self.sub_get_status().await?;
trace!(
"radio status: cmd_status: {:x}, chip_mode: {:x}",
st.cmd_status,
st.chip_mode
);
self.dio1.wait_for_high().await.map_err(|_| DIO1)?;
let operating_mode = self.brd_get_operating_mode();
let irq_flags = self.sub_get_irq_status().await?;
self.sub_clear_irq_status(irq_flags).await?;
trace!("process_irq DIO1 satisfied: irq_flags = {:x}", irq_flags);
// check for errors and unexpected interrupt masks (based on operation mode)
if (irq_flags & IrqMask::HeaderError.value()) == IrqMask::HeaderError.value() {
if !self.rx_continuous {
self.brd_set_operating_mode(RadioMode::StandbyRC);
}
return Err(RadioError::HeaderError);
} else if (irq_flags & IrqMask::CRCError.value()) == IrqMask::CRCError.value() {
if operating_mode == RadioMode::Receive {
if !self.rx_continuous {
self.brd_set_operating_mode(RadioMode::StandbyRC);
}
return Err(RadioError::CRCErrorOnReceive);
} else {
return Err(RadioError::CRCErrorUnexpected);
}
} else if (irq_flags & IrqMask::RxTxTimeout.value()) == IrqMask::RxTxTimeout.value() {
if operating_mode == RadioMode::Transmit {
self.brd_set_operating_mode(RadioMode::StandbyRC);
return Err(RadioError::TransmitTimeout);
} else if operating_mode == RadioMode::Receive {
self.brd_set_operating_mode(RadioMode::StandbyRC);
return Err(RadioError::ReceiveTimeout);
} else {
return Err(RadioError::TimeoutUnexpected);
}
} else if ((irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value())
&& (operating_mode != RadioMode::Transmit)
{
return Err(RadioError::TransmitDoneUnexpected);
} else if ((irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value())
&& (operating_mode != RadioMode::Receive)
{
return Err(RadioError::ReceiveDoneUnexpected);
} else if (((irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value())
|| ((irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value()))
&& (operating_mode != RadioMode::ChannelActivityDetection)
{
return Err(RadioError::CADUnexpected);
}
if (irq_flags & IrqMask::HeaderValid.value()) == IrqMask::HeaderValid.value() {
trace!("HeaderValid");
} else if (irq_flags & IrqMask::PreambleDetected.value()) == IrqMask::PreambleDetected.value() {
trace!("PreambleDetected");
} else if (irq_flags & IrqMask::SyncwordValid.value()) == IrqMask::SyncwordValid.value() {
trace!("SyncwordValid");
}
// handle completions
if (irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value() {
self.brd_set_operating_mode(RadioMode::StandbyRC);
return Ok(());
} else if (irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value() {
if !self.rx_continuous {
self.brd_set_operating_mode(RadioMode::StandbyRC);
// implicit header mode timeout behavior (see DS_SX1261-2_V1.2 datasheet chapter 15.3)
self.brd_write_registers(Register::RTCCtrl, &[0x00]).await?;
let mut evt_clr = [0x00u8];
self.brd_read_registers(Register::EvtClr, &mut evt_clr).await?;
evt_clr[0] |= 1 << 1;
self.brd_write_registers(Register::EvtClr, &evt_clr).await?;
}
if receiving_buffer.is_some() && received_len.is_some() {
*(received_len.unwrap()) = self.sub_get_payload(receiving_buffer.unwrap()).await?;
}
self.packet_status = self.sub_get_packet_status().await?.into();
return Ok(());
} else if (irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value() {
if cad_activity_detected.is_some() {
*(cad_activity_detected.unwrap()) =
(irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value();
}
self.brd_set_operating_mode(RadioMode::StandbyRC);
return Ok(());
}
// if DIO1 was driven high for reasons other than an error or operation completion (currently, PreambleDetected, SyncwordValid, and HeaderValid
// are in that category), loop to wait again
}
}
// SX126x-specific functions
/// Set the radio in reception mode with Max LNA gain for the given time (SX126x radios only) [0: continuous, others timeout in ms]
pub async fn set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
self.sub_set_dio_irq_params(
IrqMask::All.value(),
IrqMask::All.value(),
IrqMask::None.value(),
IrqMask::None.value(),
)
.await?;
if self.rx_continuous {
self.sub_set_rx_boosted(0xFFFFFF).await?; // Rx continuous
} else {
self.sub_set_rx_boosted(timeout << 6).await?;
}
Ok(())
}
/// Set the Rx duty cycle management parameters (SX126x radios only)
/// rx_time structure describing reception timeout value
/// sleep_time structure describing sleep timeout value
pub async fn set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError<BUS>> {
self.sub_set_rx_duty_cycle(rx_time, sleep_time).await?;
Ok(())
}
pub fn get_latest_packet_status(&mut self) -> Option<PacketStatus> {
self.packet_status
}
// Utilities
async fn add_register_to_retention_list(&mut self, register_address: u16) -> Result<(), RadioError<BUS>> {
let mut buffer = [0x00u8; (1 + (2 * MAX_NUMBER_REGS_IN_RETENTION)) as usize];
// Read the address and registers already added to the list
self.brd_read_registers(Register::RetentionList, &mut buffer).await?;
let number_of_registers = buffer[0];
for i in 0..number_of_registers {
if register_address
== ((buffer[(1 + (2 * i)) as usize] as u16) << 8) | (buffer[(2 + (2 * i)) as usize] as u16)
{
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_address >> 8) & 0xFF) as u8;
buffer[(2 + (2 * number_of_registers)) as usize] = (register_address & 0xFF) as u8;
self.brd_write_registers(Register::RetentionList, &buffer).await?;
Ok(())
} else {
Err(RadioError::RetentionListExceeded)
}
}
fn get_lora_time_on_air_numerator(
spreading_factor: SpreadingFactor,
bandwidth: Bandwidth,
coding_rate: CodingRate,
preamble_length: u16,
fixed_len: bool,
payload_len: u8,
crc_on: bool,
) -> u32 {
let cell_denominator;
let cr_denominator = (coding_rate.value() as i32) + 4;
// Ensure that the preamble length is at least 12 symbols when using SF5 or SF6
let mut preamble_length_final = preamble_length;
if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6))
&& (preamble_length < 12)
{
preamble_length_final = 12;
}
let mut low_data_rate_optimize = false;
if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12))
&& (bandwidth == Bandwidth::_125KHz))
|| ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz))
{
low_data_rate_optimize = true;
}
let mut cell_numerator = ((payload_len as i32) << 3) + (if crc_on { 16 } else { 0 })
- (4 * spreading_factor.value() as i32)
+ (if fixed_len { 0 } else { 20 });
if spreading_factor.value() <= 6 {
cell_denominator = 4 * (spreading_factor.value() as i32);
} else {
cell_numerator += 8;
if low_data_rate_optimize {
cell_denominator = 4 * ((spreading_factor.value() as i32) - 2);
} else {
cell_denominator = 4 * (spreading_factor.value() as i32);
}
}
if cell_numerator < 0 {
cell_numerator = 0;
}
let mut intermediate: i32 = (((cell_numerator + cell_denominator - 1) / cell_denominator) * cr_denominator)
+ (preamble_length_final as i32)
+ 12;
if spreading_factor.value() <= 6 {
intermediate = intermediate + 2;
}
(((4 * intermediate) + 1) * (1 << (spreading_factor.value() - 2))) as u32
}
}

View file

@ -1,469 +0,0 @@
use core::fmt::Debug;
use lorawan_device::async_device::radio as device;
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum RadioError<BUS> {
SPI(BUS),
CS,
Reset,
AntRx,
AntTx,
Busy,
DIO1,
PayloadSizeMismatch(usize, usize),
RetentionListExceeded,
InvalidBandwidth,
ModulationParamsMissing,
PacketParamsMissing,
HeaderError,
CRCErrorUnexpected,
CRCErrorOnReceive,
TransmitTimeout,
ReceiveTimeout,
TimeoutUnexpected,
TransmitDoneUnexpected,
ReceiveDoneUnexpected,
CADUnexpected,
}
pub struct RadioSystemError {
pub rc_64khz_calibration: bool,
pub rc_13mhz_calibration: bool,
pub pll_calibration: bool,
pub adc_calibration: bool,
pub image_calibration: bool,
pub xosc_start: bool,
pub pll_lock: bool,
pub pa_ramp: bool,
}
#[derive(Clone, Copy, PartialEq)]
pub enum PacketType {
GFSK = 0x00,
LoRa = 0x01,
None = 0x0F,
}
impl PacketType {
pub const fn value(self) -> u8 {
self as u8
}
pub fn to_enum(value: u8) -> Self {
if value == 0x00 {
PacketType::GFSK
} else if value == 0x01 {
PacketType::LoRa
} else {
PacketType::None
}
}
}
#[derive(Clone, Copy)]
pub struct PacketStatus {
pub rssi: i8,
pub snr: i8,
pub signal_rssi: i8,
pub freq_error: u32,
}
#[derive(Clone, Copy, PartialEq)]
pub enum RadioType {
SX1261,
SX1262,
}
#[derive(Clone, Copy, PartialEq)]
pub enum RadioMode {
Sleep = 0x00, // sleep mode
StandbyRC = 0x01, // standby mode with RC oscillator
StandbyXOSC = 0x02, // standby mode with XOSC oscillator
FrequencySynthesis = 0x03, // frequency synthesis mode
Transmit = 0x04, // transmit mode
Receive = 0x05, // receive mode
ReceiveDutyCycle = 0x06, // receive duty cycle mode
ChannelActivityDetection = 0x07, // channel activity detection mode
}
impl RadioMode {
/// Returns the value of the mode.
pub const fn value(self) -> u8 {
self as u8
}
pub fn to_enum(value: u8) -> Self {
if value == 0x00 {
RadioMode::Sleep
} else if value == 0x01 {
RadioMode::StandbyRC
} else if value == 0x02 {
RadioMode::StandbyXOSC
} else if value == 0x03 {
RadioMode::FrequencySynthesis
} else if value == 0x04 {
RadioMode::Transmit
} else if value == 0x05 {
RadioMode::Receive
} else if value == 0x06 {
RadioMode::ReceiveDutyCycle
} else if value == 0x07 {
RadioMode::ChannelActivityDetection
} else {
RadioMode::Sleep
}
}
}
pub enum RadioState {
Idle = 0x00,
RxRunning = 0x01,
TxRunning = 0x02,
ChannelActivityDetecting = 0x03,
}
impl RadioState {
/// Returns the value of the state.
pub fn value(self) -> u8 {
self as u8
}
}
pub struct RadioStatus {
pub cmd_status: u8,
pub chip_mode: u8,
}
impl RadioStatus {
pub fn value(self) -> u8 {
(self.chip_mode << 4) | (self.cmd_status << 1)
}
}
#[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
}
}
#[derive(Clone, Copy)]
pub enum Register {
PacketParams = 0x0704, // packet configuration
PayloadLength = 0x0702, // payload size
SynchTimeout = 0x0706, // recalculated number of symbols
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
IQPolarity = 0x0736, // optimize the inverted IQ operation (see DS_SX1261-2_V1.2 datasheet chapter 15.4)
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 addr(self) -> u16 {
self as u16
}
}
#[derive(Clone, Copy, PartialEq)]
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,
GetErrors = 0x17,
ClrErrors = 0x07,
SetTCXOMode = 0x97,
SetTxFallbackMode = 0x93,
SetRFSwitchMode = 0x9D,
SetStopRxTimerOnPreamble = 0x9F,
SetLoRaSymbTimeout = 0xA0,
}
impl OpCode {
pub fn value(self) -> u8 {
self as u8
}
}
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)]
pub enum StandbyMode {
RC = 0x00,
XOSC = 0x01,
}
impl StandbyMode {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
pub enum RegulatorMode {
UseLDO = 0x00,
UseDCDC = 0x01,
}
impl RegulatorMode {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
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,
}
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) << 0)
}
}
#[derive(Clone, Copy)]
pub enum TcxoCtrlVoltage {
Ctrl1V6 = 0x00,
Ctrl1V7 = 0x01,
Ctrl1V8 = 0x02,
Ctrl2V2 = 0x03,
Ctrl2V4 = 0x04,
Ctrl2V7 = 0x05,
Ctrl3V0 = 0x06,
Ctrl3V3 = 0x07,
}
impl TcxoCtrlVoltage {
pub fn value(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
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
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum SpreadingFactor {
_5 = 0x05,
_6 = 0x06,
_7 = 0x07,
_8 = 0x08,
_9 = 0x09,
_10 = 0x0A,
_11 = 0x0B,
_12 = 0x0C,
}
impl SpreadingFactor {
pub fn value(self) -> u8 {
self as u8
}
}
impl From<device::SpreadingFactor> for SpreadingFactor {
fn from(sf: device::SpreadingFactor) -> Self {
match sf {
device::SpreadingFactor::_7 => SpreadingFactor::_7,
device::SpreadingFactor::_8 => SpreadingFactor::_8,
device::SpreadingFactor::_9 => SpreadingFactor::_9,
device::SpreadingFactor::_10 => SpreadingFactor::_10,
device::SpreadingFactor::_11 => SpreadingFactor::_11,
device::SpreadingFactor::_12 => SpreadingFactor::_12,
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum Bandwidth {
_500KHz = 0x06,
_250KHz = 0x05,
_125KHz = 0x04,
}
impl Bandwidth {
pub fn value(self) -> u8 {
self as u8
}
pub fn value_in_hz(self) -> u32 {
match self {
Bandwidth::_125KHz => 125000u32,
Bandwidth::_250KHz => 250000u32,
Bandwidth::_500KHz => 500000u32,
}
}
}
impl From<device::Bandwidth> for Bandwidth {
fn from(bw: device::Bandwidth) -> Self {
match bw {
device::Bandwidth::_500KHz => Bandwidth::_500KHz,
device::Bandwidth::_250KHz => Bandwidth::_250KHz,
device::Bandwidth::_125KHz => Bandwidth::_125KHz,
}
}
}
#[derive(Clone, Copy)]
pub enum CodingRate {
_4_5 = 0x01,
_4_6 = 0x02,
_4_7 = 0x03,
_4_8 = 0x04,
}
impl CodingRate {
pub fn value(self) -> u8 {
self as u8
}
}
impl From<device::CodingRate> for CodingRate {
fn from(cr: device::CodingRate) -> Self {
match cr {
device::CodingRate::_4_5 => CodingRate::_4_5,
device::CodingRate::_4_6 => CodingRate::_4_6,
device::CodingRate::_4_7 => CodingRate::_4_7,
device::CodingRate::_4_8 => CodingRate::_4_8,
}
}
}
#[derive(Clone, Copy)]
pub struct ModulationParams {
pub spreading_factor: SpreadingFactor,
pub bandwidth: Bandwidth,
pub coding_rate: CodingRate,
pub low_data_rate_optimize: u8,
}
#[derive(Clone, Copy)]
pub struct PacketParams {
pub preamble_length: u16, // number of LoRa symbols in the preamble
pub 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 payload_length: u8,
pub crc_on: bool,
pub iq_inverted: bool,
}
#[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
}
}
#[derive(Clone, Copy)]
pub enum CADExitMode {
CADOnly = 0x00,
CADRx = 0x01,
CADLBT = 0x10,
}
impl CADExitMode {
pub fn value(self) -> u8 {
self as u8
}
}

View file

@ -1,674 +0,0 @@
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::SpiBus;
use super::mod_params::*;
use super::LoRa;
// 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;
// Provides board-specific functionality for Semtech SX126x-based boards
impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
where
SPI: SpiBus<u8, Error = BUS>,
CTRL: OutputPin,
WAIT: Wait,
{
// Initialize the radio driver
pub(super) async fn sub_init(&mut self) -> Result<(), RadioError<BUS>> {
self.brd_reset().await?;
self.brd_wakeup().await?;
self.sub_set_standby(StandbyMode::RC).await?;
self.brd_io_tcxo_init().await?;
self.brd_io_rf_switch_init().await?;
self.image_calibrated = false;
Ok(())
}
// Wakeup the radio if it is in Sleep mode and check that Busy is low
pub(super) async fn sub_check_device_ready(&mut self) -> Result<(), RadioError<BUS>> {
let operating_mode = self.brd_get_operating_mode();
if operating_mode == RadioMode::Sleep || operating_mode == RadioMode::ReceiveDutyCycle {
self.brd_wakeup().await?;
}
self.brd_wait_on_busy().await?;
Ok(())
}
// Save the payload to be sent in the radio buffer
pub(super) async fn sub_set_payload(&mut self, payload: &[u8]) -> Result<(), RadioError<BUS>> {
self.brd_write_buffer(0x00, payload).await?;
Ok(())
}
// Read the payload received.
pub(super) async fn sub_get_payload(&mut self, buffer: &mut [u8]) -> Result<u8, RadioError<BUS>> {
let (size, offset) = self.sub_get_rx_buffer_status().await?;
if (size as usize) > buffer.len() {
Err(RadioError::PayloadSizeMismatch(size as usize, buffer.len()))
} else {
self.brd_read_buffer(offset, buffer).await?;
Ok(size)
}
}
// Send a payload
pub(super) async fn sub_send_payload(&mut self, payload: &[u8], timeout: u32) -> Result<(), RadioError<BUS>> {
self.sub_set_payload(payload).await?;
self.sub_set_tx(timeout).await?;
Ok(())
}
// Get a 32-bit random value generated by the radio. A valid packet type must have been configured before using this command.
//
// The radio must be in reception mode before executing this function. This code can potentially result in interrupt generation. It is the responsibility of
// the calling code to disable radio interrupts before calling this function, and re-enable them afterwards if necessary, or be certain that any interrupts
// generated during this process will not cause undesired side-effects in the software.
//
// The random numbers produced by the generator do not have a uniform or Gaussian distribution. If uniformity is needed, perform appropriate software post-processing.
pub(super) async fn sub_get_random(&mut self) -> Result<u32, RadioError<BUS>> {
let mut reg_ana_lna_buffer_original = [0x00u8];
let mut reg_ana_mixer_buffer_original = [0x00u8];
let mut reg_ana_lna_buffer = [0x00u8];
let mut reg_ana_mixer_buffer = [0x00u8];
let mut number_buffer = [0x00u8, 0x00u8, 0x00u8, 0x00u8];
self.brd_read_registers(Register::AnaLNA, &mut reg_ana_lna_buffer_original)
.await?;
reg_ana_lna_buffer[0] = reg_ana_lna_buffer_original[0] & (!(1 << 0));
self.brd_write_registers(Register::AnaLNA, &reg_ana_lna_buffer).await?;
self.brd_read_registers(Register::AnaMixer, &mut reg_ana_mixer_buffer_original)
.await?;
reg_ana_mixer_buffer[0] = reg_ana_mixer_buffer_original[0] & (!(1 << 7));
self.brd_write_registers(Register::AnaMixer, &reg_ana_mixer_buffer)
.await?;
// Set radio in continuous reception
self.sub_set_rx(0xFFFFFFu32).await?;
self.brd_read_registers(Register::GeneratedRandomNumber, &mut number_buffer)
.await?;
self.sub_set_standby(StandbyMode::RC).await?;
self.brd_write_registers(Register::AnaLNA, &reg_ana_lna_buffer_original)
.await?;
self.brd_write_registers(Register::AnaMixer, &reg_ana_mixer_buffer_original)
.await?;
Ok(Self::convert_u8_buffer_to_u32(&number_buffer))
}
// Set the radio in sleep mode
pub(super) async fn sub_set_sleep(&mut self, sleep_config: SleepParams) -> Result<(), RadioError<BUS>> {
self.brd_ant_sleep()?;
if !sleep_config.warm_start {
self.image_calibrated = false;
}
self.brd_write_command(OpCode::SetSleep, &[sleep_config.value()])
.await?;
self.brd_set_operating_mode(RadioMode::Sleep);
Ok(())
}
// Set the radio in configuration mode
pub(super) async fn sub_set_standby(&mut self, mode: StandbyMode) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetStandby, &[mode.value()]).await?;
if mode == StandbyMode::RC {
self.brd_set_operating_mode(RadioMode::StandbyRC);
} else {
self.brd_set_operating_mode(RadioMode::StandbyXOSC);
}
self.brd_ant_sleep()?;
Ok(())
}
// Set the radio in FS mode
pub(super) async fn sub_set_fs(&mut self) -> Result<(), RadioError<BUS>> {
// antenna settings ???
self.brd_write_command(OpCode::SetFS, &[]).await?;
self.brd_set_operating_mode(RadioMode::FrequencySynthesis);
Ok(())
}
// Set the radio in transmission mode with timeout specified
pub(super) async fn sub_set_tx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
let buffer = [
Self::timeout_1(timeout),
Self::timeout_2(timeout),
Self::timeout_3(timeout),
];
self.brd_ant_set_tx()?;
self.brd_set_operating_mode(RadioMode::Transmit);
self.brd_write_command(OpCode::SetTx, &buffer).await?;
Ok(())
}
// Set the radio in reception mode with timeout specified
pub(super) async fn sub_set_rx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
let buffer = [
Self::timeout_1(timeout),
Self::timeout_2(timeout),
Self::timeout_3(timeout),
];
self.brd_ant_set_rx()?;
self.brd_set_operating_mode(RadioMode::Receive);
self.brd_write_registers(Register::RxGain, &[0x94u8]).await?;
self.brd_write_command(OpCode::SetRx, &buffer).await?;
Ok(())
}
// Set the radio in reception mode with Boosted LNA gain and timeout specified
pub(super) async fn sub_set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
let buffer = [
Self::timeout_1(timeout),
Self::timeout_2(timeout),
Self::timeout_3(timeout),
];
self.brd_ant_set_rx()?;
self.brd_set_operating_mode(RadioMode::Receive);
// set max LNA gain, increase current by ~2mA for around ~3dB in sensitivity
self.brd_write_registers(Register::RxGain, &[0x96u8]).await?;
self.brd_write_command(OpCode::SetRx, &buffer).await?;
Ok(())
}
// Set the Rx duty cycle management parameters
pub(super) async fn sub_set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError<BUS>> {
let buffer = [
((rx_time >> 16) & 0xFF) as u8,
((rx_time >> 8) & 0xFF) as u8,
(rx_time & 0xFF) as u8,
((sleep_time >> 16) & 0xFF) as u8,
((sleep_time >> 8) & 0xFF) as u8,
(sleep_time & 0xFF) as u8,
];
// antenna settings ???
self.brd_write_command(OpCode::SetRxDutyCycle, &buffer).await?;
self.brd_set_operating_mode(RadioMode::ReceiveDutyCycle);
Ok(())
}
// Set the radio in CAD mode
pub(super) async fn sub_set_cad(&mut self) -> Result<(), RadioError<BUS>> {
self.brd_ant_set_rx()?;
self.brd_write_command(OpCode::SetCAD, &[]).await?;
self.brd_set_operating_mode(RadioMode::ChannelActivityDetection);
Ok(())
}
// Set the radio in continuous wave transmission mode
pub(super) async fn sub_set_tx_continuous_wave(&mut self) -> Result<(), RadioError<BUS>> {
self.brd_ant_set_tx()?;
self.brd_write_command(OpCode::SetTxContinuousWave, &[]).await?;
self.brd_set_operating_mode(RadioMode::Transmit);
Ok(())
}
// Set the radio in continuous preamble transmission mode
pub(super) async fn sub_set_tx_infinite_preamble(&mut self) -> Result<(), RadioError<BUS>> {
self.brd_ant_set_tx()?;
self.brd_write_command(OpCode::SetTxContinuousPremable, &[]).await?;
self.brd_set_operating_mode(RadioMode::Transmit);
Ok(())
}
// Decide which interrupt will stop the internal radio rx timer.
// false timer stop after header/syncword detection
// true timer stop after preamble detection
pub(super) async fn sub_set_stop_rx_timer_on_preamble_detect(
&mut self,
enable: bool,
) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetStopRxTimerOnPreamble, &[enable as u8])
.await?;
Ok(())
}
// Set the number of symbols the radio will wait to validate a reception
pub(super) async fn sub_set_lora_symb_num_timeout(&mut self, symb_num: u16) -> Result<(), RadioError<BUS>> {
let mut exp = 0u8;
let mut reg;
let mut mant = ((core::cmp::min(symb_num, SX126X_MAX_LORA_SYMB_NUM_TIMEOUT as u16) as u8) + 1) >> 1;
while mant > 31 {
mant = (mant + 3) >> 2;
exp += 1;
}
reg = mant << ((2 * exp) + 1);
self.brd_write_command(OpCode::SetLoRaSymbTimeout, &[reg]).await?;
if symb_num != 0 {
reg = exp + (mant << 3);
self.brd_write_registers(Register::SynchTimeout, &[reg]).await?;
}
Ok(())
}
// Set the power regulators operating mode (LDO or DC_DC). Using only LDO implies that the Rx or Tx current is doubled
pub(super) async fn sub_set_regulator_mode(&mut self, mode: RegulatorMode) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetRegulatorMode, &[mode.value()])
.await?;
Ok(())
}
// Calibrate the given radio block
pub(super) async fn sub_calibrate(&mut self, calibrate_params: CalibrationParams) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::Calibrate, &[calibrate_params.value()])
.await?;
Ok(())
}
// Calibrate the image rejection based on the given frequency
pub(super) async fn sub_calibrate_image(&mut self, freq: u32) -> Result<(), RadioError<BUS>> {
let mut cal_freq = [0x00u8, 0x00u8];
if freq > 900000000 {
cal_freq[0] = 0xE1;
cal_freq[1] = 0xE9;
} else if freq > 850000000 {
cal_freq[0] = 0xD7;
cal_freq[1] = 0xDB;
} else if freq > 770000000 {
cal_freq[0] = 0xC1;
cal_freq[1] = 0xC5;
} else if freq > 460000000 {
cal_freq[0] = 0x75;
cal_freq[1] = 0x81;
} else if freq > 425000000 {
cal_freq[0] = 0x6B;
cal_freq[1] = 0x6F;
}
self.brd_write_command(OpCode::CalibrateImage, &cal_freq).await?;
Ok(())
}
// Activate the extention of the timeout when a long preamble is used
pub(super) async fn sub_set_long_preamble(&mut self, _enable: u8) -> Result<(), RadioError<BUS>> {
Ok(()) // no operation currently
}
// Set the transmission parameters
// hp_max 0 for sx1261, 7 for sx1262
// device_sel 1 for sx1261, 0 for sx1262
// pa_lut 0 for 14dBm LUT, 1 for 22dBm LUT
pub(super) async fn sub_set_pa_config(
&mut self,
pa_duty_cycle: u8,
hp_max: u8,
device_sel: u8,
pa_lut: u8,
) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetPAConfig, &[pa_duty_cycle, hp_max, device_sel, pa_lut])
.await?;
Ok(())
}
// Define into which mode the chip goes after a TX / RX done
pub(super) async fn sub_set_rx_tx_fallback_mode(&mut self, fallback_mode: u8) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetTxFallbackMode, &[fallback_mode])
.await?;
Ok(())
}
// Set the IRQ mask and DIO masks
pub(super) async fn sub_set_dio_irq_params(
&mut self,
irq_mask: u16,
dio1_mask: u16,
dio2_mask: u16,
dio3_mask: u16,
) -> Result<(), RadioError<BUS>> {
let mut buffer = [0x00u8; 8];
buffer[0] = ((irq_mask >> 8) & 0x00FF) as u8;
buffer[1] = (irq_mask & 0x00FF) as u8;
buffer[2] = ((dio1_mask >> 8) & 0x00FF) as u8;
buffer[3] = (dio1_mask & 0x00FF) as u8;
buffer[4] = ((dio2_mask >> 8) & 0x00FF) as u8;
buffer[5] = (dio2_mask & 0x00FF) as u8;
buffer[6] = ((dio3_mask >> 8) & 0x00FF) as u8;
buffer[7] = (dio3_mask & 0x00FF) as u8;
self.brd_write_command(OpCode::CfgDIOIrq, &buffer).await?;
Ok(())
}
// Return the current IRQ status
pub(super) async fn sub_get_irq_status(&mut self) -> Result<u16, RadioError<BUS>> {
let mut irq_status = [0x00u8, 0x00u8];
self.brd_read_command(OpCode::GetIrqStatus, &mut irq_status).await?;
Ok(((irq_status[0] as u16) << 8) | (irq_status[1] as u16))
}
// Indicate if DIO2 is used to control an RF Switch
pub(super) async fn sub_set_dio2_as_rf_switch_ctrl(&mut self, enable: bool) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetRFSwitchMode, &[enable as u8]).await?;
Ok(())
}
// Indicate if the radio main clock is supplied from a TCXO
// tcxo_voltage voltage used to control the TCXO on/off from DIO3
// timeout duration given to the TCXO to go to 32MHz
pub(super) async fn sub_set_dio3_as_tcxo_ctrl(
&mut self,
tcxo_voltage: TcxoCtrlVoltage,
timeout: u32,
) -> Result<(), RadioError<BUS>> {
let buffer = [
tcxo_voltage.value() & 0x07,
Self::timeout_1(timeout),
Self::timeout_2(timeout),
Self::timeout_3(timeout),
];
self.brd_write_command(OpCode::SetTCXOMode, &buffer).await?;
Ok(())
}
// Set the RF frequency (Hz)
pub(super) async fn sub_set_rf_frequency(&mut self, frequency: u32) -> Result<(), RadioError<BUS>> {
let mut buffer = [0x00u8; 4];
if !self.image_calibrated {
self.sub_calibrate_image(frequency).await?;
self.image_calibrated = true;
}
let freq_in_pll_steps = Self::convert_freq_in_hz_to_pll_step(frequency);
buffer[0] = ((freq_in_pll_steps >> 24) & 0xFF) as u8;
buffer[1] = ((freq_in_pll_steps >> 16) & 0xFF) as u8;
buffer[2] = ((freq_in_pll_steps >> 8) & 0xFF) as u8;
buffer[3] = (freq_in_pll_steps & 0xFF) as u8;
self.brd_write_command(OpCode::SetRFFrequency, &buffer).await?;
Ok(())
}
// Set the radio for the given protocol (LoRa or GFSK). This method has to be called before setting RF frequency, modulation paramaters, and packet paramaters.
pub(super) async fn sub_set_packet_type(&mut self, packet_type: PacketType) -> Result<(), RadioError<BUS>> {
self.packet_type = packet_type;
self.brd_write_command(OpCode::SetPacketType, &[packet_type.value()])
.await?;
Ok(())
}
// Get the current radio protocol (LoRa or GFSK)
pub(super) fn sub_get_packet_type(&mut self) -> PacketType {
self.packet_type
}
// Set the transmission parameters
// power RF output power [-18..13] dBm
// ramp_time transmission ramp up time
pub(super) async fn sub_set_tx_params(
&mut self,
mut power: i8,
ramp_time: RampTime,
) -> Result<(), RadioError<BUS>> {
if self.brd_get_radio_type() == RadioType::SX1261 {
if power == 15 {
self.sub_set_pa_config(0x06, 0x00, 0x01, 0x01).await?;
} else {
self.sub_set_pa_config(0x04, 0x00, 0x01, 0x01).await?;
}
if power >= 14 {
power = 14;
} else if power < -17 {
power = -17;
}
} else {
// 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.brd_read_registers(Register::TxClampCfg, &mut tx_clamp_cfg).await?;
tx_clamp_cfg[0] = tx_clamp_cfg[0] | (0x0F << 1);
self.brd_write_registers(Register::TxClampCfg, &tx_clamp_cfg).await?;
self.sub_set_pa_config(0x04, 0x07, 0x00, 0x01).await?;
if power > 22 {
power = 22;
} else if power < -9 {
power = -9;
}
}
// power conversion of negative number from i8 to u8 ???
self.brd_write_command(OpCode::SetTxParams, &[power as u8, ramp_time.value()])
.await?;
Ok(())
}
// Set the modulation parameters
pub(super) async fn sub_set_modulation_params(&mut self) -> Result<(), RadioError<BUS>> {
if self.modulation_params.is_some() {
let mut buffer = [0x00u8; 4];
// Since this driver only supports LoRa, ensure the packet type is set accordingly
self.sub_set_packet_type(PacketType::LoRa).await?;
let modulation_params = self.modulation_params.unwrap();
buffer[0] = modulation_params.spreading_factor.value();
buffer[1] = modulation_params.bandwidth.value();
buffer[2] = modulation_params.coding_rate.value();
buffer[3] = modulation_params.low_data_rate_optimize;
self.brd_write_command(OpCode::SetModulationParams, &buffer).await?;
Ok(())
} else {
Err(RadioError::ModulationParamsMissing)
}
}
// Set the packet parameters
pub(super) async fn sub_set_packet_params(&mut self) -> Result<(), RadioError<BUS>> {
if self.packet_params.is_some() {
let mut buffer = [0x00u8; 6];
// Since this driver only supports LoRa, ensure the packet type is set accordingly
self.sub_set_packet_type(PacketType::LoRa).await?;
let packet_params = self.packet_params.unwrap();
buffer[0] = ((packet_params.preamble_length >> 8) & 0xFF) as u8;
buffer[1] = (packet_params.preamble_length & 0xFF) as u8;
buffer[2] = packet_params.implicit_header as u8;
buffer[3] = packet_params.payload_length;
buffer[4] = packet_params.crc_on as u8;
buffer[5] = packet_params.iq_inverted as u8;
self.brd_write_command(OpCode::SetPacketParams, &buffer).await?;
Ok(())
} else {
Err(RadioError::PacketParamsMissing)
}
}
// Set the channel activity detection (CAD) parameters
// symbols number of symbols to use for CAD operations
// det_peak limit for detection of SNR peak used in the CAD
// det_min minimum symbol recognition for CAD
// exit_mode operation to be done at the end of CAD action
// timeout timeout value to abort the CAD activity
pub(super) async fn sub_set_cad_params(
&mut self,
symbols: CADSymbols,
det_peak: u8,
det_min: u8,
exit_mode: CADExitMode,
timeout: u32,
) -> Result<(), RadioError<BUS>> {
let mut buffer = [0x00u8; 7];
buffer[0] = symbols.value();
buffer[1] = det_peak;
buffer[2] = det_min;
buffer[3] = exit_mode.value();
buffer[4] = Self::timeout_1(timeout);
buffer[5] = Self::timeout_2(timeout);
buffer[6] = Self::timeout_3(timeout);
self.brd_write_command(OpCode::SetCADParams, &buffer).await?;
self.brd_set_operating_mode(RadioMode::ChannelActivityDetection);
Ok(())
}
// Set the data buffer base address for transmission and reception
pub(super) async fn sub_set_buffer_base_address(
&mut self,
tx_base_address: u8,
rx_base_address: u8,
) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::SetBufferBaseAddress, &[tx_base_address, rx_base_address])
.await?;
Ok(())
}
// Get the current radio status
pub(super) async fn sub_get_status(&mut self) -> Result<RadioStatus, RadioError<BUS>> {
let status = self.brd_read_command(OpCode::GetStatus, &mut []).await?;
Ok(RadioStatus {
cmd_status: (status & (0x07 << 1)) >> 1,
chip_mode: (status & (0x07 << 4)) >> 4,
})
}
// Get the instantaneous RSSI value for the last packet received
pub(super) async fn sub_get_rssi_inst(&mut self) -> Result<i8, RadioError<BUS>> {
let mut buffer = [0x00u8];
self.brd_read_command(OpCode::GetRSSIInst, &mut buffer).await?;
let rssi: i8 = ((-(buffer[0] as i32)) >> 1) as i8; // check this ???
Ok(rssi)
}
// Get the last received packet buffer status
pub(super) async fn sub_get_rx_buffer_status(&mut self) -> Result<(u8, u8), RadioError<BUS>> {
if self.packet_params.is_some() {
let mut status = [0x00u8; 2];
let mut payload_length_buffer = [0x00u8];
self.brd_read_command(OpCode::GetRxBufferStatus, &mut status).await?;
if (self.sub_get_packet_type() == PacketType::LoRa) && self.packet_params.unwrap().implicit_header {
self.brd_read_registers(Register::PayloadLength, &mut payload_length_buffer)
.await?;
} else {
payload_length_buffer[0] = status[0];
}
let payload_length = payload_length_buffer[0];
let offset = status[1];
Ok((payload_length, offset))
} else {
Err(RadioError::PacketParamsMissing)
}
}
// Get the last received packet payload status
pub(super) async fn sub_get_packet_status(&mut self) -> Result<PacketStatus, RadioError<BUS>> {
let mut status = [0x00u8; 3];
self.brd_read_command(OpCode::GetPacketStatus, &mut status).await?;
// check this ???
let rssi = ((-(status[0] as i32)) >> 1) as i8;
let snr = ((status[1] as i8) + 2) >> 2;
let signal_rssi = ((-(status[2] as i32)) >> 1) as i8;
let freq_error = self.frequency_error;
Ok(PacketStatus {
rssi,
snr,
signal_rssi,
freq_error,
})
}
// Get the possible system errors
pub(super) async fn sub_get_device_errors(&mut self) -> Result<RadioSystemError, RadioError<BUS>> {
let mut errors = [0x00u8; 2];
self.brd_read_command(OpCode::GetErrors, &mut errors).await?;
Ok(RadioSystemError {
rc_64khz_calibration: (errors[1] & (1 << 0)) != 0,
rc_13mhz_calibration: (errors[1] & (1 << 1)) != 0,
pll_calibration: (errors[1] & (1 << 2)) != 0,
adc_calibration: (errors[1] & (1 << 3)) != 0,
image_calibration: (errors[1] & (1 << 4)) != 0,
xosc_start: (errors[1] & (1 << 5)) != 0,
pll_lock: (errors[1] & (1 << 6)) != 0,
pa_ramp: (errors[0] & (1 << 0)) != 0,
})
}
// Clear all the errors in the device
pub(super) async fn sub_clear_device_errors(&mut self) -> Result<(), RadioError<BUS>> {
self.brd_write_command(OpCode::ClrErrors, &[0x00u8, 0x00u8]).await?;
Ok(())
}
// Clear the IRQs
pub(super) async fn sub_clear_irq_status(&mut self, irq: u16) -> Result<(), RadioError<BUS>> {
let mut buffer = [0x00u8, 0x00u8];
buffer[0] = ((irq >> 8) & 0xFF) as u8;
buffer[1] = (irq & 0xFF) as u8;
self.brd_write_command(OpCode::ClrIrqStatus, &buffer).await?;
Ok(())
}
// Utility functions
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
}
// check this ???
fn convert_u8_buffer_to_u32(buffer: &[u8; 4]) -> u32 {
let b0 = buffer[0] as u32;
let b1 = buffer[1] as u32;
let b2 = buffer[2] as u32;
let b3 = buffer[3] as u32;
(b0 << 24) | (b1 << 16) | (b2 << 8) | b3
}
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)
}
}

View file

@ -1,192 +0,0 @@
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::digital::Wait;
use embedded_hal_async::spi::*;
use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig};
use lorawan_device::async_device::Timings;
mod sx127x_lora;
use sx127x_lora::{Error as RadioError, LoRa, RadioMode, IRQ};
/// Trait representing a radio switch for boards using the Sx127x radio. One some
/// boards, this will be a dummy implementation that does nothing.
pub trait RadioSwitch {
fn set_tx(&mut self);
fn set_rx(&mut self);
}
/// Semtech Sx127x radio peripheral
pub struct Sx127xRadio<SPI, CS, RESET, E, I, RFS>
where
SPI: SpiBus<u8, Error = E> + 'static,
E: 'static,
CS: OutputPin + 'static,
RESET: OutputPin + 'static,
I: Wait + 'static,
RFS: RadioSwitch + 'static,
{
radio: LoRa<SPI, CS, RESET>,
rfs: RFS,
irq: I,
}
#[derive(Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum State {
Idle,
Txing,
Rxing,
}
impl<SPI, CS, RESET, E, I, RFS> Sx127xRadio<SPI, CS, RESET, E, I, RFS>
where
SPI: SpiBus<u8, Error = E> + 'static,
CS: OutputPin + 'static,
RESET: OutputPin + 'static,
I: Wait + 'static,
RFS: RadioSwitch + 'static,
E: 'static,
{
pub async fn new(
spi: SPI,
cs: CS,
reset: RESET,
irq: I,
rfs: RFS,
) -> Result<Self, RadioError<E, CS::Error, RESET::Error>> {
let mut radio = LoRa::new(spi, cs, reset);
radio.reset().await?;
Ok(Self { radio, irq, rfs })
}
}
impl<SPI, CS, RESET, E, I, RFS> Timings for Sx127xRadio<SPI, CS, RESET, E, I, RFS>
where
SPI: SpiBus<u8, Error = E> + 'static,
CS: OutputPin + 'static,
RESET: OutputPin + 'static,
I: Wait + 'static,
RFS: RadioSwitch + 'static,
{
fn get_rx_window_offset_ms(&self) -> i32 {
-3
}
fn get_rx_window_duration_ms(&self) -> u32 {
1003
}
}
impl<SPI, CS, RESET, E, I, RFS> PhyRxTx for Sx127xRadio<SPI, CS, RESET, E, I, RFS>
where
SPI: SpiBus<u8, Error = E> + 'static,
CS: OutputPin + 'static,
E: 'static,
RESET: OutputPin + 'static,
I: Wait + 'static,
RFS: RadioSwitch + 'static,
{
type PhyError = Sx127xError;
async fn tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, Self::PhyError> {
trace!("TX START");
self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap();
self.rfs.set_tx();
self.radio.set_tx_power(14, 0).await?;
self.radio.set_frequency(config.rf.frequency).await?;
// TODO: Modify radio to support other coding rates
self.radio.set_coding_rate_4(5).await?;
self.radio
.set_signal_bandwidth(bandwidth_to_i64(config.rf.bandwidth))
.await?;
self.radio
.set_spreading_factor(spreading_factor_to_u8(config.rf.spreading_factor))
.await?;
self.radio.set_preamble_length(8).await?;
self.radio.set_lora_pa_ramp().await?;
self.radio.set_lora_sync_word().await?;
self.radio.set_invert_iq(false).await?;
self.radio.set_crc(true).await?;
self.radio.set_dio0_tx_done().await?;
self.radio.transmit_start(buf).await?;
loop {
self.irq.wait_for_rising_edge().await.unwrap();
self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap();
let irq = self.radio.clear_irq().await.ok().unwrap();
if (irq & IRQ::IrqTxDoneMask.addr()) != 0 {
trace!("TX DONE");
return Ok(0);
}
}
}
async fn rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), Self::PhyError> {
self.rfs.set_rx();
self.radio.reset_payload_length().await?;
self.radio.set_frequency(config.frequency).await?;
// TODO: Modify radio to support other coding rates
self.radio.set_coding_rate_4(5).await?;
self.radio
.set_signal_bandwidth(bandwidth_to_i64(config.bandwidth))
.await?;
self.radio
.set_spreading_factor(spreading_factor_to_u8(config.spreading_factor))
.await?;
self.radio.set_preamble_length(8).await?;
self.radio.set_lora_sync_word().await?;
self.radio.set_invert_iq(true).await?;
self.radio.set_crc(true).await?;
self.radio.set_dio0_rx_done().await?;
self.radio.set_mode(RadioMode::RxContinuous).await?;
loop {
self.irq.wait_for_rising_edge().await.unwrap();
self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap();
let irq = self.radio.clear_irq().await.ok().unwrap();
if (irq & IRQ::IrqRxDoneMask.addr()) != 0 {
let rssi = self.radio.get_packet_rssi().await.unwrap_or(0) as i16;
let snr = self.radio.get_packet_snr().await.unwrap_or(0.0) as i8;
let response = if let Ok(size) = self.radio.read_packet_size().await {
self.radio.read_packet(buf).await?;
Ok((size, RxQuality::new(rssi, snr)))
} else {
Ok((0, RxQuality::new(rssi, snr)))
};
trace!("RX DONE");
return response;
}
}
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Sx127xError;
impl<A, B, C> From<sx127x_lora::Error<A, B, C>> for Sx127xError {
fn from(_: sx127x_lora::Error<A, B, C>) -> Self {
Sx127xError
}
}
fn spreading_factor_to_u8(sf: SpreadingFactor) -> u8 {
match sf {
SpreadingFactor::_7 => 7,
SpreadingFactor::_8 => 8,
SpreadingFactor::_9 => 9,
SpreadingFactor::_10 => 10,
SpreadingFactor::_11 => 11,
SpreadingFactor::_12 => 12,
}
}
fn bandwidth_to_i64(bw: Bandwidth) -> i64 {
match bw {
Bandwidth::_125KHz => 125_000,
Bandwidth::_250KHz => 250_000,
Bandwidth::_500KHz => 500_000,
}
}

View file

@ -1,539 +0,0 @@
// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0
// license
//
// Modifications made to make the driver work with the rust-lorawan link layer.
#![allow(dead_code)]
use bit_field::BitField;
use embassy_time::{Duration, Timer};
use embedded_hal::digital::v2::OutputPin;
use embedded_hal_async::spi::SpiBus;
mod register;
pub use self::register::IRQ;
use self::register::{PaConfig, Register};
/// Provides high-level access to Semtech SX1276/77/78/79 based boards connected to a Raspberry Pi
pub struct LoRa<SPI, CS, RESET> {
spi: SPI,
cs: CS,
reset: RESET,
pub explicit_header: bool,
pub mode: RadioMode,
}
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error<SPI, CS, RESET> {
Uninformative,
VersionMismatch(u8),
CS(CS),
Reset(RESET),
SPI(SPI),
Transmitting,
}
use Error::*;
use super::sx127x_lora::register::{FskDataModulationShaping, FskRampUpRamDown};
#[cfg(not(feature = "version_0x09"))]
const VERSION_CHECK: u8 = 0x12;
#[cfg(feature = "version_0x09")]
const VERSION_CHECK: u8 = 0x09;
impl<SPI, CS, RESET, E> LoRa<SPI, CS, RESET>
where
SPI: SpiBus<u8, Error = E>,
CS: OutputPin,
RESET: OutputPin,
{
/// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time.
/// This also preforms a hardware reset of the module and then puts it in standby.
pub fn new(spi: SPI, cs: CS, reset: RESET) -> Self {
Self {
spi,
cs,
reset,
explicit_header: true,
mode: RadioMode::Sleep,
}
}
pub async fn reset(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.reset.set_low().map_err(Reset)?;
Timer::after(Duration::from_millis(10)).await;
self.reset.set_high().map_err(Reset)?;
Timer::after(Duration::from_millis(10)).await;
let version = self.read_register(Register::RegVersion.addr()).await?;
if version == VERSION_CHECK {
self.set_mode(RadioMode::Sleep).await?;
self.write_register(Register::RegFifoTxBaseAddr.addr(), 0).await?;
self.write_register(Register::RegFifoRxBaseAddr.addr(), 0).await?;
let lna = self.read_register(Register::RegLna.addr()).await?;
self.write_register(Register::RegLna.addr(), lna | 0x03).await?;
self.write_register(Register::RegModemConfig3.addr(), 0x04).await?;
self.set_tcxo(true).await?;
self.set_mode(RadioMode::Stdby).await?;
self.cs.set_high().map_err(CS)?;
Ok(())
} else {
Err(Error::VersionMismatch(version))
}
}
pub async fn set_dio0_tx_done(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.write_register(Register::RegIrqFlagsMask.addr(), 0b1111_0111)
.await?;
let mapping = self.read_register(Register::RegDioMapping1.addr()).await?;
self.write_register(Register::RegDioMapping1.addr(), (mapping & 0x3F) | 0x40)
.await
}
pub async fn set_dio0_rx_done(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.write_register(Register::RegIrqFlagsMask.addr(), 0b0001_1111)
.await?;
let mapping = self.read_register(Register::RegDioMapping1.addr()).await?;
self.write_register(Register::RegDioMapping1.addr(), mapping & 0x3F)
.await
}
pub async fn transmit_start(&mut self, buffer: &[u8]) -> Result<(), Error<E, CS::Error, RESET::Error>> {
assert!(buffer.len() < 255);
if self.transmitting().await? {
//trace!("ALREADY TRANSMNITTING");
Err(Transmitting)
} else {
self.set_mode(RadioMode::Stdby).await?;
if self.explicit_header {
self.set_explicit_header_mode().await?;
} else {
self.set_implicit_header_mode().await?;
}
self.write_register(Register::RegIrqFlags.addr(), 0).await?;
self.write_register(Register::RegFifoAddrPtr.addr(), 0).await?;
self.write_register(Register::RegPayloadLength.addr(), 0).await?;
for byte in buffer.iter() {
self.write_register(Register::RegFifo.addr(), *byte).await?;
}
self.write_register(Register::RegPayloadLength.addr(), buffer.len() as u8)
.await?;
self.set_mode(RadioMode::Tx).await?;
Ok(())
}
}
pub async fn packet_ready(&mut self) -> Result<bool, Error<E, CS::Error, RESET::Error>> {
Ok(self.read_register(Register::RegIrqFlags.addr()).await?.get_bit(6))
}
pub async fn irq_flags_mask(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
Ok(self.read_register(Register::RegIrqFlagsMask.addr()).await? as u8)
}
pub async fn irq_flags(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
Ok(self.read_register(Register::RegIrqFlags.addr()).await? as u8)
}
pub async fn read_packet_size(&mut self) -> Result<usize, Error<E, CS::Error, RESET::Error>> {
let size = self.read_register(Register::RegRxNbBytes.addr()).await?;
Ok(size as usize)
}
/// Returns the contents of the fifo as a fixed 255 u8 array. This should only be called is there is a
/// new packet ready to be read.
pub async fn read_packet(&mut self, buffer: &mut [u8]) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.clear_irq().await?;
let size = self.read_register(Register::RegRxNbBytes.addr()).await?;
assert!(size as usize <= buffer.len());
let fifo_addr = self.read_register(Register::RegFifoRxCurrentAddr.addr()).await?;
self.write_register(Register::RegFifoAddrPtr.addr(), fifo_addr).await?;
for i in 0..size {
let byte = self.read_register(Register::RegFifo.addr()).await?;
buffer[i as usize] = byte;
}
self.write_register(Register::RegFifoAddrPtr.addr(), 0).await?;
Ok(())
}
/// Returns true if the radio is currently transmitting a packet.
pub async fn transmitting(&mut self) -> Result<bool, Error<E, CS::Error, RESET::Error>> {
if (self.read_register(Register::RegOpMode.addr()).await?) & RadioMode::Tx.addr() == RadioMode::Tx.addr() {
Ok(true)
} else {
if (self.read_register(Register::RegIrqFlags.addr()).await? & IRQ::IrqTxDoneMask.addr()) == 1 {
self.write_register(Register::RegIrqFlags.addr(), IRQ::IrqTxDoneMask.addr())
.await?;
}
Ok(false)
}
}
/// Clears the radio's IRQ registers.
pub async fn clear_irq(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
let irq_flags = self.read_register(Register::RegIrqFlags.addr()).await?;
self.write_register(Register::RegIrqFlags.addr(), 0xFF).await?;
Ok(irq_flags)
}
/// Sets the transmit power and pin. Levels can range from 0-14 when the output
/// pin = 0(RFO), and form 0-20 when output pin = 1(PaBoost). Power is in dB.
/// Default value is `17`.
pub async fn set_tx_power(
&mut self,
mut level: i32,
output_pin: u8,
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
if PaConfig::PaOutputRfoPin.addr() == output_pin {
// RFO
if level < 0 {
level = 0;
} else if level > 14 {
level = 14;
}
self.write_register(Register::RegPaConfig.addr(), (0x70 | level) as u8)
.await
} else {
// PA BOOST
if level > 17 {
if level > 20 {
level = 20;
}
// subtract 3 from level, so 18 - 20 maps to 15 - 17
level -= 3;
// High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.)
self.write_register(Register::RegPaDac.addr(), 0x87).await?;
self.set_ocp(140).await?;
} else {
if level < 2 {
level = 2;
}
//Default value PA_HF/LF or +17dBm
self.write_register(Register::RegPaDac.addr(), 0x84).await?;
self.set_ocp(100).await?;
}
level -= 2;
self.write_register(Register::RegPaConfig.addr(), PaConfig::PaBoost.addr() | level as u8)
.await
}
}
pub async fn get_modem_stat(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
Ok(self.read_register(Register::RegModemStat.addr()).await? as u8)
}
/// Sets the over current protection on the radio(mA).
pub async fn set_ocp(&mut self, ma: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let mut ocp_trim: u8 = 27;
if ma <= 120 {
ocp_trim = (ma - 45) / 5;
} else if ma <= 240 {
ocp_trim = (ma + 30) / 10;
}
self.write_register(Register::RegOcp.addr(), 0x20 | (0x1F & ocp_trim))
.await
}
/// Sets the state of the radio. Default mode after initiation is `Standby`.
pub async fn set_mode(&mut self, mode: RadioMode) -> Result<(), Error<E, CS::Error, RESET::Error>> {
if self.explicit_header {
self.set_explicit_header_mode().await?;
} else {
self.set_implicit_header_mode().await?;
}
self.write_register(
Register::RegOpMode.addr(),
RadioMode::LongRangeMode.addr() | mode.addr(),
)
.await?;
self.mode = mode;
Ok(())
}
pub async fn reset_payload_length(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.write_register(Register::RegPayloadLength.addr(), 0xFF).await
}
/// Sets the frequency of the radio. Values are in megahertz.
/// I.E. 915 MHz must be used for North America. Check regulation for your area.
pub async fn set_frequency(&mut self, freq: u32) -> Result<(), Error<E, CS::Error, RESET::Error>> {
const FREQ_STEP: f64 = 61.03515625;
// calculate register values
let frf = (freq as f64 / FREQ_STEP) as u32;
// write registers
self.write_register(Register::RegFrfMsb.addr(), ((frf & 0x00FF_0000) >> 16) as u8)
.await?;
self.write_register(Register::RegFrfMid.addr(), ((frf & 0x0000_FF00) >> 8) as u8)
.await?;
self.write_register(Register::RegFrfLsb.addr(), (frf & 0x0000_00FF) as u8)
.await
}
/// Sets the radio to use an explicit header. Default state is `ON`.
async fn set_explicit_header_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0xfe)
.await?;
self.explicit_header = true;
Ok(())
}
/// Sets the radio to use an implicit header. Default state is `OFF`.
async fn set_implicit_header_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0x01)
.await?;
self.explicit_header = false;
Ok(())
}
/// Sets the spreading factor of the radio. Supported values are between 6 and 12.
/// If a spreading factor of 6 is set, implicit header mode must be used to transmit
/// and receive packets. Default value is `7`.
pub async fn set_spreading_factor(&mut self, mut sf: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
if sf < 6 {
sf = 6;
} else if sf > 12 {
sf = 12;
}
if sf == 6 {
self.write_register(Register::RegDetectionOptimize.addr(), 0xc5).await?;
self.write_register(Register::RegDetectionThreshold.addr(), 0x0c)
.await?;
} else {
self.write_register(Register::RegDetectionOptimize.addr(), 0xc3).await?;
self.write_register(Register::RegDetectionThreshold.addr(), 0x0a)
.await?;
}
let modem_config_2 = self.read_register(Register::RegModemConfig2.addr()).await?;
self.write_register(
Register::RegModemConfig2.addr(),
(modem_config_2 & 0x0f) | ((sf << 4) & 0xf0),
)
.await?;
self.set_ldo_flag().await?;
self.write_register(Register::RegSymbTimeoutLsb.addr(), 0x05).await?;
Ok(())
}
pub async fn set_tcxo(&mut self, external: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
if external {
self.write_register(Register::RegTcxo.addr(), 0x10).await
} else {
self.write_register(Register::RegTcxo.addr(), 0x00).await
}
}
/// Sets the signal bandwidth of the radio. Supported values are: `7800 Hz`, `10400 Hz`,
/// `15600 Hz`, `20800 Hz`, `31250 Hz`,`41700 Hz` ,`62500 Hz`,`125000 Hz` and `250000 Hz`
/// Default value is `125000 Hz`
pub async fn set_signal_bandwidth(&mut self, sbw: i64) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let bw: i64 = match sbw {
7_800 => 0,
10_400 => 1,
15_600 => 2,
20_800 => 3,
31_250 => 4,
41_700 => 5,
62_500 => 6,
125_000 => 7,
250_000 => 8,
_ => 9,
};
let modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
self.write_register(
Register::RegModemConfig1.addr(),
(modem_config_1 & 0x0f) | ((bw << 4) as u8),
)
.await?;
self.set_ldo_flag().await?;
Ok(())
}
/// Sets the coding rate of the radio with the numerator fixed at 4. Supported values
/// are between `5` and `8`, these correspond to coding rates of `4/5` and `4/8`.
/// Default value is `5`.
pub async fn set_coding_rate_4(&mut self, mut denominator: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
if denominator < 5 {
denominator = 5;
} else if denominator > 8 {
denominator = 8;
}
let cr = denominator - 4;
let modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?;
self.write_register(Register::RegModemConfig1.addr(), (modem_config_1 & 0xf1) | (cr << 1))
.await
}
/// Sets the preamble length of the radio. Values are between 6 and 65535.
/// Default value is `8`.
pub async fn set_preamble_length(&mut self, length: i64) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.write_register(Register::RegPreambleMsb.addr(), (length >> 8) as u8)
.await?;
self.write_register(Register::RegPreambleLsb.addr(), length as u8).await
}
/// Enables are disables the radio's CRC check. Default value is `false`.
pub async fn set_crc(&mut self, value: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let modem_config_2 = self.read_register(Register::RegModemConfig2.addr()).await?;
if value {
self.write_register(Register::RegModemConfig2.addr(), modem_config_2 | 0x04)
.await
} else {
self.write_register(Register::RegModemConfig2.addr(), modem_config_2 & 0xfb)
.await
}
}
/// Inverts the radio's IQ signals. Default value is `false`.
pub async fn set_invert_iq(&mut self, value: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
if value {
self.write_register(Register::RegInvertiq.addr(), 0x66).await?;
self.write_register(Register::RegInvertiq2.addr(), 0x19).await
} else {
self.write_register(Register::RegInvertiq.addr(), 0x27).await?;
self.write_register(Register::RegInvertiq2.addr(), 0x1d).await
}
}
/// Returns the spreading factor of the radio.
pub async fn get_spreading_factor(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
Ok(self.read_register(Register::RegModemConfig2.addr()).await? >> 4)
}
/// Returns the signal bandwidth of the radio.
pub async fn get_signal_bandwidth(&mut self) -> Result<i64, Error<E, CS::Error, RESET::Error>> {
let bw = self.read_register(Register::RegModemConfig1.addr()).await? >> 4;
let bw = match bw {
0 => 7_800,
1 => 10_400,
2 => 15_600,
3 => 20_800,
4 => 31_250,
5 => 41_700,
6 => 62_500,
7 => 125_000,
8 => 250_000,
9 => 500_000,
_ => -1,
};
Ok(bw)
}
/// Returns the RSSI of the last received packet.
pub async fn get_packet_rssi(&mut self) -> Result<i32, Error<E, CS::Error, RESET::Error>> {
Ok(i32::from(self.read_register(Register::RegPktRssiValue.addr()).await?) - 157)
}
/// Returns the signal to noise radio of the the last received packet.
pub async fn get_packet_snr(&mut self) -> Result<f64, Error<E, CS::Error, RESET::Error>> {
Ok(f64::from(self.read_register(Register::RegPktSnrValue.addr()).await?))
}
/// Returns the frequency error of the last received packet in Hz.
pub async fn get_packet_frequency_error(&mut self) -> Result<i64, Error<E, CS::Error, RESET::Error>> {
let mut freq_error: i32;
freq_error = i32::from(self.read_register(Register::RegFreqErrorMsb.addr()).await? & 0x7);
freq_error <<= 8i64;
freq_error += i32::from(self.read_register(Register::RegFreqErrorMid.addr()).await?);
freq_error <<= 8i64;
freq_error += i32::from(self.read_register(Register::RegFreqErrorLsb.addr()).await?);
let f_xtal = 32_000_000; // FXOSC: crystal oscillator (XTAL) frequency (2.5. Chip Specification, p. 14)
let f_error = ((f64::from(freq_error) * (1i64 << 24) as f64) / f64::from(f_xtal))
* (self.get_signal_bandwidth().await? as f64 / 500_000.0f64); // p. 37
Ok(f_error as i64)
}
async fn set_ldo_flag(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let sw = self.get_signal_bandwidth().await?;
// Section 4.1.1.5
let symbol_duration = 1000 / (sw / ((1_i64) << self.get_spreading_factor().await?));
// Section 4.1.1.6
let ldo_on = symbol_duration > 16;
let mut config_3 = self.read_register(Register::RegModemConfig3.addr()).await?;
config_3.set_bit(3, ldo_on);
//config_3.set_bit(2, true);
self.write_register(Register::RegModemConfig3.addr(), config_3).await
}
async fn read_register(&mut self, reg: u8) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
let mut buffer = [reg & 0x7f, 0];
self.cs.set_low().map_err(CS)?;
let _ = self.spi.transfer(&mut buffer, &[reg & 0x7f, 0]).await.map_err(SPI)?;
self.cs.set_high().map_err(CS)?;
Ok(buffer[1])
}
async fn write_register(&mut self, reg: u8, byte: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.cs.set_low().map_err(CS)?;
let buffer = [reg | 0x80, byte];
self.spi.write(&buffer).await.map_err(SPI)?;
self.cs.set_high().map_err(CS)?;
Ok(())
}
pub async fn put_in_fsk_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
// Put in FSK mode
let mut op_mode = 0;
op_mode
.set_bit(7, false) // FSK mode
.set_bits(5..6, 0x00) // FSK modulation
.set_bit(3, false) //Low freq registers
.set_bits(0..2, 0b011); // Mode
self.write_register(Register::RegOpMode as u8, op_mode).await
}
pub async fn set_fsk_pa_ramp(
&mut self,
modulation_shaping: FskDataModulationShaping,
ramp: FskRampUpRamDown,
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
let mut pa_ramp = 0;
pa_ramp
.set_bits(5..6, modulation_shaping as u8)
.set_bits(0..3, ramp as u8);
self.write_register(Register::RegPaRamp as u8, pa_ramp).await
}
pub async fn set_lora_pa_ramp(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.write_register(Register::RegPaRamp as u8, 0b1000).await
}
pub async fn set_lora_sync_word(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
self.write_register(Register::RegSyncWord as u8, 0x34).await
}
}
/// Modes of the radio and their corresponding register values.
#[derive(Clone, Copy)]
pub enum RadioMode {
LongRangeMode = 0x80,
Sleep = 0x00,
Stdby = 0x01,
Tx = 0x03,
RxContinuous = 0x05,
RxSingle = 0x06,
}
impl RadioMode {
/// Returns the address of the mode.
pub fn addr(self) -> u8 {
self as u8
}
}

View file

@ -1,107 +0,0 @@
// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0
// license
//
// Modifications made to make the driver work with the rust-lorawan link layer.
#![allow(dead_code, clippy::enum_variant_names)]
#[derive(Clone, Copy)]
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,
RegTcxo = 0x4b,
RegPaDac = 0x4d,
}
#[derive(Clone, Copy)]
pub enum PaConfig {
PaBoost = 0x80,
PaOutputRfoPin = 0,
}
#[derive(Clone, Copy)]
pub enum IRQ {
IrqTxDoneMask = 0x08,
IrqPayloadCrcErrorMask = 0x20,
IrqRxDoneMask = 0x40,
}
impl Register {
pub fn addr(self) -> u8 {
self as u8
}
}
impl PaConfig {
pub fn addr(self) -> u8 {
self as u8
}
}
impl IRQ {
pub fn addr(self) -> u8 {
self as u8
}
}
#[derive(Clone, Copy)]
pub enum FskDataModulationShaping {
None = 1,
GaussianBt1d0 = 2,
GaussianBt0d5 = 10,
GaussianBt0d3 = 11,
}
#[derive(Clone, Copy)]
pub enum FskRampUpRamDown {
_3d4ms = 0b000,
_2ms = 0b0001,
_1ms = 0b0010,
_500us = 0b0011,
_250us = 0b0100,
_125us = 0b0101,
_100us = 0b0110,
_62us = 0b0111,
_50us = 0b1000,
_40us = 0b1001,
_31us = 0b1010,
_25us = 0b1011,
_20us = 0b1100,
_15us = 0b1101,
_12us = 0b1110,
_10us = 0b1111,
}

View file

@ -57,9 +57,6 @@ pub mod rtc;
pub mod sdmmc;
#[cfg(spi)]
pub mod spi;
#[cfg(stm32wl)]
#[deprecated(note = "use the external LoRa physical layer crate - https://crates.io/crates/lora-phy")]
pub mod subghz;
#[cfg(usart)]
pub mod usart;
#[cfg(all(usb, feature = "time"))]

View file

@ -1,160 +0,0 @@
/// Bit synchronization.
///
/// This must be cleared to `0x00` (the reset value) when using packet types
/// other than LoRa.
///
/// Argument of [`set_bit_sync`](crate::subghz::SubGhz::set_bit_sync).
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BitSync {
val: u8,
}
impl BitSync {
/// Bit synchronization register reset value.
pub const RESET: BitSync = BitSync { val: 0x00 };
/// Create a new [`BitSync`] structure from a raw value.
///
/// Reserved bits will be masked.
pub const fn from_raw(raw: u8) -> Self {
Self { val: raw & 0x70 }
}
/// Get the raw value of the [`BitSync`] register.
pub const fn as_bits(&self) -> u8 {
self.val
}
/// LoRa simple bit synchronization enable.
///
/// # Example
///
/// Enable simple bit synchronization.
///
/// ```
/// use stm32wlxx_hal::subghz::BitSync;
///
/// const BIT_SYNC: BitSync = BitSync::RESET.set_simple_bit_sync_en(true);
/// # assert_eq!(u8::from(BIT_SYNC), 0x40u8);
/// ```
#[must_use = "set_simple_bit_sync_en returns a modified BitSync"]
pub const fn set_simple_bit_sync_en(mut self, en: bool) -> BitSync {
if en {
self.val |= 1 << 6;
} else {
self.val &= !(1 << 6);
}
self
}
/// Returns `true` if simple bit synchronization is enabled.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::BitSync;
///
/// let bs: BitSync = BitSync::RESET;
/// assert_eq!(bs.simple_bit_sync_en(), false);
/// let bs: BitSync = bs.set_simple_bit_sync_en(true);
/// assert_eq!(bs.simple_bit_sync_en(), true);
/// let bs: BitSync = bs.set_simple_bit_sync_en(false);
/// assert_eq!(bs.simple_bit_sync_en(), false);
/// ```
pub const fn simple_bit_sync_en(&self) -> bool {
self.val & (1 << 6) != 0
}
/// LoRa RX data inversion.
///
/// # Example
///
/// Invert receive data.
///
/// ```
/// use stm32wlxx_hal::subghz::BitSync;
///
/// const BIT_SYNC: BitSync = BitSync::RESET.set_rx_data_inv(true);
/// # assert_eq!(u8::from(BIT_SYNC), 0x20u8);
/// ```
#[must_use = "set_rx_data_inv returns a modified BitSync"]
pub const fn set_rx_data_inv(mut self, inv: bool) -> BitSync {
if inv {
self.val |= 1 << 5;
} else {
self.val &= !(1 << 5);
}
self
}
/// Returns `true` if LoRa RX data is inverted.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::BitSync;
///
/// let bs: BitSync = BitSync::RESET;
/// assert_eq!(bs.rx_data_inv(), false);
/// let bs: BitSync = bs.set_rx_data_inv(true);
/// assert_eq!(bs.rx_data_inv(), true);
/// let bs: BitSync = bs.set_rx_data_inv(false);
/// assert_eq!(bs.rx_data_inv(), false);
/// ```
pub const fn rx_data_inv(&self) -> bool {
self.val & (1 << 5) != 0
}
/// LoRa normal bit synchronization enable.
///
/// # Example
///
/// Enable normal bit synchronization.
///
/// ```
/// use stm32wlxx_hal::subghz::BitSync;
///
/// const BIT_SYNC: BitSync = BitSync::RESET.set_norm_bit_sync_en(true);
/// # assert_eq!(u8::from(BIT_SYNC), 0x10u8);
/// ```
#[must_use = "set_norm_bit_sync_en returns a modified BitSync"]
pub const fn set_norm_bit_sync_en(mut self, en: bool) -> BitSync {
if en {
self.val |= 1 << 4;
} else {
self.val &= !(1 << 4);
}
self
}
/// Returns `true` if normal bit synchronization is enabled.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::BitSync;
///
/// let bs: BitSync = BitSync::RESET;
/// assert_eq!(bs.norm_bit_sync_en(), false);
/// let bs: BitSync = bs.set_norm_bit_sync_en(true);
/// assert_eq!(bs.norm_bit_sync_en(), true);
/// let bs: BitSync = bs.set_norm_bit_sync_en(false);
/// assert_eq!(bs.norm_bit_sync_en(), false);
/// ```
pub const fn norm_bit_sync_en(&self) -> bool {
self.val & (1 << 4) != 0
}
}
impl From<BitSync> for u8 {
fn from(bs: BitSync) -> Self {
bs.val
}
}
impl Default for BitSync {
fn default() -> Self {
Self::RESET
}
}

View file

@ -1,230 +0,0 @@
use super::Timeout;
/// Number of symbols used for channel activity detection scans.
///
/// Argument of [`CadParams::set_num_symbol`].
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum NbCadSymbol {
/// 1 symbol.
S1 = 0x0,
/// 2 symbols.
S2 = 0x1,
/// 4 symbols.
S4 = 0x2,
/// 8 symbols.
S8 = 0x3,
/// 16 symbols.
S16 = 0x4,
}
/// Mode to enter after a channel activity detection scan is finished.
///
/// Argument of [`CadParams::set_exit_mode`].
#[derive(Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum ExitMode {
/// Standby with RC 13 MHz mode entry after CAD.
Standby = 0,
/// Standby with RC 13 MHz mode after CAD if no LoRa symbol is detected
/// during the CAD scan.
/// If a LoRa symbol is detected, the sub-GHz radio stays in RX mode
/// until a packet is received or until the CAD timeout is reached.
StandbyLoRa = 1,
}
/// Channel activity detection (CAD) parameters.
///
/// Argument of [`set_cad_params`].
///
/// # Recommended CAD settings
///
/// This is taken directly from the datasheet.
///
/// "The correct values selected in the table below must be carefully tested to
/// ensure a good detection at sensitivity level and to limit the number of
/// false detections"
///
/// | SF (Spreading Factor) | [`set_det_peak`] | [`set_det_min`] |
/// |-----------------------|------------------|-----------------|
/// | 5 | 0x18 | 0x10 |
/// | 6 | 0x19 | 0x10 |
/// | 7 | 0x20 | 0x10 |
/// | 8 | 0x21 | 0x10 |
/// | 9 | 0x22 | 0x10 |
/// | 10 | 0x23 | 0x10 |
/// | 11 | 0x24 | 0x10 |
/// | 12 | 0x25 | 0x10 |
///
/// [`set_cad_params`]: crate::subghz::SubGhz::set_cad_params
/// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak
/// [`set_det_min`]: crate::subghz::CadParams::set_det_min
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CadParams {
buf: [u8; 8],
}
impl CadParams {
/// Create a new `CadParams`.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::CadParams;
///
/// const CAD_PARAMS: CadParams = CadParams::new();
/// assert_eq!(CAD_PARAMS, CadParams::default());
/// ```
pub const fn new() -> CadParams {
CadParams {
buf: [super::OpCode::SetCadParams as u8, 0, 0, 0, 0, 0, 0, 0],
}
.set_num_symbol(NbCadSymbol::S1)
.set_det_peak(0x18)
.set_det_min(0x10)
.set_exit_mode(ExitMode::Standby)
}
/// Number of symbols used for a CAD scan.
///
/// # Example
///
/// Set the number of symbols to 4.
///
/// ```
/// use stm32wlxx_hal::subghz::{CadParams, NbCadSymbol};
///
/// const CAD_PARAMS: CadParams = CadParams::new().set_num_symbol(NbCadSymbol::S4);
/// # assert_eq!(CAD_PARAMS.as_slice()[1], 0x2);
/// ```
#[must_use = "set_num_symbol returns a modified CadParams"]
pub const fn set_num_symbol(mut self, nb: NbCadSymbol) -> CadParams {
self.buf[1] = nb as u8;
self
}
/// Used with [`set_det_min`] to correlate the LoRa symbol.
///
/// See the table in [`CadParams`] docs for recommended values.
///
/// # Example
///
/// Setting the recommended value for a spreading factor of 7.
///
/// ```
/// use stm32wlxx_hal::subghz::CadParams;
///
/// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x20).set_det_min(0x10);
/// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x20);
/// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10);
/// ```
///
/// [`set_det_min`]: crate::subghz::CadParams::set_det_min
#[must_use = "set_det_peak returns a modified CadParams"]
pub const fn set_det_peak(mut self, peak: u8) -> CadParams {
self.buf[2] = peak;
self
}
/// Used with [`set_det_peak`] to correlate the LoRa symbol.
///
/// See the table in [`CadParams`] docs for recommended values.
///
/// # Example
///
/// Setting the recommended value for a spreading factor of 6.
///
/// ```
/// use stm32wlxx_hal::subghz::CadParams;
///
/// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x18).set_det_min(0x10);
/// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x18);
/// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10);
/// ```
///
/// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak
#[must_use = "set_det_min returns a modified CadParams"]
pub const fn set_det_min(mut self, min: u8) -> CadParams {
self.buf[3] = min;
self
}
/// Mode to enter after a channel activity detection scan is finished.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CadParams, ExitMode};
///
/// const CAD_PARAMS: CadParams = CadParams::new().set_exit_mode(ExitMode::Standby);
/// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x00);
/// # assert_eq!(CAD_PARAMS.set_exit_mode(ExitMode::StandbyLoRa).as_slice()[4], 0x01);
/// ```
#[must_use = "set_exit_mode returns a modified CadParams"]
pub const fn set_exit_mode(mut self, mode: ExitMode) -> CadParams {
self.buf[4] = mode as u8;
self
}
/// Set the timeout.
///
/// This is only used with [`ExitMode::StandbyLoRa`].
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CadParams, ExitMode, Timeout};
///
/// const TIMEOUT: Timeout = Timeout::from_raw(0x123456);
/// const CAD_PARAMS: CadParams = CadParams::new()
/// .set_exit_mode(ExitMode::StandbyLoRa)
/// .set_timeout(TIMEOUT);
/// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x01);
/// # assert_eq!(CAD_PARAMS.as_slice()[5], 0x12);
/// # assert_eq!(CAD_PARAMS.as_slice()[6], 0x34);
/// # assert_eq!(CAD_PARAMS.as_slice()[7], 0x56);
/// ```
#[must_use = "set_timeout returns a modified CadParams"]
pub const fn set_timeout(mut self, to: Timeout) -> CadParams {
let to_bytes: [u8; 3] = to.as_bytes();
self.buf[5] = to_bytes[0];
self.buf[6] = to_bytes[1];
self.buf[7] = to_bytes[2];
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CadParams, ExitMode, NbCadSymbol, Timeout};
///
/// const TIMEOUT: Timeout = Timeout::from_raw(0x123456);
/// const CAD_PARAMS: CadParams = CadParams::new()
/// .set_num_symbol(NbCadSymbol::S4)
/// .set_det_peak(0x18)
/// .set_det_min(0x10)
/// .set_exit_mode(ExitMode::StandbyLoRa)
/// .set_timeout(TIMEOUT);
///
/// assert_eq!(
/// CAD_PARAMS.as_slice(),
/// &[0x88, 0x02, 0x18, 0x10, 0x01, 0x12, 0x34, 0x56]
/// );
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for CadParams {
fn default() -> Self {
Self::new()
}
}

View file

@ -1,122 +0,0 @@
/// Image calibration.
///
/// Argument of [`calibrate_image`].
///
/// [`calibrate_image`]: crate::subghz::SubGhz::calibrate_image
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CalibrateImage(pub(crate) u8, pub(crate) u8);
impl CalibrateImage {
/// Image calibration for the 430 - 440 MHz ISM band.
pub const ISM_430_440: CalibrateImage = CalibrateImage(0x6B, 0x6F);
/// Image calibration for the 470 - 510 MHz ISM band.
pub const ISM_470_510: CalibrateImage = CalibrateImage(0x75, 0x81);
/// Image calibration for the 779 - 787 MHz ISM band.
pub const ISM_779_787: CalibrateImage = CalibrateImage(0xC1, 0xC5);
/// Image calibration for the 863 - 870 MHz ISM band.
pub const ISM_863_870: CalibrateImage = CalibrateImage(0xD7, 0xDB);
/// Image calibration for the 902 - 928 MHz ISM band.
pub const ISM_902_928: CalibrateImage = CalibrateImage(0xE1, 0xE9);
/// Create a new `CalibrateImage` structure from raw values.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::CalibrateImage;
///
/// const CAL: CalibrateImage = CalibrateImage::new(0xE1, 0xE9);
/// assert_eq!(CAL, CalibrateImage::ISM_902_928);
/// ```
pub const fn new(f1: u8, f2: u8) -> CalibrateImage {
CalibrateImage(f1, f2)
}
/// Create a new `CalibrateImage` structure from two frequencies.
///
/// # Arguments
///
/// The units for `freq1` and `freq2` are in MHz.
///
/// # Panics
///
/// * Panics if `freq1` is less than `freq2`.
/// * Panics if `freq1` or `freq2` is not a multiple of 4MHz.
/// * Panics if `freq1` or `freq2` is greater than `1020`.
///
/// # Example
///
/// Create an image calibration for the 430 - 440 MHz ISM band.
///
/// ```
/// use stm32wlxx_hal::subghz::CalibrateImage;
///
/// let cal: CalibrateImage = CalibrateImage::from_freq(428, 444);
/// assert_eq!(cal, CalibrateImage::ISM_430_440);
/// ```
pub fn from_freq(freq1: u16, freq2: u16) -> CalibrateImage {
assert!(freq2 >= freq1);
assert_eq!(freq1 % 4, 0);
assert_eq!(freq2 % 4, 0);
assert!(freq1 <= 1020);
assert!(freq2 <= 1020);
CalibrateImage((freq1 / 4) as u8, (freq2 / 4) as u8)
}
}
impl Default for CalibrateImage {
fn default() -> Self {
CalibrateImage::new(0xE1, 0xE9)
}
}
/// Block calibration.
///
/// Argument of [`calibrate`].
///
/// [`calibrate`]: crate::subghz::SubGhz::calibrate
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum Calibrate {
/// Image calibration
Image = 1 << 6,
/// RF-ADC bulk P calibration
AdcBulkP = 1 << 5,
/// RF-ADC bulk N calibration
AdcBulkN = 1 << 4,
/// RF-ADC pulse calibration
AdcPulse = 1 << 3,
/// RF-PLL calibration
Pll = 1 << 2,
/// Sub-GHz radio RC 13 MHz calibration
Rc13M = 1 << 1,
/// Sub-GHz radio RC 64 kHz calibration
Rc64K = 1,
}
impl Calibrate {
/// Get the bitmask for the block calibration.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Calibrate;
///
/// assert_eq!(Calibrate::Image.mask(), 0b0100_0000);
/// assert_eq!(Calibrate::AdcBulkP.mask(), 0b0010_0000);
/// assert_eq!(Calibrate::AdcBulkN.mask(), 0b0001_0000);
/// assert_eq!(Calibrate::AdcPulse.mask(), 0b0000_1000);
/// assert_eq!(Calibrate::Pll.mask(), 0b0000_0100);
/// assert_eq!(Calibrate::Rc13M.mask(), 0b0000_0010);
/// assert_eq!(Calibrate::Rc64K.mask(), 0b0000_0001);
/// ```
pub const fn mask(self) -> u8 {
self as u8
}
}

View file

@ -1,37 +0,0 @@
/// Fallback mode after successful packet transmission or packet reception.
///
/// Argument of [`set_tx_rx_fallback_mode`].
///
/// [`set_tx_rx_fallback_mode`]: crate::subghz::SubGhz::set_tx_rx_fallback_mode.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum FallbackMode {
/// Standby mode entry.
Standby = 0x20,
/// Standby with HSE32 enabled.
StandbyHse = 0x30,
/// Frequency synthesizer entry.
Fs = 0x40,
}
impl From<FallbackMode> for u8 {
fn from(fm: FallbackMode) -> Self {
fm as u8
}
}
impl Default for FallbackMode {
/// Default fallback mode after power-on reset.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::FallbackMode;
///
/// assert_eq!(FallbackMode::default(), FallbackMode::Standby);
/// ```
fn default() -> Self {
FallbackMode::Standby
}
}

View file

@ -1,107 +0,0 @@
use super::ValueError;
/// HSE32 load capacitor trimming.
///
/// Argument of [`set_hse_in_trim`] and [`set_hse_out_trim`].
///
/// [`set_hse_in_trim`]: crate::subghz::SubGhz::set_hse_in_trim
/// [`set_hse_out_trim`]: crate::subghz::SubGhz::set_hse_out_trim
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct HseTrim {
val: u8,
}
impl HseTrim {
/// Maximum capacitor value, ~33.4 pF
pub const MAX: HseTrim = HseTrim::from_raw(0x2F);
/// Minimum capacitor value, ~11.3 pF
pub const MIN: HseTrim = HseTrim::from_raw(0x00);
/// Power-on-reset capacitor value, ~20.3 pF
///
/// This is the same as `default`.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::HseTrim;
///
/// assert_eq!(HseTrim::POR, HseTrim::default());
/// ```
pub const POR: HseTrim = HseTrim::from_raw(0x12);
/// Create a new [`HseTrim`] structure from a raw value.
///
/// Values greater than the maximum of `0x2F` will be set to the maximum.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::HseTrim;
///
/// assert_eq!(HseTrim::from_raw(0xFF), HseTrim::MAX);
/// assert_eq!(HseTrim::from_raw(0x2F), HseTrim::MAX);
/// assert_eq!(HseTrim::from_raw(0x00), HseTrim::MIN);
/// ```
pub const fn from_raw(raw: u8) -> HseTrim {
if raw > 0x2F {
HseTrim { val: 0x2F }
} else {
HseTrim { val: raw }
}
}
/// Create a HSE trim value from farads.
///
/// Values greater than the maximum of 33.4 pF will be set to the maximum.
/// Values less than the minimum of 11.3 pF will be set to the minimum.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::HseTrim;
///
/// assert!(HseTrim::from_farads(1.0).is_err());
/// assert!(HseTrim::from_farads(1e-12).is_err());
/// assert_eq!(HseTrim::from_farads(20.2e-12), Ok(HseTrim::default()));
/// ```
pub fn from_farads(farads: f32) -> Result<HseTrim, ValueError<f32>> {
const MAX: f32 = 33.4E-12;
const MIN: f32 = 11.3E-12;
if farads > MAX {
Err(ValueError::too_high(farads, MAX))
} else if farads < MIN {
Err(ValueError::too_low(farads, MIN))
} else {
Ok(HseTrim::from_raw(((farads - 11.3e-12) / 0.47e-12) as u8))
}
}
/// Get the capacitance as farads.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::HseTrim;
///
/// assert_eq!((HseTrim::MAX.as_farads() * 10e11) as u8, 33);
/// assert_eq!((HseTrim::MIN.as_farads() * 10e11) as u8, 11);
/// ```
pub fn as_farads(&self) -> f32 {
(self.val as f32) * 0.47E-12 + 11.3E-12
}
}
impl From<HseTrim> for u8 {
fn from(ht: HseTrim) -> Self {
ht.val
}
}
impl Default for HseTrim {
fn default() -> Self {
Self::POR
}
}

View file

@ -1,292 +0,0 @@
/// Interrupt lines.
///
/// Argument of [`CfgIrq::irq_enable`] and [`CfgIrq::irq_disable`].
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum IrqLine {
/// Global interrupt.
Global,
/// Interrupt line 1.
///
/// This will output to the [`RfIrq0`](crate::gpio::RfIrq0) pin.
Line1,
/// Interrupt line 2.
///
/// This will output to the [`RfIrq1`](crate::gpio::RfIrq1) pin.
Line2,
/// Interrupt line 3.
///
/// This will output to the [`RfIrq2`](crate::gpio::RfIrq2) pin.
Line3,
}
impl IrqLine {
pub(super) const fn offset(&self) -> usize {
match self {
IrqLine::Global => 1,
IrqLine::Line1 => 3,
IrqLine::Line2 => 5,
IrqLine::Line3 => 7,
}
}
}
/// IRQ bit mapping
///
/// See table 37 "IRQ bit mapping and definition" in the reference manual for
/// more information.
#[repr(u16)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Irq {
/// Packet transmission finished.
///
/// * Packet type: LoRa and GFSK
/// * Operation: TX
TxDone = (1 << 0),
/// Packet reception finished.
///
/// * Packet type: LoRa and GFSK
/// * Operation: RX
RxDone = (1 << 1),
/// Preamble detected.
///
/// * Packet type: LoRa and GFSK
/// * Operation: RX
PreambleDetected = (1 << 2),
/// Synchronization word valid.
///
/// * Packet type: GFSK
/// * Operation: RX
SyncDetected = (1 << 3),
/// Header valid.
///
/// * Packet type: LoRa
/// * Operation: RX
HeaderValid = (1 << 4),
/// Header CRC error.
///
/// * Packet type: LoRa
/// * Operation: RX
HeaderErr = (1 << 5),
/// Dual meaning error.
///
/// For GFSK RX this indicates a preamble, syncword, address, CRC, or length
/// error.
///
/// For LoRa RX this indicates a CRC error.
Err = (1 << 6),
/// Channel activity detection finished.
///
/// * Packet type: LoRa
/// * Operation: CAD
CadDone = (1 << 7),
/// Channel activity detected.
///
/// * Packet type: LoRa
/// * Operation: CAD
CadDetected = (1 << 8),
/// RX or TX timeout.
///
/// * Packet type: LoRa and GFSK
/// * Operation: RX and TX
Timeout = (1 << 9),
}
impl Irq {
/// Get the bitmask for an IRQ.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Irq;
///
/// assert_eq!(Irq::TxDone.mask(), 0x0001);
/// assert_eq!(Irq::Timeout.mask(), 0x0200);
/// ```
pub const fn mask(self) -> u16 {
self as u16
}
}
/// Argument for [`set_irq_cfg`].
///
/// [`set_irq_cfg`]: crate::subghz::SubGhz::set_irq_cfg
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct CfgIrq {
buf: [u8; 9],
}
impl CfgIrq {
/// Create a new `CfgIrq`.
///
/// This is the same as `default`, but in a `const` function.
///
/// The default value has all interrupts disabled on all lines.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::CfgIrq;
///
/// const IRQ_CFG: CfgIrq = CfgIrq::new();
/// ```
pub const fn new() -> CfgIrq {
CfgIrq {
buf: [
super::OpCode::CfgDioIrq as u8,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
],
}
}
/// Enable an interrupt.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq, IrqLine};
///
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
/// .irq_enable(IrqLine::Global, Irq::TxDone)
/// .irq_enable(IrqLine::Global, Irq::Timeout);
/// # assert_eq!(IRQ_CFG.as_slice()[1], 0x02);
/// # assert_eq!(IRQ_CFG.as_slice()[2], 0x01);
/// # assert_eq!(IRQ_CFG.as_slice()[3], 0x00);
/// ```
#[must_use = "irq_enable returns a modified CfgIrq"]
pub const fn irq_enable(mut self, line: IrqLine, irq: Irq) -> CfgIrq {
let mask: u16 = irq as u16;
let offset: usize = line.offset();
self.buf[offset] |= ((mask >> 8) & 0xFF) as u8;
self.buf[offset + 1] |= (mask & 0xFF) as u8;
self
}
/// Enable an interrupt on all lines.
///
/// As far as I can tell with empirical testing all IRQ lines need to be
/// enabled for the internal interrupt to be pending in the NVIC.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq};
///
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
/// .irq_enable_all(Irq::TxDone)
/// .irq_enable_all(Irq::Timeout);
/// # assert_eq!(IRQ_CFG.as_slice()[1], 0x02);
/// # assert_eq!(IRQ_CFG.as_slice()[2], 0x01);
/// # assert_eq!(IRQ_CFG.as_slice()[3], 0x02);
/// # assert_eq!(IRQ_CFG.as_slice()[4], 0x01);
/// # assert_eq!(IRQ_CFG.as_slice()[5], 0x02);
/// # assert_eq!(IRQ_CFG.as_slice()[6], 0x01);
/// # assert_eq!(IRQ_CFG.as_slice()[7], 0x02);
/// # assert_eq!(IRQ_CFG.as_slice()[8], 0x01);
/// ```
#[must_use = "irq_enable_all returns a modified CfgIrq"]
pub const fn irq_enable_all(mut self, irq: Irq) -> CfgIrq {
let mask: [u8; 2] = irq.mask().to_be_bytes();
self.buf[1] |= mask[0];
self.buf[2] |= mask[1];
self.buf[3] |= mask[0];
self.buf[4] |= mask[1];
self.buf[5] |= mask[0];
self.buf[6] |= mask[1];
self.buf[7] |= mask[0];
self.buf[8] |= mask[1];
self
}
/// Disable an interrupt.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq, IrqLine};
///
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
/// .irq_enable(IrqLine::Global, Irq::TxDone)
/// .irq_enable(IrqLine::Global, Irq::Timeout)
/// .irq_disable(IrqLine::Global, Irq::TxDone)
/// .irq_disable(IrqLine::Global, Irq::Timeout);
/// # assert_eq!(IRQ_CFG.as_slice()[1], 0x00);
/// # assert_eq!(IRQ_CFG.as_slice()[2], 0x00);
/// # assert_eq!(IRQ_CFG.as_slice()[3], 0x00);
/// ```
#[must_use = "irq_disable returns a modified CfgIrq"]
pub const fn irq_disable(mut self, line: IrqLine, irq: Irq) -> CfgIrq {
let mask: u16 = !(irq as u16);
let offset: usize = line.offset();
self.buf[offset] &= ((mask >> 8) & 0xFF) as u8;
self.buf[offset + 1] &= (mask & 0xFF) as u8;
self
}
/// Disable an interrupt on all lines.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq};
///
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
/// .irq_enable_all(Irq::TxDone)
/// .irq_enable_all(Irq::Timeout)
/// .irq_disable_all(Irq::TxDone)
/// .irq_disable_all(Irq::Timeout);
/// # assert_eq!(IRQ_CFG, CfgIrq::new());
/// ```
#[must_use = "irq_disable_all returns a modified CfgIrq"]
pub const fn irq_disable_all(mut self, irq: Irq) -> CfgIrq {
let mask: [u8; 2] = (!irq.mask()).to_be_bytes();
self.buf[1] &= mask[0];
self.buf[2] &= mask[1];
self.buf[3] &= mask[0];
self.buf[4] &= mask[1];
self.buf[5] &= mask[0];
self.buf[6] &= mask[1];
self.buf[7] &= mask[0];
self.buf[8] &= mask[1];
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CfgIrq, Irq};
///
/// const IRQ_CFG: CfgIrq = CfgIrq::new()
/// .irq_enable_all(Irq::TxDone)
/// .irq_enable_all(Irq::Timeout);
///
/// assert_eq!(
/// IRQ_CFG.as_slice(),
/// &[0x08, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01]
/// );
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for CfgIrq {
fn default() -> Self {
Self::new()
}
}

View file

@ -1,20 +0,0 @@
/// LoRa synchronization word.
///
/// Argument of [`set_lora_sync_word`][crate::subghz::SubGhz::set_lora_sync_word].
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum LoRaSyncWord {
/// LoRa private network.
Private,
/// LoRa public network.
Public,
}
impl LoRaSyncWord {
pub(crate) const fn bytes(self) -> [u8; 2] {
match self {
LoRaSyncWord::Private => [0x14, 0x24],
LoRaSyncWord::Public => [0x34, 0x44],
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,14 +0,0 @@
/// Power amplifier over current protection.
///
/// Used by [`set_pa_ocp`].
///
/// [`set_pa_ocp`]: super::SubGhz::set_pa_ocp
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum Ocp {
/// Maximum 60mA current for LP PA mode.
Max60m = 0x18,
/// Maximum 140mA for HP PA mode.
Max140m = 0x38,
}

View file

@ -1,48 +0,0 @@
/// Operation Errors.
///
/// Returned by [`op_error`].
///
/// [`op_error`]: super::SubGhz::op_error
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum OpError {
/// PA ramping failed
PaRampError = 8,
/// RF-PLL locking failed
PllLockError = 6,
/// HSE32 clock startup failed
XoscStartError = 5,
/// Image calibration failed
ImageCalibrationError = 4,
/// RF-ADC calibration failed
AdcCalibrationError = 3,
/// RF-PLL calibration failed
PllCalibrationError = 2,
/// Sub-GHz radio RC 13 MHz oscillator
RC13MCalibrationError = 1,
/// Sub-GHz radio RC 64 kHz oscillator
RC64KCalibrationError = 0,
}
impl OpError {
/// Get the bitmask for the error.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::OpError;
///
/// assert_eq!(OpError::PaRampError.mask(), 0b1_0000_0000);
/// assert_eq!(OpError::PllLockError.mask(), 0b0_0100_0000);
/// assert_eq!(OpError::XoscStartError.mask(), 0b0_0010_0000);
/// assert_eq!(OpError::ImageCalibrationError.mask(), 0b0_0001_0000);
/// assert_eq!(OpError::AdcCalibrationError.mask(), 0b0_0000_1000);
/// assert_eq!(OpError::PllCalibrationError.mask(), 0b0_0000_0100);
/// assert_eq!(OpError::RC13MCalibrationError.mask(), 0b0_0000_0010);
/// assert_eq!(OpError::RC64KCalibrationError.mask(), 0b0_0000_0001);
/// ```
pub const fn mask(self) -> u16 {
1 << (self as u8)
}
}

View file

@ -1,196 +0,0 @@
/// Power amplifier configuration parameters.
///
/// Argument of [`set_pa_config`].
///
/// [`set_pa_config`]: super::SubGhz::set_pa_config
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PaConfig {
buf: [u8; 5],
}
impl PaConfig {
/// Optimal settings for +15dBm output power with the low-power PA.
///
/// This must be used with [`TxParams::LP_15`](super::TxParams::LP_15).
pub const LP_15: PaConfig = PaConfig::new().set_pa_duty_cycle(0x6).set_hp_max(0x0).set_pa(PaSel::Lp);
/// Optimal settings for +14dBm output power with the low-power PA.
///
/// This must be used with [`TxParams::LP_14`](super::TxParams::LP_14).
pub const LP_14: PaConfig = PaConfig::new().set_pa_duty_cycle(0x4).set_hp_max(0x0).set_pa(PaSel::Lp);
/// Optimal settings for +10dBm output power with the low-power PA.
///
/// This must be used with [`TxParams::LP_10`](super::TxParams::LP_10).
pub const LP_10: PaConfig = PaConfig::new().set_pa_duty_cycle(0x1).set_hp_max(0x0).set_pa(PaSel::Lp);
/// Optimal settings for +22dBm output power with the high-power PA.
///
/// This must be used with [`TxParams::HP`](super::TxParams::HP).
pub const HP_22: PaConfig = PaConfig::new().set_pa_duty_cycle(0x4).set_hp_max(0x7).set_pa(PaSel::Hp);
/// Optimal settings for +20dBm output power with the high-power PA.
///
/// This must be used with [`TxParams::HP`](super::TxParams::HP).
pub const HP_20: PaConfig = PaConfig::new().set_pa_duty_cycle(0x3).set_hp_max(0x5).set_pa(PaSel::Hp);
/// Optimal settings for +17dBm output power with the high-power PA.
///
/// This must be used with [`TxParams::HP`](super::TxParams::HP).
pub const HP_17: PaConfig = PaConfig::new().set_pa_duty_cycle(0x2).set_hp_max(0x3).set_pa(PaSel::Hp);
/// Optimal settings for +14dBm output power with the high-power PA.
///
/// This must be used with [`TxParams::HP`](super::TxParams::HP).
pub const HP_14: PaConfig = PaConfig::new().set_pa_duty_cycle(0x2).set_hp_max(0x2).set_pa(PaSel::Hp);
/// Create a new `PaConfig` struct.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PaConfig;
///
/// const PA_CONFIG: PaConfig = PaConfig::new();
/// ```
pub const fn new() -> PaConfig {
PaConfig {
buf: [super::OpCode::SetPaConfig as u8, 0x01, 0x00, 0x01, 0x01],
}
}
/// Set the power amplifier duty cycle (conduit angle) control.
///
/// **Note:** Only the first 3 bits of the `pa_duty_cycle` argument are used.
///
/// Duty cycle = 0.2 + 0.04 × bits
///
/// # Caution
///
/// The following restrictions must be observed to avoid over-stress on the PA:
/// * LP PA mode with synthesis frequency > 400 MHz, `pa_duty_cycle` must be < 0x7.
/// * LP PA mode with synthesis frequency < 400 MHz, `pa_duty_cycle` must be < 0x4.
/// * HP PA mode, `pa_duty_cycle` must be < 0x4
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{PaConfig, PaSel};
///
/// const PA_CONFIG: PaConfig = PaConfig::new().set_pa(PaSel::Lp).set_pa_duty_cycle(0x4);
/// # assert_eq!(PA_CONFIG.as_slice()[1], 0x04);
/// ```
#[must_use = "set_pa_duty_cycle returns a modified PaConfig"]
pub const fn set_pa_duty_cycle(mut self, pa_duty_cycle: u8) -> PaConfig {
self.buf[1] = pa_duty_cycle & 0b111;
self
}
/// Set the high power amplifier output power.
///
/// **Note:** Only the first 3 bits of the `hp_max` argument are used.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{PaConfig, PaSel};
///
/// const PA_CONFIG: PaConfig = PaConfig::new().set_pa(PaSel::Hp).set_hp_max(0x2);
/// # assert_eq!(PA_CONFIG.as_slice()[2], 0x02);
/// ```
#[must_use = "set_hp_max returns a modified PaConfig"]
pub const fn set_hp_max(mut self, hp_max: u8) -> PaConfig {
self.buf[2] = hp_max & 0b111;
self
}
/// Set the power amplifier to use, low or high power.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{PaConfig, PaSel};
///
/// const PA_CONFIG_HP: PaConfig = PaConfig::new().set_pa(PaSel::Hp);
/// const PA_CONFIG_LP: PaConfig = PaConfig::new().set_pa(PaSel::Lp);
/// # assert_eq!(PA_CONFIG_HP.as_slice()[3], 0x00);
/// # assert_eq!(PA_CONFIG_LP.as_slice()[3], 0x01);
/// ```
#[must_use = "set_pa returns a modified PaConfig"]
pub const fn set_pa(mut self, pa: PaSel) -> PaConfig {
self.buf[3] = pa as u8;
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{PaConfig, PaSel};
///
/// const PA_CONFIG: PaConfig = PaConfig::new()
/// .set_pa(PaSel::Hp)
/// .set_pa_duty_cycle(0x2)
/// .set_hp_max(0x3);
///
/// assert_eq!(PA_CONFIG.as_slice(), &[0x95, 0x2, 0x03, 0x00, 0x01]);
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for PaConfig {
fn default() -> Self {
Self::new()
}
}
/// Power amplifier selection.
///
/// Argument of [`PaConfig::set_pa`].
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum PaSel {
/// High power amplifier.
Hp = 0b0,
/// Low power amplifier.
Lp = 0b1,
}
impl PartialOrd for PaSel {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PaSel {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
match (self, other) {
(PaSel::Hp, PaSel::Hp) | (PaSel::Lp, PaSel::Lp) => core::cmp::Ordering::Equal,
(PaSel::Hp, PaSel::Lp) => core::cmp::Ordering::Greater,
(PaSel::Lp, PaSel::Hp) => core::cmp::Ordering::Less,
}
}
}
impl Default for PaSel {
fn default() -> Self {
PaSel::Lp
}
}
#[cfg(test)]
mod test {
use super::PaSel;
#[test]
fn pa_sel_ord() {
assert!(PaSel::Lp < PaSel::Hp);
assert!(PaSel::Hp > PaSel::Lp);
}
}

View file

@ -1,534 +0,0 @@
/// Preamble detection length for [`GenericPacketParams`].
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PreambleDetection {
/// Preamble detection disabled.
Disabled = 0x0,
/// 8-bit preamble detection.
Bit8 = 0x4,
/// 16-bit preamble detection.
Bit16 = 0x5,
/// 24-bit preamble detection.
Bit24 = 0x6,
/// 32-bit preamble detection.
Bit32 = 0x7,
}
/// Address comparison/filtering for [`GenericPacketParams`].
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum AddrComp {
/// Address comparison/filtering disabled.
Disabled = 0x0,
/// Address comparison/filtering on node address.
Node = 0x1,
/// Address comparison/filtering on node and broadcast addresses.
Broadcast = 0x2,
}
/// Packet header type.
///
/// Argument of [`GenericPacketParams::set_header_type`] and
/// [`LoRaPacketParams::set_header_type`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum HeaderType {
/// Fixed; payload length and header field not added to packet.
Fixed,
/// Variable; payload length and header field added to packet.
Variable,
}
impl HeaderType {
pub(crate) const fn to_bits_generic(self) -> u8 {
match self {
HeaderType::Fixed => 0,
HeaderType::Variable => 1,
}
}
pub(crate) const fn to_bits_lora(self) -> u8 {
match self {
HeaderType::Fixed => 1,
HeaderType::Variable => 0,
}
}
}
/// CRC type definition for [`GenericPacketParams`].
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CrcType {
/// 1-byte CRC.
Byte1 = 0x0,
/// CRC disabled.
Disabled = 0x1,
/// 2-byte CRC.
Byte2 = 0x2,
/// 1-byte inverted CRC.
Byte1Inverted = 0x4,
/// 2-byte inverted CRC.
Byte2Inverted = 0x6,
}
/// Packet parameters for [`set_packet_params`].
///
/// [`set_packet_params`]: super::SubGhz::set_packet_params
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct GenericPacketParams {
buf: [u8; 10],
}
impl GenericPacketParams {
/// Create a new `GenericPacketParams`.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::GenericPacketParams;
///
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new();
/// assert_eq!(PKT_PARAMS, GenericPacketParams::default());
/// ```
pub const fn new() -> GenericPacketParams {
const OPCODE: u8 = super::OpCode::SetPacketParams as u8;
// const variable ensure the compile always optimizes the methods
const NEW: GenericPacketParams = GenericPacketParams {
buf: [OPCODE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
}
.set_preamble_len(1)
.set_preamble_detection(PreambleDetection::Disabled)
.set_sync_word_len(0)
.set_addr_comp(AddrComp::Disabled)
.set_header_type(HeaderType::Fixed)
.set_payload_len(1);
NEW
}
/// Preamble length in number of symbols.
///
/// Values of zero are invalid, and will automatically be set to 1.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::GenericPacketParams;
///
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_preamble_len(0x1234);
/// # assert_eq!(PKT_PARAMS.as_slice()[1], 0x12);
/// # assert_eq!(PKT_PARAMS.as_slice()[2], 0x34);
/// ```
#[must_use = "preamble_length returns a modified GenericPacketParams"]
pub const fn set_preamble_len(mut self, mut len: u16) -> GenericPacketParams {
if len == 0 {
len = 1
}
self.buf[1] = ((len >> 8) & 0xFF) as u8;
self.buf[2] = (len & 0xFF) as u8;
self
}
/// Preamble detection length in number of bit symbols.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{GenericPacketParams, PreambleDetection};
///
/// const PKT_PARAMS: GenericPacketParams =
/// GenericPacketParams::new().set_preamble_detection(PreambleDetection::Bit8);
/// # assert_eq!(PKT_PARAMS.as_slice()[3], 0x4);
/// ```
#[must_use = "set_preamble_detection returns a modified GenericPacketParams"]
pub const fn set_preamble_detection(mut self, pb_det: PreambleDetection) -> GenericPacketParams {
self.buf[3] = pb_det as u8;
self
}
/// Sync word length in number of bit symbols.
///
/// Valid values are `0x00` - `0x40` for 0 to 64-bits respectively.
/// Values that exceed the maximum will saturate at `0x40`.
///
/// # Example
///
/// Set the sync word length to 4 bytes (16 bits).
///
/// ```
/// use stm32wlxx_hal::subghz::GenericPacketParams;
///
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_sync_word_len(16);
/// # assert_eq!(PKT_PARAMS.as_slice()[4], 0x10);
/// ```
#[must_use = "set_sync_word_len returns a modified GenericPacketParams"]
pub const fn set_sync_word_len(mut self, len: u8) -> GenericPacketParams {
const MAX: u8 = 0x40;
if len > MAX {
self.buf[4] = MAX;
} else {
self.buf[4] = len;
}
self
}
/// Address comparison/filtering.
///
/// # Example
///
/// Enable address on the node address.
///
/// ```
/// use stm32wlxx_hal::subghz::{AddrComp, GenericPacketParams};
///
/// const PKT_PARAMS: GenericPacketParams =
/// GenericPacketParams::new().set_addr_comp(AddrComp::Node);
/// # assert_eq!(PKT_PARAMS.as_slice()[5], 0x01);
/// ```
#[must_use = "set_addr_comp returns a modified GenericPacketParams"]
pub const fn set_addr_comp(mut self, addr_comp: AddrComp) -> GenericPacketParams {
self.buf[5] = addr_comp as u8;
self
}
/// Header type definition.
///
/// **Note:** The reference manual calls this packet type, but that results
/// in a conflicting variable name for the modulation scheme, which the
/// reference manual also calls packet type.
///
/// # Example
///
/// Set the header type to a variable length.
///
/// ```
/// use stm32wlxx_hal::subghz::{GenericPacketParams, HeaderType};
///
/// const PKT_PARAMS: GenericPacketParams =
/// GenericPacketParams::new().set_header_type(HeaderType::Variable);
/// # assert_eq!(PKT_PARAMS.as_slice()[6], 0x01);
/// ```
#[must_use = "set_header_type returns a modified GenericPacketParams"]
pub const fn set_header_type(mut self, header_type: HeaderType) -> GenericPacketParams {
self.buf[6] = header_type.to_bits_generic();
self
}
/// Set the payload length in bytes.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::GenericPacketParams;
///
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_payload_len(12);
/// # assert_eq!(PKT_PARAMS.as_slice()[7], 12);
/// ```
#[must_use = "set_payload_len returns a modified GenericPacketParams"]
pub const fn set_payload_len(mut self, len: u8) -> GenericPacketParams {
self.buf[7] = len;
self
}
/// CRC type definition.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CrcType, GenericPacketParams};
///
/// const PKT_PARAMS: GenericPacketParams =
/// GenericPacketParams::new().set_crc_type(CrcType::Byte2Inverted);
/// # assert_eq!(PKT_PARAMS.as_slice()[8], 0x6);
/// ```
#[must_use = "set_payload_len returns a modified GenericPacketParams"]
pub const fn set_crc_type(mut self, crc_type: CrcType) -> GenericPacketParams {
self.buf[8] = crc_type as u8;
self
}
/// Whitening enable.
///
/// # Example
///
/// Enable whitening.
///
/// ```
/// use stm32wlxx_hal::subghz::GenericPacketParams;
///
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_whitening_enable(true);
/// # assert_eq!(PKT_PARAMS.as_slice()[9], 1);
/// ```
#[must_use = "set_whitening_enable returns a modified GenericPacketParams"]
pub const fn set_whitening_enable(mut self, en: bool) -> GenericPacketParams {
self.buf[9] = en as u8;
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{
/// AddrComp, CrcType, GenericPacketParams, HeaderType, PreambleDetection,
/// };
///
/// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new()
/// .set_preamble_len(8)
/// .set_preamble_detection(PreambleDetection::Disabled)
/// .set_sync_word_len(2)
/// .set_addr_comp(AddrComp::Disabled)
/// .set_header_type(HeaderType::Fixed)
/// .set_payload_len(128)
/// .set_crc_type(CrcType::Byte2)
/// .set_whitening_enable(true);
///
/// assert_eq!(
/// PKT_PARAMS.as_slice(),
/// &[0x8C, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x80, 0x02, 0x01]
/// );
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for GenericPacketParams {
fn default() -> Self {
Self::new()
}
}
/// Packet parameters for [`set_lora_packet_params`].
///
/// [`set_lora_packet_params`]: super::SubGhz::set_lora_packet_params
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LoRaPacketParams {
buf: [u8; 7],
}
impl LoRaPacketParams {
/// Create a new `LoRaPacketParams`.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::LoRaPacketParams;
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new();
/// assert_eq!(PKT_PARAMS, LoRaPacketParams::default());
/// ```
pub const fn new() -> LoRaPacketParams {
const OPCODE: u8 = super::OpCode::SetPacketParams as u8;
// const variable ensure the compile always optimizes the methods
const NEW: LoRaPacketParams = LoRaPacketParams {
buf: [OPCODE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
}
.set_preamble_len(1)
.set_header_type(HeaderType::Fixed)
.set_payload_len(1)
.set_crc_en(true)
.set_invert_iq(false);
NEW
}
/// Preamble length in number of symbols.
///
/// Values of zero are invalid, and will automatically be set to 1.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::LoRaPacketParams;
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_preamble_len(0x1234);
/// # assert_eq!(PKT_PARAMS.as_slice()[1], 0x12);
/// # assert_eq!(PKT_PARAMS.as_slice()[2], 0x34);
/// ```
#[must_use = "preamble_length returns a modified LoRaPacketParams"]
pub const fn set_preamble_len(mut self, mut len: u16) -> LoRaPacketParams {
if len == 0 {
len = 1
}
self.buf[1] = ((len >> 8) & 0xFF) as u8;
self.buf[2] = (len & 0xFF) as u8;
self
}
/// Header type (fixed or variable).
///
/// # Example
///
/// Set the payload type to a fixed length.
///
/// ```
/// use stm32wlxx_hal::subghz::{HeaderType, LoRaPacketParams};
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_header_type(HeaderType::Fixed);
/// # assert_eq!(PKT_PARAMS.as_slice()[3], 0x01);
/// ```
#[must_use = "set_header_type returns a modified LoRaPacketParams"]
pub const fn set_header_type(mut self, header_type: HeaderType) -> LoRaPacketParams {
self.buf[3] = header_type.to_bits_lora();
self
}
/// Set the payload length in bytes.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::LoRaPacketParams;
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_payload_len(12);
/// # assert_eq!(PKT_PARAMS.as_slice()[4], 12);
/// ```
#[must_use = "set_payload_len returns a modified LoRaPacketParams"]
pub const fn set_payload_len(mut self, len: u8) -> LoRaPacketParams {
self.buf[4] = len;
self
}
/// CRC enable.
///
/// # Example
///
/// Enable CRC.
///
/// ```
/// use stm32wlxx_hal::subghz::LoRaPacketParams;
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_crc_en(true);
/// # assert_eq!(PKT_PARAMS.as_slice()[5], 0x1);
/// ```
#[must_use = "set_crc_en returns a modified LoRaPacketParams"]
pub const fn set_crc_en(mut self, en: bool) -> LoRaPacketParams {
self.buf[5] = en as u8;
self
}
/// IQ setup.
///
/// # Example
///
/// Use an inverted IQ setup.
///
/// ```
/// use stm32wlxx_hal::subghz::LoRaPacketParams;
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_invert_iq(true);
/// # assert_eq!(PKT_PARAMS.as_slice()[6], 0x1);
/// ```
#[must_use = "set_invert_iq returns a modified LoRaPacketParams"]
pub const fn set_invert_iq(mut self, invert: bool) -> LoRaPacketParams {
self.buf[6] = invert as u8;
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{HeaderType, LoRaPacketParams};
///
/// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new()
/// .set_preamble_len(5 * 8)
/// .set_header_type(HeaderType::Fixed)
/// .set_payload_len(64)
/// .set_crc_en(true)
/// .set_invert_iq(true);
///
/// assert_eq!(
/// PKT_PARAMS.as_slice(),
/// &[0x8C, 0x00, 0x28, 0x01, 0x40, 0x01, 0x01]
/// );
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for LoRaPacketParams {
fn default() -> Self {
Self::new()
}
}
/// Packet parameters for [`set_bpsk_packet_params`].
///
/// [`set_bpsk_packet_params`]: super::SubGhz::set_bpsk_packet_params
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct BpskPacketParams {
buf: [u8; 2],
}
impl BpskPacketParams {
/// Create a new `BpskPacketParams`.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::BpskPacketParams;
///
/// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new();
/// assert_eq!(PKT_PARAMS, BpskPacketParams::default());
/// ```
pub const fn new() -> BpskPacketParams {
BpskPacketParams {
buf: [super::OpCode::SetPacketParams as u8, 0x00],
}
}
/// Set the payload length in bytes.
///
/// The length includes preamble, sync word, device ID, and CRC.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::BpskPacketParams;
///
/// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new().set_payload_len(12);
/// # assert_eq!(PKT_PARAMS.as_slice()[1], 12);
/// ```
#[must_use = "set_payload_len returns a modified BpskPacketParams"]
pub const fn set_payload_len(mut self, len: u8) -> BpskPacketParams {
self.buf[1] = len;
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{BpskPacketParams, HeaderType};
///
/// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new().set_payload_len(24);
///
/// assert_eq!(PKT_PARAMS.as_slice(), &[0x8C, 24]);
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for BpskPacketParams {
fn default() -> Self {
Self::new()
}
}

View file

@ -1,282 +0,0 @@
use super::{Ratio, Status};
/// (G)FSK packet status.
///
/// Returned by [`fsk_packet_status`].
///
/// [`fsk_packet_status`]: super::SubGhz::fsk_packet_status
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct FskPacketStatus {
buf: [u8; 4],
}
impl From<[u8; 4]> for FskPacketStatus {
fn from(buf: [u8; 4]) -> Self {
FskPacketStatus { buf }
}
}
impl FskPacketStatus {
/// Get the status.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CmdStatus, FskPacketStatus, Status, StatusMode};
///
/// let example_data_from_radio: [u8; 4] = [0x54, 0, 0, 0];
/// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio);
/// let status: Status = pkt_status.status();
/// assert_eq!(status.mode(), Ok(StatusMode::Rx));
/// assert_eq!(status.cmd(), Ok(CmdStatus::Avaliable));
/// ```
pub const fn status(&self) -> Status {
Status::from_raw(self.buf[0])
}
/// Returns `true` if a preamble error occurred.
pub const fn preamble_err(&self) -> bool {
(self.buf[1] & (1 << 7)) != 0
}
/// Returns `true` if a synchronization error occurred.
pub const fn sync_err(&self) -> bool {
(self.buf[1] & (1 << 6)) != 0
}
/// Returns `true` if an address error occurred.
pub const fn addr_err(&self) -> bool {
(self.buf[1] & (1 << 5)) != 0
}
/// Returns `true` if an CRC error occurred.
pub const fn crc_err(&self) -> bool {
(self.buf[1] & (1 << 4)) != 0
}
/// Returns `true` if a length error occurred.
pub const fn length_err(&self) -> bool {
(self.buf[1] & (1 << 3)) != 0
}
/// Returns `true` if an abort error occurred.
pub const fn abort_err(&self) -> bool {
(self.buf[1] & (1 << 2)) != 0
}
/// Returns `true` if a packet is received.
pub const fn pkt_received(&self) -> bool {
(self.buf[1] & (1 << 1)) != 0
}
/// Returns `true` when a packet has been sent.
pub const fn pkt_sent(&self) -> bool {
(self.buf[1] & 1) != 0
}
/// Returns `true` if any error occurred.
pub const fn any_err(&self) -> bool {
(self.buf[1] & 0xFC) != 0
}
/// RSSI level when the synchronization address is detected.
///
/// Units are in dBm.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::{subghz::FskPacketStatus, Ratio};
///
/// let example_data_from_radio: [u8; 4] = [0, 0, 80, 0];
/// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio);
/// assert_eq!(pkt_status.rssi_sync().to_integer(), -40);
/// ```
pub fn rssi_sync(&self) -> Ratio<i16> {
Ratio::new_raw(i16::from(self.buf[2]), -2)
}
/// Return the RSSI level over the received packet.
///
/// Units are in dBm.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::{subghz::FskPacketStatus, Ratio};
///
/// let example_data_from_radio: [u8; 4] = [0, 0, 0, 100];
/// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio);
/// assert_eq!(pkt_status.rssi_avg().to_integer(), -50);
/// ```
pub fn rssi_avg(&self) -> Ratio<i16> {
Ratio::new_raw(i16::from(self.buf[3]), -2)
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for FskPacketStatus {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
r#"FskPacketStatus {{
status: {},
preamble_err: {},
sync_err: {},
addr_err: {},
crc_err: {},
length_err: {},
abort_err: {},
pkt_received: {},
pkt_sent: {},
rssi_sync: {},
rssi_avg: {},
}}"#,
self.status(),
self.preamble_err(),
self.sync_err(),
self.addr_err(),
self.crc_err(),
self.length_err(),
self.abort_err(),
self.pkt_received(),
self.pkt_sent(),
self.rssi_sync().to_integer(),
self.rssi_avg().to_integer()
)
}
}
impl core::fmt::Debug for FskPacketStatus {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("FskPacketStatus")
.field("status", &self.status())
.field("preamble_err", &self.preamble_err())
.field("sync_err", &self.sync_err())
.field("addr_err", &self.addr_err())
.field("crc_err", &self.crc_err())
.field("length_err", &self.length_err())
.field("abort_err", &self.abort_err())
.field("pkt_received", &self.pkt_received())
.field("pkt_sent", &self.pkt_sent())
.field("rssi_sync", &self.rssi_sync().to_integer())
.field("rssi_avg", &self.rssi_avg().to_integer())
.finish()
}
}
/// (G)FSK packet status.
///
/// Returned by [`lora_packet_status`].
///
/// [`lora_packet_status`]: super::SubGhz::lora_packet_status
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct LoRaPacketStatus {
buf: [u8; 4],
}
impl From<[u8; 4]> for LoRaPacketStatus {
fn from(buf: [u8; 4]) -> Self {
LoRaPacketStatus { buf }
}
}
impl LoRaPacketStatus {
/// Get the status.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CmdStatus, LoRaPacketStatus, Status, StatusMode};
///
/// let example_data_from_radio: [u8; 4] = [0x54, 0, 0, 0];
/// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio);
/// let status: Status = pkt_status.status();
/// assert_eq!(status.mode(), Ok(StatusMode::Rx));
/// assert_eq!(status.cmd(), Ok(CmdStatus::Avaliable));
/// ```
pub const fn status(&self) -> Status {
Status::from_raw(self.buf[0])
}
/// Average RSSI level over the received packet.
///
/// Units are in dBm.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::{subghz::LoRaPacketStatus, Ratio};
///
/// let example_data_from_radio: [u8; 4] = [0, 80, 0, 0];
/// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio);
/// assert_eq!(pkt_status.rssi_pkt().to_integer(), -40);
/// ```
pub fn rssi_pkt(&self) -> Ratio<i16> {
Ratio::new_raw(i16::from(self.buf[1]), -2)
}
/// Estimation of SNR over the received packet.
///
/// Units are in dB.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::{subghz::LoRaPacketStatus, Ratio};
///
/// let example_data_from_radio: [u8; 4] = [0, 0, 40, 0];
/// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio);
/// assert_eq!(pkt_status.snr_pkt().to_integer(), 10);
/// ```
pub fn snr_pkt(&self) -> Ratio<i16> {
Ratio::new_raw(i16::from(self.buf[2]), 4)
}
/// Estimation of RSSI level of the LoRa signal after despreading.
///
/// Units are in dBm.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::{subghz::LoRaPacketStatus, Ratio};
///
/// let example_data_from_radio: [u8; 4] = [0, 0, 0, 80];
/// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio);
/// assert_eq!(pkt_status.signal_rssi_pkt().to_integer(), -40);
/// ```
pub fn signal_rssi_pkt(&self) -> Ratio<i16> {
Ratio::new_raw(i16::from(self.buf[3]), -2)
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for LoRaPacketStatus {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
r#"LoRaPacketStatus {{
status: {},
rssi_pkt: {},
snr_pkt: {},
signal_rssi_pkt: {},
}}"#,
self.status(),
self.rssi_pkt().to_integer(),
self.snr_pkt().to_integer(),
self.signal_rssi_pkt().to_integer(),
)
}
}
impl core::fmt::Debug for LoRaPacketStatus {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("LoRaPacketStatus")
.field("status", &self.status())
.field("rssi_pkt", &self.rssi_pkt().to_integer())
.field("snr_pkt", &self.snr_pkt().to_integer())
.field("signal_rssi_pkt", &self.signal_rssi_pkt().to_integer())
.finish()
}
}

View file

@ -1,44 +0,0 @@
/// Packet type definition.
///
/// Argument of [`set_packet_type`]
///
/// [`set_packet_type`]: super::SubGhz::set_packet_type
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PacketType {
/// FSK (frequency shift keying) generic packet type.
Fsk = 0,
/// LoRa (long range) packet type.
LoRa = 1,
/// BPSK (binary phase shift keying) packet type.
Bpsk = 2,
/// MSK (minimum shift keying) generic packet type.
Msk = 3,
}
impl PacketType {
/// Create a new `PacketType` from bits.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PacketType;
///
/// assert_eq!(PacketType::from_raw(0), Ok(PacketType::Fsk));
/// assert_eq!(PacketType::from_raw(1), Ok(PacketType::LoRa));
/// assert_eq!(PacketType::from_raw(2), Ok(PacketType::Bpsk));
/// assert_eq!(PacketType::from_raw(3), Ok(PacketType::Msk));
/// // Other values are reserved
/// assert_eq!(PacketType::from_raw(4), Err(4));
/// ```
pub const fn from_raw(bits: u8) -> Result<PacketType, u8> {
match bits {
0 => Ok(PacketType::Fsk),
1 => Ok(PacketType::LoRa),
2 => Ok(PacketType::Bpsk),
3 => Ok(PacketType::Msk),
_ => Err(bits),
}
}
}

View file

@ -1,247 +0,0 @@
/// Generic packet infinite sequence selection.
///
/// Argument of [`PktCtrl::set_inf_seq_sel`].
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InfSeqSel {
/// Preamble `0x5555`.
Five = 0b00,
/// Preamble `0x0000`.
Zero = 0b01,
/// Preamble `0xFFFF`.
One = 0b10,
/// PRBS9.
Prbs9 = 0b11,
}
impl Default for InfSeqSel {
fn default() -> Self {
InfSeqSel::Five
}
}
/// Generic packet control.
///
/// Argument of [`set_pkt_ctrl`](super::SubGhz::set_pkt_ctrl).
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PktCtrl {
val: u8,
}
impl PktCtrl {
/// Reset value of the packet control register.
pub const RESET: PktCtrl = PktCtrl { val: 0x21 };
/// Create a new [`PktCtrl`] structure from a raw value.
///
/// Reserved bits will be masked.
pub const fn from_raw(raw: u8) -> Self {
Self { val: raw & 0x3F }
}
/// Get the raw value of the [`PktCtrl`] register.
pub const fn as_bits(&self) -> u8 {
self.val
}
/// Generic packet synchronization word detection enable.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_sync_det_en(true);
/// ```
#[must_use = "set_sync_det_en returns a modified PktCtrl"]
pub const fn set_sync_det_en(mut self, en: bool) -> PktCtrl {
if en {
self.val |= 1 << 5;
} else {
self.val &= !(1 << 5);
}
self
}
/// Returns `true` if generic packet synchronization word detection is
/// enabled.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// let pc: PktCtrl = PktCtrl::RESET;
/// assert_eq!(pc.sync_det_en(), true);
/// let pc: PktCtrl = pc.set_sync_det_en(false);
/// assert_eq!(pc.sync_det_en(), false);
/// let pc: PktCtrl = pc.set_sync_det_en(true);
/// assert_eq!(pc.sync_det_en(), true);
/// ```
pub const fn sync_det_en(&self) -> bool {
self.val & (1 << 5) != 0
}
/// Generic packet continuous transmit enable.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_cont_tx_en(true);
/// ```
#[must_use = "set_cont_tx_en returns a modified PktCtrl"]
pub const fn set_cont_tx_en(mut self, en: bool) -> PktCtrl {
if en {
self.val |= 1 << 4;
} else {
self.val &= !(1 << 4);
}
self
}
/// Returns `true` if generic packet continuous transmit is enabled.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// let pc: PktCtrl = PktCtrl::RESET;
/// assert_eq!(pc.cont_tx_en(), false);
/// let pc: PktCtrl = pc.set_cont_tx_en(true);
/// assert_eq!(pc.cont_tx_en(), true);
/// let pc: PktCtrl = pc.set_cont_tx_en(false);
/// assert_eq!(pc.cont_tx_en(), false);
/// ```
pub const fn cont_tx_en(&self) -> bool {
self.val & (1 << 4) != 0
}
/// Set the continuous sequence type.
#[must_use = "set_inf_seq_sel returns a modified PktCtrl"]
pub const fn set_inf_seq_sel(mut self, sel: InfSeqSel) -> PktCtrl {
self.val &= !(0b11 << 2);
self.val |= (sel as u8) << 2;
self
}
/// Get the continuous sequence type.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{InfSeqSel, PktCtrl};
///
/// let pc: PktCtrl = PktCtrl::RESET;
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Five);
///
/// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Zero);
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Zero);
///
/// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::One);
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::One);
///
/// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Prbs9);
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Prbs9);
///
/// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Five);
/// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Five);
/// ```
pub const fn inf_seq_sel(&self) -> InfSeqSel {
match (self.val >> 2) & 0b11 {
0b00 => InfSeqSel::Five,
0b01 => InfSeqSel::Zero,
0b10 => InfSeqSel::One,
_ => InfSeqSel::Prbs9,
}
}
/// Enable infinite sequence generation.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_inf_seq_en(true);
/// ```
#[must_use = "set_inf_seq_en returns a modified PktCtrl"]
pub const fn set_inf_seq_en(mut self, en: bool) -> PktCtrl {
if en {
self.val |= 1 << 1;
} else {
self.val &= !(1 << 1);
}
self
}
/// Returns `true` if infinite sequence generation is enabled.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// let pc: PktCtrl = PktCtrl::RESET;
/// assert_eq!(pc.inf_seq_en(), false);
/// let pc: PktCtrl = pc.set_inf_seq_en(true);
/// assert_eq!(pc.inf_seq_en(), true);
/// let pc: PktCtrl = pc.set_inf_seq_en(false);
/// assert_eq!(pc.inf_seq_en(), false);
/// ```
pub const fn inf_seq_en(&self) -> bool {
self.val & (1 << 1) != 0
}
/// Set the value of bit-8 (9th bit) for generic packet whitening.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_whitening_init(true);
/// ```
#[must_use = "set_whitening_init returns a modified PktCtrl"]
pub const fn set_whitening_init(mut self, val: bool) -> PktCtrl {
if val {
self.val |= 1;
} else {
self.val &= !1;
}
self
}
/// Returns `true` if bit-8 of the generic packet whitening is set.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PktCtrl;
///
/// let pc: PktCtrl = PktCtrl::RESET;
/// assert_eq!(pc.whitening_init(), true);
/// let pc: PktCtrl = pc.set_whitening_init(false);
/// assert_eq!(pc.whitening_init(), false);
/// let pc: PktCtrl = pc.set_whitening_init(true);
/// assert_eq!(pc.whitening_init(), true);
/// ```
pub const fn whitening_init(&self) -> bool {
self.val & 0b1 != 0
}
}
impl From<PktCtrl> for u8 {
fn from(pc: PktCtrl) -> Self {
pc.val
}
}
impl Default for PktCtrl {
fn default() -> Self {
Self::RESET
}
}

View file

@ -1,27 +0,0 @@
/// RX gain power modes.
///
/// Argument of [`set_rx_gain`].
///
/// [`set_rx_gain`]: super::SubGhz::set_rx_gain
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PMode {
/// Power saving mode.
///
/// Reduces sensitivity.
#[allow(clippy::identity_op)]
PowerSaving = (0x25 << 2) | 0b00,
/// Boost mode level 1.
///
/// Improves sensitivity at detriment of power consumption.
Boost1 = (0x25 << 2) | 0b01,
/// Boost mode level 2.
///
/// Improves a set further sensitivity at detriment of power consumption.
Boost2 = (0x25 << 2) | 0b10,
/// Boost mode.
///
/// Best receiver sensitivity.
Boost = (0x25 << 2) | 0b11,
}

View file

@ -1,160 +0,0 @@
/// Power-supply current limit.
///
/// Argument of [`PwrCtrl::set_current_lim`].
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum CurrentLim {
/// 25 mA
Milli25 = 0x0,
/// 50 mA (default)
Milli50 = 0x1,
/// 100 mA
Milli100 = 0x2,
/// 200 mA
Milli200 = 0x3,
}
impl CurrentLim {
/// Get the SMPS drive value as milliamps.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::CurrentLim;
///
/// assert_eq!(CurrentLim::Milli25.as_milliamps(), 25);
/// assert_eq!(CurrentLim::Milli50.as_milliamps(), 50);
/// assert_eq!(CurrentLim::Milli100.as_milliamps(), 100);
/// assert_eq!(CurrentLim::Milli200.as_milliamps(), 200);
/// ```
pub const fn as_milliamps(&self) -> u8 {
match self {
CurrentLim::Milli25 => 25,
CurrentLim::Milli50 => 50,
CurrentLim::Milli100 => 100,
CurrentLim::Milli200 => 200,
}
}
}
impl Default for CurrentLim {
fn default() -> Self {
CurrentLim::Milli50
}
}
/// Power control.
///
/// Argument of [`set_bit_sync`](super::SubGhz::set_bit_sync).
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct PwrCtrl {
val: u8,
}
impl PwrCtrl {
/// Power control register reset value.
pub const RESET: PwrCtrl = PwrCtrl { val: 0x50 };
/// Create a new [`PwrCtrl`] structure from a raw value.
///
/// Reserved bits will be masked.
pub const fn from_raw(raw: u8) -> Self {
Self { val: raw & 0x70 }
}
/// Get the raw value of the [`PwrCtrl`] register.
pub const fn as_bits(&self) -> u8 {
self.val
}
/// Set the current limiter enable.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PwrCtrl;
///
/// const PWR_CTRL: PwrCtrl = PwrCtrl::RESET.set_current_lim_en(true);
/// # assert_eq!(u8::from(PWR_CTRL), 0x50u8);
/// ```
#[must_use = "set_current_lim_en returns a modified PwrCtrl"]
pub const fn set_current_lim_en(mut self, en: bool) -> PwrCtrl {
if en {
self.val |= 1 << 6;
} else {
self.val &= !(1 << 6);
}
self
}
/// Returns `true` if current limiting is enabled
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::PwrCtrl;
///
/// let pc: PwrCtrl = PwrCtrl::RESET;
/// assert_eq!(pc.current_limit_en(), true);
/// let pc: PwrCtrl = pc.set_current_lim_en(false);
/// assert_eq!(pc.current_limit_en(), false);
/// let pc: PwrCtrl = pc.set_current_lim_en(true);
/// assert_eq!(pc.current_limit_en(), true);
/// ```
pub const fn current_limit_en(&self) -> bool {
self.val & (1 << 6) != 0
}
/// Set the current limit.
#[must_use = "set_current_lim returns a modified PwrCtrl"]
pub const fn set_current_lim(mut self, lim: CurrentLim) -> PwrCtrl {
self.val &= !(0x30);
self.val |= (lim as u8) << 4;
self
}
/// Get the current limit.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CurrentLim, PwrCtrl};
///
/// let pc: PwrCtrl = PwrCtrl::RESET;
/// assert_eq!(pc.current_lim(), CurrentLim::Milli50);
///
/// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli25);
/// assert_eq!(pc.current_lim(), CurrentLim::Milli25);
///
/// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli50);
/// assert_eq!(pc.current_lim(), CurrentLim::Milli50);
///
/// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli100);
/// assert_eq!(pc.current_lim(), CurrentLim::Milli100);
///
/// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli200);
/// assert_eq!(pc.current_lim(), CurrentLim::Milli200);
/// ```
pub const fn current_lim(&self) -> CurrentLim {
match (self.val >> 4) & 0b11 {
0x0 => CurrentLim::Milli25,
0x1 => CurrentLim::Milli50,
0x2 => CurrentLim::Milli100,
_ => CurrentLim::Milli200,
}
}
}
impl From<PwrCtrl> for u8 {
fn from(bs: PwrCtrl) -> Self {
bs.val
}
}
impl Default for PwrCtrl {
fn default() -> Self {
Self::RESET
}
}

View file

@ -1,18 +0,0 @@
/// Radio power supply selection.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum RegMode {
/// Linear dropout regulator
Ldo = 0b0,
/// Switch mode power supply.
///
/// Used in standby with HSE32, FS, RX, and TX modes.
Smps = 0b1,
}
impl Default for RegMode {
fn default() -> Self {
RegMode::Ldo
}
}

View file

@ -1,135 +0,0 @@
/// RF frequency structure.
///
/// Argument of [`set_rf_frequency`].
///
/// [`set_rf_frequency`]: super::SubGhz::set_rf_frequency
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct RfFreq {
buf: [u8; 5],
}
impl RfFreq {
/// 915MHz, often used in Australia and North America.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// assert_eq!(RfFreq::F915.freq(), 915_000_000);
/// ```
pub const F915: RfFreq = RfFreq::from_raw(0x39_30_00_00);
/// 868MHz, often used in Europe.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// assert_eq!(RfFreq::F868.freq(), 868_000_000);
/// ```
pub const F868: RfFreq = RfFreq::from_raw(0x36_40_00_00);
/// 433MHz, often used in Europe.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// assert_eq!(RfFreq::F433.freq(), 433_000_000);
/// ```
pub const F433: RfFreq = RfFreq::from_raw(0x1B_10_00_00);
/// Create a new `RfFreq` from a raw bit value.
///
/// The equation used to get the PLL frequency from the raw bits is:
///
/// RF<sub>PLL</sub> = 32e6 × bits / 2<sup>25</sup>
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// const FREQ: RfFreq = RfFreq::from_raw(0x39300000);
/// assert_eq!(FREQ, RfFreq::F915);
/// ```
pub const fn from_raw(bits: u32) -> RfFreq {
RfFreq {
buf: [
super::OpCode::SetRfFrequency as u8,
((bits >> 24) & 0xFF) as u8,
((bits >> 16) & 0xFF) as u8,
((bits >> 8) & 0xFF) as u8,
(bits & 0xFF) as u8,
],
}
}
/// Create a new `RfFreq` from a PLL frequency.
///
/// The equation used to get the raw bits from the PLL frequency is:
///
/// bits = RF<sub>PLL</sub> * 2<sup>25</sup> / 32e6
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// const FREQ: RfFreq = RfFreq::from_frequency(915_000_000);
/// assert_eq!(FREQ, RfFreq::F915);
/// ```
pub const fn from_frequency(freq: u32) -> RfFreq {
Self::from_raw((((freq as u64) * (1 << 25)) / 32_000_000) as u32)
}
// Get the frequency bit value.
const fn as_bits(&self) -> u32 {
((self.buf[1] as u32) << 24) | ((self.buf[2] as u32) << 16) | ((self.buf[3] as u32) << 8) | (self.buf[4] as u32)
}
/// Get the actual frequency.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// assert_eq!(RfFreq::from_raw(0x39300000).freq(), 915_000_000);
/// ```
pub fn freq(&self) -> u32 {
(32_000_000 * (self.as_bits() as u64) / (1 << 25)) as u32
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::RfFreq;
///
/// assert_eq!(RfFreq::F915.as_slice(), &[0x86, 0x39, 0x30, 0x00, 0x00]);
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
#[cfg(test)]
mod test {
use super::RfFreq;
#[test]
fn max() {
assert_eq!(RfFreq::from_raw(u32::MAX).freq(), 4_095_999_999);
}
#[test]
fn min() {
assert_eq!(RfFreq::from_raw(u32::MIN).freq(), 0);
}
}

View file

@ -1,21 +0,0 @@
/// Receiver event which stops the RX timeout timer.
///
/// Used by [`set_rx_timeout_stop`].
///
/// [`set_rx_timeout_stop`]: super::SubGhz::set_rx_timeout_stop
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum RxTimeoutStop {
/// Receive timeout stopped on synchronization word detection in generic
/// packet mode or header detection in LoRa packet mode.
Sync = 0b0,
/// Receive timeout stopped on preamble detection.
Preamble = 0b1,
}
impl From<RxTimeoutStop> for u8 {
fn from(rx_ts: RxTimeoutStop) -> Self {
rx_ts as u8
}
}

View file

@ -1,107 +0,0 @@
/// Startup configurations when exiting sleep mode.
///
/// Argument of [`SleepCfg::set_startup`].
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum Startup {
/// Cold startup when exiting Sleep mode, configuration registers reset.
Cold = 0,
/// Warm startup when exiting Sleep mode,
/// configuration registers kept in retention.
///
/// **Note:** Only the configuration of the activated modem,
/// before going to sleep mode, is retained.
/// The configuration of the other modes is lost and must be re-configured
/// when exiting sleep mode.
Warm = 1,
}
impl Default for Startup {
fn default() -> Self {
Startup::Warm
}
}
/// Sleep configuration.
///
/// Argument of [`set_sleep`].
///
/// [`set_sleep`]: super::SubGhz::set_sleep
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct SleepCfg(u8);
impl SleepCfg {
/// Create a new `SleepCfg` structure.
///
/// This is the same as `default`, but in a `const` function.
///
/// The defaults are a warm startup, with RTC wakeup enabled.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::SleepCfg;
///
/// const SLEEP_CFG: SleepCfg = SleepCfg::new();
/// assert_eq!(SLEEP_CFG, SleepCfg::default());
/// # assert_eq!(u8::from(SLEEP_CFG), 0b101);
/// ```
pub const fn new() -> SleepCfg {
SleepCfg(0).set_startup(Startup::Warm).set_rtc_wakeup_en(true)
}
/// Set the startup mode.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{SleepCfg, Startup};
///
/// const SLEEP_CFG: SleepCfg = SleepCfg::new().set_startup(Startup::Cold);
/// # assert_eq!(u8::from(SLEEP_CFG), 0b001);
/// # assert_eq!(u8::from(SLEEP_CFG.set_startup(Startup::Warm)), 0b101);
/// ```
pub const fn set_startup(mut self, startup: Startup) -> SleepCfg {
if startup as u8 == 1 {
self.0 |= 1 << 2
} else {
self.0 &= !(1 << 2)
}
self
}
/// Set the RTC wakeup enable.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::SleepCfg;
///
/// const SLEEP_CFG: SleepCfg = SleepCfg::new().set_rtc_wakeup_en(false);
/// # assert_eq!(u8::from(SLEEP_CFG), 0b100);
/// # assert_eq!(u8::from(SLEEP_CFG.set_rtc_wakeup_en(true)), 0b101);
/// ```
#[must_use = "set_rtc_wakeup_en returns a modified SleepCfg"]
pub const fn set_rtc_wakeup_en(mut self, en: bool) -> SleepCfg {
if en {
self.0 |= 0b1
} else {
self.0 &= !0b1
}
self
}
}
impl From<SleepCfg> for u8 {
fn from(sc: SleepCfg) -> Self {
sc.0
}
}
impl Default for SleepCfg {
fn default() -> Self {
Self::new()
}
}

View file

@ -1,45 +0,0 @@
/// SMPS maximum drive capability.
///
/// Argument of [`set_smps_drv`](super::SubGhz::set_smps_drv).
#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum SmpsDrv {
/// 20 mA
Milli20 = 0x0,
/// 40 mA
Milli40 = 0x1,
/// 60 mA
Milli60 = 0x2,
/// 100 mA (default)
Milli100 = 0x3,
}
impl SmpsDrv {
/// Get the SMPS drive value as milliamps.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::SmpsDrv;
///
/// assert_eq!(SmpsDrv::Milli20.as_milliamps(), 20);
/// assert_eq!(SmpsDrv::Milli40.as_milliamps(), 40);
/// assert_eq!(SmpsDrv::Milli60.as_milliamps(), 60);
/// assert_eq!(SmpsDrv::Milli100.as_milliamps(), 100);
/// ```
pub const fn as_milliamps(&self) -> u8 {
match self {
SmpsDrv::Milli20 => 20,
SmpsDrv::Milli40 => 40,
SmpsDrv::Milli60 => 60,
SmpsDrv::Milli100 => 100,
}
}
}
impl Default for SmpsDrv {
fn default() -> Self {
SmpsDrv::Milli100
}
}

View file

@ -1,20 +0,0 @@
/// Clock in standby mode.
///
/// Used by [`set_standby`].
///
/// [`set_standby`]: super::SubGhz::set_standby
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum StandbyClk {
/// RC 13 MHz used in standby mode.
Rc = 0b0,
/// HSE32 used in standby mode.
Hse = 0b1,
}
impl From<StandbyClk> for u8 {
fn from(sc: StandbyClk) -> Self {
sc as u8
}
}

View file

@ -1,184 +0,0 @@
use super::Status;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct LoRaStats;
impl LoRaStats {
pub const fn new() -> Self {
Self {}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct FskStats;
impl FskStats {
pub const fn new() -> Self {
Self {}
}
}
/// Packet statistics.
///
/// Returned by [`fsk_stats`] and [`lora_stats`].
///
/// [`fsk_stats`]: super::SubGhz::fsk_stats
/// [`lora_stats`]: super::SubGhz::lora_stats
#[derive(Eq, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Stats<ModType> {
status: Status,
pkt_rx: u16,
pkt_crc: u16,
pkt_len_or_hdr_err: u16,
ty: ModType,
}
impl<ModType> Stats<ModType> {
const fn from_buf(buf: [u8; 7], ty: ModType) -> Stats<ModType> {
Stats {
status: Status::from_raw(buf[0]),
pkt_rx: u16::from_be_bytes([buf[1], buf[2]]),
pkt_crc: u16::from_be_bytes([buf[3], buf[4]]),
pkt_len_or_hdr_err: u16::from_be_bytes([buf[5], buf[6]]),
ty,
}
}
/// Get the radio status returned with the packet statistics.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CmdStatus, FskStats, Stats, StatusMode};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0];
/// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio);
/// assert_eq!(stats.status().mode(), Ok(StatusMode::Rx));
/// assert_eq!(stats.status().cmd(), Ok(CmdStatus::Avaliable));
/// ```
pub const fn status(&self) -> Status {
self.status
}
/// Number of packets received.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{FskStats, Stats};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 3, 0, 0, 0, 0];
/// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio);
/// assert_eq!(stats.pkt_rx(), 3);
/// ```
pub const fn pkt_rx(&self) -> u16 {
self.pkt_rx
}
/// Number of packets received with a payload CRC error
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{LoRaStats, Stats};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 1, 0, 0];
/// let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio);
/// assert_eq!(stats.pkt_crc(), 1);
/// ```
pub const fn pkt_crc(&self) -> u16 {
self.pkt_crc
}
}
impl Stats<FskStats> {
/// Create a new FSK packet statistics structure from a raw buffer.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{FskStats, Stats};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0];
/// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio);
/// ```
pub const fn from_raw_fsk(buf: [u8; 7]) -> Stats<FskStats> {
Self::from_buf(buf, FskStats::new())
}
/// Number of packets received with a payload length error.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{FskStats, Stats};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 1];
/// let stats: Stats<FskStats> = Stats::from_raw_fsk(example_data_from_radio);
/// assert_eq!(stats.pkt_len_err(), 1);
/// ```
pub const fn pkt_len_err(&self) -> u16 {
self.pkt_len_or_hdr_err
}
}
impl Stats<LoRaStats> {
/// Create a new LoRa packet statistics structure from a raw buffer.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{LoRaStats, Stats};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0];
/// let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio);
/// ```
pub const fn from_raw_lora(buf: [u8; 7]) -> Stats<LoRaStats> {
Self::from_buf(buf, LoRaStats::new())
}
/// Number of packets received with a header CRC error.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{LoRaStats, Stats};
///
/// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 1];
/// let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio);
/// assert_eq!(stats.pkt_hdr_err(), 1);
/// ```
pub const fn pkt_hdr_err(&self) -> u16 {
self.pkt_len_or_hdr_err
}
}
impl core::fmt::Debug for Stats<FskStats> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Stats")
.field("status", &self.status())
.field("pkt_rx", &self.pkt_rx())
.field("pkt_crc", &self.pkt_crc())
.field("pkt_len_err", &self.pkt_len_err())
.finish()
}
}
#[cfg(test)]
mod test {
use super::super::{CmdStatus, LoRaStats, Stats, StatusMode};
#[test]
fn mixed() {
let example_data_from_radio: [u8; 7] = [0x54, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06];
let stats: Stats<LoRaStats> = Stats::from_raw_lora(example_data_from_radio);
assert_eq!(stats.status().mode(), Ok(StatusMode::Rx));
assert_eq!(stats.status().cmd(), Ok(CmdStatus::Avaliable));
assert_eq!(stats.pkt_rx(), 0x0102);
assert_eq!(stats.pkt_crc(), 0x0304);
assert_eq!(stats.pkt_hdr_err(), 0x0506);
}
}

View file

@ -1,197 +0,0 @@
/// sub-GHz radio operating mode.
///
/// See `Get_Status` under section 5.8.5 "Communication status information commands"
/// in the reference manual.
///
/// This is returned by [`Status::mode`].
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum StatusMode {
/// Standby mode with RC 13MHz.
StandbyRc = 0x2,
/// Standby mode with HSE32.
StandbyHse = 0x3,
/// Frequency Synthesis mode.
Fs = 0x4,
/// Receive mode.
Rx = 0x5,
/// Transmit mode.
Tx = 0x6,
}
impl StatusMode {
/// Create a new `StatusMode` from bits.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::StatusMode;
///
/// assert_eq!(StatusMode::from_raw(0x2), Ok(StatusMode::StandbyRc));
/// assert_eq!(StatusMode::from_raw(0x3), Ok(StatusMode::StandbyHse));
/// assert_eq!(StatusMode::from_raw(0x4), Ok(StatusMode::Fs));
/// assert_eq!(StatusMode::from_raw(0x5), Ok(StatusMode::Rx));
/// assert_eq!(StatusMode::from_raw(0x6), Ok(StatusMode::Tx));
/// // Other values are reserved
/// assert_eq!(StatusMode::from_raw(0), Err(0));
/// ```
pub const fn from_raw(bits: u8) -> Result<Self, u8> {
match bits {
0x2 => Ok(StatusMode::StandbyRc),
0x3 => Ok(StatusMode::StandbyHse),
0x4 => Ok(StatusMode::Fs),
0x5 => Ok(StatusMode::Rx),
0x6 => Ok(StatusMode::Tx),
_ => Err(bits),
}
}
}
/// Command status.
///
/// See `Get_Status` under section 5.8.5 "Communication status information commands"
/// in the reference manual.
///
/// This is returned by [`Status::cmd`].
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum CmdStatus {
/// Data available to host.
///
/// Packet received successfully and data can be retrieved.
Avaliable = 0x2,
/// Command time out.
///
/// Command took too long to complete triggering a sub-GHz radio watchdog
/// timeout.
Timeout = 0x3,
/// Command processing error.
///
/// Invalid opcode or incorrect number of parameters.
ProcessingError = 0x4,
/// Command execution failure.
///
/// Command successfully received but cannot be executed at this time,
/// requested operating mode cannot be entered or requested data cannot be
/// sent.
ExecutionFailure = 0x5,
/// Transmit command completed.
///
/// Current packet transmission completed.
Complete = 0x6,
}
impl CmdStatus {
/// Create a new `CmdStatus` from bits.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::CmdStatus;
///
/// assert_eq!(CmdStatus::from_raw(0x2), Ok(CmdStatus::Avaliable));
/// assert_eq!(CmdStatus::from_raw(0x3), Ok(CmdStatus::Timeout));
/// assert_eq!(CmdStatus::from_raw(0x4), Ok(CmdStatus::ProcessingError));
/// assert_eq!(CmdStatus::from_raw(0x5), Ok(CmdStatus::ExecutionFailure));
/// assert_eq!(CmdStatus::from_raw(0x6), Ok(CmdStatus::Complete));
/// // Other values are reserved
/// assert_eq!(CmdStatus::from_raw(0), Err(0));
/// ```
pub const fn from_raw(bits: u8) -> Result<Self, u8> {
match bits {
0x2 => Ok(CmdStatus::Avaliable),
0x3 => Ok(CmdStatus::Timeout),
0x4 => Ok(CmdStatus::ProcessingError),
0x5 => Ok(CmdStatus::ExecutionFailure),
0x6 => Ok(CmdStatus::Complete),
_ => Err(bits),
}
}
}
/// Radio status.
///
/// This is returned by [`status`].
///
/// [`status`]: super::SubGhz::status
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct Status(u8);
impl From<u8> for Status {
fn from(x: u8) -> Self {
Status(x)
}
}
impl From<Status> for u8 {
fn from(x: Status) -> Self {
x.0
}
}
impl Status {
/// Create a new `Status` from a raw `u8` value.
///
/// This is the same as `Status::from(u8)`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CmdStatus, Status, StatusMode};
///
/// const STATUS: Status = Status::from_raw(0x54_u8);
/// assert_eq!(STATUS.mode(), Ok(StatusMode::Rx));
/// assert_eq!(STATUS.cmd(), Ok(CmdStatus::Avaliable));
/// ```
pub const fn from_raw(value: u8) -> Status {
Status(value)
}
/// sub-GHz radio operating mode.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{Status, StatusMode};
///
/// let status: Status = 0xACu8.into();
/// assert_eq!(status.mode(), Ok(StatusMode::StandbyRc));
/// ```
pub const fn mode(&self) -> Result<StatusMode, u8> {
StatusMode::from_raw((self.0 >> 4) & 0b111)
}
/// Command status.
///
/// This method frequently returns reserved values such as `Err(1)`.
/// ST support has confirmed that this is normal and should be ignored.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{CmdStatus, Status};
///
/// let status: Status = 0xACu8.into();
/// assert_eq!(status.cmd(), Ok(CmdStatus::Complete));
/// ```
pub const fn cmd(&self) -> Result<CmdStatus, u8> {
CmdStatus::from_raw((self.0 >> 1) & 0b111)
}
}
impl core::fmt::Debug for Status {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Status")
.field("mode", &self.mode())
.field("cmd", &self.cmd())
.finish()
}
}
#[cfg(feature = "defmt")]
impl defmt::Format for Status {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(fmt, "Status {{ mode: {}, cmd: {} }}", self.mode(), self.cmd())
}
}

View file

@ -1,170 +0,0 @@
use super::Timeout;
/// TCXO trim.
///
/// **Note:** To use V<sub>DDTCXO</sub>, the V<sub>DDRF</sub> supply must be at
/// least + 200 mV higher than the selected `TcxoTrim` voltage level.
///
/// Used by [`TcxoMode`].
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum TcxoTrim {
/// 1.6V
Volts1pt6 = 0x0,
/// 1.7V
Volts1pt7 = 0x1,
/// 1.8V
Volts1pt8 = 0x2,
/// 2.2V
Volts2pt2 = 0x3,
/// 2.4V
Volts2pt4 = 0x4,
/// 2.7V
Volts2pt7 = 0x5,
/// 3.0V
Volts3pt0 = 0x6,
/// 3.3V
Volts3pt3 = 0x7,
}
impl core::fmt::Display for TcxoTrim {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
TcxoTrim::Volts1pt6 => write!(f, "1.6V"),
TcxoTrim::Volts1pt7 => write!(f, "1.7V"),
TcxoTrim::Volts1pt8 => write!(f, "1.8V"),
TcxoTrim::Volts2pt2 => write!(f, "2.2V"),
TcxoTrim::Volts2pt4 => write!(f, "2.4V"),
TcxoTrim::Volts2pt7 => write!(f, "2.7V"),
TcxoTrim::Volts3pt0 => write!(f, "3.0V"),
TcxoTrim::Volts3pt3 => write!(f, "3.3V"),
}
}
}
impl TcxoTrim {
/// Get the value of the TXCO trim in millivolts.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::TcxoTrim;
///
/// assert_eq!(TcxoTrim::Volts1pt6.as_millivolts(), 1600);
/// assert_eq!(TcxoTrim::Volts1pt7.as_millivolts(), 1700);
/// assert_eq!(TcxoTrim::Volts1pt8.as_millivolts(), 1800);
/// assert_eq!(TcxoTrim::Volts2pt2.as_millivolts(), 2200);
/// assert_eq!(TcxoTrim::Volts2pt4.as_millivolts(), 2400);
/// assert_eq!(TcxoTrim::Volts2pt7.as_millivolts(), 2700);
/// assert_eq!(TcxoTrim::Volts3pt0.as_millivolts(), 3000);
/// assert_eq!(TcxoTrim::Volts3pt3.as_millivolts(), 3300);
/// ```
pub const fn as_millivolts(&self) -> u16 {
match self {
TcxoTrim::Volts1pt6 => 1600,
TcxoTrim::Volts1pt7 => 1700,
TcxoTrim::Volts1pt8 => 1800,
TcxoTrim::Volts2pt2 => 2200,
TcxoTrim::Volts2pt4 => 2400,
TcxoTrim::Volts2pt7 => 2700,
TcxoTrim::Volts3pt0 => 3000,
TcxoTrim::Volts3pt3 => 3300,
}
}
}
/// TCXO trim and HSE32 ready timeout.
///
/// Argument of [`set_tcxo_mode`].
///
/// [`set_tcxo_mode`]: super::SubGhz::set_tcxo_mode
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TcxoMode {
buf: [u8; 5],
}
impl TcxoMode {
/// Create a new `TcxoMode` struct.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::TcxoMode;
///
/// const TCXO_MODE: TcxoMode = TcxoMode::new();
/// ```
pub const fn new() -> TcxoMode {
TcxoMode {
buf: [super::OpCode::SetTcxoMode as u8, 0x00, 0x00, 0x00, 0x00],
}
}
/// Set the TCXO trim.
///
/// **Note:** To use V<sub>DDTCXO</sub>, the V<sub>DDRF</sub> supply must be
/// at least + 200 mV higher than the selected `TcxoTrim` voltage level.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{TcxoMode, TcxoTrim};
///
/// const TCXO_MODE: TcxoMode = TcxoMode::new().set_txco_trim(TcxoTrim::Volts1pt6);
/// # assert_eq!(TCXO_MODE.as_slice()[1], 0x00);
/// ```
#[must_use = "set_txco_trim returns a modified TcxoMode"]
pub const fn set_txco_trim(mut self, tcxo_trim: TcxoTrim) -> TcxoMode {
self.buf[1] = tcxo_trim as u8;
self
}
/// Set the ready timeout duration.
///
/// # Example
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::{TcxoMode, Timeout};
///
/// // 15.625 ms timeout
/// const TIMEOUT: Timeout = Timeout::from_duration_sat(Duration::from_millis(15_625));
/// const TCXO_MODE: TcxoMode = TcxoMode::new().set_timeout(TIMEOUT);
/// # assert_eq!(TCXO_MODE.as_slice()[2], 0x0F);
/// # assert_eq!(TCXO_MODE.as_slice()[3], 0x42);
/// # assert_eq!(TCXO_MODE.as_slice()[4], 0x40);
/// ```
#[must_use = "set_timeout returns a modified TcxoMode"]
pub const fn set_timeout(mut self, timeout: Timeout) -> TcxoMode {
let timeout_bits: u32 = timeout.into_bits();
self.buf[2] = ((timeout_bits >> 16) & 0xFF) as u8;
self.buf[3] = ((timeout_bits >> 8) & 0xFF) as u8;
self.buf[4] = (timeout_bits & 0xFF) as u8;
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{TcxoMode, TcxoTrim, Timeout};
///
/// const TCXO_MODE: TcxoMode = TcxoMode::new()
/// .set_txco_trim(TcxoTrim::Volts1pt7)
/// .set_timeout(Timeout::from_raw(0x123456));
/// assert_eq!(TCXO_MODE.as_slice(), &[0x97, 0x1, 0x12, 0x34, 0x56]);
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for TcxoMode {
fn default() -> Self {
Self::new()
}
}

View file

@ -1,492 +0,0 @@
use core::time::Duration;
use super::ValueError;
const fn abs_diff(a: u64, b: u64) -> u64 {
if a > b {
a - b
} else {
b - a
}
}
/// Timeout argument.
///
/// This is used by:
/// * [`set_rx`]
/// * [`set_tx`]
/// * [`TcxoMode`]
///
/// Each timeout has 3 bytes, with a resolution of 15.625µs per bit, giving a
/// range of 0s to 262.143984375s.
///
/// [`set_rx`]: super::SubGhz::set_rx
/// [`set_tx`]: super::SubGhz::set_tx
/// [`TcxoMode`]: super::TcxoMode
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Timeout {
bits: u32,
}
impl Timeout {
const BITS_PER_MILLI: u32 = 64; // 1e-3 / 15.625e-6
const BITS_PER_SEC: u32 = 64_000; // 1 / 15.625e-6
/// Disable the timeout (0s timeout).
///
/// # Example
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::Timeout;
///
/// const TIMEOUT: Timeout = Timeout::DISABLED;
/// assert_eq!(TIMEOUT.as_duration(), Duration::from_secs(0));
/// ```
pub const DISABLED: Timeout = Timeout { bits: 0x0 };
/// Minimum timeout, 15.625µs.
///
/// # Example
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::Timeout;
///
/// const TIMEOUT: Timeout = Timeout::MIN;
/// assert_eq!(TIMEOUT.into_bits(), 1);
/// ```
pub const MIN: Timeout = Timeout { bits: 1 };
/// Maximum timeout, 262.143984375s.
///
/// # Example
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::Timeout;
///
/// const TIMEOUT: Timeout = Timeout::MAX;
/// assert_eq!(TIMEOUT.as_duration(), Duration::from_nanos(262_143_984_375));
/// ```
pub const MAX: Timeout = Timeout { bits: 0x00FF_FFFF };
/// Timeout resolution in nanoseconds, 15.625µs.
pub const RESOLUTION_NANOS: u16 = 15_625;
/// Timeout resolution, 15.625µs.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(
/// Timeout::RESOLUTION.as_nanos(),
/// Timeout::RESOLUTION_NANOS as u128
/// );
/// ```
pub const RESOLUTION: Duration = Duration::from_nanos(Self::RESOLUTION_NANOS as u64);
/// Create a new timeout from a [`Duration`].
///
/// This will return the nearest timeout value possible, or a
/// [`ValueError`] if the value is out of bounds.
///
/// Use [`from_millis_sat`](Self::from_millis_sat) for runtime timeout
/// construction.
/// This is not _that_ useful right now, it is simply future proofing for a
/// time when `Result::unwrap` is available for `const fn`.
///
/// # Example
///
/// Value within bounds:
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::{Timeout, ValueError};
///
/// const MIN: Duration = Timeout::RESOLUTION;
/// assert_eq!(Timeout::from_duration(MIN).unwrap(), Timeout::MIN);
/// ```
///
/// Value too low:
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::{Timeout, ValueError};
///
/// const LOWER_LIMIT_NANOS: u128 = 7813;
/// const TOO_LOW_NANOS: u128 = LOWER_LIMIT_NANOS - 1;
/// const TOO_LOW_DURATION: Duration = Duration::from_nanos(TOO_LOW_NANOS as u64);
/// assert_eq!(
/// Timeout::from_duration(TOO_LOW_DURATION),
/// Err(ValueError::too_low(TOO_LOW_NANOS, LOWER_LIMIT_NANOS))
/// );
/// ```
///
/// Value too high:
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::{Timeout, ValueError};
///
/// const UPPER_LIMIT_NANOS: u128 = Timeout::MAX.as_nanos() as u128 + 7812;
/// const TOO_HIGH_NANOS: u128 = UPPER_LIMIT_NANOS + 1;
/// const TOO_HIGH_DURATION: Duration = Duration::from_nanos(TOO_HIGH_NANOS as u64);
/// assert_eq!(
/// Timeout::from_duration(TOO_HIGH_DURATION),
/// Err(ValueError::too_high(TOO_HIGH_NANOS, UPPER_LIMIT_NANOS))
/// );
/// ```
pub const fn from_duration(duration: Duration) -> Result<Timeout, ValueError<u128>> {
// at the time of development many methods in
// `core::Duration` were not `const fn`, which leads to the hacks
// you see here.
let nanos: u128 = duration.as_nanos();
const UPPER_LIMIT: u128 = Timeout::MAX.as_nanos() as u128 + (Timeout::RESOLUTION_NANOS as u128) / 2;
const LOWER_LIMIT: u128 = (((Timeout::RESOLUTION_NANOS as u128) + 1) / 2) as u128;
if nanos > UPPER_LIMIT {
Err(ValueError::too_high(nanos, UPPER_LIMIT))
} else if nanos < LOWER_LIMIT {
Err(ValueError::too_low(nanos, LOWER_LIMIT))
} else {
// safe to truncate here because of previous bounds check.
let duration_nanos: u64 = nanos as u64;
let div_floor: u64 = duration_nanos / (Self::RESOLUTION_NANOS as u64);
let div_ceil: u64 = 1 + (duration_nanos - 1) / (Self::RESOLUTION_NANOS as u64);
let timeout_ceil: Timeout = Timeout::from_raw(div_ceil as u32);
let timeout_floor: Timeout = Timeout::from_raw(div_floor as u32);
let error_ceil: u64 = abs_diff(timeout_ceil.as_nanos(), duration_nanos);
let error_floor: u64 = abs_diff(timeout_floor.as_nanos(), duration_nanos);
if error_ceil < error_floor {
Ok(timeout_ceil)
} else {
Ok(timeout_floor)
}
}
}
/// Create a new timeout from a [`Duration`].
///
/// This will return the nearest timeout value possible, saturating at the
/// limits.
///
/// This is an expensive function to call outside of `const` contexts.
/// Use [`from_millis_sat`](Self::from_millis_sat) for runtime timeout
/// construction.
///
/// # Example
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::Timeout;
///
/// const DURATION_MAX_NS: u64 = 262_143_984_376;
///
/// assert_eq!(
/// Timeout::from_duration_sat(Duration::from_millis(0)),
/// Timeout::MIN
/// );
/// assert_eq!(
/// Timeout::from_duration_sat(Duration::from_nanos(DURATION_MAX_NS)),
/// Timeout::MAX
/// );
/// assert_eq!(
/// Timeout::from_duration_sat(Timeout::RESOLUTION).into_bits(),
/// 1
/// );
/// ```
pub const fn from_duration_sat(duration: Duration) -> Timeout {
// at the time of development many methods in
// `core::Duration` were not `const fn`, which leads to the hacks
// you see here.
let nanos: u128 = duration.as_nanos();
const UPPER_LIMIT: u128 = Timeout::MAX.as_nanos() as u128;
if nanos > UPPER_LIMIT {
Timeout::MAX
} else if nanos < (Timeout::RESOLUTION_NANOS as u128) {
Timeout::from_raw(1)
} else {
// safe to truncate here because of previous bounds check.
let duration_nanos: u64 = duration.as_nanos() as u64;
let div_floor: u64 = duration_nanos / (Self::RESOLUTION_NANOS as u64);
let div_ceil: u64 = 1 + (duration_nanos - 1) / (Self::RESOLUTION_NANOS as u64);
let timeout_ceil: Timeout = Timeout::from_raw(div_ceil as u32);
let timeout_floor: Timeout = Timeout::from_raw(div_floor as u32);
let error_ceil: u64 = abs_diff(timeout_ceil.as_nanos(), duration_nanos);
let error_floor: u64 = abs_diff(timeout_floor.as_nanos(), duration_nanos);
if error_ceil < error_floor {
timeout_ceil
} else {
timeout_floor
}
}
}
/// Create a new timeout from a milliseconds value.
///
/// This will round towards zero and saturate at the limits.
///
/// This is the preferred method to call when you need to generate a
/// timeout value at runtime.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::from_millis_sat(0), Timeout::MIN);
/// assert_eq!(Timeout::from_millis_sat(262_144), Timeout::MAX);
/// assert_eq!(Timeout::from_millis_sat(1).into_bits(), 64);
/// ```
pub const fn from_millis_sat(millis: u32) -> Timeout {
if millis == 0 {
Timeout::MIN
} else if millis >= 262_144 {
Timeout::MAX
} else {
Timeout::from_raw(millis * Self::BITS_PER_MILLI)
}
}
/// Create a timeout from raw bits, where each bit has the resolution of
/// [`Timeout::RESOLUTION`].
///
/// **Note:** Only the first 24 bits of the `u32` are used, the `bits`
/// argument will be masked.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::from_raw(u32::MAX), Timeout::MAX);
/// assert_eq!(Timeout::from_raw(0x00_FF_FF_FF), Timeout::MAX);
/// assert_eq!(Timeout::from_raw(1).as_duration(), Timeout::RESOLUTION);
/// assert_eq!(Timeout::from_raw(0), Timeout::DISABLED);
/// ```
pub const fn from_raw(bits: u32) -> Timeout {
Timeout {
bits: bits & 0x00FF_FFFF,
}
}
/// Get the timeout as nanoseconds.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::MAX.as_nanos(), 262_143_984_375);
/// assert_eq!(Timeout::DISABLED.as_nanos(), 0);
/// assert_eq!(Timeout::from_raw(1).as_nanos(), 15_625);
/// assert_eq!(Timeout::from_raw(64_000).as_nanos(), 1_000_000_000);
/// ```
pub const fn as_nanos(&self) -> u64 {
(self.bits as u64) * (Timeout::RESOLUTION_NANOS as u64)
}
/// Get the timeout as microseconds, rounding towards zero.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::MAX.as_micros(), 262_143_984);
/// assert_eq!(Timeout::DISABLED.as_micros(), 0);
/// assert_eq!(Timeout::from_raw(1).as_micros(), 15);
/// assert_eq!(Timeout::from_raw(64_000).as_micros(), 1_000_000);
/// ```
pub const fn as_micros(&self) -> u32 {
(self.as_nanos() / 1_000) as u32
}
/// Get the timeout as milliseconds, rounding towards zero.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::MAX.as_millis(), 262_143);
/// assert_eq!(Timeout::DISABLED.as_millis(), 0);
/// assert_eq!(Timeout::from_raw(1).as_millis(), 0);
/// assert_eq!(Timeout::from_raw(64_000).as_millis(), 1_000);
/// ```
pub const fn as_millis(&self) -> u32 {
self.into_bits() / Self::BITS_PER_MILLI
}
/// Get the timeout as seconds, rounding towards zero.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::MAX.as_secs(), 262);
/// assert_eq!(Timeout::DISABLED.as_secs(), 0);
/// assert_eq!(Timeout::from_raw(1).as_secs(), 0);
/// assert_eq!(Timeout::from_raw(64_000).as_secs(), 1);
/// ```
pub const fn as_secs(&self) -> u16 {
(self.into_bits() / Self::BITS_PER_SEC) as u16
}
/// Get the timeout as a [`Duration`].
///
/// # Example
///
/// ```
/// use core::time::Duration;
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(
/// Timeout::MAX.as_duration(),
/// Duration::from_nanos(262_143_984_375)
/// );
/// assert_eq!(Timeout::DISABLED.as_duration(), Duration::from_nanos(0));
/// assert_eq!(Timeout::from_raw(1).as_duration(), Timeout::RESOLUTION);
/// ```
pub const fn as_duration(&self) -> Duration {
Duration::from_nanos((self.bits as u64) * (Timeout::RESOLUTION_NANOS as u64))
}
/// Get the bit value for the timeout.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::from_raw(u32::MAX).into_bits(), 0x00FF_FFFF);
/// assert_eq!(Timeout::from_raw(1).into_bits(), 1);
/// ```
pub const fn into_bits(self) -> u32 {
self.bits
}
/// Get the byte value for the timeout.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(Timeout::from_raw(u32::MAX).as_bytes(), [0xFF, 0xFF, 0xFF]);
/// assert_eq!(Timeout::from_raw(1).as_bytes(), [0, 0, 1]);
/// ```
pub const fn as_bytes(self) -> [u8; 3] {
[
((self.bits >> 16) & 0xFF) as u8,
((self.bits >> 8) & 0xFF) as u8,
(self.bits & 0xFF) as u8,
]
}
/// Saturating timeout addition. Computes `self + rhs`, saturating at the
/// numeric bounds instead of overflowing.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::Timeout;
///
/// assert_eq!(
/// Timeout::from_raw(0xFF_FF_F0).saturating_add(Timeout::from_raw(0xFF)),
/// Timeout::from_raw(0xFF_FF_FF)
/// );
/// assert_eq!(
/// Timeout::from_raw(100).saturating_add(Timeout::from_raw(23)),
/// Timeout::from_raw(123)
/// );
/// ```
#[must_use = "saturating_add returns a new Timeout"]
pub const fn saturating_add(self, rhs: Self) -> Self {
// TODO: use core::cmp::min when it is const
let bits: u32 = self.bits.saturating_add(rhs.bits);
if bits > Self::MAX.bits {
Self::MAX
} else {
Self { bits }
}
}
}
impl From<Timeout> for Duration {
fn from(to: Timeout) -> Self {
to.as_duration()
}
}
impl From<Timeout> for [u8; 3] {
fn from(to: Timeout) -> Self {
to.as_bytes()
}
}
#[cfg(feature = "time")]
impl From<Timeout> for embassy_time::Duration {
fn from(to: Timeout) -> Self {
embassy_time::Duration::from_micros(to.as_micros().into())
}
}
#[cfg(test)]
mod tests {
use core::time::Duration;
use super::{Timeout, ValueError};
#[test]
fn saturate() {
assert_eq!(Timeout::from_duration_sat(Duration::from_secs(u64::MAX)), Timeout::MAX);
}
#[test]
fn rounding() {
const NANO1: Duration = Duration::from_nanos(1);
let res_sub_1_ns: Duration = Timeout::RESOLUTION - NANO1;
let res_add_1_ns: Duration = Timeout::RESOLUTION + NANO1;
assert_eq!(Timeout::from_duration_sat(res_sub_1_ns).into_bits(), 1);
assert_eq!(Timeout::from_duration_sat(res_add_1_ns).into_bits(), 1);
}
#[test]
fn lower_limit() {
let low: Duration = (Timeout::RESOLUTION + Duration::from_nanos(1)) / 2;
assert_eq!(Timeout::from_duration(low), Ok(Timeout::from_raw(1)));
let too_low: Duration = low - Duration::from_nanos(1);
assert_eq!(
Timeout::from_duration(too_low),
Err(ValueError::too_low(too_low.as_nanos(), low.as_nanos()))
);
}
#[test]
fn upper_limit() {
let high: Duration = Timeout::MAX.as_duration() + Timeout::RESOLUTION / 2;
assert_eq!(Timeout::from_duration(high), Ok(Timeout::from_raw(0xFFFFFF)));
let too_high: Duration = high + Duration::from_nanos(1);
assert_eq!(
Timeout::from_duration(too_high),
Err(ValueError::too_high(too_high.as_nanos(), high.as_nanos()))
);
}
}

View file

@ -1,192 +0,0 @@
/// Power amplifier ramp time for FSK, MSK, and LoRa modulation.
///
/// Argument of [`set_ramp_time`][`super::TxParams::set_ramp_time`].
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u8)]
pub enum RampTime {
/// 10µs
Micros10 = 0x00,
/// 20µs
Micros20 = 0x01,
/// 40µs
Micros40 = 0x02,
/// 80µs
Micros80 = 0x03,
/// 200µs
Micros200 = 0x04,
/// 800µs
Micros800 = 0x05,
/// 1.7ms
Micros1700 = 0x06,
/// 3.4ms
Micros3400 = 0x07,
}
impl From<RampTime> for u8 {
fn from(rt: RampTime) -> Self {
rt as u8
}
}
impl From<RampTime> for core::time::Duration {
fn from(rt: RampTime) -> Self {
match rt {
RampTime::Micros10 => core::time::Duration::from_micros(10),
RampTime::Micros20 => core::time::Duration::from_micros(20),
RampTime::Micros40 => core::time::Duration::from_micros(40),
RampTime::Micros80 => core::time::Duration::from_micros(80),
RampTime::Micros200 => core::time::Duration::from_micros(200),
RampTime::Micros800 => core::time::Duration::from_micros(800),
RampTime::Micros1700 => core::time::Duration::from_micros(1700),
RampTime::Micros3400 => core::time::Duration::from_micros(3400),
}
}
}
#[cfg(feature = "time")]
impl From<RampTime> for embassy_time::Duration {
fn from(rt: RampTime) -> Self {
match rt {
RampTime::Micros10 => embassy_time::Duration::from_micros(10),
RampTime::Micros20 => embassy_time::Duration::from_micros(20),
RampTime::Micros40 => embassy_time::Duration::from_micros(40),
RampTime::Micros80 => embassy_time::Duration::from_micros(80),
RampTime::Micros200 => embassy_time::Duration::from_micros(200),
RampTime::Micros800 => embassy_time::Duration::from_micros(800),
RampTime::Micros1700 => embassy_time::Duration::from_micros(1700),
RampTime::Micros3400 => embassy_time::Duration::from_micros(3400),
}
}
}
/// Transmit parameters, output power and power amplifier ramp up time.
///
/// Argument of [`set_tx_params`][`super::SubGhz::set_tx_params`].
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct TxParams {
buf: [u8; 3],
}
impl TxParams {
/// Optimal power setting for +15dBm output power with the low-power PA.
///
/// This must be used with [`PaConfig::LP_15`](super::PaConfig::LP_15).
pub const LP_15: TxParams = TxParams::new().set_power(0x0E);
/// Optimal power setting for +14dBm output power with the low-power PA.
///
/// This must be used with [`PaConfig::LP_14`](super::PaConfig::LP_14).
pub const LP_14: TxParams = TxParams::new().set_power(0x0E);
/// Optimal power setting for +10dBm output power with the low-power PA.
///
/// This must be used with [`PaConfig::LP_10`](super::PaConfig::LP_10).
pub const LP_10: TxParams = TxParams::new().set_power(0x0D);
/// Optimal power setting for the high-power PA.
///
/// This must be used with one of:
///
/// * [`PaConfig::HP_22`](super::PaConfig::HP_22)
/// * [`PaConfig::HP_20`](super::PaConfig::HP_20)
/// * [`PaConfig::HP_17`](super::PaConfig::HP_17)
/// * [`PaConfig::HP_14`](super::PaConfig::HP_14)
pub const HP: TxParams = TxParams::new().set_power(0x16);
/// Create a new `TxParams` struct.
///
/// This is the same as `default`, but in a `const` function.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::TxParams;
///
/// const TX_PARAMS: TxParams = TxParams::new();
/// assert_eq!(TX_PARAMS, TxParams::default());
/// ```
pub const fn new() -> TxParams {
TxParams {
buf: [super::OpCode::SetTxParams as u8, 0x00, 0x00],
}
}
/// Set the output power.
///
/// For low power selected in [`set_pa_config`]:
///
/// * 0x0E: +14 dB
/// * ...
/// * 0x00: 0 dB
/// * ...
/// * 0xEF: -17 dB
/// * Others: reserved
///
/// For high power selected in [`set_pa_config`]:
///
/// * 0x16: +22 dB
/// * ...
/// * 0x00: 0 dB
/// * ...
/// * 0xF7: -9 dB
/// * Others: reserved
///
/// # Example
///
/// Set the output power to 0 dB.
///
/// ```
/// use stm32wlxx_hal::subghz::{RampTime, TxParams};
///
/// const TX_PARAMS: TxParams = TxParams::new().set_power(0x00);
/// # assert_eq!(TX_PARAMS.as_slice()[1], 0x00);
/// ```
///
/// [`set_pa_config`]: super::SubGhz::set_pa_config
#[must_use = "set_power returns a modified TxParams"]
pub const fn set_power(mut self, power: u8) -> TxParams {
self.buf[1] = power;
self
}
/// Set the Power amplifier ramp time for FSK, MSK, and LoRa modulation.
///
/// # Example
///
/// Set the ramp time to 200 microseconds.
///
/// ```
/// use stm32wlxx_hal::subghz::{RampTime, TxParams};
///
/// const TX_PARAMS: TxParams = TxParams::new().set_ramp_time(RampTime::Micros200);
/// # assert_eq!(TX_PARAMS.as_slice()[2], 0x04);
/// ```
#[must_use = "set_ramp_time returns a modified TxParams"]
pub const fn set_ramp_time(mut self, rt: RampTime) -> TxParams {
self.buf[2] = rt as u8;
self
}
/// Extracts a slice containing the packet.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::{RampTime, TxParams};
///
/// const TX_PARAMS: TxParams = TxParams::new()
/// .set_ramp_time(RampTime::Micros80)
/// .set_power(0x0E);
/// assert_eq!(TX_PARAMS.as_slice(), &[0x8E, 0x0E, 0x03]);
/// ```
pub const fn as_slice(&self) -> &[u8] {
&self.buf
}
}
impl Default for TxParams {
fn default() -> Self {
Self::new()
}
}

View file

@ -1,129 +0,0 @@
/// Error for a value that is out-of-bounds.
///
/// Used by [`Timeout::from_duration`].
///
/// [`Timeout::from_duration`]: super::Timeout::from_duration
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ValueError<T> {
value: T,
limit: T,
over: bool,
}
impl<T> ValueError<T> {
/// Create a new `ValueError` for a value that exceeded an upper bound.
///
/// Unfortunately panic is not available in `const fn`, so there are no
/// guarantees on the value being greater than the limit.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::ValueError;
///
/// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8);
/// assert!(ERROR.over());
/// assert!(!ERROR.under());
/// ```
pub const fn too_high(value: T, limit: T) -> ValueError<T> {
ValueError {
value,
limit,
over: true,
}
}
/// Create a new `ValueError` for a value that exceeded a lower bound.
///
/// Unfortunately panic is not available in `const fn`, so there are no
/// guarantees on the value being less than the limit.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::ValueError;
///
/// const ERROR: ValueError<u8> = ValueError::too_low(200u8, 201u8);
/// assert!(ERROR.under());
/// assert!(!ERROR.over());
/// ```
pub const fn too_low(value: T, limit: T) -> ValueError<T> {
ValueError {
value,
limit,
over: false,
}
}
/// Get the value that caused the error.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::ValueError;
///
/// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8);
/// assert_eq!(ERROR.value(), &101u8);
/// ```
pub const fn value(&self) -> &T {
&self.value
}
/// Get the limit for the value.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::ValueError;
///
/// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8);
/// assert_eq!(ERROR.limit(), &100u8);
/// ```
pub const fn limit(&self) -> &T {
&self.limit
}
/// Returns `true` if the value was over the limit.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::ValueError;
///
/// const ERROR: ValueError<u8> = ValueError::too_high(101u8, 100u8);
/// assert!(ERROR.over());
/// assert!(!ERROR.under());
/// ```
pub const fn over(&self) -> bool {
self.over
}
/// Returns `true` if the value was under the limit.
///
/// # Example
///
/// ```
/// use stm32wlxx_hal::subghz::ValueError;
///
/// const ERROR: ValueError<u8> = ValueError::too_low(200u8, 201u8);
/// assert!(ERROR.under());
/// assert!(!ERROR.over());
/// ```
pub const fn under(&self) -> bool {
!self.over
}
}
impl<T> core::fmt::Display for ValueError<T>
where
T: core::fmt::Display,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.over {
write!(f, "Value is too high {} > {}", self.value, self.limit)
} else {
write!(f, "Value is too low {} < {}", self.value, self.limit)
}
}
}

View file

@ -18,7 +18,7 @@ embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defm
embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"], optional = true }
embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt", "msos-descriptor",], optional = true }
embedded-io = "0.4.0"
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx126x", "time", "defmt", "external-lora-phy"], optional = true }
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"], optional = true }
lora-phy = { version = "1", optional = true }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true }
lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true }

View file

@ -15,7 +15,7 @@ embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defm
embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] }
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" }
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt", "external-lora-phy"] }
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"] }
lora-phy = { version = "1" }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"] }
lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"] }

View file

@ -14,7 +14,7 @@ embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["de
embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "unstable-traits", "memory-x"] }
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx127x", "time", "defmt", "external-lora-phy"], optional = true }
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"], optional = true }
lora-phy = { version = "1", optional = true }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true }
lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true }

View file

@ -10,7 +10,7 @@ embassy-executor = { version = "0.2.0", path = "../../embassy-executor", feature
embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti"] }
embassy-embedded-hal = {version = "0.1.0", path = "../../embassy-embedded-hal" }
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt", "external-lora-phy"] }
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] }
lora-phy = { version = "1" }
lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"] }
lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"] }