Remove legacy LoRa drivers.
This commit is contained in:
parent
03d6363d5a
commit
9d610c6866
@ -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.
|
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** -
|
- **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** -
|
- **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.
|
<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.
|
||||||
|
@ -9,19 +9,15 @@ src_base = "https://github.com/embassy-rs/embassy/blob/embassy-lora-v$VERSION/em
|
|||||||
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/"
|
src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/"
|
||||||
features = ["time", "defmt"]
|
features = ["time", "defmt"]
|
||||||
flavors = [
|
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"] },
|
{ name = "stm32wl", target = "thumbv7em-none-eabihf", features = ["stm32wl", "embassy-stm32?/stm32wl55jc-cm4", "embassy-stm32?/time-driver-any"] },
|
||||||
]
|
]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
sx126x = []
|
|
||||||
sx127x = []
|
|
||||||
stm32wl = ["dep:embassy-stm32"]
|
stm32wl = ["dep:embassy-stm32"]
|
||||||
time = []
|
time = []
|
||||||
defmt = ["dep:defmt", "lorawan/defmt", "lorawan-device/defmt"]
|
defmt = ["dep:defmt", "lorawan-device/defmt"]
|
||||||
external-lora-phy = ["dep:lora-phy"]
|
external-lora-phy = ["dep:lora-phy"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@ -41,4 +37,3 @@ bit_field = { version = "0.10" }
|
|||||||
|
|
||||||
lora-phy = { version = "1", optional = true }
|
lora-phy = { version = "1", optional = true }
|
||||||
lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] }
|
lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] }
|
||||||
lorawan = { version = "0.7.3", default-features = false }
|
|
||||||
|
@ -1,24 +1,13 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![feature(async_fn_in_trait, impl_trait_projections)]
|
#![feature(async_fn_in_trait, impl_trait_projections)]
|
||||||
#![allow(incomplete_features)]
|
#![allow(incomplete_features)]
|
||||||
//! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device
|
//! embassy-lora holds LoRa-specific functionality.
|
||||||
//! crate's async LoRaWAN MAC implementation.
|
|
||||||
|
|
||||||
pub(crate) mod fmt;
|
pub(crate) mod fmt;
|
||||||
#[cfg(feature = "external-lora-phy")]
|
#[cfg(feature = "external-lora-phy")]
|
||||||
/// interface variants required by the external lora crate
|
/// interface variants required by the external lora physical layer crate (lora-phy)
|
||||||
pub mod iv;
|
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;
|
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
#[cfg(feature = "time")]
|
||||||
use embassy_time::{Duration, Instant, Timer};
|
use embassy_time::{Duration, Instant, Timer};
|
||||||
|
|
||||||
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)))
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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, ®_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, ®_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, ®_ana_lna_buffer_original)
|
|
||||||
.await?;
|
|
||||||
self.brd_write_registers(Register::AnaMixer, ®_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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
|
||||||
}
|
|
@ -55,9 +55,6 @@ pub mod rtc;
|
|||||||
pub mod sdmmc;
|
pub mod sdmmc;
|
||||||
#[cfg(spi)]
|
#[cfg(spi)]
|
||||||
pub mod 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)]
|
#[cfg(usart)]
|
||||||
pub mod usart;
|
pub mod usart;
|
||||||
#[cfg(all(usb, feature = "time"))]
|
#[cfg(all(usb, feature = "time"))]
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
@ -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,
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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())
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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-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 }
|
embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt", "msos-descriptor",], optional = true }
|
||||||
embedded-io = "0.4.0"
|
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", "external-lora-phy"], optional = true }
|
||||||
lora-phy = { version = "1", 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-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 }
|
lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true }
|
||||||
|
@ -14,7 +14,7 @@ embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["de
|
|||||||
embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
|
embassy-executor = { version = "0.1.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-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-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", "external-lora-phy"], optional = true }
|
||||||
lora-phy = { version = "1", 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-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 }
|
lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true }
|
||||||
|
Loading…
Reference in New Issue
Block a user