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