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 | 171 | ||||
| -rw-r--r-- | embassy-lora/src/sx126x/sx126x_lora/board_specific.rs | 251 | ||||
| -rw-r--r-- | embassy-lora/src/sx126x/sx126x_lora/mod.rs | 735 | ||||
| -rw-r--r-- | embassy-lora/src/sx126x/sx126x_lora/mod_params.rs | 469 | ||||
| -rw-r--r-- | embassy-lora/src/sx126x/sx126x_lora/subroutine.rs | 688 | ||||
| -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, 2582 insertions, 2 deletions
| @@ -31,7 +31,7 @@ The <a href="https://docs.embassy.dev/embassy-net/">embassy-net</a> network stac | |||
| 31 | 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. | 31 | 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. |
| 32 | 32 | ||
| 33 | - **LoRa** - | 33 | - **LoRa** - |
| 34 | <a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX127x transceivers. | 34 | <a href="https://docs.embassy.dev/embassy-lora/">embassy-lora</a> supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX126x and SX127x transceivers. |
| 35 | 35 | ||
| 36 | - **USB** - | 36 | - **USB** - |
| 37 | <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. | 37 | <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 1757efa8b..0c2f983f6 100644 --- a/embassy-lora/Cargo.toml +++ b/embassy-lora/Cargo.toml | |||
| @@ -8,6 +8,7 @@ src_base = "https://github.com/embassy-rs/embassy/blob/embassy-lora-v$VERSION/em | |||
| 8 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/" | 8 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/" |
| 9 | features = ["time", "defmt"] | 9 | features = ["time", "defmt"] |
| 10 | flavors = [ | 10 | flavors = [ |
| 11 | { name = "rak4631", target = "thumbv7em-none-eabihf", features = ["rak4631"] }, | ||
| 11 | { name = "sx127x", target = "thumbv7em-none-eabihf", features = ["sx127x", "embassy-stm32/stm32wl55jc-cm4", "embassy-stm32/time-driver-any"] }, | 12 | { name = "sx127x", target = "thumbv7em-none-eabihf", features = ["sx127x", "embassy-stm32/stm32wl55jc-cm4", "embassy-stm32/time-driver-any"] }, |
| 12 | { name = "stm32wl", target = "thumbv7em-none-eabihf", features = ["stm32wl", "embassy-stm32/stm32wl55jc-cm4", "embassy-stm32/time-driver-any"] }, | 13 | { name = "stm32wl", target = "thumbv7em-none-eabihf", features = ["stm32wl", "embassy-stm32/stm32wl55jc-cm4", "embassy-stm32/time-driver-any"] }, |
| 13 | ] | 14 | ] |
| @@ -15,6 +16,7 @@ flavors = [ | |||
| 15 | [lib] | 16 | [lib] |
| 16 | 17 | ||
| 17 | [features] | 18 | [features] |
| 19 | rak4631 = [] | ||
| 18 | sx127x = [] | 20 | sx127x = [] |
| 19 | stm32wl = ["embassy-stm32", "embassy-stm32/subghz"] | 21 | stm32wl = ["embassy-stm32", "embassy-stm32/subghz"] |
| 20 | time = [] | 22 | time = [] |
diff --git a/embassy-lora/src/lib.rs b/embassy-lora/src/lib.rs index 90ba0d1d4..342f66b2a 100644 --- a/embassy-lora/src/lib.rs +++ b/embassy-lora/src/lib.rs | |||
| @@ -7,6 +7,8 @@ pub(crate) mod fmt; | |||
| 7 | 7 | ||
| 8 | #[cfg(feature = "stm32wl")] | 8 | #[cfg(feature = "stm32wl")] |
| 9 | pub mod stm32wl; | 9 | pub mod stm32wl; |
| 10 | #[cfg(feature = "rak4631")] | ||
| 11 | pub mod sx126x; | ||
| 10 | #[cfg(feature = "sx127x")] | 12 | #[cfg(feature = "sx127x")] |
| 11 | pub mod sx127x; | 13 | pub mod sx127x; |
| 12 | 14 | ||
diff --git a/embassy-lora/src/sx126x/mod.rs b/embassy-lora/src/sx126x/mod.rs new file mode 100644 index 000000000..d67c7b109 --- /dev/null +++ b/embassy-lora/src/sx126x/mod.rs | |||
| @@ -0,0 +1,171 @@ | |||
| 1 | use core::future::Future; | ||
| 2 | |||
| 3 | use defmt::Format; | ||
| 4 | use embedded_hal::digital::v2::OutputPin; | ||
| 5 | use embedded_hal_async::digital::Wait; | ||
| 6 | use embedded_hal_async::spi::*; | ||
| 7 | use lorawan_device::async_device::radio::{PhyRxTx, RfConfig, RxQuality, TxConfig}; | ||
| 8 | use lorawan_device::async_device::Timings; | ||
| 9 | |||
| 10 | mod sx126x_lora; | ||
| 11 | use sx126x_lora::LoRa; | ||
| 12 | |||
| 13 | use self::sx126x_lora::mod_params::RadioError; | ||
| 14 | |||
| 15 | /// Semtech Sx126x LoRa peripheral | ||
| 16 | pub struct Sx126xRadio<SPI, CS, RESET, ANTRX, ANTTX, WAIT, BUS> | ||
| 17 | where | ||
| 18 | SPI: SpiBus<u8, Error = BUS> + 'static, | ||
| 19 | CS: OutputPin + 'static, | ||
| 20 | RESET: OutputPin + 'static, | ||
| 21 | ANTRX: OutputPin + 'static, | ||
| 22 | ANTTX: OutputPin + 'static, | ||
| 23 | WAIT: Wait + 'static, | ||
| 24 | BUS: Error + Format + 'static, | ||
| 25 | { | ||
| 26 | pub lora: LoRa<SPI, CS, RESET, ANTRX, ANTTX, WAIT>, | ||
| 27 | } | ||
| 28 | |||
| 29 | impl<SPI, CS, RESET, ANTRX, ANTTX, WAIT, BUS> Sx126xRadio<SPI, CS, RESET, ANTRX, ANTTX, WAIT, BUS> | ||
| 30 | where | ||
| 31 | SPI: SpiBus<u8, Error = BUS> + 'static, | ||
| 32 | CS: OutputPin + 'static, | ||
| 33 | RESET: OutputPin + 'static, | ||
| 34 | ANTRX: OutputPin + 'static, | ||
| 35 | ANTTX: OutputPin + 'static, | ||
| 36 | WAIT: Wait + 'static, | ||
| 37 | BUS: Error + Format + 'static, | ||
| 38 | { | ||
| 39 | pub async fn new( | ||
| 40 | spi: SPI, | ||
| 41 | cs: CS, | ||
| 42 | reset: RESET, | ||
| 43 | antenna_rx: ANTRX, | ||
| 44 | antenna_tx: ANTTX, | ||
| 45 | dio1: WAIT, | ||
| 46 | busy: WAIT, | ||
| 47 | enable_public_network: bool, | ||
| 48 | ) -> Result<Self, RadioError<BUS>> { | ||
| 49 | let mut lora = LoRa::new(spi, cs, reset, antenna_rx, antenna_tx, dio1, busy); | ||
| 50 | lora.init().await?; | ||
| 51 | lora.set_lora_modem(enable_public_network).await?; | ||
| 52 | Ok(Self { lora }) | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | impl<SPI, CS, RESET, ANTRX, ANTTX, WAIT, BUS> Timings for Sx126xRadio<SPI, CS, RESET, ANTRX, ANTTX, WAIT, BUS> | ||
| 57 | where | ||
| 58 | SPI: SpiBus<u8, Error = BUS> + 'static, | ||
| 59 | CS: OutputPin + 'static, | ||
| 60 | RESET: OutputPin + 'static, | ||
| 61 | ANTRX: OutputPin + 'static, | ||
| 62 | ANTTX: OutputPin + 'static, | ||
| 63 | WAIT: Wait + 'static, | ||
| 64 | BUS: Error + Format + 'static, | ||
| 65 | { | ||
| 66 | fn get_rx_window_offset_ms(&self) -> i32 { | ||
| 67 | -500 | ||
| 68 | } | ||
| 69 | fn get_rx_window_duration_ms(&self) -> u32 { | ||
| 70 | 800 | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | impl<SPI, CS, RESET, ANTRX, ANTTX, WAIT, BUS> PhyRxTx for Sx126xRadio<SPI, CS, RESET, ANTRX, ANTTX, WAIT, BUS> | ||
| 75 | where | ||
| 76 | SPI: SpiBus<u8, Error = BUS> + 'static, | ||
| 77 | CS: OutputPin + 'static, | ||
| 78 | RESET: OutputPin + 'static, | ||
| 79 | ANTRX: OutputPin + 'static, | ||
| 80 | ANTTX: OutputPin + 'static, | ||
| 81 | WAIT: Wait + 'static, | ||
| 82 | BUS: Error + Format + 'static, | ||
| 83 | { | ||
| 84 | type PhyError = RadioError<BUS>; | ||
| 85 | |||
| 86 | type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm | ||
| 87 | where | ||
| 88 | SPI: 'm, | ||
| 89 | CS: 'm, | ||
| 90 | RESET: 'm, | ||
| 91 | ANTRX: 'm, | ||
| 92 | ANTTX: 'm, | ||
| 93 | WAIT: 'm, | ||
| 94 | BUS: 'm; | ||
| 95 | |||
| 96 | fn tx<'m>(&'m mut self, config: TxConfig, buffer: &'m [u8]) -> Self::TxFuture<'m> { | ||
| 97 | trace!("TX START"); | ||
| 98 | async move { | ||
| 99 | self.lora | ||
| 100 | .set_tx_config( | ||
| 101 | config.pw, | ||
| 102 | config.rf.spreading_factor.into(), | ||
| 103 | config.rf.bandwidth.into(), | ||
| 104 | config.rf.coding_rate.into(), | ||
| 105 | 4, | ||
| 106 | false, | ||
| 107 | true, | ||
| 108 | false, | ||
| 109 | 0, | ||
| 110 | false, | ||
| 111 | ) | ||
| 112 | .await?; | ||
| 113 | self.lora.set_max_payload_length(buffer.len() as u8).await?; | ||
| 114 | self.lora.set_channel(config.rf.frequency).await?; | ||
| 115 | self.lora.send(buffer, 0xffffff).await?; | ||
| 116 | self.lora.process_irq(None, None, None).await?; | ||
| 117 | trace!("TX DONE"); | ||
| 118 | return Ok(0); | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm | ||
| 123 | where | ||
| 124 | SPI: 'm, | ||
| 125 | CS: 'm, | ||
| 126 | RESET: 'm, | ||
| 127 | ANTRX: 'm, | ||
| 128 | ANTTX: 'm, | ||
| 129 | WAIT: 'm, | ||
| 130 | BUS: 'm; | ||
| 131 | |||
| 132 | fn rx<'m>(&'m mut self, config: RfConfig, receiving_buffer: &'m mut [u8]) -> Self::RxFuture<'m> { | ||
| 133 | trace!("RX START"); | ||
| 134 | async move { | ||
| 135 | self.lora | ||
| 136 | .set_rx_config( | ||
| 137 | config.spreading_factor.into(), | ||
| 138 | config.bandwidth.into(), | ||
| 139 | config.coding_rate.into(), | ||
| 140 | 4, | ||
| 141 | 4, | ||
| 142 | false, | ||
| 143 | 0u8, | ||
| 144 | true, | ||
| 145 | false, | ||
| 146 | 0, | ||
| 147 | false, | ||
| 148 | true, | ||
| 149 | ) | ||
| 150 | .await?; | ||
| 151 | self.lora.set_max_payload_length(receiving_buffer.len() as u8).await?; | ||
| 152 | self.lora.set_channel(config.frequency).await?; | ||
| 153 | self.lora.rx(90 * 1000).await?; | ||
| 154 | let mut received_len = 0u8; | ||
| 155 | self.lora | ||
| 156 | .process_irq(Some(receiving_buffer), Some(&mut received_len), None) | ||
| 157 | .await?; | ||
| 158 | trace!("RX DONE"); | ||
| 159 | |||
| 160 | let packet_status = self.lora.get_latest_packet_status(); | ||
| 161 | let mut rssi = 0i16; | ||
| 162 | let mut snr = 0i8; | ||
| 163 | if packet_status.is_some() { | ||
| 164 | rssi = packet_status.unwrap().rssi as i16; | ||
| 165 | snr = packet_status.unwrap().snr; | ||
| 166 | } | ||
| 167 | |||
| 168 | Ok((received_len as usize, RxQuality::new(rssi, snr))) | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
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 000000000..5b4891c04 --- /dev/null +++ b/embassy-lora/src/sx126x/sx126x_lora/board_specific.rs | |||
| @@ -0,0 +1,251 @@ | |||
| 1 | use embassy_time::{Duration, Timer}; | ||
| 2 | use embedded_hal::digital::v2::OutputPin; | ||
| 3 | use embedded_hal_async::digital::Wait; | ||
| 4 | use embedded_hal_async::spi::SpiBus; | ||
| 5 | |||
| 6 | use super::mod_params::RadioError::*; | ||
| 7 | use super::mod_params::*; | ||
| 8 | use super::LoRa; | ||
| 9 | |||
| 10 | // Defines the time required for the TCXO to wakeup [ms]. | ||
| 11 | const BRD_TCXO_WAKEUP_TIME: u32 = 10; | ||
| 12 | |||
| 13 | // Provides board-specific functionality for Semtech SX126x-based boards. Use #[cfg(feature = "board_type")] to specify unique board functionality. | ||
| 14 | // The base implementation supports the RAK4631 board. | ||
| 15 | |||
| 16 | impl<SPI, CS, RESET, ANTRX, ANTTX, WAIT, BUS> LoRa<SPI, CS, RESET, ANTRX, ANTTX, WAIT> | ||
| 17 | where | ||
| 18 | SPI: SpiBus<u8, Error = BUS>, | ||
| 19 | CS: OutputPin, | ||
| 20 | RESET: OutputPin, | ||
| 21 | ANTRX: OutputPin, | ||
| 22 | ANTTX: OutputPin, | ||
| 23 | WAIT: Wait, | ||
| 24 | { | ||
| 25 | // De-initialize the radio I/Os pins interface. Useful when going into MCU low power modes. | ||
| 26 | pub(super) async fn brd_io_deinit(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 27 | Ok(()) // no operation currently | ||
| 28 | } | ||
| 29 | |||
| 30 | // Initialize the TCXO power pin | ||
| 31 | pub(super) async fn brd_io_tcxo_init(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 32 | let timeout = self.brd_get_board_tcxo_wakeup_time() << 6; | ||
| 33 | self.sub_set_dio3_as_tcxo_ctrl(TcxoCtrlVoltage::Ctrl1V7, timeout) | ||
| 34 | .await?; | ||
| 35 | Ok(()) | ||
| 36 | } | ||
| 37 | |||
| 38 | // Initialize RF switch control pins | ||
| 39 | pub(super) async fn brd_io_rf_switch_init(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 40 | self.sub_set_dio2_as_rf_switch_ctrl(true).await?; | ||
| 41 | Ok(()) | ||
| 42 | } | ||
| 43 | |||
| 44 | // Initialize the radio debug pins | ||
| 45 | pub(super) async fn brd_io_dbg_init(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 46 | Ok(()) // no operation currently | ||
| 47 | } | ||
| 48 | |||
| 49 | // Hardware reset of the radio | ||
| 50 | pub(super) async fn brd_reset(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 51 | Timer::after(Duration::from_millis(10)).await; | ||
| 52 | self.reset.set_low().map_err(|_| Reset)?; | ||
| 53 | Timer::after(Duration::from_millis(20)).await; | ||
| 54 | self.reset.set_high().map_err(|_| Reset)?; | ||
| 55 | Timer::after(Duration::from_millis(10)).await; | ||
| 56 | Ok(()) | ||
| 57 | } | ||
| 58 | |||
| 59 | // Wait while the busy pin is high | ||
| 60 | pub(super) async fn brd_wait_on_busy(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 61 | self.busy.wait_for_low().await.map_err(|_| Busy)?; | ||
| 62 | Ok(()) | ||
| 63 | } | ||
| 64 | |||
| 65 | // Wake up the radio | ||
| 66 | pub(super) async fn brd_wakeup(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 67 | self.cs.set_low().map_err(|_| CS)?; | ||
| 68 | self.spi.write(&[OpCode::GetStatus.value()]).await.map_err(SPI)?; | ||
| 69 | self.spi.write(&[0x00]).await.map_err(SPI)?; | ||
| 70 | self.cs.set_high().map_err(|_| CS)?; | ||
| 71 | |||
| 72 | self.brd_wait_on_busy().await?; | ||
| 73 | self.brd_set_operating_mode(RadioMode::StandbyRC); | ||
| 74 | Ok(()) | ||
| 75 | } | ||
| 76 | |||
| 77 | // Send a command that writes data to the radio | ||
| 78 | pub(super) async fn brd_write_command(&mut self, op_code: OpCode, buffer: &[u8]) -> Result<(), RadioError<BUS>> { | ||
| 79 | self.sub_check_device_ready().await?; | ||
| 80 | |||
| 81 | self.cs.set_low().map_err(|_| CS)?; | ||
| 82 | self.spi.write(&[op_code.value()]).await.map_err(SPI)?; | ||
| 83 | self.spi.write(buffer).await.map_err(SPI)?; | ||
| 84 | self.cs.set_high().map_err(|_| CS)?; | ||
| 85 | |||
| 86 | if op_code != OpCode::SetSleep { | ||
| 87 | self.brd_wait_on_busy().await?; | ||
| 88 | } | ||
| 89 | Ok(()) | ||
| 90 | } | ||
| 91 | |||
| 92 | // Send a command that reads data from the radio, filling the provided buffer and returning a status | ||
| 93 | pub(super) async fn brd_read_command(&mut self, op_code: OpCode, buffer: &mut [u8]) -> Result<u8, RadioError<BUS>> { | ||
| 94 | let mut status = [0u8]; | ||
| 95 | let mut input = [0u8]; | ||
| 96 | |||
| 97 | self.sub_check_device_ready().await?; | ||
| 98 | |||
| 99 | self.cs.set_low().map_err(|_| CS)?; | ||
| 100 | self.spi.write(&[op_code.value()]).await.map_err(SPI)?; | ||
| 101 | self.spi.transfer(&mut status, &[0x00]).await.map_err(SPI)?; | ||
| 102 | for i in 0..buffer.len() { | ||
| 103 | self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?; | ||
| 104 | buffer[i] = input[0]; | ||
| 105 | } | ||
| 106 | self.cs.set_high().map_err(|_| CS)?; | ||
| 107 | |||
| 108 | self.brd_wait_on_busy().await?; | ||
| 109 | |||
| 110 | Ok(status[0]) | ||
| 111 | } | ||
| 112 | |||
| 113 | // Write one or more bytes of data to the radio memory | ||
| 114 | pub(super) async fn brd_write_registers( | ||
| 115 | &mut self, | ||
| 116 | start_register: Register, | ||
| 117 | buffer: &[u8], | ||
| 118 | ) -> Result<(), RadioError<BUS>> { | ||
| 119 | self.sub_check_device_ready().await?; | ||
| 120 | |||
| 121 | self.cs.set_low().map_err(|_| CS)?; | ||
| 122 | self.spi.write(&[OpCode::WriteRegister.value()]).await.map_err(SPI)?; | ||
| 123 | self.spi | ||
| 124 | .write(&[ | ||
| 125 | ((start_register.addr() & 0xFF00) >> 8) as u8, | ||
| 126 | (start_register.addr() & 0x00FF) as u8, | ||
| 127 | ]) | ||
| 128 | .await | ||
| 129 | .map_err(SPI)?; | ||
| 130 | self.spi.write(buffer).await.map_err(SPI)?; | ||
| 131 | self.cs.set_high().map_err(|_| CS)?; | ||
| 132 | |||
| 133 | self.brd_wait_on_busy().await?; | ||
| 134 | Ok(()) | ||
| 135 | } | ||
| 136 | |||
| 137 | // Read one or more bytes of data from the radio memory | ||
| 138 | pub(super) async fn brd_read_registers( | ||
| 139 | &mut self, | ||
| 140 | start_register: Register, | ||
| 141 | buffer: &mut [u8], | ||
| 142 | ) -> Result<(), RadioError<BUS>> { | ||
| 143 | let mut input = [0u8]; | ||
| 144 | |||
| 145 | self.sub_check_device_ready().await?; | ||
| 146 | |||
| 147 | self.cs.set_low().map_err(|_| CS)?; | ||
| 148 | self.spi.write(&[OpCode::ReadRegister.value()]).await.map_err(SPI)?; | ||
| 149 | self.spi | ||
| 150 | .write(&[ | ||
| 151 | ((start_register.addr() & 0xFF00) >> 8) as u8, | ||
| 152 | (start_register.addr() & 0x00FF) as u8, | ||
| 153 | 0x00u8, | ||
| 154 | ]) | ||
| 155 | .await | ||
| 156 | .map_err(SPI)?; | ||
| 157 | for i in 0..buffer.len() { | ||
| 158 | self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?; | ||
| 159 | buffer[i] = input[0]; | ||
| 160 | } | ||
| 161 | self.cs.set_high().map_err(|_| CS)?; | ||
| 162 | |||
| 163 | self.brd_wait_on_busy().await?; | ||
| 164 | Ok(()) | ||
| 165 | } | ||
| 166 | |||
| 167 | // Write data to the buffer holding the payload in the radio | ||
| 168 | pub(super) async fn brd_write_buffer(&mut self, offset: u8, buffer: &[u8]) -> Result<(), RadioError<BUS>> { | ||
| 169 | self.sub_check_device_ready().await?; | ||
| 170 | |||
| 171 | self.cs.set_low().map_err(|_| CS)?; | ||
| 172 | self.spi.write(&[OpCode::WriteBuffer.value()]).await.map_err(SPI)?; | ||
| 173 | self.spi.write(&[offset]).await.map_err(SPI)?; | ||
| 174 | self.spi.write(buffer).await.map_err(SPI)?; | ||
| 175 | self.cs.set_high().map_err(|_| CS)?; | ||
| 176 | |||
| 177 | self.brd_wait_on_busy().await?; | ||
| 178 | Ok(()) | ||
| 179 | } | ||
| 180 | |||
| 181 | // Read data from the buffer holding the payload in the radio | ||
| 182 | pub(super) async fn brd_read_buffer(&mut self, offset: u8, buffer: &mut [u8]) -> Result<(), RadioError<BUS>> { | ||
| 183 | let mut input = [0u8]; | ||
| 184 | |||
| 185 | self.sub_check_device_ready().await?; | ||
| 186 | |||
| 187 | self.cs.set_low().map_err(|_| CS)?; | ||
| 188 | self.spi.write(&[OpCode::ReadBuffer.value()]).await.map_err(SPI)?; | ||
| 189 | self.spi.write(&[offset]).await.map_err(SPI)?; | ||
| 190 | self.spi.write(&[0x00]).await.map_err(SPI)?; | ||
| 191 | for i in 0..buffer.len() { | ||
| 192 | self.spi.transfer(&mut input, &[0x00]).await.map_err(SPI)?; | ||
| 193 | buffer[i] = input[0]; | ||
| 194 | } | ||
| 195 | self.cs.set_high().map_err(|_| CS)?; | ||
| 196 | |||
| 197 | self.brd_wait_on_busy().await?; | ||
| 198 | Ok(()) | ||
| 199 | } | ||
| 200 | |||
| 201 | // Set the radio output power | ||
| 202 | pub(super) async fn brd_set_rf_tx_power(&mut self, power: i8) -> Result<(), RadioError<BUS>> { | ||
| 203 | self.sub_set_tx_params(power, RampTime::Ramp40Us).await?; | ||
| 204 | Ok(()) | ||
| 205 | } | ||
| 206 | |||
| 207 | // Get the radio type | ||
| 208 | pub(super) fn brd_get_radio_type(&mut self) -> RadioType { | ||
| 209 | #[cfg(feature = "rak4631")] | ||
| 210 | RadioType::SX1262 | ||
| 211 | } | ||
| 212 | |||
| 213 | // Initialize the RF switch I/O pins interface | ||
| 214 | pub(super) async fn brd_ant_sw_on(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 215 | Ok(()) // no operation currently | ||
| 216 | } | ||
| 217 | |||
| 218 | // De-initialize the RF switch I/O pins interface for MCU low power modes | ||
| 219 | pub(super) async fn brd_ant_sw_off(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 220 | Ok(()) // no operation currently | ||
| 221 | } | ||
| 222 | |||
| 223 | // Check if the given RF frequency is supported by the hardware | ||
| 224 | pub(super) async fn brd_check_rf_frequency(&mut self, _frequency: u32) -> Result<bool, RadioError<BUS>> { | ||
| 225 | #[cfg(feature = "rak4631")] | ||
| 226 | Ok(true) // all frequencies currently supported for the SX1262 within a rak4631 | ||
| 227 | } | ||
| 228 | |||
| 229 | // Get the duration required for the TCXO to wakeup [ms]. | ||
| 230 | pub(super) fn brd_get_board_tcxo_wakeup_time(&mut self) -> u32 { | ||
| 231 | BRD_TCXO_WAKEUP_TIME | ||
| 232 | } | ||
| 233 | |||
| 234 | /* Get current state of the DIO1 pin - not currently needed if waiting on DIO1 instead of using an IRQ process | ||
| 235 | pub(super) async fn brd_get_dio1_pin_state( | ||
| 236 | &mut self, | ||
| 237 | ) -> Result<u32, RadioError<BUS>> { | ||
| 238 | Ok(0) | ||
| 239 | } | ||
| 240 | */ | ||
| 241 | |||
| 242 | // Get the current radio operatiing mode | ||
| 243 | pub(super) fn brd_get_operating_mode(&mut self) -> RadioMode { | ||
| 244 | self.operating_mode | ||
| 245 | } | ||
| 246 | |||
| 247 | // Set/Update the current radio operating mode This function is only required to reflect the current radio operating mode when processing interrupts. | ||
| 248 | pub(super) fn brd_set_operating_mode(&mut self, mode: RadioMode) { | ||
| 249 | self.operating_mode = mode; | ||
| 250 | } | ||
| 251 | } | ||
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 000000000..02f5f83a2 --- /dev/null +++ b/embassy-lora/src/sx126x/sx126x_lora/mod.rs | |||
| @@ -0,0 +1,735 @@ | |||
| 1 | #![allow(dead_code)] | ||
| 2 | |||
| 3 | use embassy_time::{Duration, Timer}; | ||
| 4 | use embedded_hal::digital::v2::OutputPin; | ||
| 5 | use embedded_hal_async::digital::Wait; | ||
| 6 | use embedded_hal_async::spi::SpiBus; | ||
| 7 | |||
| 8 | mod board_specific; | ||
| 9 | pub mod mod_params; | ||
| 10 | mod subroutine; | ||
| 11 | |||
| 12 | use mod_params::RadioError::*; | ||
| 13 | use mod_params::*; | ||
| 14 | |||
| 15 | // Syncwords for public and private networks | ||
| 16 | const LORA_MAC_PUBLIC_SYNCWORD: u16 = 0x3444; | ||
| 17 | const LORA_MAC_PRIVATE_SYNCWORD: u16 = 0x1424; | ||
| 18 | |||
| 19 | // Maximum number of registers that can be added to the retention list | ||
| 20 | const MAX_NUMBER_REGS_IN_RETENTION: u8 = 4; | ||
| 21 | |||
| 22 | // Possible LoRa bandwidths | ||
| 23 | const LORA_BANDWIDTHS: [Bandwidth; 3] = [Bandwidth::_125KHz, Bandwidth::_250KHz, Bandwidth::_500KHz]; | ||
| 24 | |||
| 25 | // Radio complete wakeup time with margin for temperature compensation [ms] | ||
| 26 | const RADIO_WAKEUP_TIME: u32 = 3; | ||
| 27 | |||
| 28 | /// Provides high-level access to Semtech SX126x-based boards | ||
| 29 | pub struct LoRa<SPI, CS, RESET, ANTRX, ANTTX, WAIT> { | ||
| 30 | spi: SPI, | ||
| 31 | cs: CS, | ||
| 32 | reset: RESET, | ||
| 33 | antenna_rx: ANTRX, | ||
| 34 | antenna_tx: ANTTX, | ||
| 35 | dio1: WAIT, | ||
| 36 | busy: WAIT, | ||
| 37 | operating_mode: RadioMode, | ||
| 38 | rx_continuous: bool, | ||
| 39 | max_payload_length: u8, | ||
| 40 | modulation_params: Option<ModulationParams>, | ||
| 41 | packet_type: PacketType, | ||
| 42 | packet_params: Option<PacketParams>, | ||
| 43 | packet_status: Option<PacketStatus>, | ||
| 44 | image_calibrated: bool, | ||
| 45 | frequency_error: u32, | ||
| 46 | } | ||
| 47 | |||
| 48 | impl<SPI, CS, RESET, ANTRX, ANTTX, WAIT, BUS> LoRa<SPI, CS, RESET, ANTRX, ANTTX, WAIT> | ||
| 49 | where | ||
| 50 | SPI: SpiBus<u8, Error = BUS>, | ||
| 51 | CS: OutputPin, | ||
| 52 | RESET: OutputPin, | ||
| 53 | ANTRX: OutputPin, | ||
| 54 | ANTTX: OutputPin, | ||
| 55 | WAIT: Wait, | ||
| 56 | { | ||
| 57 | /// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time () | ||
| 58 | pub fn new(spi: SPI, cs: CS, reset: RESET, antenna_rx: ANTRX, antenna_tx: ANTTX, dio1: WAIT, busy: WAIT) -> Self { | ||
| 59 | Self { | ||
| 60 | spi, | ||
| 61 | cs, | ||
| 62 | reset, | ||
| 63 | antenna_rx, | ||
| 64 | antenna_tx, | ||
| 65 | dio1, | ||
| 66 | busy, | ||
| 67 | operating_mode: RadioMode::Sleep, | ||
| 68 | rx_continuous: false, | ||
| 69 | max_payload_length: 0xFFu8, | ||
| 70 | modulation_params: None, | ||
| 71 | packet_type: PacketType::LoRa, | ||
| 72 | packet_params: None, | ||
| 73 | packet_status: None, | ||
| 74 | image_calibrated: false, | ||
| 75 | frequency_error: 0u32, // where is volatile FrequencyError modified ??? | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | /// Initialize the radio | ||
| 80 | pub async fn init(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 81 | self.sub_init().await?; | ||
| 82 | self.sub_set_standby(StandbyMode::RC).await?; | ||
| 83 | self.sub_set_regulator_mode(RegulatorMode::UseDCDC).await?; | ||
| 84 | self.sub_set_buffer_base_address(0x00u8, 0x00u8).await?; | ||
| 85 | self.sub_set_tx_params(0i8, RampTime::Ramp200Us).await?; | ||
| 86 | self.sub_set_dio_irq_params( | ||
| 87 | IrqMask::All.value(), | ||
| 88 | IrqMask::All.value(), | ||
| 89 | IrqMask::None.value(), | ||
| 90 | IrqMask::None.value(), | ||
| 91 | ) | ||
| 92 | .await?; | ||
| 93 | self.add_register_to_retention_list(Register::RxGain.addr()).await?; | ||
| 94 | self.add_register_to_retention_list(Register::TxModulation.addr()) | ||
| 95 | .await?; | ||
| 96 | Ok(()) | ||
| 97 | } | ||
| 98 | |||
| 99 | /// Return current radio state | ||
| 100 | pub fn get_status(&mut self) -> RadioState { | ||
| 101 | match self.brd_get_operating_mode() { | ||
| 102 | RadioMode::Transmit => RadioState::TxRunning, | ||
| 103 | RadioMode::Receive => RadioState::RxRunning, | ||
| 104 | RadioMode::ChannelActivityDetection => RadioState::ChannelActivityDetecting, | ||
| 105 | _ => RadioState::Idle, | ||
| 106 | } | ||
| 107 | } | ||
| 108 | |||
| 109 | /// Configure the radio for LoRa (FSK support should be provided in a separate driver, if desired) | ||
| 110 | pub async fn set_lora_modem(&mut self, enable_public_network: bool) -> Result<(), RadioError<BUS>> { | ||
| 111 | self.sub_set_packet_type(PacketType::LoRa).await?; | ||
| 112 | if enable_public_network { | ||
| 113 | self.brd_write_registers( | ||
| 114 | Register::LoRaSyncword, | ||
| 115 | &[ | ||
| 116 | ((LORA_MAC_PUBLIC_SYNCWORD >> 8) & 0xFF) as u8, | ||
| 117 | (LORA_MAC_PUBLIC_SYNCWORD & 0xFF) as u8, | ||
| 118 | ], | ||
| 119 | ) | ||
| 120 | .await?; | ||
| 121 | } else { | ||
| 122 | self.brd_write_registers( | ||
| 123 | Register::LoRaSyncword, | ||
| 124 | &[ | ||
| 125 | ((LORA_MAC_PRIVATE_SYNCWORD >> 8) & 0xFF) as u8, | ||
| 126 | (LORA_MAC_PRIVATE_SYNCWORD & 0xFF) as u8, | ||
| 127 | ], | ||
| 128 | ) | ||
| 129 | .await?; | ||
| 130 | } | ||
| 131 | |||
| 132 | Ok(()) | ||
| 133 | } | ||
| 134 | |||
| 135 | /// Sets the channel frequency | ||
| 136 | pub async fn set_channel(&mut self, frequency: u32) -> Result<(), RadioError<BUS>> { | ||
| 137 | self.sub_set_rf_frequency(frequency).await?; | ||
| 138 | Ok(()) | ||
| 139 | } | ||
| 140 | |||
| 141 | /* Checks if the channel is free for the given time. This is currently not implemented until a substitute | ||
| 142 | for switching to the FSK modem is found. | ||
| 143 | |||
| 144 | pub async fn is_channel_free(&mut self, frequency: u32, rxBandwidth: u32, rssiThresh: i16, maxCarrierSenseTime: u32) -> bool; | ||
| 145 | */ | ||
| 146 | |||
| 147 | /// Generate a 32 bit random value based on the RSSI readings, after disabling all interrupts. Ensure set_lora_modem() is called befrorehand. | ||
| 148 | /// After calling this function either set_rx_config() or set_tx_config() must be called. | ||
| 149 | pub async fn get_random_value(&mut self) -> Result<u32, RadioError<BUS>> { | ||
| 150 | self.sub_set_dio_irq_params( | ||
| 151 | IrqMask::None.value(), | ||
| 152 | IrqMask::None.value(), | ||
| 153 | IrqMask::None.value(), | ||
| 154 | IrqMask::None.value(), | ||
| 155 | ) | ||
| 156 | .await?; | ||
| 157 | |||
| 158 | let result = self.sub_get_random().await?; | ||
| 159 | Ok(result) | ||
| 160 | } | ||
| 161 | |||
| 162 | /// Set the reception parameters for the LoRa modem (only). Ensure set_lora_modem() is called befrorehand. | ||
| 163 | /// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol] | ||
| 164 | /// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved] | ||
| 165 | /// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] | ||
| 166 | /// preamble_length length in symbols (the hardware adds 4 more symbols) | ||
| 167 | /// symb_timeout RxSingle timeout value in symbols | ||
| 168 | /// fixed_len fixed length packets [0: variable, 1: fixed] | ||
| 169 | /// payload_len payload length when fixed length is used | ||
| 170 | /// crc_on [0: OFF, 1: ON] | ||
| 171 | /// freq_hop_on intra-packet frequency hopping [0: OFF, 1: ON] | ||
| 172 | /// hop_period number of symbols between each hop | ||
| 173 | /// iq_inverted invert IQ signals [0: not inverted, 1: inverted] | ||
| 174 | /// rx_continuous reception mode [false: single mode, true: continuous mode] | ||
| 175 | pub async fn set_rx_config( | ||
| 176 | &mut self, | ||
| 177 | spreading_factor: SpreadingFactor, | ||
| 178 | bandwidth: Bandwidth, | ||
| 179 | coding_rate: CodingRate, | ||
| 180 | preamble_length: u16, | ||
| 181 | symb_timeout: u16, | ||
| 182 | fixed_len: bool, | ||
| 183 | payload_len: u8, | ||
| 184 | crc_on: bool, | ||
| 185 | _freq_hop_on: bool, | ||
| 186 | _hop_period: u8, | ||
| 187 | iq_inverted: bool, | ||
| 188 | rx_continuous: bool, | ||
| 189 | ) -> Result<(), RadioError<BUS>> { | ||
| 190 | let mut symb_timeout_final = symb_timeout; | ||
| 191 | |||
| 192 | self.rx_continuous = rx_continuous; | ||
| 193 | if self.rx_continuous { | ||
| 194 | symb_timeout_final = 0; | ||
| 195 | } | ||
| 196 | if fixed_len { | ||
| 197 | self.max_payload_length = payload_len; | ||
| 198 | } else { | ||
| 199 | self.max_payload_length = 0xFFu8; | ||
| 200 | } | ||
| 201 | |||
| 202 | self.sub_set_stop_rx_timer_on_preamble_detect(false).await?; | ||
| 203 | |||
| 204 | let mut low_data_rate_optimize = 0x00u8; | ||
| 205 | if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12)) | ||
| 206 | && (bandwidth == Bandwidth::_125KHz)) | ||
| 207 | || ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz)) | ||
| 208 | { | ||
| 209 | low_data_rate_optimize = 0x01u8; | ||
| 210 | } | ||
| 211 | |||
| 212 | let modulation_params = ModulationParams { | ||
| 213 | spreading_factor: spreading_factor, | ||
| 214 | bandwidth: bandwidth, | ||
| 215 | coding_rate: coding_rate, | ||
| 216 | low_data_rate_optimize: low_data_rate_optimize, | ||
| 217 | }; | ||
| 218 | |||
| 219 | let mut preamble_length_final = preamble_length; | ||
| 220 | if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6)) | ||
| 221 | && (preamble_length < 12) | ||
| 222 | { | ||
| 223 | preamble_length_final = 12; | ||
| 224 | } | ||
| 225 | |||
| 226 | let packet_params = PacketParams { | ||
| 227 | preamble_length: preamble_length_final, | ||
| 228 | implicit_header: fixed_len, | ||
| 229 | payload_length: self.max_payload_length, | ||
| 230 | crc_on: crc_on, | ||
| 231 | iq_inverted: iq_inverted, | ||
| 232 | }; | ||
| 233 | |||
| 234 | self.modulation_params = Some(modulation_params); | ||
| 235 | self.packet_params = Some(packet_params); | ||
| 236 | |||
| 237 | self.standby().await?; | ||
| 238 | self.sub_set_modulation_params().await?; | ||
| 239 | self.sub_set_packet_params().await?; | ||
| 240 | self.sub_set_lora_symb_num_timeout(symb_timeout_final).await?; | ||
| 241 | |||
| 242 | // Optimize the Inverted IQ Operation (see DS_SX1261-2_V1.2 datasheet chapter 15.4) | ||
| 243 | let mut iq_polarity = [0x00u8]; | ||
| 244 | self.brd_read_registers(Register::IQPolarity, &mut iq_polarity).await?; | ||
| 245 | if iq_inverted { | ||
| 246 | self.brd_write_registers(Register::IQPolarity, &[iq_polarity[0] & (!(1 << 2))]) | ||
| 247 | .await?; | ||
| 248 | } else { | ||
| 249 | self.brd_write_registers(Register::IQPolarity, &[iq_polarity[0] | (1 << 2)]) | ||
| 250 | .await?; | ||
| 251 | } | ||
| 252 | Ok(()) | ||
| 253 | } | ||
| 254 | |||
| 255 | /// Set the transmission parameters for the LoRa modem (only). | ||
| 256 | /// power output power [dBm] | ||
| 257 | /// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol] | ||
| 258 | /// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved] | ||
| 259 | /// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] | ||
| 260 | /// preamble_length length in symbols (the hardware adds 4 more symbols) | ||
| 261 | /// fixed_len fixed length packets [0: variable, 1: fixed] | ||
| 262 | /// crc_on [0: OFF, 1: ON] | ||
| 263 | /// freq_hop_on intra-packet frequency hopping [0: OFF, 1: ON] | ||
| 264 | /// hop_period number of symbols between each hop | ||
| 265 | /// iq_inverted invert IQ signals [0: not inverted, 1: inverted] | ||
| 266 | pub async fn set_tx_config( | ||
| 267 | &mut self, | ||
| 268 | power: i8, | ||
| 269 | spreading_factor: SpreadingFactor, | ||
| 270 | bandwidth: Bandwidth, | ||
| 271 | coding_rate: CodingRate, | ||
| 272 | preamble_length: u16, | ||
| 273 | fixed_len: bool, | ||
| 274 | crc_on: bool, | ||
| 275 | _freq_hop_on: bool, | ||
| 276 | _hop_period: u8, | ||
| 277 | iq_inverted: bool, | ||
| 278 | ) -> Result<(), RadioError<BUS>> { | ||
| 279 | let mut low_data_rate_optimize = 0x00u8; | ||
| 280 | if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12)) | ||
| 281 | && (bandwidth == Bandwidth::_125KHz)) | ||
| 282 | || ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz)) | ||
| 283 | { | ||
| 284 | low_data_rate_optimize = 0x01u8; | ||
| 285 | } | ||
| 286 | |||
| 287 | let modulation_params = ModulationParams { | ||
| 288 | spreading_factor: spreading_factor, | ||
| 289 | bandwidth: bandwidth, | ||
| 290 | coding_rate: coding_rate, | ||
| 291 | low_data_rate_optimize: low_data_rate_optimize, | ||
| 292 | }; | ||
| 293 | |||
| 294 | let mut preamble_length_final = preamble_length; | ||
| 295 | if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6)) | ||
| 296 | && (preamble_length < 12) | ||
| 297 | { | ||
| 298 | preamble_length_final = 12; | ||
| 299 | } | ||
| 300 | |||
| 301 | let packet_params = PacketParams { | ||
| 302 | preamble_length: preamble_length_final, | ||
| 303 | implicit_header: fixed_len, | ||
| 304 | payload_length: self.max_payload_length, | ||
| 305 | crc_on: crc_on, | ||
| 306 | iq_inverted: iq_inverted, | ||
| 307 | }; | ||
| 308 | |||
| 309 | self.modulation_params = Some(modulation_params); | ||
| 310 | self.packet_params = Some(packet_params); | ||
| 311 | |||
| 312 | self.standby().await?; | ||
| 313 | self.sub_set_modulation_params().await?; | ||
| 314 | self.sub_set_packet_params().await?; | ||
| 315 | |||
| 316 | // Handle modulation quality with the 500 kHz LoRa bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1) | ||
| 317 | |||
| 318 | let mut tx_modulation = [0x00u8]; | ||
| 319 | self.brd_read_registers(Register::TxModulation, &mut tx_modulation) | ||
| 320 | .await?; | ||
| 321 | if bandwidth == Bandwidth::_500KHz { | ||
| 322 | self.brd_write_registers(Register::TxModulation, &[tx_modulation[0] & (!(1 << 2))]) | ||
| 323 | .await?; | ||
| 324 | } else { | ||
| 325 | self.brd_write_registers(Register::TxModulation, &[tx_modulation[0] | (1 << 2)]) | ||
| 326 | .await?; | ||
| 327 | } | ||
| 328 | |||
| 329 | self.brd_set_rf_tx_power(power).await?; | ||
| 330 | Ok(()) | ||
| 331 | } | ||
| 332 | |||
| 333 | /// Check if the given RF frequency is supported by the hardware [true: supported, false: unsupported] | ||
| 334 | pub async fn check_rf_frequency(&mut self, frequency: u32) -> Result<bool, RadioError<BUS>> { | ||
| 335 | Ok(self.brd_check_rf_frequency(frequency).await?) | ||
| 336 | } | ||
| 337 | |||
| 338 | /// 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) | ||
| 339 | /// spreading_factor [6: 64, 7: 128, 8: 256, 9: 512, 10: 1024, 11: 2048, 12: 4096 chips/symbol] | ||
| 340 | /// bandwidth [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3: Reserved] | ||
| 341 | /// coding_rate [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8] | ||
| 342 | /// preamble_length length in symbols (the hardware adds 4 more symbols) | ||
| 343 | /// fixed_len fixed length packets [0: variable, 1: fixed] | ||
| 344 | /// payload_len sets payload length when fixed length is used | ||
| 345 | /// crc_on [0: OFF, 1: ON] | ||
| 346 | pub fn get_time_on_air( | ||
| 347 | &mut self, | ||
| 348 | spreading_factor: SpreadingFactor, | ||
| 349 | bandwidth: Bandwidth, | ||
| 350 | coding_rate: CodingRate, | ||
| 351 | preamble_length: u16, | ||
| 352 | fixed_len: bool, | ||
| 353 | payload_len: u8, | ||
| 354 | crc_on: bool, | ||
| 355 | ) -> Result<u32, RadioError<BUS>> { | ||
| 356 | let numerator = 1000 | ||
| 357 | * Self::get_lora_time_on_air_numerator( | ||
| 358 | spreading_factor, | ||
| 359 | bandwidth, | ||
| 360 | coding_rate, | ||
| 361 | preamble_length, | ||
| 362 | fixed_len, | ||
| 363 | payload_len, | ||
| 364 | crc_on, | ||
| 365 | ); | ||
| 366 | let denominator = bandwidth.value_in_hz(); | ||
| 367 | if denominator == 0 { | ||
| 368 | Err(RadioError::InvalidBandwidth) | ||
| 369 | } else { | ||
| 370 | Ok((numerator + denominator - 1) / denominator) | ||
| 371 | } | ||
| 372 | } | ||
| 373 | |||
| 374 | /// Send the buffer of the given size. Prepares the packet to be sent and sets the radio in transmission [timeout in ms] | ||
| 375 | pub async fn send(&mut self, buffer: &[u8], timeout: u32) -> Result<(), RadioError<BUS>> { | ||
| 376 | if self.packet_params.is_some() { | ||
| 377 | self.sub_set_dio_irq_params( | ||
| 378 | IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value(), | ||
| 379 | IrqMask::TxDone.value() | IrqMask::RxTxTimeout.value(), | ||
| 380 | IrqMask::None.value(), | ||
| 381 | IrqMask::None.value(), | ||
| 382 | ) | ||
| 383 | .await?; | ||
| 384 | |||
| 385 | let mut packet_params = self.packet_params.as_mut().unwrap(); | ||
| 386 | packet_params.payload_length = buffer.len() as u8; | ||
| 387 | self.sub_set_packet_params().await?; | ||
| 388 | self.sub_send_payload(buffer, timeout).await?; | ||
| 389 | Ok(()) | ||
| 390 | } else { | ||
| 391 | Err(RadioError::PacketParamsMissing) | ||
| 392 | } | ||
| 393 | } | ||
| 394 | |||
| 395 | /// Set the radio in sleep mode | ||
| 396 | pub async fn sleep(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 397 | self.sub_set_sleep(SleepParams { | ||
| 398 | wakeup_rtc: false, | ||
| 399 | reset: false, | ||
| 400 | warm_start: true, | ||
| 401 | }) | ||
| 402 | .await?; | ||
| 403 | Timer::after(Duration::from_millis(2)).await; | ||
| 404 | Ok(()) | ||
| 405 | } | ||
| 406 | |||
| 407 | /// Set the radio in standby mode | ||
| 408 | pub async fn standby(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 409 | self.sub_set_standby(StandbyMode::RC).await?; | ||
| 410 | Ok(()) | ||
| 411 | } | ||
| 412 | |||
| 413 | /// Set the radio in reception mode for the given duration [0: continuous, others: timeout (ms)] | ||
| 414 | pub async fn rx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> { | ||
| 415 | self.sub_set_dio_irq_params( | ||
| 416 | IrqMask::All.value(), | ||
| 417 | IrqMask::All.value(), | ||
| 418 | IrqMask::None.value(), | ||
| 419 | IrqMask::None.value(), | ||
| 420 | ) | ||
| 421 | .await?; | ||
| 422 | |||
| 423 | if self.rx_continuous { | ||
| 424 | self.sub_set_rx(0xFFFFFF).await?; | ||
| 425 | } else { | ||
| 426 | self.sub_set_rx(timeout << 6).await?; | ||
| 427 | } | ||
| 428 | |||
| 429 | Ok(()) | ||
| 430 | } | ||
| 431 | |||
| 432 | /// Start a Channel Activity Detection | ||
| 433 | pub async fn start_cad(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 434 | self.sub_set_dio_irq_params( | ||
| 435 | IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value(), | ||
| 436 | IrqMask::CADDone.value() | IrqMask::CADActivityDetected.value(), | ||
| 437 | IrqMask::None.value(), | ||
| 438 | IrqMask::None.value(), | ||
| 439 | ) | ||
| 440 | .await?; | ||
| 441 | self.sub_set_cad().await?; | ||
| 442 | Ok(()) | ||
| 443 | } | ||
| 444 | |||
| 445 | /// Sets the radio in continuous wave transmission mode | ||
| 446 | /// frequency channel RF frequency | ||
| 447 | /// power output power [dBm] | ||
| 448 | /// timeout transmission mode timeout [s] | ||
| 449 | pub async fn set_tx_continuous_wave( | ||
| 450 | &mut self, | ||
| 451 | frequency: u32, | ||
| 452 | power: i8, | ||
| 453 | _timeout: u16, | ||
| 454 | ) -> Result<(), RadioError<BUS>> { | ||
| 455 | self.sub_set_rf_frequency(frequency).await?; | ||
| 456 | self.brd_set_rf_tx_power(power).await?; | ||
| 457 | self.sub_set_tx_continuous_wave().await?; | ||
| 458 | |||
| 459 | Ok(()) | ||
| 460 | } | ||
| 461 | |||
| 462 | /// Read the current RSSI value for the LoRa modem (only) [dBm] | ||
| 463 | pub async fn get_rssi(&mut self) -> Result<i16, RadioError<BUS>> { | ||
| 464 | let value = self.sub_get_rssi_inst().await?; | ||
| 465 | Ok(value as i16) | ||
| 466 | } | ||
| 467 | |||
| 468 | /// Write one or more radio registers with a buffer of a given size, starting at the first register address | ||
| 469 | pub async fn write_registers_from_buffer( | ||
| 470 | &mut self, | ||
| 471 | start_register: Register, | ||
| 472 | buffer: &[u8], | ||
| 473 | ) -> Result<(), RadioError<BUS>> { | ||
| 474 | self.brd_write_registers(start_register, buffer).await?; | ||
| 475 | Ok(()) | ||
| 476 | } | ||
| 477 | |||
| 478 | /// Read one or more radio registers into a buffer of a given size, starting at the first register address | ||
| 479 | pub async fn read_registers_into_buffer( | ||
| 480 | &mut self, | ||
| 481 | start_register: Register, | ||
| 482 | buffer: &mut [u8], | ||
| 483 | ) -> Result<(), RadioError<BUS>> { | ||
| 484 | self.brd_read_registers(start_register, buffer).await?; | ||
| 485 | Ok(()) | ||
| 486 | } | ||
| 487 | |||
| 488 | /// Set the maximum payload length (in bytes) for a LoRa modem (only). | ||
| 489 | pub async fn set_max_payload_length(&mut self, max: u8) -> Result<(), RadioError<BUS>> { | ||
| 490 | if self.packet_params.is_some() { | ||
| 491 | let packet_params = self.packet_params.as_mut().unwrap(); | ||
| 492 | self.max_payload_length = max; | ||
| 493 | packet_params.payload_length = max; | ||
| 494 | self.sub_set_packet_params().await?; | ||
| 495 | Ok(()) | ||
| 496 | } else { | ||
| 497 | Err(RadioError::PacketParamsMissing) | ||
| 498 | } | ||
| 499 | } | ||
| 500 | |||
| 501 | /// Get the time required for the board plus radio to get out of sleep [ms] | ||
| 502 | pub fn get_wakeup_time(&mut self) -> u32 { | ||
| 503 | self.brd_get_board_tcxo_wakeup_time() + RADIO_WAKEUP_TIME | ||
| 504 | } | ||
| 505 | |||
| 506 | /// Process the radio irq | ||
| 507 | pub async fn process_irq( | ||
| 508 | &mut self, | ||
| 509 | receiving_buffer: Option<&mut [u8]>, | ||
| 510 | received_len: Option<&mut u8>, | ||
| 511 | cad_activity_detected: Option<&mut bool>, | ||
| 512 | ) -> Result<(), RadioError<BUS>> { | ||
| 513 | loop { | ||
| 514 | info!("process_irq loop entered"); // debug ??? | ||
| 515 | |||
| 516 | let de = self.sub_get_device_errors().await?; | ||
| 517 | info!("device_errors: rc_64khz_calibration = {}, rc_13mhz_calibration = {}, pll_calibration = {}, adc_calibration = {}, image_calibration = {}, xosc_start = {}, pll_lock = {}, pa_ramp = {}", | ||
| 518 | 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); | ||
| 519 | let st = self.sub_get_status().await?; | ||
| 520 | info!( | ||
| 521 | "radio status: cmd_status: {:x}, chip_mode: {:x}", | ||
| 522 | st.cmd_status, st.chip_mode | ||
| 523 | ); | ||
| 524 | |||
| 525 | self.dio1.wait_for_high().await.map_err(|_| DIO1)?; | ||
| 526 | let operating_mode = self.brd_get_operating_mode(); | ||
| 527 | let irq_flags = self.sub_get_irq_status().await?; | ||
| 528 | self.sub_clear_irq_status(irq_flags).await?; | ||
| 529 | info!("process_irq DIO1 satisfied: irq_flags = {:x}", irq_flags); // debug ??? | ||
| 530 | |||
| 531 | // check for errors and unexpected interrupt masks (based on operation mode) | ||
| 532 | if (irq_flags & IrqMask::HeaderError.value()) == IrqMask::HeaderError.value() { | ||
| 533 | if !self.rx_continuous { | ||
| 534 | self.brd_set_operating_mode(RadioMode::StandbyRC); | ||
| 535 | } | ||
| 536 | return Err(RadioError::HeaderError); | ||
| 537 | } else if (irq_flags & IrqMask::CRCError.value()) == IrqMask::CRCError.value() { | ||
| 538 | if operating_mode == RadioMode::Receive { | ||
| 539 | if !self.rx_continuous { | ||
| 540 | self.brd_set_operating_mode(RadioMode::StandbyRC); | ||
| 541 | } | ||
| 542 | return Err(RadioError::CRCErrorOnReceive); | ||
| 543 | } else { | ||
| 544 | return Err(RadioError::CRCErrorUnexpected); | ||
| 545 | } | ||
| 546 | } else if (irq_flags & IrqMask::RxTxTimeout.value()) == IrqMask::RxTxTimeout.value() { | ||
| 547 | if operating_mode == RadioMode::Transmit { | ||
| 548 | self.brd_set_operating_mode(RadioMode::StandbyRC); | ||
| 549 | return Err(RadioError::TransmitTimeout); | ||
| 550 | } else if operating_mode == RadioMode::Receive { | ||
| 551 | self.brd_set_operating_mode(RadioMode::StandbyRC); | ||
| 552 | return Err(RadioError::ReceiveTimeout); | ||
| 553 | } else { | ||
| 554 | return Err(RadioError::TimeoutUnexpected); | ||
| 555 | } | ||
| 556 | } else if ((irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value()) | ||
| 557 | && (operating_mode != RadioMode::Transmit) | ||
| 558 | { | ||
| 559 | return Err(RadioError::TransmitDoneUnexpected); | ||
| 560 | } else if ((irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value()) | ||
| 561 | && (operating_mode != RadioMode::Receive) | ||
| 562 | { | ||
| 563 | return Err(RadioError::ReceiveDoneUnexpected); | ||
| 564 | } else if (((irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value()) | ||
| 565 | || ((irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value())) | ||
| 566 | && (operating_mode != RadioMode::ChannelActivityDetection) | ||
| 567 | { | ||
| 568 | return Err(RadioError::CADUnexpected); | ||
| 569 | } | ||
| 570 | |||
| 571 | // debug ??? | ||
| 572 | if (irq_flags & IrqMask::HeaderValid.value()) == IrqMask::HeaderValid.value() { | ||
| 573 | info!("HeaderValid"); | ||
| 574 | } else if (irq_flags & IrqMask::PreambleDetected.value()) == IrqMask::PreambleDetected.value() { | ||
| 575 | info!("PreambleDetected"); | ||
| 576 | } else if (irq_flags & IrqMask::SyncwordValid.value()) == IrqMask::SyncwordValid.value() { | ||
| 577 | info!("SyncwordValid"); | ||
| 578 | } | ||
| 579 | |||
| 580 | // handle completions | ||
| 581 | if (irq_flags & IrqMask::TxDone.value()) == IrqMask::TxDone.value() { | ||
| 582 | self.brd_set_operating_mode(RadioMode::StandbyRC); | ||
| 583 | return Ok(()); | ||
| 584 | } else if (irq_flags & IrqMask::RxDone.value()) == IrqMask::RxDone.value() { | ||
| 585 | if !self.rx_continuous { | ||
| 586 | self.brd_set_operating_mode(RadioMode::StandbyRC); | ||
| 587 | |||
| 588 | // implicit header mode timeout behavior (see DS_SX1261-2_V1.2 datasheet chapter 15.3) | ||
| 589 | self.brd_write_registers(Register::RTCCtrl, &[0x00]).await?; | ||
| 590 | let mut evt_clr = [0x00u8]; | ||
| 591 | self.brd_read_registers(Register::EvtClr, &mut evt_clr).await?; | ||
| 592 | evt_clr[0] |= 1 << 1; | ||
| 593 | self.brd_write_registers(Register::EvtClr, &evt_clr).await?; | ||
| 594 | } | ||
| 595 | |||
| 596 | if receiving_buffer.is_some() && received_len.is_some() { | ||
| 597 | *(received_len.unwrap()) = self.sub_get_payload(receiving_buffer.unwrap()).await?; | ||
| 598 | } | ||
| 599 | self.packet_status = self.sub_get_packet_status().await?.into(); | ||
| 600 | return Ok(()); | ||
| 601 | } else if (irq_flags & IrqMask::CADDone.value()) == IrqMask::CADDone.value() { | ||
| 602 | if cad_activity_detected.is_some() { | ||
| 603 | *(cad_activity_detected.unwrap()) = | ||
| 604 | (irq_flags & IrqMask::CADActivityDetected.value()) == IrqMask::CADActivityDetected.value(); | ||
| 605 | } | ||
| 606 | self.brd_set_operating_mode(RadioMode::StandbyRC); | ||
| 607 | return Ok(()); | ||
| 608 | } | ||
| 609 | |||
| 610 | // if DIO1 was driven high for reasons other than an error or operation completion (currently, PreambleDetected, SyncwordValid, and HeaderValid | ||
| 611 | // are in that category), loop to wait again | ||
| 612 | } | ||
| 613 | } | ||
| 614 | |||
| 615 | // SX126x-specific functions | ||
| 616 | |||
| 617 | /// Set the radio in reception mode with Max LNA gain for the given time (SX126x radios only) [0: continuous, others timeout in ms] | ||
| 618 | pub async fn set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> { | ||
| 619 | self.sub_set_dio_irq_params( | ||
| 620 | IrqMask::All.value(), | ||
| 621 | IrqMask::All.value(), | ||
| 622 | IrqMask::None.value(), | ||
| 623 | IrqMask::None.value(), | ||
| 624 | ) | ||
| 625 | .await?; | ||
| 626 | |||
| 627 | if self.rx_continuous { | ||
| 628 | self.sub_set_rx_boosted(0xFFFFFF).await?; // Rx continuous | ||
| 629 | } else { | ||
| 630 | self.sub_set_rx_boosted(timeout << 6).await?; | ||
| 631 | } | ||
| 632 | |||
| 633 | Ok(()) | ||
| 634 | } | ||
| 635 | |||
| 636 | /// Set the Rx duty cycle management parameters (SX126x radios only) | ||
| 637 | /// rx_time structure describing reception timeout value | ||
| 638 | /// sleep_time structure describing sleep timeout value | ||
| 639 | pub async fn set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError<BUS>> { | ||
| 640 | self.sub_set_rx_duty_cycle(rx_time, sleep_time).await?; | ||
| 641 | Ok(()) | ||
| 642 | } | ||
| 643 | |||
| 644 | pub fn get_latest_packet_status(&mut self) -> Option<PacketStatus> { | ||
| 645 | self.packet_status | ||
| 646 | } | ||
| 647 | |||
| 648 | // Utilities | ||
| 649 | |||
| 650 | async fn add_register_to_retention_list(&mut self, register_address: u16) -> Result<(), RadioError<BUS>> { | ||
| 651 | let mut buffer = [0x00u8; (1 + (2 * MAX_NUMBER_REGS_IN_RETENTION)) as usize]; | ||
| 652 | |||
| 653 | // Read the address and registers already added to the list | ||
| 654 | self.brd_read_registers(Register::RetentionList, &mut buffer).await?; | ||
| 655 | |||
| 656 | let number_of_registers = buffer[0]; | ||
| 657 | for i in 0..number_of_registers { | ||
| 658 | if register_address | ||
| 659 | == ((buffer[(1 + (2 * i)) as usize] as u16) << 8) | (buffer[(2 + (2 * i)) as usize] as u16) | ||
| 660 | { | ||
| 661 | return Ok(()); // register already in list | ||
| 662 | } | ||
| 663 | } | ||
| 664 | |||
| 665 | if number_of_registers < MAX_NUMBER_REGS_IN_RETENTION { | ||
| 666 | buffer[0] += 1; // increment number of registers | ||
| 667 | |||
| 668 | buffer[(1 + (2 * number_of_registers)) as usize] = ((register_address >> 8) & 0xFF) as u8; | ||
| 669 | buffer[(2 + (2 * number_of_registers)) as usize] = (register_address & 0xFF) as u8; | ||
| 670 | self.brd_write_registers(Register::RetentionList, &buffer).await?; | ||
| 671 | |||
| 672 | Ok(()) | ||
| 673 | } else { | ||
| 674 | Err(RadioError::RetentionListExceeded) | ||
| 675 | } | ||
| 676 | } | ||
| 677 | |||
| 678 | fn get_lora_time_on_air_numerator( | ||
| 679 | spreading_factor: SpreadingFactor, | ||
| 680 | bandwidth: Bandwidth, | ||
| 681 | coding_rate: CodingRate, | ||
| 682 | preamble_length: u16, | ||
| 683 | fixed_len: bool, | ||
| 684 | payload_len: u8, | ||
| 685 | crc_on: bool, | ||
| 686 | ) -> u32 { | ||
| 687 | let cell_denominator; | ||
| 688 | let cr_denominator = (coding_rate.value() as i32) + 4; | ||
| 689 | |||
| 690 | // Ensure that the preamble length is at least 12 symbols when using SF5 or SF6 | ||
| 691 | let mut preamble_length_final = preamble_length; | ||
| 692 | if ((spreading_factor == SpreadingFactor::_5) || (spreading_factor == SpreadingFactor::_6)) | ||
| 693 | && (preamble_length < 12) | ||
| 694 | { | ||
| 695 | preamble_length_final = 12; | ||
| 696 | } | ||
| 697 | |||
| 698 | let mut low_data_rate_optimize = false; | ||
| 699 | if (((spreading_factor == SpreadingFactor::_11) || (spreading_factor == SpreadingFactor::_12)) | ||
| 700 | && (bandwidth == Bandwidth::_125KHz)) | ||
| 701 | || ((spreading_factor == SpreadingFactor::_12) && (bandwidth == Bandwidth::_250KHz)) | ||
| 702 | { | ||
| 703 | low_data_rate_optimize = true; | ||
| 704 | } | ||
| 705 | |||
| 706 | let mut cell_numerator = ((payload_len as i32) << 3) + (if crc_on { 16 } else { 0 }) | ||
| 707 | - (4 * spreading_factor.value() as i32) | ||
| 708 | + (if fixed_len { 0 } else { 20 }); | ||
| 709 | |||
| 710 | if spreading_factor.value() <= 6 { | ||
| 711 | cell_denominator = 4 * (spreading_factor.value() as i32); | ||
| 712 | } else { | ||
| 713 | cell_numerator += 8; | ||
| 714 | if low_data_rate_optimize { | ||
| 715 | cell_denominator = 4 * ((spreading_factor.value() as i32) - 2); | ||
| 716 | } else { | ||
| 717 | cell_denominator = 4 * (spreading_factor.value() as i32); | ||
| 718 | } | ||
| 719 | } | ||
| 720 | |||
| 721 | if cell_numerator < 0 { | ||
| 722 | cell_numerator = 0; | ||
| 723 | } | ||
| 724 | |||
| 725 | let mut intermediate: i32 = (((cell_numerator + cell_denominator - 1) / cell_denominator) * cr_denominator) | ||
| 726 | + (preamble_length_final as i32) | ||
| 727 | + 12; | ||
| 728 | |||
| 729 | if spreading_factor.value() <= 6 { | ||
| 730 | intermediate = intermediate + 2; | ||
| 731 | } | ||
| 732 | |||
| 733 | (((4 * intermediate) + 1) * (1 << (spreading_factor.value() - 2))) as u32 | ||
| 734 | } | ||
| 735 | } | ||
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 000000000..e270b2a09 --- /dev/null +++ b/embassy-lora/src/sx126x/sx126x_lora/mod_params.rs | |||
| @@ -0,0 +1,469 @@ | |||
| 1 | use core::fmt::Debug; | ||
| 2 | |||
| 3 | use lorawan_device::async_device::radio as device; | ||
| 4 | |||
| 5 | #[allow(clippy::upper_case_acronyms)] | ||
| 6 | #[derive(Debug)] | ||
| 7 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 8 | pub enum RadioError<BUS> { | ||
| 9 | SPI(BUS), | ||
| 10 | CS, | ||
| 11 | Reset, | ||
| 12 | AntRx, | ||
| 13 | AntTx, | ||
| 14 | Busy, | ||
| 15 | DIO1, | ||
| 16 | PayloadSizeMismatch(usize, usize), | ||
| 17 | RetentionListExceeded, | ||
| 18 | InvalidBandwidth, | ||
| 19 | ModulationParamsMissing, | ||
| 20 | PacketParamsMissing, | ||
| 21 | HeaderError, | ||
| 22 | CRCErrorUnexpected, | ||
| 23 | CRCErrorOnReceive, | ||
| 24 | TransmitTimeout, | ||
| 25 | ReceiveTimeout, | ||
| 26 | TimeoutUnexpected, | ||
| 27 | TransmitDoneUnexpected, | ||
| 28 | ReceiveDoneUnexpected, | ||
| 29 | CADUnexpected, | ||
| 30 | } | ||
| 31 | |||
| 32 | pub struct RadioSystemError { | ||
| 33 | pub rc_64khz_calibration: bool, | ||
| 34 | pub rc_13mhz_calibration: bool, | ||
| 35 | pub pll_calibration: bool, | ||
| 36 | pub adc_calibration: bool, | ||
| 37 | pub image_calibration: bool, | ||
| 38 | pub xosc_start: bool, | ||
| 39 | pub pll_lock: bool, | ||
| 40 | pub pa_ramp: bool, | ||
| 41 | } | ||
| 42 | |||
| 43 | #[derive(Clone, Copy, PartialEq)] | ||
| 44 | pub enum PacketType { | ||
| 45 | GFSK = 0x00, | ||
| 46 | LoRa = 0x01, | ||
| 47 | None = 0x0F, | ||
| 48 | } | ||
| 49 | |||
| 50 | impl PacketType { | ||
| 51 | pub const fn value(self) -> u8 { | ||
| 52 | self as u8 | ||
| 53 | } | ||
| 54 | pub fn to_enum(value: u8) -> Self { | ||
| 55 | if value == 0x00 { | ||
| 56 | PacketType::GFSK | ||
| 57 | } else if value == 0x01 { | ||
| 58 | PacketType::LoRa | ||
| 59 | } else { | ||
| 60 | PacketType::None | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | #[derive(Clone, Copy)] | ||
| 66 | pub struct PacketStatus { | ||
| 67 | pub rssi: i8, | ||
| 68 | pub snr: i8, | ||
| 69 | pub signal_rssi: i8, | ||
| 70 | pub freq_error: u32, | ||
| 71 | } | ||
| 72 | |||
| 73 | #[derive(Clone, Copy, PartialEq)] | ||
| 74 | pub enum RadioType { | ||
| 75 | SX1261, | ||
| 76 | SX1262, | ||
| 77 | } | ||
| 78 | |||
| 79 | #[derive(Clone, Copy, PartialEq)] | ||
| 80 | pub enum RadioMode { | ||
| 81 | Sleep = 0x00, // sleep mode | ||
| 82 | StandbyRC = 0x01, // standby mode with RC oscillator | ||
| 83 | StandbyXOSC = 0x02, // standby mode with XOSC oscillator | ||
| 84 | FrequencySynthesis = 0x03, // frequency synthesis mode | ||
| 85 | Transmit = 0x04, // transmit mode | ||
| 86 | Receive = 0x05, // receive mode | ||
| 87 | ReceiveDutyCycle = 0x06, // receive duty cycle mode | ||
| 88 | ChannelActivityDetection = 0x07, // channel activity detection mode | ||
| 89 | } | ||
| 90 | |||
| 91 | impl RadioMode { | ||
| 92 | /// Returns the value of the mode. | ||
| 93 | pub const fn value(self) -> u8 { | ||
| 94 | self as u8 | ||
| 95 | } | ||
| 96 | pub fn to_enum(value: u8) -> Self { | ||
| 97 | if value == 0x00 { | ||
| 98 | RadioMode::Sleep | ||
| 99 | } else if value == 0x01 { | ||
| 100 | RadioMode::StandbyRC | ||
| 101 | } else if value == 0x02 { | ||
| 102 | RadioMode::StandbyXOSC | ||
| 103 | } else if value == 0x03 { | ||
| 104 | RadioMode::FrequencySynthesis | ||
| 105 | } else if value == 0x04 { | ||
| 106 | RadioMode::Transmit | ||
| 107 | } else if value == 0x05 { | ||
| 108 | RadioMode::Receive | ||
| 109 | } else if value == 0x06 { | ||
| 110 | RadioMode::ReceiveDutyCycle | ||
| 111 | } else if value == 0x07 { | ||
| 112 | RadioMode::ChannelActivityDetection | ||
| 113 | } else { | ||
| 114 | RadioMode::Sleep | ||
| 115 | } | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | pub enum RadioState { | ||
| 120 | Idle = 0x00, | ||
| 121 | RxRunning = 0x01, | ||
| 122 | TxRunning = 0x02, | ||
| 123 | ChannelActivityDetecting = 0x03, | ||
| 124 | } | ||
| 125 | |||
| 126 | impl RadioState { | ||
| 127 | /// Returns the value of the state. | ||
| 128 | pub fn value(self) -> u8 { | ||
| 129 | self as u8 | ||
| 130 | } | ||
| 131 | } | ||
| 132 | |||
| 133 | pub struct RadioStatus { | ||
| 134 | pub cmd_status: u8, | ||
| 135 | pub chip_mode: u8, | ||
| 136 | } | ||
| 137 | |||
| 138 | impl RadioStatus { | ||
| 139 | pub fn value(self) -> u8 { | ||
| 140 | (self.chip_mode << 4) | (self.cmd_status << 1) | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | #[derive(Clone, Copy)] | ||
| 145 | pub enum IrqMask { | ||
| 146 | None = 0x0000, | ||
| 147 | TxDone = 0x0001, | ||
| 148 | RxDone = 0x0002, | ||
| 149 | PreambleDetected = 0x0004, | ||
| 150 | SyncwordValid = 0x0008, | ||
| 151 | HeaderValid = 0x0010, | ||
| 152 | HeaderError = 0x0020, | ||
| 153 | CRCError = 0x0040, | ||
| 154 | CADDone = 0x0080, | ||
| 155 | CADActivityDetected = 0x0100, | ||
| 156 | RxTxTimeout = 0x0200, | ||
| 157 | All = 0xFFFF, | ||
| 158 | } | ||
| 159 | |||
| 160 | impl IrqMask { | ||
| 161 | pub fn value(self) -> u16 { | ||
| 162 | self as u16 | ||
| 163 | } | ||
| 164 | } | ||
| 165 | |||
| 166 | #[derive(Clone, Copy)] | ||
| 167 | pub enum Register { | ||
| 168 | PacketParams = 0x0704, // packet configuration | ||
| 169 | PayloadLength = 0x0702, // payload size | ||
| 170 | SynchTimeout = 0x0706, // recalculated number of symbols | ||
| 171 | Syncword = 0x06C0, // Syncword values | ||
| 172 | LoRaSyncword = 0x0740, // LoRa Syncword value | ||
| 173 | GeneratedRandomNumber = 0x0819, //32-bit generated random number | ||
| 174 | AnaLNA = 0x08E2, // disable the LNA | ||
| 175 | AnaMixer = 0x08E5, // disable the mixer | ||
| 176 | RxGain = 0x08AC, // RX gain (0x94: power saving, 0x96: rx boosted) | ||
| 177 | XTATrim = 0x0911, // device internal trimming capacitor | ||
| 178 | OCP = 0x08E7, // over current protection max value | ||
| 179 | RetentionList = 0x029F, // retention list | ||
| 180 | IQPolarity = 0x0736, // optimize the inverted IQ operation (see DS_SX1261-2_V1.2 datasheet chapter 15.4) | ||
| 181 | TxModulation = 0x0889, // modulation quality with 500 kHz LoRa Bandwidth (see DS_SX1261-2_V1.2 datasheet chapter 15.1) | ||
| 182 | TxClampCfg = 0x08D8, // better resistance to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2) | ||
| 183 | RTCCtrl = 0x0902, // RTC control | ||
| 184 | EvtClr = 0x0944, // event clear | ||
| 185 | } | ||
| 186 | |||
| 187 | impl Register { | ||
| 188 | pub fn addr(self) -> u16 { | ||
| 189 | self as u16 | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | #[derive(Clone, Copy, PartialEq)] | ||
| 194 | pub enum OpCode { | ||
| 195 | GetStatus = 0xC0, | ||
| 196 | WriteRegister = 0x0D, | ||
| 197 | ReadRegister = 0x1D, | ||
| 198 | WriteBuffer = 0x0E, | ||
| 199 | ReadBuffer = 0x1E, | ||
| 200 | SetSleep = 0x84, | ||
| 201 | SetStandby = 0x80, | ||
| 202 | SetFS = 0xC1, | ||
| 203 | SetTx = 0x83, | ||
| 204 | SetRx = 0x82, | ||
| 205 | SetRxDutyCycle = 0x94, | ||
| 206 | SetCAD = 0xC5, | ||
| 207 | SetTxContinuousWave = 0xD1, | ||
| 208 | SetTxContinuousPremable = 0xD2, | ||
| 209 | SetPacketType = 0x8A, | ||
| 210 | GetPacketType = 0x11, | ||
| 211 | SetRFFrequency = 0x86, | ||
| 212 | SetTxParams = 0x8E, | ||
| 213 | SetPAConfig = 0x95, | ||
| 214 | SetCADParams = 0x88, | ||
| 215 | SetBufferBaseAddress = 0x8F, | ||
| 216 | SetModulationParams = 0x8B, | ||
| 217 | SetPacketParams = 0x8C, | ||
| 218 | GetRxBufferStatus = 0x13, | ||
| 219 | GetPacketStatus = 0x14, | ||
| 220 | GetRSSIInst = 0x15, | ||
| 221 | GetStats = 0x10, | ||
| 222 | ResetStats = 0x00, | ||
| 223 | CfgDIOIrq = 0x08, | ||
| 224 | GetIrqStatus = 0x12, | ||
| 225 | ClrIrqStatus = 0x02, | ||
| 226 | Calibrate = 0x89, | ||
| 227 | CalibrateImage = 0x98, | ||
| 228 | SetRegulatorMode = 0x96, | ||
| 229 | GetErrors = 0x17, | ||
| 230 | ClrErrors = 0x07, | ||
| 231 | SetTCXOMode = 0x97, | ||
| 232 | SetTxFallbackMode = 0x93, | ||
| 233 | SetRFSwitchMode = 0x9D, | ||
| 234 | SetStopRxTimerOnPreamble = 0x9F, | ||
| 235 | SetLoRaSymbTimeout = 0xA0, | ||
| 236 | } | ||
| 237 | |||
| 238 | impl OpCode { | ||
| 239 | pub fn value(self) -> u8 { | ||
| 240 | self as u8 | ||
| 241 | } | ||
| 242 | } | ||
| 243 | |||
| 244 | pub struct SleepParams { | ||
| 245 | pub wakeup_rtc: bool, // get out of sleep mode if wakeup signal received from RTC | ||
| 246 | pub reset: bool, | ||
| 247 | pub warm_start: bool, | ||
| 248 | } | ||
| 249 | |||
| 250 | impl SleepParams { | ||
| 251 | pub fn value(self) -> u8 { | ||
| 252 | ((self.warm_start as u8) << 2) | ((self.reset as u8) << 1) | (self.wakeup_rtc as u8) | ||
| 253 | } | ||
| 254 | } | ||
| 255 | |||
| 256 | #[derive(Clone, Copy, PartialEq)] | ||
| 257 | pub enum StandbyMode { | ||
| 258 | RC = 0x00, | ||
| 259 | XOSC = 0x01, | ||
| 260 | } | ||
| 261 | |||
| 262 | impl StandbyMode { | ||
| 263 | pub fn value(self) -> u8 { | ||
| 264 | self as u8 | ||
| 265 | } | ||
| 266 | } | ||
| 267 | |||
| 268 | #[derive(Clone, Copy)] | ||
| 269 | pub enum RegulatorMode { | ||
| 270 | UseLDO = 0x00, | ||
| 271 | UseDCDC = 0x01, | ||
| 272 | } | ||
| 273 | |||
| 274 | impl RegulatorMode { | ||
| 275 | pub fn value(self) -> u8 { | ||
| 276 | self as u8 | ||
| 277 | } | ||
| 278 | } | ||
| 279 | |||
| 280 | #[derive(Clone, Copy)] | ||
| 281 | pub struct CalibrationParams { | ||
| 282 | pub rc64k_enable: bool, // calibrate RC64K clock | ||
| 283 | pub rc13m_enable: bool, // calibrate RC13M clock | ||
| 284 | pub pll_enable: bool, // calibrate PLL | ||
| 285 | pub adc_pulse_enable: bool, // calibrate ADC Pulse | ||
| 286 | pub adc_bulkn_enable: bool, // calibrate ADC bulkN | ||
| 287 | pub adc_bulkp_enable: bool, // calibrate ADC bulkP | ||
| 288 | pub img_enable: bool, | ||
| 289 | } | ||
| 290 | |||
| 291 | impl CalibrationParams { | ||
| 292 | pub fn value(self) -> u8 { | ||
| 293 | ((self.img_enable as u8) << 6) | ||
| 294 | | ((self.adc_bulkp_enable as u8) << 5) | ||
| 295 | | ((self.adc_bulkn_enable as u8) << 4) | ||
| 296 | | ((self.adc_pulse_enable as u8) << 3) | ||
| 297 | | ((self.pll_enable as u8) << 2) | ||
| 298 | | ((self.rc13m_enable as u8) << 1) | ||
| 299 | | ((self.rc64k_enable as u8) << 0) | ||
| 300 | } | ||
| 301 | } | ||
| 302 | |||
| 303 | #[derive(Clone, Copy)] | ||
| 304 | pub enum TcxoCtrlVoltage { | ||
| 305 | Ctrl1V6 = 0x00, | ||
| 306 | Ctrl1V7 = 0x01, | ||
| 307 | Ctrl1V8 = 0x02, | ||
| 308 | Ctrl2V2 = 0x03, | ||
| 309 | Ctrl2V4 = 0x04, | ||
| 310 | Ctrl2V7 = 0x05, | ||
| 311 | Ctrl3V0 = 0x06, | ||
| 312 | Ctrl3V3 = 0x07, | ||
| 313 | } | ||
| 314 | |||
| 315 | impl TcxoCtrlVoltage { | ||
| 316 | pub fn value(self) -> u8 { | ||
| 317 | self as u8 | ||
| 318 | } | ||
| 319 | } | ||
| 320 | |||
| 321 | #[derive(Clone, Copy)] | ||
| 322 | pub enum RampTime { | ||
| 323 | Ramp10Us = 0x00, | ||
| 324 | Ramp20Us = 0x01, | ||
| 325 | Ramp40Us = 0x02, | ||
| 326 | Ramp80Us = 0x03, | ||
| 327 | Ramp200Us = 0x04, | ||
| 328 | Ramp800Us = 0x05, | ||
| 329 | Ramp1700Us = 0x06, | ||
| 330 | Ramp3400Us = 0x07, | ||
| 331 | } | ||
| 332 | |||
| 333 | impl RampTime { | ||
| 334 | pub fn value(self) -> u8 { | ||
| 335 | self as u8 | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | #[derive(Clone, Copy, PartialEq)] | ||
| 340 | pub enum SpreadingFactor { | ||
| 341 | _5 = 0x05, | ||
| 342 | _6 = 0x06, | ||
| 343 | _7 = 0x07, | ||
| 344 | _8 = 0x08, | ||
| 345 | _9 = 0x09, | ||
| 346 | _10 = 0x0A, | ||
| 347 | _11 = 0x0B, | ||
| 348 | _12 = 0x0C, | ||
| 349 | } | ||
| 350 | |||
| 351 | impl SpreadingFactor { | ||
| 352 | pub fn value(self) -> u8 { | ||
| 353 | self as u8 | ||
| 354 | } | ||
| 355 | } | ||
| 356 | |||
| 357 | impl From<device::SpreadingFactor> for SpreadingFactor { | ||
| 358 | fn from(sf: device::SpreadingFactor) -> Self { | ||
| 359 | match sf { | ||
| 360 | device::SpreadingFactor::_7 => SpreadingFactor::_7, | ||
| 361 | device::SpreadingFactor::_8 => SpreadingFactor::_8, | ||
| 362 | device::SpreadingFactor::_9 => SpreadingFactor::_9, | ||
| 363 | device::SpreadingFactor::_10 => SpreadingFactor::_10, | ||
| 364 | device::SpreadingFactor::_11 => SpreadingFactor::_11, | ||
| 365 | device::SpreadingFactor::_12 => SpreadingFactor::_12, | ||
| 366 | } | ||
| 367 | } | ||
| 368 | } | ||
| 369 | |||
| 370 | #[derive(Clone, Copy, PartialEq)] | ||
| 371 | pub enum Bandwidth { | ||
| 372 | _500KHz = 0x06, | ||
| 373 | _250KHz = 0x05, | ||
| 374 | _125KHz = 0x04, | ||
| 375 | } | ||
| 376 | |||
| 377 | impl Bandwidth { | ||
| 378 | pub fn value(self) -> u8 { | ||
| 379 | self as u8 | ||
| 380 | } | ||
| 381 | |||
| 382 | pub fn value_in_hz(self) -> u32 { | ||
| 383 | match self { | ||
| 384 | Bandwidth::_125KHz => 125000u32, | ||
| 385 | Bandwidth::_250KHz => 250000u32, | ||
| 386 | Bandwidth::_500KHz => 500000u32, | ||
| 387 | } | ||
| 388 | } | ||
| 389 | } | ||
| 390 | |||
| 391 | impl From<device::Bandwidth> for Bandwidth { | ||
| 392 | fn from(bw: device::Bandwidth) -> Self { | ||
| 393 | match bw { | ||
| 394 | device::Bandwidth::_500KHz => Bandwidth::_500KHz, | ||
| 395 | device::Bandwidth::_250KHz => Bandwidth::_250KHz, | ||
| 396 | device::Bandwidth::_125KHz => Bandwidth::_125KHz, | ||
| 397 | } | ||
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | #[derive(Clone, Copy)] | ||
| 402 | pub enum CodingRate { | ||
| 403 | _4_5 = 0x01, | ||
| 404 | _4_6 = 0x02, | ||
| 405 | _4_7 = 0x03, | ||
| 406 | _4_8 = 0x04, | ||
| 407 | } | ||
| 408 | |||
| 409 | impl CodingRate { | ||
| 410 | pub fn value(self) -> u8 { | ||
| 411 | self as u8 | ||
| 412 | } | ||
| 413 | } | ||
| 414 | |||
| 415 | impl From<device::CodingRate> for CodingRate { | ||
| 416 | fn from(cr: device::CodingRate) -> Self { | ||
| 417 | match cr { | ||
| 418 | device::CodingRate::_4_5 => CodingRate::_4_5, | ||
| 419 | device::CodingRate::_4_6 => CodingRate::_4_6, | ||
| 420 | device::CodingRate::_4_7 => CodingRate::_4_7, | ||
| 421 | device::CodingRate::_4_8 => CodingRate::_4_8, | ||
| 422 | } | ||
| 423 | } | ||
| 424 | } | ||
| 425 | |||
| 426 | #[derive(Clone, Copy)] | ||
| 427 | pub struct ModulationParams { | ||
| 428 | pub spreading_factor: SpreadingFactor, | ||
| 429 | pub bandwidth: Bandwidth, | ||
| 430 | pub coding_rate: CodingRate, | ||
| 431 | pub low_data_rate_optimize: u8, | ||
| 432 | } | ||
| 433 | |||
| 434 | #[derive(Clone, Copy)] | ||
| 435 | pub struct PacketParams { | ||
| 436 | pub preamble_length: u16, // number of LoRa symbols in the preamble | ||
| 437 | 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) | ||
| 438 | pub payload_length: u8, | ||
| 439 | pub crc_on: bool, | ||
| 440 | pub iq_inverted: bool, | ||
| 441 | } | ||
| 442 | |||
| 443 | #[derive(Clone, Copy)] | ||
| 444 | pub enum CADSymbols { | ||
| 445 | _1 = 0x00, | ||
| 446 | _2 = 0x01, | ||
| 447 | _4 = 0x02, | ||
| 448 | _8 = 0x03, | ||
| 449 | _16 = 0x04, | ||
| 450 | } | ||
| 451 | |||
| 452 | impl CADSymbols { | ||
| 453 | pub fn value(self) -> u8 { | ||
| 454 | self as u8 | ||
| 455 | } | ||
| 456 | } | ||
| 457 | |||
| 458 | #[derive(Clone, Copy)] | ||
| 459 | pub enum CADExitMode { | ||
| 460 | CADOnly = 0x00, | ||
| 461 | CADRx = 0x01, | ||
| 462 | CADLBT = 0x10, | ||
| 463 | } | ||
| 464 | |||
| 465 | impl CADExitMode { | ||
| 466 | pub fn value(self) -> u8 { | ||
| 467 | self as u8 | ||
| 468 | } | ||
| 469 | } | ||
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 000000000..02a0a72ec --- /dev/null +++ b/embassy-lora/src/sx126x/sx126x_lora/subroutine.rs | |||
| @@ -0,0 +1,688 @@ | |||
| 1 | use embedded_hal::digital::v2::OutputPin; | ||
| 2 | use embedded_hal_async::digital::Wait; | ||
| 3 | use embedded_hal_async::spi::SpiBus; | ||
| 4 | |||
| 5 | use super::mod_params::*; | ||
| 6 | use super::LoRa; | ||
| 7 | |||
| 8 | // Internal frequency of the radio | ||
| 9 | const SX126X_XTAL_FREQ: u32 = 32000000; | ||
| 10 | |||
| 11 | // Scaling factor used to perform fixed-point operations | ||
| 12 | const SX126X_PLL_STEP_SHIFT_AMOUNT: u32 = 14; | ||
| 13 | |||
| 14 | // PLL step - scaled with SX126X_PLL_STEP_SHIFT_AMOUNT | ||
| 15 | const SX126X_PLL_STEP_SCALED: u32 = SX126X_XTAL_FREQ >> (25 - SX126X_PLL_STEP_SHIFT_AMOUNT); | ||
| 16 | |||
| 17 | // Maximum value for parameter symbNum | ||
| 18 | const SX126X_MAX_LORA_SYMB_NUM_TIMEOUT: u8 = 248; | ||
| 19 | |||
| 20 | // Provides board-specific functionality for Semtech SX126x-based boards | ||
| 21 | |||
| 22 | impl<SPI, CS, RESET, ANTRX, ANTTX, WAIT, BUS> LoRa<SPI, CS, RESET, ANTRX, ANTTX, WAIT> | ||
| 23 | where | ||
| 24 | SPI: SpiBus<u8, Error = BUS>, | ||
| 25 | CS: OutputPin, | ||
| 26 | RESET: OutputPin, | ||
| 27 | ANTRX: OutputPin, | ||
| 28 | ANTTX: OutputPin, | ||
| 29 | WAIT: Wait, | ||
| 30 | { | ||
| 31 | // Initialize the radio driver | ||
| 32 | pub(super) async fn sub_init(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 33 | self.brd_reset().await?; | ||
| 34 | self.brd_wakeup().await?; | ||
| 35 | self.sub_set_standby(StandbyMode::RC).await?; | ||
| 36 | self.brd_io_tcxo_init().await?; | ||
| 37 | self.brd_io_rf_switch_init().await?; | ||
| 38 | self.image_calibrated = false; | ||
| 39 | Ok(()) | ||
| 40 | } | ||
| 41 | |||
| 42 | // Wakeup the radio if it is in Sleep mode and check that Busy is low | ||
| 43 | pub(super) async fn sub_check_device_ready(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 44 | let operating_mode = self.brd_get_operating_mode(); | ||
| 45 | if operating_mode == RadioMode::Sleep || operating_mode == RadioMode::ReceiveDutyCycle { | ||
| 46 | self.brd_wakeup().await?; | ||
| 47 | self.brd_ant_sw_on().await?; | ||
| 48 | } | ||
| 49 | self.brd_wait_on_busy().await?; | ||
| 50 | Ok(()) | ||
| 51 | } | ||
| 52 | |||
| 53 | // Save the payload to be sent in the radio buffer | ||
| 54 | pub(super) async fn sub_set_payload(&mut self, payload: &[u8]) -> Result<(), RadioError<BUS>> { | ||
| 55 | self.brd_write_buffer(0x00, payload).await?; | ||
| 56 | Ok(()) | ||
| 57 | } | ||
| 58 | |||
| 59 | // Read the payload received. | ||
| 60 | pub(super) async fn sub_get_payload(&mut self, buffer: &mut [u8]) -> Result<u8, RadioError<BUS>> { | ||
| 61 | let (size, offset) = self.sub_get_rx_buffer_status().await?; | ||
| 62 | if (size as usize) > buffer.len() { | ||
| 63 | Err(RadioError::PayloadSizeMismatch(size as usize, buffer.len())) | ||
| 64 | } else { | ||
| 65 | self.brd_read_buffer(offset, buffer).await?; | ||
| 66 | Ok(size) | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | // Send a payload | ||
| 71 | pub(super) async fn sub_send_payload(&mut self, payload: &[u8], timeout: u32) -> Result<(), RadioError<BUS>> { | ||
| 72 | self.sub_set_payload(payload).await?; | ||
| 73 | self.sub_set_tx(timeout).await?; | ||
| 74 | Ok(()) | ||
| 75 | } | ||
| 76 | |||
| 77 | // Get a 32-bit random value generated by the radio. A valid packet type must have been configured before using this command. | ||
| 78 | // | ||
| 79 | // The radio must be in reception mode before executing this function. This code can potentially result in interrupt generation. It is the responsibility of | ||
| 80 | // the calling code to disable radio interrupts before calling this function, and re-enable them afterwards if necessary, or be certain that any interrupts | ||
| 81 | // generated during this process will not cause undesired side-effects in the software. | ||
| 82 | // | ||
| 83 | // The random numbers produced by the generator do not have a uniform or Gaussian distribution. If uniformity is needed, perform appropriate software post-processing. | ||
| 84 | pub(super) async fn sub_get_random(&mut self) -> Result<u32, RadioError<BUS>> { | ||
| 85 | let mut reg_ana_lna_buffer_original = [0x00u8]; | ||
| 86 | let mut reg_ana_mixer_buffer_original = [0x00u8]; | ||
| 87 | let mut reg_ana_lna_buffer = [0x00u8]; | ||
| 88 | let mut reg_ana_mixer_buffer = [0x00u8]; | ||
| 89 | let mut number_buffer = [0x00u8, 0x00u8, 0x00u8, 0x00u8]; | ||
| 90 | |||
| 91 | self.brd_read_registers(Register::AnaLNA, &mut reg_ana_lna_buffer_original) | ||
| 92 | .await?; | ||
| 93 | reg_ana_lna_buffer[0] = reg_ana_lna_buffer_original[0] & (!(1 << 0)); | ||
| 94 | self.brd_write_registers(Register::AnaLNA, ®_ana_lna_buffer).await?; | ||
| 95 | |||
| 96 | self.brd_read_registers(Register::AnaMixer, &mut reg_ana_mixer_buffer_original) | ||
| 97 | .await?; | ||
| 98 | reg_ana_mixer_buffer[0] = reg_ana_mixer_buffer_original[0] & (!(1 << 7)); | ||
| 99 | self.brd_write_registers(Register::AnaMixer, ®_ana_mixer_buffer) | ||
| 100 | .await?; | ||
| 101 | |||
| 102 | // Set radio in continuous reception | ||
| 103 | self.sub_set_rx(0xFFFFFFu32).await?; | ||
| 104 | |||
| 105 | self.brd_read_registers(Register::GeneratedRandomNumber, &mut number_buffer) | ||
| 106 | .await?; | ||
| 107 | |||
| 108 | self.sub_set_standby(StandbyMode::RC).await?; | ||
| 109 | |||
| 110 | self.brd_write_registers(Register::AnaLNA, ®_ana_lna_buffer_original) | ||
| 111 | .await?; | ||
| 112 | self.brd_write_registers(Register::AnaMixer, ®_ana_mixer_buffer_original) | ||
| 113 | .await?; | ||
| 114 | |||
| 115 | Ok(Self::convert_u8_buffer_to_u32(&number_buffer)) | ||
| 116 | } | ||
| 117 | |||
| 118 | // Set the radio in sleep mode | ||
| 119 | pub(super) async fn sub_set_sleep(&mut self, sleep_config: SleepParams) -> Result<(), RadioError<BUS>> { | ||
| 120 | self.brd_ant_sw_off().await?; | ||
| 121 | |||
| 122 | let _fix_rx = self.antenna_rx.set_low(); | ||
| 123 | let _fix_tx = self.antenna_tx.set_low(); | ||
| 124 | |||
| 125 | if !sleep_config.warm_start { | ||
| 126 | self.image_calibrated = false; | ||
| 127 | } | ||
| 128 | |||
| 129 | self.brd_write_command(OpCode::SetSleep, &[sleep_config.value()]) | ||
| 130 | .await?; | ||
| 131 | self.brd_set_operating_mode(RadioMode::Sleep); | ||
| 132 | Ok(()) | ||
| 133 | } | ||
| 134 | |||
| 135 | // Set the radio in configuration mode | ||
| 136 | pub(super) async fn sub_set_standby(&mut self, mode: StandbyMode) -> Result<(), RadioError<BUS>> { | ||
| 137 | self.brd_write_command(OpCode::SetStandby, &[mode.value()]).await?; | ||
| 138 | if mode == StandbyMode::RC { | ||
| 139 | self.brd_set_operating_mode(RadioMode::StandbyRC); | ||
| 140 | } else { | ||
| 141 | self.brd_set_operating_mode(RadioMode::StandbyXOSC); | ||
| 142 | } | ||
| 143 | |||
| 144 | let _fix_rx = self.antenna_rx.set_low(); | ||
| 145 | let _fix_tx = self.antenna_tx.set_low(); | ||
| 146 | Ok(()) | ||
| 147 | } | ||
| 148 | |||
| 149 | // Set the radio in FS mode | ||
| 150 | pub(super) async fn sub_set_fs(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 151 | // antenna settings ??? | ||
| 152 | self.brd_write_command(OpCode::SetFS, &[]).await?; | ||
| 153 | self.brd_set_operating_mode(RadioMode::FrequencySynthesis); | ||
| 154 | Ok(()) | ||
| 155 | } | ||
| 156 | |||
| 157 | // Set the radio in transmission mode with timeout specified | ||
| 158 | pub(super) async fn sub_set_tx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> { | ||
| 159 | let buffer = [ | ||
| 160 | Self::timeout_1(timeout), | ||
| 161 | Self::timeout_2(timeout), | ||
| 162 | Self::timeout_3(timeout), | ||
| 163 | ]; | ||
| 164 | |||
| 165 | let _fix_rx = self.antenna_rx.set_low(); | ||
| 166 | let _fix_tx = self.antenna_tx.set_high(); | ||
| 167 | |||
| 168 | self.brd_set_operating_mode(RadioMode::Transmit); | ||
| 169 | self.brd_write_command(OpCode::SetTx, &buffer).await?; | ||
| 170 | Ok(()) | ||
| 171 | } | ||
| 172 | |||
| 173 | // Set the radio in reception mode with timeout specified | ||
| 174 | pub(super) async fn sub_set_rx(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> { | ||
| 175 | let buffer = [ | ||
| 176 | Self::timeout_1(timeout), | ||
| 177 | Self::timeout_2(timeout), | ||
| 178 | Self::timeout_3(timeout), | ||
| 179 | ]; | ||
| 180 | |||
| 181 | let _fix_rx = self.antenna_rx.set_high(); | ||
| 182 | let _fix_tx = self.antenna_tx.set_low(); | ||
| 183 | |||
| 184 | self.brd_set_operating_mode(RadioMode::Receive); | ||
| 185 | self.brd_write_registers(Register::RxGain, &[0x94u8]).await?; | ||
| 186 | self.brd_write_command(OpCode::SetRx, &buffer).await?; | ||
| 187 | Ok(()) | ||
| 188 | } | ||
| 189 | |||
| 190 | // Set the radio in reception mode with Boosted LNA gain and timeout specified | ||
| 191 | pub(super) async fn sub_set_rx_boosted(&mut self, timeout: u32) -> Result<(), RadioError<BUS>> { | ||
| 192 | let buffer = [ | ||
| 193 | Self::timeout_1(timeout), | ||
| 194 | Self::timeout_2(timeout), | ||
| 195 | Self::timeout_3(timeout), | ||
| 196 | ]; | ||
| 197 | |||
| 198 | let _fix_rx = self.antenna_rx.set_high(); | ||
| 199 | let _fix_tx = self.antenna_tx.set_low(); | ||
| 200 | |||
| 201 | self.brd_set_operating_mode(RadioMode::Receive); | ||
| 202 | // set max LNA gain, increase current by ~2mA for around ~3dB in sensitivity | ||
| 203 | self.brd_write_registers(Register::RxGain, &[0x96u8]).await?; | ||
| 204 | self.brd_write_command(OpCode::SetRx, &buffer).await?; | ||
| 205 | Ok(()) | ||
| 206 | } | ||
| 207 | |||
| 208 | // Set the Rx duty cycle management parameters | ||
| 209 | pub(super) async fn sub_set_rx_duty_cycle(&mut self, rx_time: u32, sleep_time: u32) -> Result<(), RadioError<BUS>> { | ||
| 210 | let buffer = [ | ||
| 211 | ((rx_time >> 16) & 0xFF) as u8, | ||
| 212 | ((rx_time >> 8) & 0xFF) as u8, | ||
| 213 | (rx_time & 0xFF) as u8, | ||
| 214 | ((sleep_time >> 16) & 0xFF) as u8, | ||
| 215 | ((sleep_time >> 8) & 0xFF) as u8, | ||
| 216 | (sleep_time & 0xFF) as u8, | ||
| 217 | ]; | ||
| 218 | |||
| 219 | // antenna settings ??? | ||
| 220 | |||
| 221 | self.brd_write_command(OpCode::SetRxDutyCycle, &buffer).await?; | ||
| 222 | self.brd_set_operating_mode(RadioMode::ReceiveDutyCycle); | ||
| 223 | Ok(()) | ||
| 224 | } | ||
| 225 | |||
| 226 | // Set the radio in CAD mode | ||
| 227 | pub(super) async fn sub_set_cad(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 228 | let _fix_rx = self.antenna_rx.set_high(); | ||
| 229 | let _fix_tx = self.antenna_tx.set_low(); | ||
| 230 | |||
| 231 | self.brd_write_command(OpCode::SetCAD, &[]).await?; | ||
| 232 | self.brd_set_operating_mode(RadioMode::ChannelActivityDetection); | ||
| 233 | Ok(()) | ||
| 234 | } | ||
| 235 | |||
| 236 | // Set the radio in continuous wave transmission mode | ||
| 237 | pub(super) async fn sub_set_tx_continuous_wave(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 238 | let _fix_rx = self.antenna_rx.set_low(); | ||
| 239 | let _fix_tx = self.antenna_tx.set_high(); | ||
| 240 | |||
| 241 | self.brd_write_command(OpCode::SetTxContinuousWave, &[]).await?; | ||
| 242 | self.brd_set_operating_mode(RadioMode::Transmit); | ||
| 243 | Ok(()) | ||
| 244 | } | ||
| 245 | |||
| 246 | // Set the radio in continuous preamble transmission mode | ||
| 247 | pub(super) async fn sub_set_tx_infinite_preamble(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 248 | let _fix_rx = self.antenna_rx.set_low(); | ||
| 249 | let _fix_tx = self.antenna_tx.set_high(); | ||
| 250 | |||
| 251 | self.brd_write_command(OpCode::SetTxContinuousPremable, &[]).await?; | ||
| 252 | self.brd_set_operating_mode(RadioMode::Transmit); | ||
| 253 | Ok(()) | ||
| 254 | } | ||
| 255 | |||
| 256 | // Decide which interrupt will stop the internal radio rx timer. | ||
| 257 | // false timer stop after header/syncword detection | ||
| 258 | // true timer stop after preamble detection | ||
| 259 | pub(super) async fn sub_set_stop_rx_timer_on_preamble_detect( | ||
| 260 | &mut self, | ||
| 261 | enable: bool, | ||
| 262 | ) -> Result<(), RadioError<BUS>> { | ||
| 263 | self.brd_write_command(OpCode::SetStopRxTimerOnPreamble, &[enable as u8]) | ||
| 264 | .await?; | ||
| 265 | Ok(()) | ||
| 266 | } | ||
| 267 | |||
| 268 | // Set the number of symbols the radio will wait to validate a reception | ||
| 269 | pub(super) async fn sub_set_lora_symb_num_timeout(&mut self, symb_num: u16) -> Result<(), RadioError<BUS>> { | ||
| 270 | let mut exp = 0u8; | ||
| 271 | let mut reg; | ||
| 272 | let mut mant = ((core::cmp::min(symb_num, SX126X_MAX_LORA_SYMB_NUM_TIMEOUT as u16) as u8) + 1) >> 1; | ||
| 273 | while mant > 31 { | ||
| 274 | mant = (mant + 3) >> 2; | ||
| 275 | exp += 1; | ||
| 276 | } | ||
| 277 | reg = mant << ((2 * exp) + 1); | ||
| 278 | |||
| 279 | self.brd_write_command(OpCode::SetLoRaSymbTimeout, &[reg]).await?; | ||
| 280 | |||
| 281 | if symb_num != 0 { | ||
| 282 | reg = exp + (mant << 3); | ||
| 283 | self.brd_write_registers(Register::SynchTimeout, &[reg]).await?; | ||
| 284 | } | ||
| 285 | |||
| 286 | Ok(()) | ||
| 287 | } | ||
| 288 | |||
| 289 | // Set the power regulators operating mode (LDO or DC_DC). Using only LDO implies that the Rx or Tx current is doubled | ||
| 290 | pub(super) async fn sub_set_regulator_mode(&mut self, mode: RegulatorMode) -> Result<(), RadioError<BUS>> { | ||
| 291 | self.brd_write_command(OpCode::SetRegulatorMode, &[mode.value()]) | ||
| 292 | .await?; | ||
| 293 | Ok(()) | ||
| 294 | } | ||
| 295 | |||
| 296 | // Calibrate the given radio block | ||
| 297 | pub(super) async fn sub_calibrate(&mut self, calibrate_params: CalibrationParams) -> Result<(), RadioError<BUS>> { | ||
| 298 | self.brd_write_command(OpCode::Calibrate, &[calibrate_params.value()]) | ||
| 299 | .await?; | ||
| 300 | Ok(()) | ||
| 301 | } | ||
| 302 | |||
| 303 | // Calibrate the image rejection based on the given frequency | ||
| 304 | pub(super) async fn sub_calibrate_image(&mut self, freq: u32) -> Result<(), RadioError<BUS>> { | ||
| 305 | let mut cal_freq = [0x00u8, 0x00u8]; | ||
| 306 | |||
| 307 | if freq > 900000000 { | ||
| 308 | cal_freq[0] = 0xE1; | ||
| 309 | cal_freq[1] = 0xE9; | ||
| 310 | } else if freq > 850000000 { | ||
| 311 | cal_freq[0] = 0xD7; | ||
| 312 | cal_freq[1] = 0xDB; | ||
| 313 | } else if freq > 770000000 { | ||
| 314 | cal_freq[0] = 0xC1; | ||
| 315 | cal_freq[1] = 0xC5; | ||
| 316 | } else if freq > 460000000 { | ||
| 317 | cal_freq[0] = 0x75; | ||
| 318 | cal_freq[1] = 0x81; | ||
| 319 | } else if freq > 425000000 { | ||
| 320 | cal_freq[0] = 0x6B; | ||
| 321 | cal_freq[1] = 0x6F; | ||
| 322 | } | ||
| 323 | self.brd_write_command(OpCode::CalibrateImage, &cal_freq).await?; | ||
| 324 | Ok(()) | ||
| 325 | } | ||
| 326 | |||
| 327 | // Activate the extention of the timeout when a long preamble is used | ||
| 328 | pub(super) async fn sub_set_long_preamble(&mut self, _enable: u8) -> Result<(), RadioError<BUS>> { | ||
| 329 | Ok(()) // no operation currently | ||
| 330 | } | ||
| 331 | |||
| 332 | // Set the transmission parameters | ||
| 333 | // hp_max 0 for sx1261, 7 for sx1262 | ||
| 334 | // device_sel 1 for sx1261, 0 for sx1262 | ||
| 335 | // pa_lut 0 for 14dBm LUT, 1 for 22dBm LUT | ||
| 336 | pub(super) async fn sub_set_pa_config( | ||
| 337 | &mut self, | ||
| 338 | pa_duty_cycle: u8, | ||
| 339 | hp_max: u8, | ||
| 340 | device_sel: u8, | ||
| 341 | pa_lut: u8, | ||
| 342 | ) -> Result<(), RadioError<BUS>> { | ||
| 343 | self.brd_write_command(OpCode::SetPAConfig, &[pa_duty_cycle, hp_max, device_sel, pa_lut]) | ||
| 344 | .await?; | ||
| 345 | Ok(()) | ||
| 346 | } | ||
| 347 | |||
| 348 | // Define into which mode the chip goes after a TX / RX done | ||
| 349 | pub(super) async fn sub_set_rx_tx_fallback_mode(&mut self, fallback_mode: u8) -> Result<(), RadioError<BUS>> { | ||
| 350 | self.brd_write_command(OpCode::SetTxFallbackMode, &[fallback_mode]) | ||
| 351 | .await?; | ||
| 352 | Ok(()) | ||
| 353 | } | ||
| 354 | |||
| 355 | // Set the IRQ mask and DIO masks | ||
| 356 | pub(super) async fn sub_set_dio_irq_params( | ||
| 357 | &mut self, | ||
| 358 | irq_mask: u16, | ||
| 359 | dio1_mask: u16, | ||
| 360 | dio2_mask: u16, | ||
| 361 | dio3_mask: u16, | ||
| 362 | ) -> Result<(), RadioError<BUS>> { | ||
| 363 | let mut buffer = [0x00u8; 8]; | ||
| 364 | |||
| 365 | buffer[0] = ((irq_mask >> 8) & 0x00FF) as u8; | ||
| 366 | buffer[1] = (irq_mask & 0x00FF) as u8; | ||
| 367 | buffer[2] = ((dio1_mask >> 8) & 0x00FF) as u8; | ||
| 368 | buffer[3] = (dio1_mask & 0x00FF) as u8; | ||
| 369 | buffer[4] = ((dio2_mask >> 8) & 0x00FF) as u8; | ||
| 370 | buffer[5] = (dio2_mask & 0x00FF) as u8; | ||
| 371 | buffer[6] = ((dio3_mask >> 8) & 0x00FF) as u8; | ||
| 372 | buffer[7] = (dio3_mask & 0x00FF) as u8; | ||
| 373 | self.brd_write_command(OpCode::CfgDIOIrq, &buffer).await?; | ||
| 374 | Ok(()) | ||
| 375 | } | ||
| 376 | |||
| 377 | // Return the current IRQ status | ||
| 378 | pub(super) async fn sub_get_irq_status(&mut self) -> Result<u16, RadioError<BUS>> { | ||
| 379 | let mut irq_status = [0x00u8, 0x00u8]; | ||
| 380 | self.brd_read_command(OpCode::GetIrqStatus, &mut irq_status).await?; | ||
| 381 | Ok(((irq_status[0] as u16) << 8) | (irq_status[1] as u16)) | ||
| 382 | } | ||
| 383 | |||
| 384 | // Indicate if DIO2 is used to control an RF Switch | ||
| 385 | pub(super) async fn sub_set_dio2_as_rf_switch_ctrl(&mut self, enable: bool) -> Result<(), RadioError<BUS>> { | ||
| 386 | self.brd_write_command(OpCode::SetRFSwitchMode, &[enable as u8]).await?; | ||
| 387 | Ok(()) | ||
| 388 | } | ||
| 389 | |||
| 390 | // Indicate if the radio main clock is supplied from a TCXO | ||
| 391 | // tcxo_voltage voltage used to control the TCXO on/off from DIO3 | ||
| 392 | // timeout duration given to the TCXO to go to 32MHz | ||
| 393 | pub(super) async fn sub_set_dio3_as_tcxo_ctrl( | ||
| 394 | &mut self, | ||
| 395 | tcxo_voltage: TcxoCtrlVoltage, | ||
| 396 | timeout: u32, | ||
| 397 | ) -> Result<(), RadioError<BUS>> { | ||
| 398 | let buffer = [ | ||
| 399 | tcxo_voltage.value() & 0x07, | ||
| 400 | Self::timeout_1(timeout), | ||
| 401 | Self::timeout_2(timeout), | ||
| 402 | Self::timeout_3(timeout), | ||
| 403 | ]; | ||
| 404 | self.brd_write_command(OpCode::SetTCXOMode, &buffer).await?; | ||
| 405 | |||
| 406 | Ok(()) | ||
| 407 | } | ||
| 408 | |||
| 409 | // Set the RF frequency (Hz) | ||
| 410 | pub(super) async fn sub_set_rf_frequency(&mut self, frequency: u32) -> Result<(), RadioError<BUS>> { | ||
| 411 | let mut buffer = [0x00u8; 4]; | ||
| 412 | |||
| 413 | if !self.image_calibrated { | ||
| 414 | self.sub_calibrate_image(frequency).await?; | ||
| 415 | self.image_calibrated = true; | ||
| 416 | } | ||
| 417 | |||
| 418 | let freq_in_pll_steps = Self::convert_freq_in_hz_to_pll_step(frequency); | ||
| 419 | |||
| 420 | buffer[0] = ((freq_in_pll_steps >> 24) & 0xFF) as u8; | ||
| 421 | buffer[1] = ((freq_in_pll_steps >> 16) & 0xFF) as u8; | ||
| 422 | buffer[2] = ((freq_in_pll_steps >> 8) & 0xFF) as u8; | ||
| 423 | buffer[3] = (freq_in_pll_steps & 0xFF) as u8; | ||
| 424 | self.brd_write_command(OpCode::SetRFFrequency, &buffer).await?; | ||
| 425 | Ok(()) | ||
| 426 | } | ||
| 427 | |||
| 428 | // 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. | ||
| 429 | pub(super) async fn sub_set_packet_type(&mut self, packet_type: PacketType) -> Result<(), RadioError<BUS>> { | ||
| 430 | self.packet_type = packet_type; | ||
| 431 | self.brd_write_command(OpCode::SetPacketType, &[packet_type.value()]) | ||
| 432 | .await?; | ||
| 433 | Ok(()) | ||
| 434 | } | ||
| 435 | |||
| 436 | // Get the current radio protocol (LoRa or GFSK) | ||
| 437 | pub(super) fn sub_get_packet_type(&mut self) -> PacketType { | ||
| 438 | self.packet_type | ||
| 439 | } | ||
| 440 | |||
| 441 | // Set the transmission parameters | ||
| 442 | // power RF output power [-18..13] dBm | ||
| 443 | // ramp_time transmission ramp up time | ||
| 444 | pub(super) async fn sub_set_tx_params( | ||
| 445 | &mut self, | ||
| 446 | mut power: i8, | ||
| 447 | ramp_time: RampTime, | ||
| 448 | ) -> Result<(), RadioError<BUS>> { | ||
| 449 | if self.brd_get_radio_type() == RadioType::SX1261 { | ||
| 450 | if power == 15 { | ||
| 451 | self.sub_set_pa_config(0x06, 0x00, 0x01, 0x01).await?; | ||
| 452 | } else { | ||
| 453 | self.sub_set_pa_config(0x04, 0x00, 0x01, 0x01).await?; | ||
| 454 | } | ||
| 455 | |||
| 456 | if power >= 14 { | ||
| 457 | power = 14; | ||
| 458 | } else if power < -17 { | ||
| 459 | power = -17; | ||
| 460 | } | ||
| 461 | } else { | ||
| 462 | // Provide better resistance of the SX1262 Tx to antenna mismatch (see DS_SX1261-2_V1.2 datasheet chapter 15.2) | ||
| 463 | let mut tx_clamp_cfg = [0x00u8]; | ||
| 464 | self.brd_read_registers(Register::TxClampCfg, &mut tx_clamp_cfg).await?; | ||
| 465 | tx_clamp_cfg[0] = tx_clamp_cfg[0] | (0x0F << 1); | ||
| 466 | self.brd_write_registers(Register::TxClampCfg, &tx_clamp_cfg).await?; | ||
| 467 | |||
| 468 | self.sub_set_pa_config(0x04, 0x07, 0x00, 0x01).await?; | ||
| 469 | |||
| 470 | if power > 22 { | ||
| 471 | power = 22; | ||
| 472 | } else if power < -9 { | ||
| 473 | power = -9; | ||
| 474 | } | ||
| 475 | } | ||
| 476 | |||
| 477 | // power conversion of negative number from i8 to u8 ??? | ||
| 478 | self.brd_write_command(OpCode::SetTxParams, &[power as u8, ramp_time.value()]) | ||
| 479 | .await?; | ||
| 480 | Ok(()) | ||
| 481 | } | ||
| 482 | |||
| 483 | // Set the modulation parameters | ||
| 484 | pub(super) async fn sub_set_modulation_params(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 485 | if self.modulation_params.is_some() { | ||
| 486 | let mut buffer = [0x00u8; 4]; | ||
| 487 | |||
| 488 | // Since this driver only supports LoRa, ensure the packet type is set accordingly | ||
| 489 | self.sub_set_packet_type(PacketType::LoRa).await?; | ||
| 490 | |||
| 491 | let modulation_params = self.modulation_params.unwrap(); | ||
| 492 | buffer[0] = modulation_params.spreading_factor.value(); | ||
| 493 | buffer[1] = modulation_params.bandwidth.value(); | ||
| 494 | buffer[2] = modulation_params.coding_rate.value(); | ||
| 495 | buffer[3] = modulation_params.low_data_rate_optimize; | ||
| 496 | |||
| 497 | self.brd_write_command(OpCode::SetModulationParams, &buffer).await?; | ||
| 498 | Ok(()) | ||
| 499 | } else { | ||
| 500 | Err(RadioError::ModulationParamsMissing) | ||
| 501 | } | ||
| 502 | } | ||
| 503 | |||
| 504 | // Set the packet parameters | ||
| 505 | pub(super) async fn sub_set_packet_params(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 506 | if self.packet_params.is_some() { | ||
| 507 | let mut buffer = [0x00u8; 6]; | ||
| 508 | |||
| 509 | // Since this driver only supports LoRa, ensure the packet type is set accordingly | ||
| 510 | self.sub_set_packet_type(PacketType::LoRa).await?; | ||
| 511 | |||
| 512 | let packet_params = self.packet_params.unwrap(); | ||
| 513 | buffer[0] = ((packet_params.preamble_length >> 8) & 0xFF) as u8; | ||
| 514 | buffer[1] = (packet_params.preamble_length & 0xFF) as u8; | ||
| 515 | buffer[2] = packet_params.implicit_header as u8; | ||
| 516 | buffer[3] = packet_params.payload_length; | ||
| 517 | buffer[4] = packet_params.crc_on as u8; | ||
| 518 | buffer[5] = packet_params.iq_inverted as u8; | ||
| 519 | |||
| 520 | self.brd_write_command(OpCode::SetPacketParams, &buffer).await?; | ||
| 521 | Ok(()) | ||
| 522 | } else { | ||
| 523 | Err(RadioError::PacketParamsMissing) | ||
| 524 | } | ||
| 525 | } | ||
| 526 | |||
| 527 | // Set the channel activity detection (CAD) parameters | ||
| 528 | // symbols number of symbols to use for CAD operations | ||
| 529 | // det_peak limit for detection of SNR peak used in the CAD | ||
| 530 | // det_min minimum symbol recognition for CAD | ||
| 531 | // exit_mode operation to be done at the end of CAD action | ||
| 532 | // timeout timeout value to abort the CAD activity | ||
| 533 | |||
| 534 | pub(super) async fn sub_set_cad_params( | ||
| 535 | &mut self, | ||
| 536 | symbols: CADSymbols, | ||
| 537 | det_peak: u8, | ||
| 538 | det_min: u8, | ||
| 539 | exit_mode: CADExitMode, | ||
| 540 | timeout: u32, | ||
| 541 | ) -> Result<(), RadioError<BUS>> { | ||
| 542 | let mut buffer = [0x00u8; 7]; | ||
| 543 | |||
| 544 | buffer[0] = symbols.value(); | ||
| 545 | buffer[1] = det_peak; | ||
| 546 | buffer[2] = det_min; | ||
| 547 | buffer[3] = exit_mode.value(); | ||
| 548 | buffer[4] = Self::timeout_1(timeout); | ||
| 549 | buffer[5] = Self::timeout_2(timeout); | ||
| 550 | buffer[6] = Self::timeout_3(timeout); | ||
| 551 | |||
| 552 | self.brd_write_command(OpCode::SetCADParams, &buffer).await?; | ||
| 553 | self.brd_set_operating_mode(RadioMode::ChannelActivityDetection); | ||
| 554 | Ok(()) | ||
| 555 | } | ||
| 556 | |||
| 557 | // Set the data buffer base address for transmission and reception | ||
| 558 | pub(super) async fn sub_set_buffer_base_address( | ||
| 559 | &mut self, | ||
| 560 | tx_base_address: u8, | ||
| 561 | rx_base_address: u8, | ||
| 562 | ) -> Result<(), RadioError<BUS>> { | ||
| 563 | self.brd_write_command(OpCode::SetBufferBaseAddress, &[tx_base_address, rx_base_address]) | ||
| 564 | .await?; | ||
| 565 | Ok(()) | ||
| 566 | } | ||
| 567 | |||
| 568 | // Get the current radio status | ||
| 569 | pub(super) async fn sub_get_status(&mut self) -> Result<RadioStatus, RadioError<BUS>> { | ||
| 570 | let status = self.brd_read_command(OpCode::GetStatus, &mut []).await?; | ||
| 571 | Ok(RadioStatus { | ||
| 572 | cmd_status: (status & (0x07 << 1)) >> 1, | ||
| 573 | chip_mode: (status & (0x07 << 4)) >> 4, | ||
| 574 | }) | ||
| 575 | } | ||
| 576 | |||
| 577 | // Get the instantaneous RSSI value for the last packet received | ||
| 578 | pub(super) async fn sub_get_rssi_inst(&mut self) -> Result<i8, RadioError<BUS>> { | ||
| 579 | let mut buffer = [0x00u8]; | ||
| 580 | self.brd_read_command(OpCode::GetRSSIInst, &mut buffer).await?; | ||
| 581 | let rssi: i8 = (-(buffer[0] as i8)) >> 1; // check this ??? | ||
| 582 | Ok(rssi) | ||
| 583 | } | ||
| 584 | |||
| 585 | // Get the last received packet buffer status | ||
| 586 | pub(super) async fn sub_get_rx_buffer_status(&mut self) -> Result<(u8, u8), RadioError<BUS>> { | ||
| 587 | if self.packet_params.is_some() { | ||
| 588 | let mut status = [0x00u8; 2]; | ||
| 589 | let mut payload_length_buffer = [0x00u8]; | ||
| 590 | |||
| 591 | self.brd_read_command(OpCode::GetRxBufferStatus, &mut status).await?; | ||
| 592 | if (self.sub_get_packet_type() == PacketType::LoRa) && self.packet_params.unwrap().implicit_header { | ||
| 593 | self.brd_read_registers(Register::PayloadLength, &mut payload_length_buffer) | ||
| 594 | .await?; | ||
| 595 | } else { | ||
| 596 | payload_length_buffer[0] = status[0]; | ||
| 597 | } | ||
| 598 | |||
| 599 | let payload_length = payload_length_buffer[0]; | ||
| 600 | let offset = status[1]; | ||
| 601 | |||
| 602 | Ok((payload_length, offset)) | ||
| 603 | } else { | ||
| 604 | Err(RadioError::PacketParamsMissing) | ||
| 605 | } | ||
| 606 | } | ||
| 607 | |||
| 608 | // Get the last received packet payload status | ||
| 609 | pub(super) async fn sub_get_packet_status(&mut self) -> Result<PacketStatus, RadioError<BUS>> { | ||
| 610 | let mut status = [0x00u8; 3]; | ||
| 611 | self.brd_read_command(OpCode::GetPacketStatus, &mut status).await?; | ||
| 612 | |||
| 613 | // check this ??? | ||
| 614 | let rssi = (-(status[0] as i8)) >> 1; | ||
| 615 | let snr = ((status[1] as i8) + 2) >> 2; | ||
| 616 | let signal_rssi = (-(status[2] as i8)) >> 1; | ||
| 617 | let freq_error = self.frequency_error; | ||
| 618 | |||
| 619 | Ok(PacketStatus { | ||
| 620 | rssi, | ||
| 621 | snr, | ||
| 622 | signal_rssi, | ||
| 623 | freq_error, | ||
| 624 | }) | ||
| 625 | } | ||
| 626 | |||
| 627 | // Get the possible system errors | ||
| 628 | pub(super) async fn sub_get_device_errors(&mut self) -> Result<RadioSystemError, RadioError<BUS>> { | ||
| 629 | let mut errors = [0x00u8; 2]; | ||
| 630 | self.brd_read_command(OpCode::GetErrors, &mut errors).await?; | ||
| 631 | |||
| 632 | Ok(RadioSystemError { | ||
| 633 | rc_64khz_calibration: (errors[1] & (1 << 0)) != 0, | ||
| 634 | rc_13mhz_calibration: (errors[1] & (1 << 1)) != 0, | ||
| 635 | pll_calibration: (errors[1] & (1 << 2)) != 0, | ||
| 636 | adc_calibration: (errors[1] & (1 << 3)) != 0, | ||
| 637 | image_calibration: (errors[1] & (1 << 4)) != 0, | ||
| 638 | xosc_start: (errors[1] & (1 << 5)) != 0, | ||
| 639 | pll_lock: (errors[1] & (1 << 6)) != 0, | ||
| 640 | pa_ramp: (errors[0] & (1 << 0)) != 0, | ||
| 641 | }) | ||
| 642 | } | ||
| 643 | |||
| 644 | // Clear all the errors in the device | ||
| 645 | pub(super) async fn sub_clear_device_errors(&mut self) -> Result<(), RadioError<BUS>> { | ||
| 646 | self.brd_write_command(OpCode::ClrErrors, &[0x00u8, 0x00u8]).await?; | ||
| 647 | Ok(()) | ||
| 648 | } | ||
| 649 | |||
| 650 | // Clear the IRQs | ||
| 651 | pub(super) async fn sub_clear_irq_status(&mut self, irq: u16) -> Result<(), RadioError<BUS>> { | ||
| 652 | let mut buffer = [0x00u8, 0x00u8]; | ||
| 653 | buffer[0] = ((irq >> 8) & 0xFF) as u8; | ||
| 654 | buffer[1] = (irq & 0xFF) as u8; | ||
| 655 | self.brd_write_command(OpCode::ClrIrqStatus, &buffer).await?; | ||
| 656 | Ok(()) | ||
| 657 | } | ||
| 658 | |||
| 659 | // Utility functions | ||
| 660 | |||
| 661 | fn timeout_1(timeout: u32) -> u8 { | ||
| 662 | ((timeout >> 16) & 0xFF) as u8 | ||
| 663 | } | ||
| 664 | fn timeout_2(timeout: u32) -> u8 { | ||
| 665 | ((timeout >> 8) & 0xFF) as u8 | ||
| 666 | } | ||
| 667 | fn timeout_3(timeout: u32) -> u8 { | ||
| 668 | (timeout & 0xFF) as u8 | ||
| 669 | } | ||
| 670 | |||
| 671 | // check this ??? | ||
| 672 | fn convert_u8_buffer_to_u32(buffer: &[u8; 4]) -> u32 { | ||
| 673 | let b0 = buffer[0] as u32; | ||
| 674 | let b1 = buffer[1] as u32; | ||
| 675 | let b2 = buffer[2] as u32; | ||
| 676 | let b3 = buffer[3] as u32; | ||
| 677 | (b0 << 24) | (b1 << 16) | (b2 << 8) | b3 | ||
| 678 | } | ||
| 679 | |||
| 680 | fn convert_freq_in_hz_to_pll_step(freq_in_hz: u32) -> u32 { | ||
| 681 | // Get integer and fractional parts of the frequency computed with a PLL step scaled value | ||
| 682 | let steps_int = freq_in_hz / SX126X_PLL_STEP_SCALED; | ||
| 683 | let steps_frac = freq_in_hz - (steps_int * SX126X_PLL_STEP_SCALED); | ||
| 684 | |||
| 685 | (steps_int << SX126X_PLL_STEP_SHIFT_AMOUNT) | ||
| 686 | + (((steps_frac << SX126X_PLL_STEP_SHIFT_AMOUNT) + (SX126X_PLL_STEP_SCALED >> 1)) / SX126X_PLL_STEP_SCALED) | ||
| 687 | } | ||
| 688 | } | ||
diff --git a/examples/nrf/Cargo.toml b/examples/nrf/Cargo.toml index a5d340c69..54df16ba0 100644 --- a/examples/nrf/Cargo.toml +++ b/examples/nrf/Cargo.toml | |||
| @@ -5,7 +5,8 @@ version = "0.1.0" | |||
| 5 | 5 | ||
| 6 | [features] | 6 | [features] |
| 7 | default = ["nightly"] | 7 | default = ["nightly"] |
| 8 | nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net"] | 8 | nightly = ["embassy-executor/nightly", "embassy-nrf/nightly", "embassy-net/nightly", "embassy-nrf/unstable-traits", "embassy-usb", "embedded-io/async", "embassy-net", |
| 9 | "embassy-lora", "lorawan-device", "lorawan"] | ||
| 9 | 10 | ||
| 10 | [dependencies] | 11 | [dependencies] |
| 11 | embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } | 12 | embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } |
| @@ -16,6 +17,10 @@ embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defm | |||
| 16 | embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"], optional = true } | 17 | embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"], optional = true } |
| 17 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } | 18 | embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"], optional = true } |
| 18 | embedded-io = "0.3.0" | 19 | embedded-io = "0.3.0" |
| 20 | embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["rak4631", "time", "defmt"], optional = true } | ||
| 21 | |||
| 22 | lorawan-device = { version = "0.8.0", default-features = false, features = ["async"], optional = true } | ||
| 23 | lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"], optional = true } | ||
| 19 | 24 | ||
| 20 | defmt = "0.3" | 25 | defmt = "0.3" |
| 21 | defmt-rtt = "0.3" | 26 | 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 000000000..46cb848b1 --- /dev/null +++ b/examples/nrf/src/bin/lora_p2p_report.rs | |||
| @@ -0,0 +1,84 @@ | |||
| 1 | //! This example runs on the RAK4631 WisBlock, which has an nRF52840 MCU and Semtech Sx126x radio. | ||
| 2 | #![no_std] | ||
| 3 | #![no_main] | ||
| 4 | #![macro_use] | ||
| 5 | #![allow(dead_code)] | ||
| 6 | #![feature(type_alias_impl_trait)] | ||
| 7 | |||
| 8 | use defmt::*; | ||
| 9 | use embassy_executor::Spawner; | ||
| 10 | use embassy_lora::sx126x::*; | ||
| 11 | use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pin as _, Pull}; | ||
| 12 | use embassy_nrf::{interrupt, spim}; | ||
| 13 | use embassy_time::{Duration, Timer}; | ||
| 14 | use lorawan_device::async_device::radio::{Bandwidth, CodingRate, PhyRxTx, RfConfig, SpreadingFactor}; | ||
| 15 | use {defmt_rtt as _, panic_probe as _}; | ||
| 16 | |||
| 17 | #[embassy_executor::main] | ||
| 18 | async fn main(_spawner: Spawner) { | ||
| 19 | let p = embassy_nrf::init(Default::default()); | ||
| 20 | let mut spi_config = spim::Config::default(); | ||
| 21 | spi_config.frequency = spim::Frequency::M1; // M16 ??? | ||
| 22 | |||
| 23 | let mut radio = { | ||
| 24 | let irq = interrupt::take!(SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); | ||
| 25 | let spim = spim::Spim::new(p.TWISPI1, irq, p.P1_11, p.P1_13, p.P1_12, spi_config); | ||
| 26 | |||
| 27 | let cs = Output::new(p.P1_10, Level::High, OutputDrive::Standard); | ||
| 28 | let reset = Output::new(p.P1_06, Level::High, OutputDrive::Standard); | ||
| 29 | let dio1 = Input::new(p.P1_15.degrade(), Pull::Down); | ||
| 30 | let busy = Input::new(p.P1_14.degrade(), Pull::Down); | ||
| 31 | let antenna_rx = Output::new(p.P1_05, Level::Low, OutputDrive::Standard); | ||
| 32 | let antenna_tx = Output::new(p.P1_07, Level::Low, OutputDrive::Standard); | ||
| 33 | |||
| 34 | match Sx126xRadio::new(spim, cs, reset, antenna_rx, antenna_tx, dio1, busy, false).await { | ||
| 35 | Ok(r) => r, | ||
| 36 | Err(err) => { | ||
| 37 | info!("Sx126xRadio error = {}", err); | ||
| 38 | return; | ||
| 39 | } | ||
| 40 | } | ||
| 41 | }; | ||
| 42 | |||
| 43 | let mut debug_indicator = Output::new(p.P1_03, Level::Low, OutputDrive::Standard); | ||
| 44 | let mut start_indicator = Output::new(p.P1_04, Level::Low, OutputDrive::Standard); | ||
| 45 | |||
| 46 | start_indicator.set_high(); | ||
| 47 | Timer::after(Duration::from_secs(5)).await; | ||
| 48 | start_indicator.set_low(); | ||
| 49 | |||
| 50 | match radio.lora.sleep().await { | ||
| 51 | Ok(()) => info!("Sleep successful"), | ||
| 52 | Err(err) => info!("Sleep unsuccessful = {}", err), | ||
| 53 | } | ||
| 54 | |||
| 55 | let rf_config = RfConfig { | ||
| 56 | frequency: 903900000, // channel in Hz | ||
| 57 | bandwidth: Bandwidth::_250KHz, | ||
| 58 | spreading_factor: SpreadingFactor::_10, | ||
| 59 | coding_rate: CodingRate::_4_8, | ||
| 60 | }; | ||
| 61 | |||
| 62 | let mut buffer = [00u8; 100]; | ||
| 63 | |||
| 64 | // P2P receive | ||
| 65 | match radio.rx(rf_config, &mut buffer).await { | ||
| 66 | Ok((buffer_len, rx_quality)) => info!( | ||
| 67 | "RX received = {:?} with length = {} rssi = {} snr = {}", | ||
| 68 | &buffer[0..buffer_len], | ||
| 69 | buffer_len, | ||
| 70 | rx_quality.rssi(), | ||
| 71 | rx_quality.snr() | ||
| 72 | ), | ||
| 73 | Err(err) => info!("RX error = {}", err), | ||
| 74 | } | ||
| 75 | |||
| 76 | match radio.lora.sleep().await { | ||
| 77 | Ok(()) => info!("Sleep successful"), | ||
| 78 | Err(err) => info!("Sleep unsuccessful = {}", err), | ||
| 79 | } | ||
| 80 | |||
| 81 | debug_indicator.set_high(); | ||
| 82 | Timer::after(Duration::from_secs(5)).await; | ||
| 83 | debug_indicator.set_low(); | ||
| 84 | } | ||
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 000000000..57aaea665 --- /dev/null +++ b/examples/nrf/src/bin/lora_p2p_sense.rs | |||
| @@ -0,0 +1,173 @@ | |||
| 1 | //! This example runs on the RAK4631 WisBlock, which has an nRF52840 MCU and Semtech Sx126x radio. | ||
| 2 | #![no_std] | ||
| 3 | #![no_main] | ||
| 4 | #![macro_use] | ||
| 5 | #![feature(type_alias_impl_trait)] | ||
| 6 | #![feature(alloc_error_handler)] | ||
| 7 | #![allow(incomplete_features)] | ||
| 8 | |||
| 9 | use defmt::*; | ||
| 10 | use embassy_executor::Spawner; | ||
| 11 | use embassy_lora::sx126x::*; | ||
| 12 | use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin as _, Pull}; | ||
| 13 | use embassy_nrf::temp::Temp; | ||
| 14 | use embassy_nrf::{interrupt, spim}; | ||
| 15 | use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; | ||
| 16 | use embassy_sync::pubsub::{PubSubChannel, Publisher}; | ||
| 17 | use embassy_time::{Duration, Timer}; | ||
| 18 | use lorawan_device::async_device::radio::{Bandwidth, CodingRate, PhyRxTx, RfConfig, SpreadingFactor, TxConfig}; | ||
| 19 | use {defmt_rtt as _, panic_probe as _, panic_probe as _}; | ||
| 20 | |||
| 21 | // Sensor packet constants | ||
| 22 | const TEMPERATURE_UID: u8 = 0x01; | ||
| 23 | const MOTION_UID: u8 = 0x02; | ||
| 24 | |||
| 25 | // Message bus: queue of 2, 1 subscriber (Lora P2P), 2 publishers (temperature, motion detection) | ||
| 26 | static MESSAGE_BUS: PubSubChannel<CriticalSectionRawMutex, Message, 2, 1, 2> = PubSubChannel::new(); | ||
| 27 | |||
| 28 | #[derive(Clone, defmt::Format)] | ||
| 29 | enum Message { | ||
| 30 | Temperature(i32), | ||
| 31 | MotionDetected, | ||
| 32 | } | ||
| 33 | |||
| 34 | #[embassy_executor::task] | ||
| 35 | async fn temperature_task( | ||
| 36 | mut temperature: Temp<'static>, | ||
| 37 | publisher: Publisher<'static, CriticalSectionRawMutex, Message, 2, 1, 2>, | ||
| 38 | ) { | ||
| 39 | Timer::after(Duration::from_secs(45)).await; // stabilize for 45 seconds | ||
| 40 | |||
| 41 | let mut temperature_reporting_threshhold = 10; | ||
| 42 | |||
| 43 | loop { | ||
| 44 | let value = temperature.read().await; | ||
| 45 | let mut temperature_val = value.to_num::<i32>(); | ||
| 46 | |||
| 47 | info!("Temperature: {}", temperature_val); // debug ??? | ||
| 48 | |||
| 49 | // only report every 2 degree Celsius drops, from 9 through 5, but starting at 3 always report | ||
| 50 | |||
| 51 | if temperature_val == 8 || temperature_val == 6 || temperature_val == 4 { | ||
| 52 | temperature_val += 1; | ||
| 53 | } | ||
| 54 | |||
| 55 | if temperature_reporting_threshhold > temperature_val | ||
| 56 | && (temperature_val == 9 || temperature_val == 7 || temperature_val == 5) | ||
| 57 | { | ||
| 58 | temperature_reporting_threshhold = temperature_val; | ||
| 59 | publisher.publish(Message::Temperature(temperature_val)).await; | ||
| 60 | } else if temperature_val <= 3 { | ||
| 61 | publisher.publish(Message::Temperature(temperature_val)).await; | ||
| 62 | } | ||
| 63 | |||
| 64 | Timer::after(Duration::from_secs(20 * 60)).await; | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | #[embassy_executor::task] | ||
| 69 | async fn motion_detection_task( | ||
| 70 | mut pir_pin: Input<'static, AnyPin>, | ||
| 71 | publisher: Publisher<'static, CriticalSectionRawMutex, Message, 2, 1, 2>, | ||
| 72 | ) { | ||
| 73 | Timer::after(Duration::from_secs(30)).await; // stabilize for 30 seconds | ||
| 74 | |||
| 75 | loop { | ||
| 76 | // wait for motion detection | ||
| 77 | pir_pin.wait_for_low().await; | ||
| 78 | publisher.publish(Message::MotionDetected).await; | ||
| 79 | |||
| 80 | // wait a minute before setting up for more motion detection | ||
| 81 | Timer::after(Duration::from_secs(60)).await; | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | #[embassy_executor::main] | ||
| 86 | async fn main(spawner: Spawner) { | ||
| 87 | let p = embassy_nrf::init(Default::default()); | ||
| 88 | // set up to funnel temperature and motion detection events to the Lora Tx task | ||
| 89 | let mut lora_tx_subscriber = unwrap!(MESSAGE_BUS.subscriber()); | ||
| 90 | let temperature_publisher = unwrap!(MESSAGE_BUS.publisher()); | ||
| 91 | let motion_detection_publisher = unwrap!(MESSAGE_BUS.publisher()); | ||
| 92 | |||
| 93 | let mut spi_config = spim::Config::default(); | ||
| 94 | spi_config.frequency = spim::Frequency::M1; // M16 ??? | ||
| 95 | |||
| 96 | let mut radio = { | ||
| 97 | let irq = interrupt::take!(SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); | ||
| 98 | let spim = spim::Spim::new(p.TWISPI1, irq, p.P1_11, p.P1_13, p.P1_12, spi_config); | ||
| 99 | |||
| 100 | let cs = Output::new(p.P1_10, Level::High, OutputDrive::Standard); | ||
| 101 | let reset = Output::new(p.P1_06, Level::High, OutputDrive::Standard); | ||
| 102 | let dio1 = Input::new(p.P1_15.degrade(), Pull::Down); | ||
| 103 | let busy = Input::new(p.P1_14.degrade(), Pull::Down); | ||
| 104 | let antenna_rx = Output::new(p.P1_05, Level::Low, OutputDrive::Standard); | ||
| 105 | let antenna_tx = Output::new(p.P1_07, Level::Low, OutputDrive::Standard); | ||
| 106 | |||
| 107 | match Sx126xRadio::new(spim, cs, reset, antenna_rx, antenna_tx, dio1, busy, false).await { | ||
| 108 | Ok(r) => r, | ||
| 109 | Err(err) => { | ||
| 110 | info!("Sx126xRadio error = {}", err); | ||
| 111 | return; | ||
| 112 | } | ||
| 113 | } | ||
| 114 | }; | ||
| 115 | |||
| 116 | // set up for the temperature task | ||
| 117 | let temperature_irq = interrupt::take!(TEMP); | ||
| 118 | let temperature = Temp::new(p.TEMP, temperature_irq); | ||
| 119 | |||
| 120 | // set the motion detection pin | ||
| 121 | let pir_pin = Input::new(p.P0_10.degrade(), Pull::Up); | ||
| 122 | |||
| 123 | let mut start_indicator = Output::new(p.P1_04, Level::Low, OutputDrive::Standard); | ||
| 124 | let mut debug_indicator = Output::new(p.P1_03, Level::Low, OutputDrive::Standard); | ||
| 125 | |||
| 126 | start_indicator.set_high(); | ||
| 127 | Timer::after(Duration::from_secs(5)).await; | ||
| 128 | start_indicator.set_low(); | ||
| 129 | |||
| 130 | match radio.lora.sleep().await { | ||
| 131 | Ok(()) => info!("Sleep successful"), | ||
| 132 | Err(err) => info!("Sleep unsuccessful = {}", err), | ||
| 133 | } | ||
| 134 | |||
| 135 | unwrap!(spawner.spawn(temperature_task(temperature, temperature_publisher))); | ||
| 136 | unwrap!(spawner.spawn(motion_detection_task(pir_pin, motion_detection_publisher))); | ||
| 137 | |||
| 138 | loop { | ||
| 139 | let message = lora_tx_subscriber.next_message_pure().await; | ||
| 140 | |||
| 141 | let tx_config = TxConfig { | ||
| 142 | // 11 byte maximum payload for Bandwidth 125 and SF 10 | ||
| 143 | pw: 20, // up to 20 // 5 ??? | ||
| 144 | rf: RfConfig { | ||
| 145 | frequency: 903900000, // channel in Hz, not MHz | ||
| 146 | bandwidth: Bandwidth::_250KHz, | ||
| 147 | spreading_factor: SpreadingFactor::_10, | ||
| 148 | coding_rate: CodingRate::_4_8, | ||
| 149 | }, | ||
| 150 | }; | ||
| 151 | |||
| 152 | let mut buffer = [TEMPERATURE_UID, 0xffu8, MOTION_UID, 0x00u8]; | ||
| 153 | match message { | ||
| 154 | Message::Temperature(temperature) => buffer[1] = temperature as u8, | ||
| 155 | Message::MotionDetected => buffer[3] = 0x01u8, | ||
| 156 | }; | ||
| 157 | |||
| 158 | // crypto for text ??? | ||
| 159 | match radio.tx(tx_config, &buffer).await { | ||
| 160 | Ok(ret_val) => info!("TX ret_val = {}", ret_val), | ||
| 161 | Err(err) => info!("TX error = {}", err), | ||
| 162 | } | ||
| 163 | |||
| 164 | match radio.lora.sleep().await { | ||
| 165 | Ok(()) => info!("Sleep successful"), | ||
| 166 | Err(err) => info!("Sleep unsuccessful = {}", err), | ||
| 167 | } | ||
| 168 | |||
| 169 | debug_indicator.set_high(); | ||
| 170 | Timer::after(Duration::from_secs(5)).await; | ||
| 171 | debug_indicator.set_low(); | ||
| 172 | } | ||
| 173 | } | ||
