summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--embassy-lora/Cargo.toml2
-rw-r--r--embassy-lora/src/lib.rs2
-rw-r--r--embassy-lora/src/sx126x/mod.rs153
-rw-r--r--embassy-lora/src/sx126x/sx126x_lora/board_specific.rs268
-rw-r--r--embassy-lora/src/sx126x/sx126x_lora/mod.rs732
-rw-r--r--embassy-lora/src/sx126x/sx126x_lora/mod_params.rs469
-rw-r--r--embassy-lora/src/sx126x/sx126x_lora/subroutine.rs674
-rw-r--r--examples/nrf/Cargo.toml7
-rw-r--r--examples/nrf/src/bin/lora_p2p_report.rs84
-rw-r--r--examples/nrf/src/bin/lora_p2p_sense.rs173
11 files changed, 2564 insertions, 2 deletions
diff --git a/README.md b/README.md
index 9f08bf67..eaa91012 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,7 @@ The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stac
The <a href="https://github.com/embassy-rs/nrf-softdevice">nrf-softdevice</a> crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers.
- **LoRa** -
-<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX127x transceivers.
+<a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX126x and SX127x transceivers.
- **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.
diff --git a/embassy-lora/Cargo.toml b/embassy-lora/Cargo.toml
index 0e7a982a..bcb837d9 100644
--- a/embassy-lora/Cargo.toml
+++ b/embassy-lora/Cargo.toml
@@ -9,6 +9,7 @@ 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/"
features = ["time", "defmt"]
flavors = [
+ { name = "rak4631", target = "thumbv7em-none-eabihf", features = ["rak4631"] },
{ name = "sx127x", target = "thumbv7em-none-eabihf", features = ["sx127x", "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"] },
]
@@ -16,6 +17,7 @@ flavors = [
[lib]
[features]
+rak4631 = []
sx127x = []
stm32wl = ["embassy-stm32", "embassy-stm32/subghz"]
time = []
diff --git a/embassy-lora/src/lib.rs b/embassy-lora/src/lib.rs
index 90ba0d1d..342f66b2 100644
--- a/embassy-lora/src/lib.rs
+++ b/embassy-lora/src/lib.rs
@@ -7,6 +7,8 @@ pub(crate) mod fmt;
#[cfg(feature = "stm32wl")]
pub mod stm32wl;
+#[cfg(feature = "rak4631")]
+pub mod sx126x;
#[cfg(feature = "sx127x")]
pub mod sx127x;
diff --git a/embassy-lora/src/sx126x/mod.rs b/embassy-lora/src/sx126x/mod.rs
new file mode 100644
index 00000000..ed8cb405
--- /dev/null
+++ b/embassy-lora/src/sx126x/mod.rs
@@ -0,0 +1,153 @@
+use core::future::Future;
+
+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 {
+ -500
+ }
+ fn get_rx_window_duration_ms(&self) -> u32 {
+ 800
+ }
+}
+
+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>;
+
+ type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm
+ where
+ SPI: 'm,
+ CTRL: 'm,
+ WAIT: 'm,
+ BUS: 'm;
+
+ fn tx<'m>(&'m mut self, config: TxConfig, buffer: &'m [u8]) -> Self::TxFuture<'m> {
+ trace!("TX START");
+ async move {
+ self.lora
+ .set_tx_config(
+ config.pw,
+ config.rf.spreading_factor.into(),
+ config.rf.bandwidth.into(),
+ config.rf.coding_rate.into(),
+ 4,
+ 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);
+ }
+ }
+
+ type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm
+ where
+ SPI: 'm,
+ CTRL: 'm,
+ WAIT: 'm,
+ BUS: 'm;
+
+ fn rx<'m>(&'m mut self, config: RfConfig, receiving_buffer: &'m mut [u8]) -> Self::RxFuture<'m> {
+ trace!("RX START");
+ async move {
+ self.lora
+ .set_rx_config(
+ config.spreading_factor.into(),
+ config.bandwidth.into(),
+ config.coding_rate.into(),
+ 4,
+ 4,
+ false,
+ 0u8,
+ true,
+ false,
+ 0,
+ false,
+ 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)))
+ }
+ }
+}
diff --git a/embassy-lora/src/sx126x/sx126x_lora/board_specific.rs b/embassy-lora/src/sx126x/sx126x_lora/board_specific.rs
new file mode 100644
index 00000000..1fb08588
--- /dev/null
+++ b/embassy-lora/src/sx126x/sx126x_lora/board_specific.rs
@@ -0,0 +1,268 @@
+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. Use #[cfg(feature = "board_type")] to specify unique board functionality.
+// The base implementation supports the RAK4631 board.
+
+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 {
+ #[cfg(feature = "rak4631")]
+ RadioType::SX1262
+ }
+
+ // Quiesce the antenna(s).
+ pub(super) fn brd_ant_sleep(&mut self) -> Result<(), RadioError<BUS>> {
+ #[cfg(feature = "rak4631")]
+ {
+ 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>> {
+ #[cfg(feature = "rak4631")]
+ {
+ 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>> {
+ #[cfg(feature = "rak4631")]
+ {
+ 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>> {
+ #[cfg(feature = "rak4631")]
+ Ok(true) // all frequencies currently supported for the SX1262 within a rak4631
+ }
+
+ // 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;
+ }
+}
diff --git a/embassy-lora/src/sx126x/sx126x_lora/mod.rs b/embassy-lora/src/sx126x/sx126x_lora/mod.rs
new file mode 100644
index 00000000..280f26d5
--- /dev/null
+++ b/embassy-lora/src/sx126x/sx126x_lora/mod.rs
@@ -0,0 +1,732 @@
+#![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
+ }
+}
diff --git a/embassy-lora/src/sx126x/sx126x_lora/mod_params.rs b/embassy-lora/src/sx126x/sx126x_lora/mod_params.rs
new file mode 100644
index 00000000..e270b2a0
--- /dev/null
+++ b/embassy-lora/src/sx126x/sx126x_lora/mod_params.rs
@@ -0,0 +1,469 @@
+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
+ }
+}
diff --git a/embassy-lora/src/sx126x/sx126x_lora/subroutine.rs b/embassy-lora/src/sx126x/sx126x_lora/subroutine.rs
new file mode 100644
index 00000000..283e6099
--- /dev/null
+++ b/embassy-lora/src/sx126x/sx126x_lora/subroutine.rs
@@ -0,0 +1,674 @@
+use embedded_hal::digital::v2::OutputPin;
+use embedded_hal_async::digital::Wait;
+use embedded_hal_async::spi::SpiBus;
+
+use super::mod_params::*;
+use super::LoRa;
+
+// Internal frequency of the radio
+const SX126X_XTAL_FREQ: u32 = 32000000;
+
+// Scaling factor used to perform fixed-point operations
+const SX126X_PLL_STEP_SHIFT_AMOUNT: u32 = 14;
+
+// PLL step - scaled with SX126X_PLL_STEP_SHIFT_AMOUNT
+const SX126X_PLL_STEP_SCALED: u32 = SX126X_XTAL_FREQ >> (25 - SX126X_PLL_STEP_SHIFT_AMOUNT);
+
+// Maximum value for parameter symbNum
+const SX126X_MAX_LORA_SYMB_NUM_TIMEOUT: u8 = 248;
+
+// Provides board-specific functionality for Semtech SX126x-based boards
+
+impl<SPI, CTRL, WAIT, BUS> LoRa<SPI, CTRL, WAIT>
+where
+ SPI: SpiBus<u8, Error = BUS>,
+ CTRL: OutputPin,
+ WAIT: Wait,
+{
+ // Initialize the radio driver
+ pub(super) async fn sub_init(&mut self) -> Result<(), RadioError<BUS>> {
+ self.brd_reset().await?;
+ self.brd_wakeup().await?;
+ self.sub_set_standby(StandbyMode::RC).await?;
+ self.brd_io_tcxo_init().await?;
+ self.brd_io_rf_switch_init().await?;
+ self.image_calibrated = false;
+ Ok(())
+ }
+
+ // Wakeup the radio if it is in Sleep mode and check that Busy is low
+ pub(super) async fn sub_check_device_ready(&mut self) -> Result<(), RadioError<BUS>> {
+ let operating_mode = self.brd_get_operating_mode();
+ if operating_mode == RadioMode::Sleep || operating_mode == RadioMode::ReceiveDutyCycle {
+ self.brd_wakeup().await?;
+ }
+ self.brd_wait_on_busy().await?;
+ Ok(())
+ }
+
+ // Save the payload to be sent in the radio buffer
+ pub(super) async fn sub_set_payload(&mut self, payload: &[u8]) -> Result<(), RadioError<BUS>> {
+ self.brd_write_buffer(0x00, payload).await?;
+ Ok(())
+ }
+
+ // Read the payload received.
+ pub(super) async fn sub_get_payload(&mut self, buffer: &mut [u8]) -> Result<u8, RadioError<BUS>> {
+ let (size, offset) = self.sub_get_rx_buffer_status().await?;
+ if (size as usize) > buffer.len() {
+ Err(RadioError::PayloadSizeMismatch(size as usize, buffer.len()))
+ } else {
+ self.brd_read_buffer(offset, buffer).await?;
+ Ok(size)
+ }
+ }
+
+ // Send a payload
+ pub(super) async fn sub_send_payload(&mut self, payload: &[u8], timeout: u32) -> Result<(), RadioError<BUS>> {
+ self.sub_set_payload(payload).await?;
+ self.sub_set_tx(timeout).await?;
+ Ok(())
+ }
+
+ // Get a 32-bit random value generated by the radio. A valid packet type must have been configured before using this command.
+ //
+ // The radio must be in reception mode before executing this function. This code can potentially result in interrupt generation. It is the responsibility of
+ // the calling code to disable radio interrupts before calling this function, and re-enable them afterwards if necessary, or be certain that any interrupts
+ // generated during this process will not cause undesired side-effects in the software.
+ //
+ // The random numbers produced by the generator do not have a uniform or Gaussian distribution. If uniformity is needed, perform appropriate software post-processing.
+ pub(super) async fn sub_get_random(&mut self) -> Result<u32, RadioError<BUS>> {
+ let mut reg_ana_lna_buffer_original = [0x00u8];
+ let mut reg_ana_mixer_buffer_original = [0x00u8];
+ let mut reg_ana_lna_buffer = [0x00u8];
+ let mut reg_ana_mixer_buffer = [0x00u8];
+ let mut number_buffer = [0x00u8, 0x00u8, 0x00u8, 0x00u8];
+
+ self.brd_read_registers(Register::AnaLNA, &mut reg_ana_lna_buffer_original)
+ .await?;
+ reg_ana_lna_buffer[0] = reg_ana_lna_buffer_original[0] & (!(1 << 0));
+ self.brd_write_registers(Register::AnaLNA, &reg_ana_lna_buffer).await?;
+
+ self.brd_read_registers(Register::AnaMixer, &mut reg_ana_mixer_buffer_original)
+ .await?;
+ reg_ana_mixer_buffer[0] = reg_ana_mixer_buffer_original[0] & (!(1 << 7));
+ self.brd_write_registers(Register::AnaMixer, &reg_ana_mixer_buffer)
+ .await?;
+
+ // Set radio in continuous reception
+ self.sub_set_rx(0xFFFFFFu32).await?;
+
+ self.brd_read_registers(Register::GeneratedRandomNumber, &mut number_buffer)
+ .await?;
+
+ self.sub_set_standby(StandbyMode::RC).await?;
+
+ self.brd_write_registers(Register::AnaLNA, &reg_ana_lna_buffer_original)
+ .await?;
+ self.brd_write_registers(Register::AnaMixer, &reg_ana_mixer_buffer_original)
+ .await?;
+
+ Ok(Self::convert_u8_buffer_to_u32(&number_buffer))
+ }
+
+ // Set the radio in sleep mode
+ pub(super) async fn sub_set_sleep(&mut self, sleep_config: SleepParams) -> Result<(), RadioError<BUS>> {
+ self.brd_ant_sleep()?;
+
+ if !sleep_config.warm_start {
+ self.image_calibrated = false;
+ }
+
+ self.brd_write_command(OpCode::SetSleep, &[sleep_config.value()])
+ .await?;
+ self.brd_set_operating_mode(RadioMode::Sleep);
+ Ok(())
+ }
+
+ // Set the radio in configuration mode
+ pub(super) async fn sub_set_standby(&mut self, mode: StandbyMode) -> Result<(), RadioError<BUS>> {
+ self.brd_write_command(OpCode::SetStandby, &[mode.value()]).await?;
+ if mode == StandbyMode::RC {
+ self.brd_set_operating_mode(RadioMode::StandbyRC);
+ } else {
+ self.brd_set_operating_mode(RadioMode::StandbyXOSC);
+ }
+
+ self.brd_ant_sleep()?;
+ Ok(())
+ }
+
+ // Set the radio in FS mode
+ pub(super) async fn sub_set_fs(&mut self) -> Result<(), RadioError<BUS>> {
+ // antenna settings ???
+ self.brd_write_command(OpCode::SetFS, &[]).await?;
+ self.brd_set_operating_mode(RadioMode::FrequencySynthesis);
+ Ok(())
+ }
+
+ // Set the radio in transmission mode with timeout specified
+ pub(super) async fn sub_set_tx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
+ let buffer = [
+ Self::timeout_1(timeout),
+ Self::timeout_2(timeout),
+ Self::timeout_3(timeout),
+ ];
+
+ self.brd_ant_set_tx()?;
+
+ self.brd_set_operating_mode(RadioMode::Transmit);
+ self.brd_write_command(OpCode::SetTx, &buffer).await?;
+ Ok(())
+ }
+
+ // Set the radio in reception mode with timeout specified
+ pub(super) async fn sub_set_rx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
+ let buffer = [
+ Self::timeout_1(timeout),
+ Self::timeout_2(timeout),
+ Self::timeout_3(timeout),
+ ];
+
+ self.brd_ant_set_rx()?;
+
+ self.brd_set_operating_mode(RadioMode::Receive);
+ self.brd_write_registers(Register::RxGain, &[0x94u8]).await?;
+ self.brd_write_command(OpCode::SetRx, &buffer).await?;
+ Ok(())
+ }
+
+ // Set the radio in reception mode with Boosted LNA gain and timeout specified
+ pub(super) async fn sub_set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> {
+ let buffer = [
+ Self::timeout_1(timeout),
+ Self::timeout_2(timeout),
+ Self::timeout_3(timeout),
+ ];
+
+ self.brd_ant_set_rx()?;
+
+ self.brd_set_operating_mode(RadioMode::Receive);
+ // set max LNA gain, increase current by ~2mA for around ~3dB in sensitivity
+ self.brd_write_registers(Register::RxGain, &[0x96u8]).await?;
+ self.brd_write_command(OpCode::SetRx, &buffer).await?;
+ Ok(())
+ }
+
+ // Set the Rx duty cycle management parameters
+ pub(super) async fn sub_set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError<BUS>> {
+ let buffer = [
+ ((rx_time >> 16) & 0xFF) as u8,
+ ((rx_time >> 8) & 0xFF) as u8,
+ (rx_time & 0xFF) as u8,
+ ((sleep_time >> 16) & 0xFF) as u8,
+ ((sleep_time >> 8) & 0xFF) as u8,
+ (sleep_time & 0xFF) as u8,
+ ];
+
+ // antenna settings ???
+
+ self.brd_write_command(OpCode::SetRxDutyCycle, &buffer).await?;
+ self.brd_set_operating_mode(RadioMode::ReceiveDutyCycle);
+ Ok(())
+ }
+
+ // Set the radio in CAD mode
+ pub(super) async fn sub_set_cad(&mut self) -> Result<(), RadioError<BUS>> {
+ self.brd_ant_set_rx()?;
+
+ self.brd_write_command(OpCode::SetCAD, &[]).await?;
+ self.brd_set_operating_mode(RadioMode::ChannelActivityDetection);
+ Ok(())
+ }
+
+ // Set the radio in continuous wave transmission mode
+ pub(super) async fn sub_set_tx_continuous_wave(&mut self) -> Result<(), RadioError<BUS>> {
+ self.brd_ant_set_tx()?;
+
+ self.brd_write_command(OpCode::SetTxContinuousWave, &[]).await?;
+ self.brd_set_operating_mode(RadioMode::Transmit);
+ Ok(())
+ }
+
+ // Set the radio in continuous preamble transmission mode
+ pub(super) async fn sub_set_tx_infinite_preamble(&mut self) -> Result<(), RadioError<BUS>> {
+ self.brd_ant_set_tx()?;
+
+ self.brd_write_command(OpCode::SetTxContinuousPremable, &[]).await?;
+ self.brd_set_operating_mode(RadioMode::Transmit);
+ Ok(())
+ }
+
+ // Decide which interrupt will stop the internal radio rx timer.
+ // false timer stop after header/syncword detection
+ // true timer stop after preamble detection
+ pub(super) async fn sub_set_stop_rx_timer_on_preamble_detect(
+ &mut self,
+ enable: bool,
+ ) -> Result<(), RadioError<BUS>> {
+ self.brd_write_command(OpCode::SetStopRxTimerOnPreamble, &[enable as u8])
+ .await?;
+ Ok(())
+ }
+
+ // Set the number of symbols the radio will wait to validate a reception
+ pub(super) async fn sub_set_lora_symb_num_timeout(&mut self, symb_num: u16) -> Result<(), RadioError<BUS>> {
+ let mut exp = 0u8;
+ let mut reg;
+ let mut mant = ((core::cmp::min(symb_num, SX126X_MAX_LORA_SYMB_NUM_TIMEOUT as u16) as u8) + 1) >> 1;
+ while mant > 31 {
+ mant = (mant + 3) >> 2;
+ exp += 1;
+ }
+ reg = mant << ((2 * exp) + 1);
+
+ self.brd_write_command(OpCode::SetLoRaSymbTimeout, &[reg]).await?;
+
+ if symb_num != 0 {
+ reg = exp + (mant << 3);
+ self.brd_write_registers(Register::SynchTimeout, &[reg]).await?;
+ }
+
+ Ok(())
+ }
+
+ // Set the power regulators operating mode (LDO or DC_DC). Using only LDO implies that the Rx or Tx current is doubled
+ pub(super) async fn sub_set_regulator_mode(&mut self, mode: RegulatorMode) -> Result<(), RadioError<BUS>> {
+ self.brd_write_command(OpCode::SetRegulatorMode, &[mode.value()])
+ .await?;
+ Ok(())
+ }
+
+ // Calibrate the given radio block
+ pub(super) async fn sub_calibrate(&mut self, calibrate_params: CalibrationParams) -> Result<(), RadioError<BUS>> {
+ self.brd_write_command(OpCode::Calibrate, &[calibrate_params.value()])
+ .await?;
+ Ok(())
+ }
+
+ // Calibrate the image rejection based on the given frequency
+ pub(super) async fn sub_calibrate_image(&mut self, freq: u32) -> Result<(), RadioError<BUS>> {
+ let mut cal_freq = [0x00u8, 0x00u8];
+
+ if freq > 900000000 {
+ cal_freq[0] = 0xE1;
+ cal_freq[1] = 0xE9;
+ } else if freq > 850000000 {
+ cal_freq[0] = 0xD7;
+ cal_freq[1] = 0xDB;
+ } else if freq > 770000000 {
+ cal_freq[0] = 0xC1;
+ cal_freq[1] = 0xC5;
+ } else if freq > 460000000 {
+ cal_freq[0] = 0x75;
+ cal_freq[1] = 0x81;
+ } else if freq > 425000000 {
+ cal_freq[0] = 0x6B;
+ cal_freq[1] = 0x6F;
+ }
+ self.brd_write_command(OpCode::CalibrateImage, &cal_freq).await?;
+ Ok(())
+ }
+
+ // Activate the extention of the timeout when a long preamble is used
+ pub(super) async fn sub_set_long_preamble(&mut self, _enable: u8) -> Result<(), RadioError<BUS>> {
+ Ok(()) // no operation currently
+ }
+
+ // Set the transmission parameters
+ // hp_max 0 for sx1261, 7 for sx1262
+ // device_sel 1 for sx1261, 0 for sx1262
+ // pa_lut 0 for 14dBm LUT, 1 for 22dBm LUT
+ pub(super) async fn sub_set_pa_config(
+ &mut self,
+ pa_duty_cycle: u8,
+ hp_max: u8,
+ device_sel: u8,
+ pa_lut: u8,
+ ) -> Result<(), RadioError<BUS>> {
+ self.brd_write_command(OpCode::SetPAConfig, &[pa_duty_cycle, hp_max, device_sel, pa_lut])
+ .await?;
+ Ok(())
+ }
+
+ // Define into which mode the chip goes after a TX / RX done
+ pub(super) async fn sub_set_rx_tx_fallback_mode(&mut self, fallback_mode: u8) -> Result<(), RadioError<BUS>> {
+ self.brd_write_command(OpCode::SetTxFallbackMode, &[fallback_mode])
+ .await?;
+ Ok(())
+ }
+
+ // Set the IRQ mask and DIO masks
+ pub(super) async fn sub_set_dio_irq_params(
+ &mut self,
+ irq_mask: u16,
+ dio1_mask: u16,
+ dio2_mask: u16,
+ dio3_mask: u16,
+ ) -> Result<(), RadioError<BUS>> {
+ let mut buffer = [0x00u8; 8];
+
+ buffer[0] = ((irq_mask >> 8) & 0x00FF) as u8;
+ buffer[1] = (irq_mask & 0x00FF) as u8;
+ buffer[2] = ((dio1_mask >> 8) & 0x00FF) as u8;
+ buffer[3] = (dio1_mask & 0x00FF) as u8;
+ buffer[4] = ((dio2_mask >> 8) & 0x00FF) as u8;
+ buffer[5] = (dio2_mask & 0x00FF) as u8;
+ buffer[6] = ((dio3_mask >> 8) & 0x00FF) as u8;
+ buffer[7] = (dio3_mask & 0x00FF) as u8;
+ self.brd_write_command(OpCode::CfgDIOIrq, &buffer).await?;
+ Ok(())
+ }
+
+ // Return the current IRQ status
+ pub(super) async fn sub_get_irq_status(&mut self) -> Result<u16, RadioError<BUS>> {
+ let mut irq_status = [0x00u8, 0x00u8];
+ self.brd_read_command(OpCode::GetIrqStatus, &mut irq_status).await?;
+ Ok(((irq_status[0] as u16) << 8) | (irq_status[1] as u16))
+ }
+
+ // Indicate if DIO2 is used to control an RF Switch
+ pub(super) async fn sub_set_dio2_as_rf_switch_ctrl(&mut self, enable: bool) -> Result<(), RadioError<BUS>> {
+ self.brd_write_command(OpCode::SetRFSwitchMode, &[enable as u8]).await?;
+ Ok(())
+ }
+
+ // Indicate if the radio main clock is supplied from a TCXO
+ // tcxo_voltage voltage used to control the TCXO on/off from DIO3
+ // timeout duration given to the TCXO to go to 32MHz
+ pub(super) async fn sub_set_dio3_as_tcxo_ctrl(
+ &mut self,
+ tcxo_voltage: TcxoCtrlVoltage,
+ timeout: u32,
+ ) -> Result<(), RadioError<BUS>> {
+ let buffer = [
+ tcxo_voltage.value() & 0x07,
+ Self::timeout_1(timeout),
+ Self::timeout_2(timeout),
+ Self::timeout_3(timeout),
+ ];
+ self.brd_write_command(OpCode::SetTCXOMode, &buffer).await?;
+
+ Ok(())
+ }
+
+ // Set the RF frequency (Hz)
+ pub(super) async fn sub_set_rf_frequency(&mut self, frequency: u32) -> Result<(), RadioError<BUS>> {
+ let mut buffer = [0x00u8; 4];
+
+ if !self.image_calibrated {
+ self.sub_calibrate_image(frequency).await?;
+ self.image_calibrated = true;
+ }
+
+ let freq_in_pll_steps = Self::convert_freq_in_hz_to_pll_step(frequency);
+
+ buffer[0] = ((freq_in_pll_steps >> 24) & 0xFF) as u8;
+ buffer[1] = ((freq_in_pll_steps >> 16) & 0xFF) as u8;
+ buffer[2] = ((freq_in_pll_steps >> 8) & 0xFF) as u8;
+ buffer[3] = (freq_in_pll_steps & 0xFF) as u8;
+ self.brd_write_command(OpCode::SetRFFrequency, &buffer).await?;
+ Ok(())
+ }
+
+ // Set the radio for the given protocol (LoRa or GFSK). This method has to be called before setting RF frequency, modulation paramaters, and packet paramaters.
+ pub(super) async fn sub_set_packet_type(&mut self, packet_type: PacketType) -> Result<(), RadioError<BUS>> {
+ self.packet_type = packet_type;
+ self.brd_write_command(OpCode::SetPacketType, &[packet_type.value()])
+ .await?;
+ Ok(())
+ }
+
+ // Get the current radio protocol (LoRa or GFSK)
+ pub(super) fn sub_get_packet_type(&mut self) -> PacketType {
+ self.packet_type
+ }
+
+ // Set the transmission parameters
+ // power RF output power [-18..13] dBm
+ // ramp_time transmission ramp up time
+ pub(super) async fn sub_set_tx_params(
+ &mut self,
+ mut power: i8,
+ ramp_time: RampTime,
+ ) -> Result<(), RadioError<BUS>> {
+ if self.brd_get_radio_type() == RadioType::SX1261 {
+ if power == 15 {
+ self.sub_set_pa_config(0x06, 0x00, 0x01, 0x01).await?;
+ } else {
+ self.sub_set_pa_config(0x04, 0x00, 0x01, 0x01).await?;
+ }
+
+ if power >= 14 {
+ power = 14;
+ } else if power < -17 {
+ power = -17;
+ }
+ } else {
+ // Provide better resistance of the SX1262 Tx to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2)
+ let mut tx_clamp_cfg = [0x00u8];
+ self.brd_read_registers(Register::TxClampCfg, &mut tx_clamp_cfg).await?;
+ tx_clamp_cfg[0] = tx_clamp_cfg[0] | (0x0F << 1);
+ self.brd_write_registers(Register::TxClampCfg, &tx_clamp_cfg).await?;
+
+ self.sub_set_pa_config(0x04, 0x07, 0x00, 0x01).await?;
+
+ if power > 22 {
+ power = 22;
+ } else if power < -9 {
+ power = -9;
+ }
+ }
+
+ // power conversion of negative number from i8 to u8 ???
+ self.brd_write_command(OpCode::SetTxParams, &[power as u8, ramp_time.value()])
+ .await?;
+ Ok(())
+ }
+
+ // Set the modulation parameters
+ pub(super) async fn sub_set_modulation_params(&mut self) -> Result<(), RadioError<BUS>> {
+ if self.modulation_params.is_some() {
+ let mut buffer = [0x00u8; 4];
+
+ // Since this driver only supports LoRa, ensure the packet type is set accordingly
+ self.sub_set_packet_type(PacketType::LoRa).await?;
+
+ let modulation_params = self.modulation_params.unwrap();
+ buffer[0] = modulation_params.spreading_factor.value();
+ buffer[1] = modulation_params.bandwidth.value();
+ buffer[2] = modulation_params.coding_rate.value();
+ buffer[3] = modulation_params.low_data_rate_optimize;
+
+ self.brd_write_command(OpCode::SetModulationParams, &buffer).await?;
+ Ok(())
+ } else {
+ Err(RadioError::ModulationParamsMissing)
+ }
+ }
+
+ // Set the packet parameters
+ pub(super) async fn sub_set_packet_params(&mut self) -> Result<(), RadioError<BUS>> {
+ if self.packet_params.is_some() {
+ let mut buffer = [0x00u8; 6];
+
+ // Since this driver only supports LoRa, ensure the packet type is set accordingly
+ self.sub_set_packet_type(PacketType::LoRa).await?;
+
+ let packet_params = self.packet_params.unwrap();
+ buffer[0] = ((packet_params.preamble_length >> 8) & 0xFF) as u8;
+ buffer[1] = (packet_params.preamble_length & 0xFF) as u8;
+ buffer[2] = packet_params.implicit_header as u8;
+ buffer[3] = packet_params.payload_length;
+ buffer[4] = packet_params.crc_on as u8;
+ buffer[5] = packet_params.iq_inverted as u8;
+
+ self.brd_write_command(OpCode::SetPacketParams, &buffer).await?;
+ Ok(())
+ } else {
+ Err(RadioError::PacketParamsMissing)
+ }
+ }
+
+ // Set the channel activity detection (CAD) parameters
+ // symbols number of symbols to use for CAD operations
+ // det_peak limit for detection of SNR peak used in the CAD
+ // det_min minimum symbol recognition for CAD
+ // exit_mode operation to be done at the end of CAD action
+ // timeout timeout value to abort the CAD activity
+
+ pub(super) async fn sub_set_cad_params(
+ &mut self,
+ symbols: CADSymbols,
+ det_peak: u8,
+ det_min: u8,
+ exit_mode: CADExitMode,
+ timeout: u32,
+ ) -> Result<(), RadioError<BUS>> {
+ let mut buffer = [0x00u8; 7];
+
+ buffer[0] = symbols.value();
+ buffer[1] = det_peak;
+ buffer[2] = det_min;
+ buffer[3] = exit_mode.value();
+ buffer[4] = Self::timeout_1(timeout);
+ buffer[5] = Self::timeout_2(timeout);
+ buffer[6] = Self::timeout_3(timeout);
+
+ self.brd_write_command(OpCode::SetCADParams, &buffer).await?;
+ self.brd_set_operating_mode(RadioMode::ChannelActivityDetection);
+ Ok(())
+ }
+
+ // Set the data buffer base address for transmission and reception
+ pub(super) async fn sub_set_buffer_base_address(
+ &mut self,
+ tx_base_address: u8,
+ rx_base_address: u8,
+ ) -> Result<(), RadioError<BUS>> {
+ self.brd_write_command(OpCode::SetBufferBaseAddress, &[tx_base_address, rx_base_address])
+ .await?;
+ Ok(())
+ }
+
+ // Get the current radio status
+ pub(super) async fn sub_get_status(&mut self) -> Result<RadioStatus, RadioError<BUS>> {
+ let status = self.brd_read_command(OpCode::GetStatus, &mut []).await?;
+ Ok(RadioStatus {
+ cmd_status: (status & (0x07 << 1)) >> 1,
+ chip_mode: (status & (0x07 << 4)) >> 4,
+ })
+ }
+
+ // Get the instantaneous RSSI value for the last packet received
+ pub(super) async fn sub_get_rssi_inst(&mut self) -> Result<i8, RadioError<BUS>> {
+ let mut buffer = [0x00u8];
+ self.brd_read_command(OpCode::GetRSSIInst, &mut buffer).await?;
+ let rssi: i8 = (-(buffer[0] as i8)) >> 1; // 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 i8)) >> 1;
+ let snr = ((status[1] as i8) + 2) >> 2;
+ let signal_rssi = (-(status[2] as i8)) >> 1;
+ 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)
+ }
+}
diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml
index 9ebd0484..91c41821 100644
--- a/examples/nrf/Cargo.toml
+++ b/examples/nrf/Cargo.toml
@@ -6,7 +6,8 @@ license = "MIT OR Apache-2.0"
[features]
default = ["nightly"]
-nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net"]
+nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net",
+ "embassy-lora", "lorawan-device", "lorawan"]
[dependencies]
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
@@ -17,6 +18,10 @@ 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", "pool-16"], optional = true }
embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true }
embedded-io = "0.3.0"
+embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["rak4631", "time", "defmt"], optional = true }
+
+lorawan-device = { version = "0.8.0", default-features = false, features = ["async"], optional = true }
+lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"], optional = true }
defmt = "0.3"
defmt-rtt = "0.3"
diff --git a/examples/nrf/src/bin/lora_p2p_report.rs b/examples/nrf/src/bin/lora_p2p_report.rs
new file mode 100644
index 00000000..4ba3d30c
--- /dev/null
+++ b/examples/nrf/src/bin/lora_p2p_report.rs
@@ -0,0 +1,84 @@
+//! This example runs on the RAK4631 WisBlock, which has an nRF52840 MCU and Semtech Sx126x radio.
+#![no_std]
+#![no_main]
+#![macro_use]
+#![allow(dead_code)]
+#![feature(type_alias_impl_trait)]
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_lora::sx126x::*;
+use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pin as _, Pull};
+use embassy_nrf::{interrupt, spim};
+use embassy_time::{Duration, Timer};
+use lorawan_device::async_device::radio::{Bandwidth, CodingRate, PhyRxTx, RfConfig, SpreadingFactor};
+use {defmt_rtt as _, panic_probe as _};
+
+#[embassy_executor::main]
+async fn main(_spawner: Spawner) {
+ let p = embassy_nrf::init(Default::default());
+ let mut spi_config = spim::Config::default();
+ spi_config.frequency = spim::Frequency::M1; // M16 ???
+
+ let mut radio = {
+ let irq = interrupt::take!(SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
+ let spim = spim::Spim::new(p.TWISPI1, irq, p.P1_11, p.P1_13, p.P1_12, spi_config);
+
+ let cs = Output::new(p.P1_10.degrade(), Level::High, OutputDrive::Standard);
+ let reset = Output::new(p.P1_06.degrade(), Level::High, OutputDrive::Standard);
+ let dio1 = Input::new(p.P1_15.degrade(), Pull::Down);
+ let busy = Input::new(p.P1_14.degrade(), Pull::Down);
+ let antenna_rx = Output::new(p.P1_05.degrade(), Level::Low, OutputDrive::Standard);
+ let antenna_tx = Output::new(p.P1_07.degrade(), Level::Low, OutputDrive::Standard);
+
+ match Sx126xRadio::new(spim, cs, reset, antenna_rx, antenna_tx, dio1, busy, false).await {
+ Ok(r) => r,
+ Err(err) => {
+ info!("Sx126xRadio error = {}", err);
+ return;
+ }
+ }
+ };
+
+ let mut debug_indicator = Output::new(p.P1_03, Level::Low, OutputDrive::Standard);
+ let mut start_indicator = Output::new(p.P1_04, Level::Low, OutputDrive::Standard);
+
+ start_indicator.set_high();
+ Timer::after(Duration::from_secs(5)).await;
+ start_indicator.set_low();
+
+ match radio.lora.sleep().await {
+ Ok(()) => info!("Sleep successful"),
+ Err(err) => info!("Sleep unsuccessful = {}", err),
+ }
+
+ let rf_config = RfConfig {
+ frequency: 903900000, // channel in Hz
+ bandwidth: Bandwidth::_250KHz,
+ spreading_factor: SpreadingFactor::_10,
+ coding_rate: CodingRate::_4_8,
+ };
+
+ let mut buffer = [00u8; 100];
+
+ // P2P receive
+ match radio.rx(rf_config, &mut buffer).await {
+ Ok((buffer_len, rx_quality)) => info!(
+ "RX received = {:?} with length = {} rssi = {} snr = {}",
+ &buffer[0..buffer_len],
+ buffer_len,
+ rx_quality.rssi(),
+ rx_quality.snr()
+ ),
+ Err(err) => info!("RX error = {}", err),
+ }
+
+ match radio.lora.sleep().await {
+ Ok(()) => info!("Sleep successful"),
+ Err(err) => info!("Sleep unsuccessful = {}", err),
+ }
+
+ debug_indicator.set_high();
+ Timer::after(Duration::from_secs(5)).await;
+ debug_indicator.set_low();
+}
diff --git a/examples/nrf/src/bin/lora_p2p_sense.rs b/examples/nrf/src/bin/lora_p2p_sense.rs
new file mode 100644
index 00000000..405a8403
--- /dev/null
+++ b/examples/nrf/src/bin/lora_p2p_sense.rs
@@ -0,0 +1,173 @@
+//! This example runs on the RAK4631 WisBlock, which has an nRF52840 MCU and Semtech Sx126x radio.
+#![no_std]
+#![no_main]
+#![macro_use]
+#![feature(type_alias_impl_trait)]
+#![feature(alloc_error_handler)]
+#![allow(incomplete_features)]
+
+use defmt::*;
+use embassy_executor::Spawner;
+use embassy_lora::sx126x::*;
+use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin as _, Pull};
+use embassy_nrf::temp::Temp;
+use embassy_nrf::{interrupt, spim};
+use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
+use embassy_sync::pubsub::{PubSubChannel, Publisher};
+use embassy_time::{Duration, Timer};
+use lorawan_device::async_device::radio::{Bandwidth, CodingRate, PhyRxTx, RfConfig, SpreadingFactor, TxConfig};
+use {defmt_rtt as _, panic_probe as _, panic_probe as _};
+
+// Sensor packet constants
+const TEMPERATURE_UID: u8 = 0x01;
+const MOTION_UID: u8 = 0x02;
+
+// Message bus: queue of 2, 1 subscriber (Lora P2P), 2 publishers (temperature, motion detection)
+static MESSAGE_BUS: PubSubChannel<CriticalSectionRawMutex, Message, 2, 1, 2> = PubSubChannel::new();
+
+#[derive(Clone, defmt::Format)]
+enum Message {
+ Temperature(i32),
+ MotionDetected,
+}
+
+#[embassy_executor::task]
+async fn temperature_task(
+ mut temperature: Temp<'static>,
+ publisher: Publisher<'static, CriticalSectionRawMutex, Message, 2, 1, 2>,
+) {
+ Timer::after(Duration::from_secs(45)).await; // stabilize for 45 seconds
+
+ let mut temperature_reporting_threshhold = 10;
+
+ loop {
+ let value = temperature.read().await;
+ let mut temperature_val = value.to_num::<i32>();
+
+ info!("Temperature: {}", temperature_val);
+
+ // only report every 2 degree Celsius drops, from 9 through 5, but starting at 3 always report
+
+ if temperature_val == 8 || temperature_val == 6 || temperature_val == 4 {
+ temperature_val += 1;
+ }
+
+ if temperature_reporting_threshhold > temperature_val
+ && (temperature_val == 9 || temperature_val == 7 || temperature_val == 5)
+ {
+ temperature_reporting_threshhold = temperature_val;
+ publisher.publish(Message::Temperature(temperature_val)).await;
+ } else if temperature_val <= 3 {
+ publisher.publish(Message::Temperature(temperature_val)).await;
+ }
+
+ Timer::after(Duration::from_secs(20 * 60)).await;
+ }
+}
+
+#[embassy_executor::task]
+async fn motion_detection_task(
+ mut pir_pin: Input<'static, AnyPin>,
+ publisher: Publisher<'static, CriticalSectionRawMutex, Message, 2, 1, 2>,
+) {
+ Timer::after(Duration::from_secs(30)).await; // stabilize for 30 seconds
+
+ loop {
+ // wait for motion detection
+ pir_pin.wait_for_low().await;
+ publisher.publish(Message::MotionDetected).await;
+
+ // wait a minute before setting up for more motion detection
+ Timer::after(Duration::from_secs(60)).await;
+ }
+}
+
+#[embassy_executor::main]
+async fn main(spawner: Spawner) {
+ let p = embassy_nrf::init(Default::default());
+ // set up to funnel temperature and motion detection events to the Lora Tx task
+ let mut lora_tx_subscriber = unwrap!(MESSAGE_BUS.subscriber());
+ let temperature_publisher = unwrap!(MESSAGE_BUS.publisher());
+ let motion_detection_publisher = unwrap!(MESSAGE_BUS.publisher());
+
+ let mut spi_config = spim::Config::default();
+ spi_config.frequency = spim::Frequency::M1; // M16 ???
+
+ let mut radio = {
+ let irq = interrupt::take!(SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1);
+ let spim = spim::Spim::new(p.TWISPI1, irq, p.P1_11, p.P1_13, p.P1_12, spi_config);
+
+ let cs = Output::new(p.P1_10.degrade(), Level::High, OutputDrive::Standard);
+ let reset = Output::new(p.P1_06.degrade(), Level::High, OutputDrive::Standard);
+ let dio1 = Input::new(p.P1_15.degrade(), Pull::Down);
+ let busy = Input::new(p.P1_14.degrade(), Pull::Down);
+ let antenna_rx = Output::new(p.P1_05.degrade(), Level::Low, OutputDrive::Standard);
+ let antenna_tx = Output::new(p.P1_07.degrade(), Level::Low, OutputDrive::Standard);
+
+ match Sx126xRadio::new(spim, cs, reset, antenna_rx, antenna_tx, dio1, busy, false).await {
+ Ok(r) => r,
+ Err(err) => {
+ info!("Sx126xRadio error = {}", err);
+ return;
+ }
+ }
+ };
+
+ // set up for the temperature task
+ let temperature_irq = interrupt::take!(TEMP);
+ let temperature = Temp::new(p.TEMP, temperature_irq);
+
+ // set the motion detection pin
+ let pir_pin = Input::new(p.P0_10.degrade(), Pull::Up);
+
+ let mut start_indicator = Output::new(p.P1_04, Level::Low, OutputDrive::Standard);
+ let mut debug_indicator = Output::new(p.P1_03, Level::Low, OutputDrive::Standard);
+
+ start_indicator.set_high();
+ Timer::after(Duration::from_secs(5)).await;
+ start_indicator.set_low();
+
+ match radio.lora.sleep().await {
+ Ok(()) => info!("Sleep successful"),
+ Err(err) => info!("Sleep unsuccessful = {}", err),
+ }
+
+ unwrap!(spawner.spawn(temperature_task(temperature, temperature_publisher)));
+ unwrap!(spawner.spawn(motion_detection_task(pir_pin, motion_detection_publisher)));
+
+ loop {
+ let message = lora_tx_subscriber.next_message_pure().await;
+
+ let tx_config = TxConfig {
+ // 11 byte maximum payload for Bandwidth 125 and SF 10
+ pw: 20, // up to 20 // 5 ???
+ rf: RfConfig {
+ frequency: 903900000, // channel in Hz, not MHz
+ bandwidth: Bandwidth::_250KHz,
+ spreading_factor: SpreadingFactor::_10,
+ coding_rate: CodingRate::_4_8,
+ },
+ };
+
+ let mut buffer = [TEMPERATURE_UID, 0xffu8, MOTION_UID, 0x00u8];
+ match message {
+ Message::Temperature(temperature) => buffer[1] = temperature as u8,
+ Message::MotionDetected => buffer[3] = 0x01u8,
+ };
+
+ // crypto for text ???
+ match radio.tx(tx_config, &buffer).await {
+ Ok(ret_val) => info!("TX ret_val = {}", ret_val),
+ Err(err) => info!("TX error = {}", err),
+ }
+
+ match radio.lora.sleep().await {
+ Ok(()) => info!("Sleep successful"),
+ Err(err) => info!("Sleep unsuccessful = {}", err),
+ }
+
+ debug_indicator.set_high();
+ Timer::after(Duration::from_secs(5)).await;
+ debug_indicator.set_low();
+ }
+}