diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | embassy-lora/Cargo.toml | 2 | ||||
-rw-r--r-- | embassy-lora/src/lib.rs | 2 | ||||
-rw-r--r-- | embassy-lora/src/sx126x/mod.rs | 153 | ||||
-rw-r--r-- | embassy-lora/src/sx126x/sx126x_lora/board_specific.rs | 268 | ||||
-rw-r--r-- | embassy-lora/src/sx126x/sx126x_lora/mod.rs | 732 | ||||
-rw-r--r-- | embassy-lora/src/sx126x/sx126x_lora/mod_params.rs | 469 | ||||
-rw-r--r-- | embassy-lora/src/sx126x/sx126x_lora/subroutine.rs | 674 | ||||
-rw-r--r-- | examples/nrf/Cargo.toml | 7 | ||||
-rw-r--r-- | examples/nrf/src/bin/lora_p2p_report.rs | 84 | ||||
-rw-r--r-- | examples/nrf/src/bin/lora_p2p_sense.rs | 173 |
11 files changed, 2564 insertions, 2 deletions
@@ -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, ®_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 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(); + } +} |