aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--embassy-lora/Cargo.toml2
-rw-r--r--embassy-lora/src/lib.rs2
-rw-r--r--embassy-lora/src/sx126x/mod.rs153
-rw-r--r--embassy-lora/src/sx126x/sx126x_lora/board_specific.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}