aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2021-09-30 09:25:45 +0200
committerUlf Lilleengen <[email protected]>2021-09-30 10:32:24 +0200
commit16a47a0ad9eb5f40aa1f202d3fbaa03c7b77b836 (patch)
tree924181ab08c98318901ad840aa071614fb02644e
parentd9e2d176256ef4959c1ba60893ea361363d480d3 (diff)
Add embassy-lora crate
This crate contains async radio drivers for various lora drivers that work with embassy timers. The code is imported from Drogue Device ( https://github.com/drogue-iot/drogue-device) The radio drivers integrate with the async LoRaWAN MAC layer in the lorawan-device crate. Also added is an example for the STM32WL55 and for STM32L0 (requires the LoRa Discovery board) for LoRaWAN. Future work is to make the underlying radio drivers using fully async SPI when communicating with the peripheral.
-rw-r--r--embassy-lora/Cargo.toml33
-rw-r--r--embassy-lora/src/fmt.rs225
-rw-r--r--embassy-lora/src/lib.rs23
-rw-r--r--embassy-lora/src/stm32wl/mod.rs368
-rw-r--r--embassy-lora/src/sx127x/mod.rs201
-rw-r--r--embassy-lora/src/sx127x/sx127x_lora/mod.rs593
-rw-r--r--embassy-lora/src/sx127x/sx127x_lora/register.rs107
-rw-r--r--examples/stm32l0/Cargo.toml4
-rw-r--r--examples/stm32l0/src/bin/lorawan.rs104
-rw-r--r--examples/stm32wl55/Cargo.toml6
-rw-r--r--examples/stm32wl55/src/bin/lorawan.rs79
11 files changed, 1742 insertions, 1 deletions
diff --git a/embassy-lora/Cargo.toml b/embassy-lora/Cargo.toml
new file mode 100644
index 000000000..af0f25d5a
--- /dev/null
+++ b/embassy-lora/Cargo.toml
@@ -0,0 +1,33 @@
1[package]
2name = "embassy-lora"
3version = "0.1.0"
4authors = ["Ulf Lilleengen <[email protected]>"]
5edition = "2018"
6
7[lib]
8
9[features]
10sx127x = []
11stm32wl = ["embassy-stm32", "embassy-stm32/subghz"]
12time = []
13
14defmt-trace = []
15defmt-debug = []
16defmt-info = []
17defmt-warn = []
18defmt-error = []
19
20[dependencies]
21
22defmt = { version = "0.2.3", optional = true }
23log = { version = "0.4.14", optional = true }
24
25embassy = { version = "0.1.0", path = "../embassy", default-features = false }
26embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true }
27embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common", default-features = false }
28futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
29embedded-hal = { version = "0.2", features = ["unproven"] }
30bit_field = { version = "0.10" }
31
32lorawan-device = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["async"] }
33lorawan-encoding = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false } \ No newline at end of file
diff --git a/embassy-lora/src/fmt.rs b/embassy-lora/src/fmt.rs
new file mode 100644
index 000000000..066970813
--- /dev/null
+++ b/embassy-lora/src/fmt.rs
@@ -0,0 +1,225 @@
1#![macro_use]
2#![allow(unused_macros)]
3
4#[cfg(all(feature = "defmt", feature = "log"))]
5compile_error!("You may not enable both `defmt` and `log` features.");
6
7macro_rules! assert {
8 ($($x:tt)*) => {
9 {
10 #[cfg(not(feature = "defmt"))]
11 ::core::assert!($($x)*);
12 #[cfg(feature = "defmt")]
13 ::defmt::assert!($($x)*);
14 }
15 };
16}
17
18macro_rules! assert_eq {
19 ($($x:tt)*) => {
20 {
21 #[cfg(not(feature = "defmt"))]
22 ::core::assert_eq!($($x)*);
23 #[cfg(feature = "defmt")]
24 ::defmt::assert_eq!($($x)*);
25 }
26 };
27}
28
29macro_rules! assert_ne {
30 ($($x:tt)*) => {
31 {
32 #[cfg(not(feature = "defmt"))]
33 ::core::assert_ne!($($x)*);
34 #[cfg(feature = "defmt")]
35 ::defmt::assert_ne!($($x)*);
36 }
37 };
38}
39
40macro_rules! debug_assert {
41 ($($x:tt)*) => {
42 {
43 #[cfg(not(feature = "defmt"))]
44 ::core::debug_assert!($($x)*);
45 #[cfg(feature = "defmt")]
46 ::defmt::debug_assert!($($x)*);
47 }
48 };
49}
50
51macro_rules! debug_assert_eq {
52 ($($x:tt)*) => {
53 {
54 #[cfg(not(feature = "defmt"))]
55 ::core::debug_assert_eq!($($x)*);
56 #[cfg(feature = "defmt")]
57 ::defmt::debug_assert_eq!($($x)*);
58 }
59 };
60}
61
62macro_rules! debug_assert_ne {
63 ($($x:tt)*) => {
64 {
65 #[cfg(not(feature = "defmt"))]
66 ::core::debug_assert_ne!($($x)*);
67 #[cfg(feature = "defmt")]
68 ::defmt::debug_assert_ne!($($x)*);
69 }
70 };
71}
72
73macro_rules! todo {
74 ($($x:tt)*) => {
75 {
76 #[cfg(not(feature = "defmt"))]
77 ::core::todo!($($x)*);
78 #[cfg(feature = "defmt")]
79 ::defmt::todo!($($x)*);
80 }
81 };
82}
83
84macro_rules! unreachable {
85 ($($x:tt)*) => {
86 {
87 #[cfg(not(feature = "defmt"))]
88 ::core::unreachable!($($x)*);
89 #[cfg(feature = "defmt")]
90 ::defmt::unreachable!($($x)*);
91 }
92 };
93}
94
95macro_rules! panic {
96 ($($x:tt)*) => {
97 {
98 #[cfg(not(feature = "defmt"))]
99 ::core::panic!($($x)*);
100 #[cfg(feature = "defmt")]
101 ::defmt::panic!($($x)*);
102 }
103 };
104}
105
106macro_rules! trace {
107 ($s:literal $(, $x:expr)* $(,)?) => {
108 {
109 #[cfg(feature = "log")]
110 ::log::trace!($s $(, $x)*);
111 #[cfg(feature = "defmt")]
112 ::defmt::trace!($s $(, $x)*);
113 #[cfg(not(any(feature = "log", feature="defmt")))]
114 let _ = ($( & $x ),*);
115 }
116 };
117}
118
119macro_rules! debug {
120 ($s:literal $(, $x:expr)* $(,)?) => {
121 {
122 #[cfg(feature = "log")]
123 ::log::debug!($s $(, $x)*);
124 #[cfg(feature = "defmt")]
125 ::defmt::debug!($s $(, $x)*);
126 #[cfg(not(any(feature = "log", feature="defmt")))]
127 let _ = ($( & $x ),*);
128 }
129 };
130}
131
132macro_rules! info {
133 ($s:literal $(, $x:expr)* $(,)?) => {
134 {
135 #[cfg(feature = "log")]
136 ::log::info!($s $(, $x)*);
137 #[cfg(feature = "defmt")]
138 ::defmt::info!($s $(, $x)*);
139 #[cfg(not(any(feature = "log", feature="defmt")))]
140 let _ = ($( & $x ),*);
141 }
142 };
143}
144
145macro_rules! warn {
146 ($s:literal $(, $x:expr)* $(,)?) => {
147 {
148 #[cfg(feature = "log")]
149 ::log::warn!($s $(, $x)*);
150 #[cfg(feature = "defmt")]
151 ::defmt::warn!($s $(, $x)*);
152 #[cfg(not(any(feature = "log", feature="defmt")))]
153 let _ = ($( & $x ),*);
154 }
155 };
156}
157
158macro_rules! error {
159 ($s:literal $(, $x:expr)* $(,)?) => {
160 {
161 #[cfg(feature = "log")]
162 ::log::error!($s $(, $x)*);
163 #[cfg(feature = "defmt")]
164 ::defmt::error!($s $(, $x)*);
165 #[cfg(not(any(feature = "log", feature="defmt")))]
166 let _ = ($( & $x ),*);
167 }
168 };
169}
170
171#[cfg(feature = "defmt")]
172macro_rules! unwrap {
173 ($($x:tt)*) => {
174 ::defmt::unwrap!($($x)*)
175 };
176}
177
178#[cfg(not(feature = "defmt"))]
179macro_rules! unwrap {
180 ($arg:expr) => {
181 match $crate::fmt::Try::into_result($arg) {
182 ::core::result::Result::Ok(t) => t,
183 ::core::result::Result::Err(e) => {
184 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
185 }
186 }
187 };
188 ($arg:expr, $($msg:expr),+ $(,)? ) => {
189 match $crate::fmt::Try::into_result($arg) {
190 ::core::result::Result::Ok(t) => t,
191 ::core::result::Result::Err(e) => {
192 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
193 }
194 }
195 }
196}
197
198#[derive(Debug, Copy, Clone, Eq, PartialEq)]
199pub struct NoneError;
200
201pub trait Try {
202 type Ok;
203 type Error;
204 fn into_result(self) -> Result<Self::Ok, Self::Error>;
205}
206
207impl<T> Try for Option<T> {
208 type Ok = T;
209 type Error = NoneError;
210
211 #[inline]
212 fn into_result(self) -> Result<T, NoneError> {
213 self.ok_or(NoneError)
214 }
215}
216
217impl<T, E> Try for Result<T, E> {
218 type Ok = T;
219 type Error = E;
220
221 #[inline]
222 fn into_result(self) -> Self {
223 self
224 }
225}
diff --git a/embassy-lora/src/lib.rs b/embassy-lora/src/lib.rs
new file mode 100644
index 000000000..b2da22090
--- /dev/null
+++ b/embassy-lora/src/lib.rs
@@ -0,0 +1,23 @@
1#![no_std]
2#![feature(type_alias_impl_trait)]
3#![feature(generic_associated_types)]
4//! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device
5//! crate's async LoRaWAN MAC implementation.
6
7pub(crate) mod fmt;
8
9#[cfg(feature = "stm32wl")]
10pub mod stm32wl;
11#[cfg(feature = "sx127x")]
12pub mod sx127x;
13
14/// A convenience timer to use with the LoRaWAN crate
15pub struct LoraTimer;
16
17#[cfg(feature = "time")]
18impl lorawan_device::async_device::radio::Timer for LoraTimer {
19 type DelayFuture<'m> = impl core::future::Future<Output = ()> + 'm;
20 fn delay_ms<'m>(&'m mut self, millis: u64) -> Self::DelayFuture<'m> {
21 embassy::time::Timer::after(embassy::time::Duration::from_millis(millis))
22 }
23}
diff --git a/embassy-lora/src/stm32wl/mod.rs b/embassy-lora/src/stm32wl/mod.rs
new file mode 100644
index 000000000..8cac46f7a
--- /dev/null
+++ b/embassy-lora/src/stm32wl/mod.rs
@@ -0,0 +1,368 @@
1//! A radio driver integration for the radio found on STM32WL family devices.
2use core::future::Future;
3use core::mem::MaybeUninit;
4use embassy::channel::signal::Signal;
5use embassy::interrupt::InterruptExt;
6use embassy::util::Unborrow;
7use embassy_hal_common::unborrow;
8use embassy_stm32::{
9 dma::NoDma,
10 gpio::{AnyPin, Output},
11 interrupt::SUBGHZ_RADIO,
12 subghz::{
13 CalibrateImage, CfgIrq, CodingRate, HeaderType, Irq, LoRaBandwidth, LoRaModParams,
14 LoRaPacketParams, LoRaSyncWord, Ocp, PaConfig, PaSel, PacketType, RampTime, RegMode,
15 RfFreq, SpreadingFactor as SF, StandbyClk, Status, SubGhz, TcxoMode, TcxoTrim, Timeout,
16 TxParams,
17 },
18};
19use embedded_hal::digital::v2::OutputPin;
20use lorawan_device::async_device::{
21 radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig},
22 Timings,
23};
24
25#[derive(Debug, Copy, Clone)]
26#[cfg_attr(feature = "defmt", derive(defmt::Format))]
27pub enum State {
28 Idle,
29 Txing,
30 Rxing,
31}
32
33#[derive(Debug, Copy, Clone)]
34#[cfg_attr(feature = "defmt", derive(defmt::Format))]
35pub struct RadioError;
36
37static IRQ: Signal<(Status, u16)> = Signal::new();
38
39struct StateInner<'a> {
40 radio: SubGhz<'a, NoDma, NoDma>,
41 switch: RadioSwitch<'a>,
42}
43
44/// External state storage for the radio state
45pub struct SubGhzState<'a>(MaybeUninit<StateInner<'a>>);
46impl<'a> SubGhzState<'a> {
47 pub const fn new() -> Self {
48 Self(MaybeUninit::uninit())
49 }
50}
51
52/// The radio peripheral keeping the radio state and owning the radio IRQ.
53pub struct SubGhzRadio<'a> {
54 state: *mut StateInner<'a>,
55 _irq: SUBGHZ_RADIO,
56}
57
58impl<'a> SubGhzRadio<'a> {
59 /// Create a new instance of a SubGhz radio for LoRaWAN.
60 ///
61 /// # Safety
62 /// Do not leak self or futures
63 pub unsafe fn new(
64 state: &'a mut SubGhzState<'a>,
65 radio: SubGhz<'a, NoDma, NoDma>,
66 switch: RadioSwitch<'a>,
67 irq: impl Unborrow<Target = SUBGHZ_RADIO>,
68 ) -> Self {
69 unborrow!(irq);
70
71 let mut inner = StateInner { radio, switch };
72 inner.radio.reset();
73
74 let state_ptr = state.0.as_mut_ptr();
75 state_ptr.write(inner);
76
77 irq.disable();
78 irq.set_handler(|p| {
79 // This is safe because we only get interrupts when configured for, so
80 // the radio will be awaiting on the signal at this point. If not, the ISR will
81 // anyway only adjust the state in the IRQ signal state.
82 let state = unsafe { &mut *(p as *mut StateInner<'a>) };
83 state.on_interrupt();
84 });
85 irq.set_handler_context(state_ptr as *mut ());
86 irq.enable();
87
88 Self {
89 state: state_ptr,
90 _irq: irq,
91 }
92 }
93}
94
95impl<'a> StateInner<'a> {
96 /// Configure radio settings in preparation for TX or RX
97 pub(crate) fn configure(&mut self) -> Result<(), RadioError> {
98 trace!("Configuring STM32WL SUBGHZ radio");
99 self.radio.set_standby(StandbyClk::Rc)?;
100 let tcxo_mode = TcxoMode::new()
101 .set_txco_trim(TcxoTrim::Volts1pt7)
102 .set_timeout(Timeout::from_duration_sat(
103 core::time::Duration::from_millis(40),
104 ));
105
106 self.radio.set_tcxo_mode(&tcxo_mode)?;
107 self.radio.set_regulator_mode(RegMode::Ldo)?;
108
109 self.radio.calibrate_image(CalibrateImage::ISM_863_870)?;
110
111 self.radio.set_buffer_base_address(0, 0)?;
112
113 self.radio.set_pa_config(
114 &PaConfig::new()
115 .set_pa_duty_cycle(0x1)
116 .set_hp_max(0x0)
117 .set_pa(PaSel::Lp),
118 )?;
119
120 self.radio.set_pa_ocp(Ocp::Max140m)?;
121
122 // let tx_params = TxParams::LP_14.set_ramp_time(RampTime::Micros40);
123 self.radio.set_tx_params(
124 &TxParams::new()
125 .set_ramp_time(RampTime::Micros40)
126 .set_power(0x0A),
127 )?;
128
129 self.radio.set_packet_type(PacketType::LoRa)?;
130 self.radio.set_lora_sync_word(LoRaSyncWord::Public)?;
131 trace!("Done initializing STM32WL SUBGHZ radio");
132 Ok(())
133 }
134
135 /// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form
136 /// the upcoming RX window start.
137 async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> {
138 //trace!("TX Request: {}", config);
139 trace!("TX START");
140 self.switch.set_tx_lp();
141 self.configure()?;
142
143 self.radio
144 .set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?;
145
146 let mod_params = LoRaModParams::new()
147 .set_sf(convert_spreading_factor(config.rf.spreading_factor))
148 .set_bw(convert_bandwidth(config.rf.bandwidth))
149 .set_cr(CodingRate::Cr45)
150 .set_ldro_en(true);
151 self.radio.set_lora_mod_params(&mod_params)?;
152
153 let packet_params = LoRaPacketParams::new()
154 .set_preamble_len(8)
155 .set_header_type(HeaderType::Variable)
156 .set_payload_len(buf.len() as u8)
157 .set_crc_en(true)
158 .set_invert_iq(false);
159
160 self.radio.set_lora_packet_params(&packet_params)?;
161
162 let irq_cfg = CfgIrq::new()
163 .irq_enable_all(Irq::TxDone)
164 .irq_enable_all(Irq::RxDone)
165 .irq_enable_all(Irq::Timeout);
166 self.radio.set_irq_cfg(&irq_cfg)?;
167
168 self.radio.set_buffer_base_address(0, 0)?;
169 self.radio.write_buffer(0, buf)?;
170
171 self.radio.set_tx(Timeout::DISABLED)?;
172
173 loop {
174 let (_status, irq_status) = IRQ.wait().await;
175 IRQ.reset();
176
177 if irq_status & Irq::TxDone.mask() != 0 {
178 let stats = self.radio.lora_stats()?;
179 let (status, error_mask) = self.radio.op_error()?;
180 trace!(
181 "TX done. Stats: {:?}. OP error: {:?}, mask {:?}",
182 stats,
183 status,
184 error_mask
185 );
186
187 return Ok(0);
188 } else if irq_status & Irq::Timeout.mask() != 0 {
189 trace!("TX timeout");
190 return Err(RadioError);
191 }
192 }
193 }
194
195 /// Perform a radio receive operation with the radio config and receive buffer. The receive buffer must
196 /// be able to hold a single LoRaWAN packet.
197 async fn do_rx(
198 &mut self,
199 config: RfConfig,
200 buf: &mut [u8],
201 ) -> Result<(usize, RxQuality), RadioError> {
202 assert!(buf.len() >= 255);
203 trace!("RX START");
204 // trace!("Starting RX: {}", config);
205 self.switch.set_rx();
206 self.configure()?;
207
208 self.radio
209 .set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
210
211 let mod_params = LoRaModParams::new()
212 .set_sf(convert_spreading_factor(config.spreading_factor))
213 .set_bw(convert_bandwidth(config.bandwidth))
214 .set_cr(CodingRate::Cr45)
215 .set_ldro_en(true);
216 self.radio.set_lora_mod_params(&mod_params)?;
217
218 let packet_params = LoRaPacketParams::new()
219 .set_preamble_len(8)
220 .set_header_type(HeaderType::Variable)
221 .set_payload_len(0xFF)
222 .set_crc_en(true)
223 .set_invert_iq(true);
224 self.radio.set_lora_packet_params(&packet_params)?;
225
226 let irq_cfg = CfgIrq::new()
227 .irq_enable_all(Irq::RxDone)
228 .irq_enable_all(Irq::PreambleDetected)
229 .irq_enable_all(Irq::HeaderErr)
230 .irq_enable_all(Irq::Timeout)
231 .irq_enable_all(Irq::Err);
232 self.radio.set_irq_cfg(&irq_cfg)?;
233
234 self.radio.set_rx(Timeout::DISABLED)?;
235 trace!("RX started");
236
237 loop {
238 let (status, irq_status) = IRQ.wait().await;
239 IRQ.reset();
240 trace!("RX IRQ {:?}, {:?}", status, irq_status);
241 if irq_status & Irq::RxDone.mask() != 0 {
242 let (status, len, ptr) = self.radio.rx_buffer_status()?;
243
244 let packet_status = self.radio.lora_packet_status()?;
245 let rssi = packet_status.rssi_pkt().to_integer();
246 let snr = packet_status.snr_pkt().to_integer();
247 trace!(
248 "RX done. Received {} bytes. RX status: {:?}. Pkt status: {:?}",
249 len,
250 status.cmd(),
251 packet_status,
252 );
253 self.radio.read_buffer(ptr, &mut buf[..len as usize])?;
254 self.radio.set_standby(StandbyClk::Rc)?;
255 return Ok((len as usize, RxQuality::new(rssi, snr as i8)));
256 } else if irq_status & (Irq::Timeout.mask() | Irq::TxDone.mask()) != 0 {
257 return Err(RadioError);
258 }
259 }
260 }
261
262 /// Read interrupt status and store in global signal
263 fn on_interrupt(&mut self) {
264 let (status, irq_status) = self.radio.irq_status().expect("error getting irq status");
265 self.radio
266 .clear_irq_status(irq_status)
267 .expect("error clearing irq status");
268 if irq_status & Irq::PreambleDetected.mask() != 0 {
269 trace!("Preamble detected, ignoring");
270 } else {
271 IRQ.signal((status, irq_status));
272 }
273 }
274}
275
276impl PhyRxTx for SubGhzRadio<'static> {
277 type PhyError = RadioError;
278
279 type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm;
280 fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
281 async move {
282 let inner = unsafe { &mut *self.state };
283 inner.do_tx(config, buf).await
284 }
285 }
286
287 type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm;
288 fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
289 async move {
290 let inner = unsafe { &mut *self.state };
291 inner.do_rx(config, buf).await
292 }
293 }
294}
295
296impl<'a> From<embassy_stm32::spi::Error> for RadioError {
297 fn from(_: embassy_stm32::spi::Error) -> Self {
298 RadioError
299 }
300}
301
302impl<'a> Timings for SubGhzRadio<'a> {
303 fn get_rx_window_offset_ms(&self) -> i32 {
304 -200
305 }
306 fn get_rx_window_duration_ms(&self) -> u32 {
307 800
308 }
309}
310
311/// Represents the radio switch found on STM32WL based boards, used to control the radio for transmission or reception.
312pub struct RadioSwitch<'a> {
313 ctrl1: Output<'a, AnyPin>,
314 ctrl2: Output<'a, AnyPin>,
315 ctrl3: Output<'a, AnyPin>,
316}
317
318impl<'a> RadioSwitch<'a> {
319 pub fn new(
320 ctrl1: Output<'a, AnyPin>,
321 ctrl2: Output<'a, AnyPin>,
322 ctrl3: Output<'a, AnyPin>,
323 ) -> Self {
324 Self {
325 ctrl1,
326 ctrl2,
327 ctrl3,
328 }
329 }
330
331 pub(crate) fn set_rx(&mut self) {
332 self.ctrl1.set_high().unwrap();
333 self.ctrl2.set_low().unwrap();
334 self.ctrl3.set_high().unwrap();
335 }
336
337 pub(crate) fn set_tx_lp(&mut self) {
338 self.ctrl1.set_high().unwrap();
339 self.ctrl2.set_high().unwrap();
340 self.ctrl3.set_high().unwrap();
341 }
342
343 #[allow(dead_code)]
344 pub(crate) fn set_tx_hp(&mut self) {
345 self.ctrl2.set_high().unwrap();
346 self.ctrl1.set_low().unwrap();
347 self.ctrl3.set_high().unwrap();
348 }
349}
350
351fn convert_spreading_factor(sf: SpreadingFactor) -> SF {
352 match sf {
353 SpreadingFactor::_7 => SF::Sf7,
354 SpreadingFactor::_8 => SF::Sf8,
355 SpreadingFactor::_9 => SF::Sf9,
356 SpreadingFactor::_10 => SF::Sf10,
357 SpreadingFactor::_11 => SF::Sf11,
358 SpreadingFactor::_12 => SF::Sf12,
359 }
360}
361
362fn convert_bandwidth(bw: Bandwidth) -> LoRaBandwidth {
363 match bw {
364 Bandwidth::_125KHz => LoRaBandwidth::Bw125,
365 Bandwidth::_250KHz => LoRaBandwidth::Bw250,
366 Bandwidth::_500KHz => LoRaBandwidth::Bw500,
367 }
368}
diff --git a/embassy-lora/src/sx127x/mod.rs b/embassy-lora/src/sx127x/mod.rs
new file mode 100644
index 000000000..a9736b85f
--- /dev/null
+++ b/embassy-lora/src/sx127x/mod.rs
@@ -0,0 +1,201 @@
1use core::future::Future;
2use embassy::traits::gpio::WaitForRisingEdge;
3use embedded_hal::blocking::delay::DelayMs;
4use embedded_hal::blocking::spi::{Transfer, Write};
5use embedded_hal::digital::v2::OutputPin;
6use lorawan_device::async_device::{
7 radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig},
8 Timings,
9};
10
11mod sx127x_lora;
12use sx127x_lora::{Error as RadioError, LoRa, RadioMode, IRQ};
13
14/// Trait representing a radio switch for boards using the Sx127x radio. One some
15/// boards, this will be a dummy implementation that does nothing.
16pub trait RadioSwitch {
17 fn set_tx(&mut self);
18 fn set_rx(&mut self);
19}
20
21/// Semtech Sx127x radio peripheral
22pub struct Sx127xRadio<SPI, CS, RESET, E, I, RFS>
23where
24 SPI: Transfer<u8, Error = E> + Write<u8, Error = E> + 'static,
25 E: 'static,
26 CS: OutputPin + 'static,
27 RESET: OutputPin + 'static,
28 I: WaitForRisingEdge + 'static,
29 RFS: RadioSwitch + 'static,
30{
31 radio: LoRa<SPI, CS, RESET>,
32 rfs: RFS,
33 irq: I,
34}
35
36#[derive(Debug, Copy, Clone)]
37#[cfg_attr(feature = "defmt", derive(defmt::Format))]
38pub enum State {
39 Idle,
40 Txing,
41 Rxing,
42}
43
44impl<SPI, CS, RESET, E, I, RFS> Sx127xRadio<SPI, CS, RESET, E, I, RFS>
45where
46 SPI: Transfer<u8, Error = E> + Write<u8, Error = E> + 'static,
47 CS: OutputPin + 'static,
48 RESET: OutputPin + 'static,
49 I: WaitForRisingEdge + 'static,
50 RFS: RadioSwitch + 'static,
51{
52 pub fn new<D: DelayMs<u32>>(
53 spi: SPI,
54 cs: CS,
55 reset: RESET,
56 irq: I,
57 rfs: RFS,
58 d: &mut D,
59 ) -> Result<Self, RadioError<E, CS::Error, RESET::Error>> {
60 let mut radio = LoRa::new(spi, cs, reset);
61 radio.reset(d)?;
62 Ok(Self { radio, irq, rfs })
63 }
64}
65
66impl<SPI, CS, RESET, E, I, RFS> Timings for Sx127xRadio<SPI, CS, RESET, E, I, RFS>
67where
68 SPI: Transfer<u8, Error = E> + Write<u8, Error = E> + 'static,
69 CS: OutputPin + 'static,
70 RESET: OutputPin + 'static,
71 I: WaitForRisingEdge + 'static,
72 RFS: RadioSwitch + 'static,
73{
74 fn get_rx_window_offset_ms(&self) -> i32 {
75 -500
76 }
77 fn get_rx_window_duration_ms(&self) -> u32 {
78 800
79 }
80}
81
82impl<SPI, CS, RESET, E, I, RFS> PhyRxTx for Sx127xRadio<SPI, CS, RESET, E, I, RFS>
83where
84 SPI: Transfer<u8, Error = E> + Write<u8, Error = E> + 'static,
85 CS: OutputPin + 'static,
86 E: 'static,
87 RESET: OutputPin + 'static,
88 I: WaitForRisingEdge + 'static,
89 RFS: RadioSwitch + 'static,
90{
91 type PhyError = Sx127xError;
92
93 #[rustfmt::skip]
94 type TxFuture<'m> where SPI: 'm, CS: 'm, RESET: 'm, E: 'm, I: 'm, RFS: 'm = impl Future<Output = Result<u32, Self::PhyError>> + 'm;
95
96 fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
97 trace!("TX START");
98 async move {
99 self.rfs.set_tx();
100 self.radio.set_tx_power(14, 0)?;
101 self.radio.set_frequency(config.rf.frequency)?;
102 // TODO: Modify radio to support other coding rates
103 self.radio.set_coding_rate_4(5)?;
104 self.radio
105 .set_signal_bandwidth(bandwidth_to_i64(config.rf.bandwidth))?;
106 self.radio
107 .set_spreading_factor(spreading_factor_to_u8(config.rf.spreading_factor))?;
108
109 self.radio.set_preamble_length(8)?;
110 self.radio.set_lora_pa_ramp()?;
111 self.radio.set_lora_sync_word()?;
112 self.radio.set_invert_iq(false)?;
113 self.radio.set_crc(true)?;
114
115 self.radio.set_dio0_tx_done()?;
116 self.radio.transmit_payload(buf)?;
117
118 loop {
119 self.irq.wait_for_rising_edge().await;
120 self.radio.set_mode(RadioMode::Stdby).ok().unwrap();
121 let irq = self.radio.clear_irq().ok().unwrap();
122 if (irq & IRQ::IrqTxDoneMask.addr()) != 0 {
123 trace!("TX DONE");
124 return Ok(0);
125 }
126 }
127 }
128 }
129
130 #[rustfmt::skip]
131 type RxFuture<'m> where SPI: 'm, CS: 'm, RESET: 'm, E: 'm, I: 'm, RFS: 'm = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm;
132
133 fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
134 trace!("RX START");
135 async move {
136 self.rfs.set_rx();
137 self.radio.reset_payload_length()?;
138 self.radio.set_frequency(config.frequency)?;
139 // TODO: Modify radio to support other coding rates
140 self.radio.set_coding_rate_4(5)?;
141 self.radio
142 .set_signal_bandwidth(bandwidth_to_i64(config.bandwidth))?;
143 self.radio
144 .set_spreading_factor(spreading_factor_to_u8(config.spreading_factor))?;
145
146 self.radio.set_preamble_length(8)?;
147 self.radio.set_lora_sync_word()?;
148 self.radio.set_invert_iq(true)?;
149 self.radio.set_crc(true)?;
150
151 self.radio.set_dio0_rx_done()?;
152 self.radio.set_mode(RadioMode::RxContinuous)?;
153
154 loop {
155 self.irq.wait_for_rising_edge().await;
156 self.radio.set_mode(RadioMode::Stdby).ok().unwrap();
157 let irq = self.radio.clear_irq().ok().unwrap();
158 if (irq & IRQ::IrqRxDoneMask.addr()) != 0 {
159 let rssi = self.radio.get_packet_rssi().unwrap_or(0) as i16;
160 let snr = self.radio.get_packet_snr().unwrap_or(0.0) as i8;
161 let response = if let Ok(size) = self.radio.read_packet_size() {
162 self.radio.read_packet(buf)?;
163 Ok((size, RxQuality::new(rssi, snr)))
164 } else {
165 Ok((0, RxQuality::new(rssi, snr)))
166 };
167 trace!("RX DONE");
168 return response;
169 }
170 }
171 }
172 }
173}
174
175#[cfg_attr(feature = "defmt", derive(defmt::Format))]
176pub struct Sx127xError;
177
178impl<A, B, C> From<sx127x_lora::Error<A, B, C>> for Sx127xError {
179 fn from(_: sx127x_lora::Error<A, B, C>) -> Self {
180 Sx127xError
181 }
182}
183
184fn spreading_factor_to_u8(sf: SpreadingFactor) -> u8 {
185 match sf {
186 SpreadingFactor::_7 => 7,
187 SpreadingFactor::_8 => 8,
188 SpreadingFactor::_9 => 9,
189 SpreadingFactor::_10 => 10,
190 SpreadingFactor::_11 => 11,
191 SpreadingFactor::_12 => 12,
192 }
193}
194
195fn bandwidth_to_i64(bw: Bandwidth) -> i64 {
196 match bw {
197 Bandwidth::_125KHz => 125_000,
198 Bandwidth::_250KHz => 250_000,
199 Bandwidth::_500KHz => 500_000,
200 }
201}
diff --git a/embassy-lora/src/sx127x/sx127x_lora/mod.rs b/embassy-lora/src/sx127x/sx127x_lora/mod.rs
new file mode 100644
index 000000000..c541815fe
--- /dev/null
+++ b/embassy-lora/src/sx127x/sx127x_lora/mod.rs
@@ -0,0 +1,593 @@
1// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0
2// license
3//
4// Modifications made to make the driver work with the rust-lorawan link layer.
5
6#![allow(dead_code)]
7
8use bit_field::BitField;
9use embedded_hal::blocking::{
10 delay::DelayMs,
11 spi::{Transfer, Write},
12};
13use embedded_hal::digital::v2::OutputPin;
14use embedded_hal::spi::{Mode, Phase, Polarity};
15
16mod register;
17use self::register::PaConfig;
18use self::register::Register;
19pub use self::register::IRQ;
20
21/// Provides the necessary SPI mode configuration for the radio
22pub const MODE: Mode = Mode {
23 phase: Phase::CaptureOnSecondTransition,
24 polarity: Polarity::IdleHigh,
25};
26
27/// Provides high-level access to Semtech SX1276/77/78/79 based boards connected to a Raspberry Pi
28pub struct LoRa<SPI, CS, RESET> {
29 spi: SPI,
30 cs: CS,
31 reset: RESET,
32 pub explicit_header: bool,
33 pub mode: RadioMode,
34}
35
36#[allow(clippy::upper_case_acronyms)]
37#[derive(Debug)]
38#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39pub enum Error<SPI, CS, RESET> {
40 Uninformative,
41 VersionMismatch(u8),
42 CS(CS),
43 Reset(RESET),
44 SPI(SPI),
45 Transmitting,
46}
47
48use super::sx127x_lora::register::{FskDataModulationShaping, FskRampUpRamDown};
49use Error::*;
50
51#[cfg(not(feature = "version_0x09"))]
52const VERSION_CHECK: u8 = 0x12;
53
54#[cfg(feature = "version_0x09")]
55const VERSION_CHECK: u8 = 0x09;
56
57impl<SPI, CS, RESET, E> LoRa<SPI, CS, RESET>
58where
59 SPI: Transfer<u8, Error = E> + Write<u8, Error = E>,
60 CS: OutputPin,
61 RESET: OutputPin,
62{
63 /// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time.
64 /// This also preforms a hardware reset of the module and then puts it in standby.
65 pub fn new(spi: SPI, cs: CS, reset: RESET) -> Self {
66 Self {
67 spi,
68 cs,
69 reset,
70 explicit_header: true,
71 mode: RadioMode::Sleep,
72 }
73 }
74
75 pub fn reset<D: DelayMs<u32>>(
76 &mut self,
77 d: &mut D,
78 ) -> Result<(), Error<E, CS::Error, RESET::Error>> {
79 self.reset.set_low().map_err(Reset)?;
80 d.delay_ms(10_u32);
81 self.reset.set_high().map_err(Reset)?;
82 d.delay_ms(10_u32);
83 let version = self.read_register(Register::RegVersion.addr())?;
84 if version == VERSION_CHECK {
85 self.set_mode(RadioMode::Sleep)?;
86 self.write_register(Register::RegFifoTxBaseAddr.addr(), 0)?;
87 self.write_register(Register::RegFifoRxBaseAddr.addr(), 0)?;
88 let lna = self.read_register(Register::RegLna.addr())?;
89 self.write_register(Register::RegLna.addr(), lna | 0x03)?;
90 self.write_register(Register::RegModemConfig3.addr(), 0x04)?;
91 self.set_tcxo(true)?;
92 self.set_mode(RadioMode::Stdby)?;
93 self.cs.set_high().map_err(CS)?;
94 Ok(())
95 } else {
96 Err(Error::VersionMismatch(version))
97 }
98 }
99
100 /// Transmits up to 255 bytes of data. To avoid the use of an allocator, this takes a fixed 255 u8
101 /// array and a payload size and returns the number of bytes sent if successful.
102 pub fn transmit_payload_busy(
103 &mut self,
104 buffer: [u8; 255],
105 payload_size: usize,
106 ) -> Result<usize, Error<E, CS::Error, RESET::Error>> {
107 if self.transmitting()? {
108 Err(Transmitting)
109 } else {
110 self.set_mode(RadioMode::Stdby)?;
111 if self.explicit_header {
112 self.set_explicit_header_mode()?;
113 } else {
114 self.set_implicit_header_mode()?;
115 }
116
117 self.write_register(Register::RegIrqFlags.addr(), 0)?;
118 self.write_register(Register::RegFifoAddrPtr.addr(), 0)?;
119 self.write_register(Register::RegPayloadLength.addr(), 0)?;
120 for byte in buffer.iter().take(payload_size) {
121 self.write_register(Register::RegFifo.addr(), *byte)?;
122 }
123 self.write_register(Register::RegPayloadLength.addr(), payload_size as u8)?;
124 self.set_mode(RadioMode::Tx)?;
125 while self.transmitting()? {}
126 Ok(payload_size)
127 }
128 }
129
130 pub fn set_dio0_tx_done(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
131 self.write_register(Register::RegIrqFlagsMask.addr(), 0b1111_0111)?;
132 let mapping = self.read_register(Register::RegDioMapping1.addr())?;
133 self.write_register(Register::RegDioMapping1.addr(), (mapping & 0x3F) | 0x40)
134 }
135
136 pub fn set_dio0_rx_done(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
137 self.write_register(Register::RegIrqFlagsMask.addr(), 0b0001_1111)?;
138 let mapping = self.read_register(Register::RegDioMapping1.addr())?;
139 self.write_register(Register::RegDioMapping1.addr(), mapping & 0x3F)
140 }
141
142 pub fn transmit_payload(
143 &mut self,
144 buffer: &[u8],
145 ) -> Result<(), Error<E, CS::Error, RESET::Error>> {
146 assert!(buffer.len() < 255);
147 if self.transmitting()? {
148 Err(Transmitting)
149 } else {
150 self.set_mode(RadioMode::Stdby)?;
151 if self.explicit_header {
152 self.set_explicit_header_mode()?;
153 } else {
154 self.set_implicit_header_mode()?;
155 }
156
157 self.write_register(Register::RegIrqFlags.addr(), 0)?;
158 self.write_register(Register::RegFifoAddrPtr.addr(), 0)?;
159 self.write_register(Register::RegPayloadLength.addr(), 0)?;
160 for byte in buffer.iter() {
161 self.write_register(Register::RegFifo.addr(), *byte)?;
162 }
163 self.write_register(Register::RegPayloadLength.addr(), buffer.len() as u8)?;
164 self.set_mode(RadioMode::Tx)?;
165 Ok(())
166 }
167 }
168
169 pub fn packet_ready(&mut self) -> Result<bool, Error<E, CS::Error, RESET::Error>> {
170 Ok(self.read_register(Register::RegIrqFlags.addr())?.get_bit(6))
171 }
172
173 pub fn irq_flags_mask(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
174 Ok(self.read_register(Register::RegIrqFlagsMask.addr())? as u8)
175 }
176
177 pub fn irq_flags(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
178 Ok(self.read_register(Register::RegIrqFlags.addr())? as u8)
179 }
180
181 pub fn read_packet_size(&mut self) -> Result<usize, Error<E, CS::Error, RESET::Error>> {
182 let size = self.read_register(Register::RegRxNbBytes.addr())?;
183 Ok(size as usize)
184 }
185
186 /// Returns the contents of the fifo as a fixed 255 u8 array. This should only be called is there is a
187 /// new packet ready to be read.
188 pub fn read_packet(
189 &mut self,
190 buffer: &mut [u8],
191 ) -> Result<(), Error<E, CS::Error, RESET::Error>> {
192 self.clear_irq()?;
193 let size = self.read_register(Register::RegRxNbBytes.addr())?;
194 assert!(size as usize <= buffer.len());
195 let fifo_addr = self.read_register(Register::RegFifoRxCurrentAddr.addr())?;
196 self.write_register(Register::RegFifoAddrPtr.addr(), fifo_addr)?;
197 for i in 0..size {
198 let byte = self.read_register(Register::RegFifo.addr())?;
199 buffer[i as usize] = byte;
200 }
201 self.write_register(Register::RegFifoAddrPtr.addr(), 0)?;
202 Ok(())
203 }
204
205 /// Returns true if the radio is currently transmitting a packet.
206 pub fn transmitting(&mut self) -> Result<bool, Error<E, CS::Error, RESET::Error>> {
207 if (self.read_register(Register::RegOpMode.addr())? & RadioMode::Tx.addr())
208 == RadioMode::Tx.addr()
209 {
210 Ok(true)
211 } else {
212 if (self.read_register(Register::RegIrqFlags.addr())? & IRQ::IrqTxDoneMask.addr()) == 1
213 {
214 self.write_register(Register::RegIrqFlags.addr(), IRQ::IrqTxDoneMask.addr())?;
215 }
216 Ok(false)
217 }
218 }
219
220 /// Clears the radio's IRQ registers.
221 pub fn clear_irq(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
222 let irq_flags = self.read_register(Register::RegIrqFlags.addr())?;
223 self.write_register(Register::RegIrqFlags.addr(), 0xFF)?;
224 Ok(irq_flags)
225 }
226
227 /// Sets the transmit power and pin. Levels can range from 0-14 when the output
228 /// pin = 0(RFO), and form 0-20 when output pin = 1(PaBoost). Power is in dB.
229 /// Default value is `17`.
230 pub fn set_tx_power(
231 &mut self,
232 mut level: i32,
233 output_pin: u8,
234 ) -> Result<(), Error<E, CS::Error, RESET::Error>> {
235 if PaConfig::PaOutputRfoPin.addr() == output_pin {
236 // RFO
237 if level < 0 {
238 level = 0;
239 } else if level > 14 {
240 level = 14;
241 }
242 self.write_register(Register::RegPaConfig.addr(), (0x70 | level) as u8)
243 } else {
244 // PA BOOST
245 if level > 17 {
246 if level > 20 {
247 level = 20;
248 }
249 // subtract 3 from level, so 18 - 20 maps to 15 - 17
250 level -= 3;
251
252 // High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.)
253 self.write_register(Register::RegPaDac.addr(), 0x87)?;
254 self.set_ocp(140)?;
255 } else {
256 if level < 2 {
257 level = 2;
258 }
259 //Default value PA_HF/LF or +17dBm
260 self.write_register(Register::RegPaDac.addr(), 0x84)?;
261 self.set_ocp(100)?;
262 }
263 level -= 2;
264 self.write_register(
265 Register::RegPaConfig.addr(),
266 PaConfig::PaBoost.addr() | level as u8,
267 )
268 }
269 }
270
271 pub fn get_modem_stat(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
272 Ok(self.read_register(Register::RegModemStat.addr())? as u8)
273 }
274
275 /// Sets the over current protection on the radio(mA).
276 pub fn set_ocp(&mut self, ma: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
277 let mut ocp_trim: u8 = 27;
278
279 if ma <= 120 {
280 ocp_trim = (ma - 45) / 5;
281 } else if ma <= 240 {
282 ocp_trim = (ma + 30) / 10;
283 }
284 self.write_register(Register::RegOcp.addr(), 0x20 | (0x1F & ocp_trim))
285 }
286
287 /// Sets the state of the radio. Default mode after initiation is `Standby`.
288 pub fn set_mode(&mut self, mode: RadioMode) -> Result<(), Error<E, CS::Error, RESET::Error>> {
289 if self.explicit_header {
290 self.set_explicit_header_mode()?;
291 } else {
292 self.set_implicit_header_mode()?;
293 }
294 self.write_register(
295 Register::RegOpMode.addr(),
296 RadioMode::LongRangeMode.addr() | mode.addr(),
297 )?;
298
299 self.mode = mode;
300 Ok(())
301 }
302
303 pub fn reset_payload_length(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
304 self.write_register(Register::RegPayloadLength.addr(), 0xFF)
305 }
306
307 /// Sets the frequency of the radio. Values are in megahertz.
308 /// I.E. 915 MHz must be used for North America. Check regulation for your area.
309 pub fn set_frequency(&mut self, freq: u32) -> Result<(), Error<E, CS::Error, RESET::Error>> {
310 const FREQ_STEP: f64 = 61.03515625;
311 // calculate register values
312 let frf = (freq as f64 / FREQ_STEP) as u32;
313 // write registers
314 self.write_register(
315 Register::RegFrfMsb.addr(),
316 ((frf & 0x00FF_0000) >> 16) as u8,
317 )?;
318 self.write_register(Register::RegFrfMid.addr(), ((frf & 0x0000_FF00) >> 8) as u8)?;
319 self.write_register(Register::RegFrfLsb.addr(), (frf & 0x0000_00FF) as u8)
320 }
321
322 /// Sets the radio to use an explicit header. Default state is `ON`.
323 fn set_explicit_header_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
324 let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr())?;
325 self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0xfe)?;
326 self.explicit_header = true;
327 Ok(())
328 }
329
330 /// Sets the radio to use an implicit header. Default state is `OFF`.
331 fn set_implicit_header_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
332 let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr())?;
333 self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0x01)?;
334 self.explicit_header = false;
335 Ok(())
336 }
337
338 /// Sets the spreading factor of the radio. Supported values are between 6 and 12.
339 /// If a spreading factor of 6 is set, implicit header mode must be used to transmit
340 /// and receive packets. Default value is `7`.
341 pub fn set_spreading_factor(
342 &mut self,
343 mut sf: u8,
344 ) -> Result<(), Error<E, CS::Error, RESET::Error>> {
345 if sf < 6 {
346 sf = 6;
347 } else if sf > 12 {
348 sf = 12;
349 }
350
351 if sf == 6 {
352 self.write_register(Register::RegDetectionOptimize.addr(), 0xc5)?;
353 self.write_register(Register::RegDetectionThreshold.addr(), 0x0c)?;
354 } else {
355 self.write_register(Register::RegDetectionOptimize.addr(), 0xc3)?;
356 self.write_register(Register::RegDetectionThreshold.addr(), 0x0a)?;
357 }
358 let modem_config_2 = self.read_register(Register::RegModemConfig2.addr())?;
359 self.write_register(
360 Register::RegModemConfig2.addr(),
361 (modem_config_2 & 0x0f) | ((sf << 4) & 0xf0),
362 )?;
363 self.set_ldo_flag()?;
364
365 self.write_register(Register::RegSymbTimeoutLsb.addr(), 0x05)?;
366
367 Ok(())
368 }
369
370 pub fn set_tcxo(&mut self, external: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
371 if external {
372 self.write_register(Register::RegTcxo.addr(), 0x10)
373 } else {
374 self.write_register(Register::RegTcxo.addr(), 0x00)
375 }
376 }
377
378 /// Sets the signal bandwidth of the radio. Supported values are: `7800 Hz`, `10400 Hz`,
379 /// `15600 Hz`, `20800 Hz`, `31250 Hz`,`41700 Hz` ,`62500 Hz`,`125000 Hz` and `250000 Hz`
380 /// Default value is `125000 Hz`
381 pub fn set_signal_bandwidth(
382 &mut self,
383 sbw: i64,
384 ) -> Result<(), Error<E, CS::Error, RESET::Error>> {
385 let bw: i64 = match sbw {
386 7_800 => 0,
387 10_400 => 1,
388 15_600 => 2,
389 20_800 => 3,
390 31_250 => 4,
391 41_700 => 5,
392 62_500 => 6,
393 125_000 => 7,
394 250_000 => 8,
395 _ => 9,
396 };
397 let modem_config_1 = self.read_register(Register::RegModemConfig1.addr())?;
398 self.write_register(
399 Register::RegModemConfig1.addr(),
400 (modem_config_1 & 0x0f) | ((bw << 4) as u8),
401 )?;
402 self.set_ldo_flag()?;
403 Ok(())
404 }
405
406 /// Sets the coding rate of the radio with the numerator fixed at 4. Supported values
407 /// are between `5` and `8`, these correspond to coding rates of `4/5` and `4/8`.
408 /// Default value is `5`.
409 pub fn set_coding_rate_4(
410 &mut self,
411 mut denominator: u8,
412 ) -> Result<(), Error<E, CS::Error, RESET::Error>> {
413 if denominator < 5 {
414 denominator = 5;
415 } else if denominator > 8 {
416 denominator = 8;
417 }
418 let cr = denominator - 4;
419 let modem_config_1 = self.read_register(Register::RegModemConfig1.addr())?;
420 self.write_register(
421 Register::RegModemConfig1.addr(),
422 (modem_config_1 & 0xf1) | (cr << 1),
423 )
424 }
425
426 /// Sets the preamble length of the radio. Values are between 6 and 65535.
427 /// Default value is `8`.
428 pub fn set_preamble_length(
429 &mut self,
430 length: i64,
431 ) -> Result<(), Error<E, CS::Error, RESET::Error>> {
432 self.write_register(Register::RegPreambleMsb.addr(), (length >> 8) as u8)?;
433 self.write_register(Register::RegPreambleLsb.addr(), length as u8)
434 }
435
436 /// Enables are disables the radio's CRC check. Default value is `false`.
437 pub fn set_crc(&mut self, value: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
438 let modem_config_2 = self.read_register(Register::RegModemConfig2.addr())?;
439 if value {
440 self.write_register(Register::RegModemConfig2.addr(), modem_config_2 | 0x04)
441 } else {
442 self.write_register(Register::RegModemConfig2.addr(), modem_config_2 & 0xfb)
443 }
444 }
445
446 /// Inverts the radio's IQ signals. Default value is `false`.
447 pub fn set_invert_iq(&mut self, value: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
448 if value {
449 self.write_register(Register::RegInvertiq.addr(), 0x66)?;
450 self.write_register(Register::RegInvertiq2.addr(), 0x19)
451 } else {
452 self.write_register(Register::RegInvertiq.addr(), 0x27)?;
453 self.write_register(Register::RegInvertiq2.addr(), 0x1d)
454 }
455 }
456
457 /// Returns the spreading factor of the radio.
458 pub fn get_spreading_factor(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
459 Ok(self.read_register(Register::RegModemConfig2.addr())? >> 4)
460 }
461
462 /// Returns the signal bandwidth of the radio.
463 pub fn get_signal_bandwidth(&mut self) -> Result<i64, Error<E, CS::Error, RESET::Error>> {
464 let bw = self.read_register(Register::RegModemConfig1.addr())? >> 4;
465 let bw = match bw {
466 0 => 7_800,
467 1 => 10_400,
468 2 => 15_600,
469 3 => 20_800,
470 4 => 31_250,
471 5 => 41_700,
472 6 => 62_500,
473 7 => 125_000,
474 8 => 250_000,
475 9 => 500_000,
476 _ => -1,
477 };
478 Ok(bw)
479 }
480
481 /// Returns the RSSI of the last received packet.
482 pub fn get_packet_rssi(&mut self) -> Result<i32, Error<E, CS::Error, RESET::Error>> {
483 Ok(i32::from(self.read_register(Register::RegPktRssiValue.addr())?) - 157)
484 }
485
486 /// Returns the signal to noise radio of the the last received packet.
487 pub fn get_packet_snr(&mut self) -> Result<f64, Error<E, CS::Error, RESET::Error>> {
488 Ok(f64::from(
489 self.read_register(Register::RegPktSnrValue.addr())?,
490 ))
491 }
492
493 /// Returns the frequency error of the last received packet in Hz.
494 pub fn get_packet_frequency_error(&mut self) -> Result<i64, Error<E, CS::Error, RESET::Error>> {
495 let mut freq_error: i32;
496 freq_error = i32::from(self.read_register(Register::RegFreqErrorMsb.addr())? & 0x7);
497 freq_error <<= 8i64;
498 freq_error += i32::from(self.read_register(Register::RegFreqErrorMid.addr())?);
499 freq_error <<= 8i64;
500 freq_error += i32::from(self.read_register(Register::RegFreqErrorLsb.addr())?);
501
502 let f_xtal = 32_000_000; // FXOSC: crystal oscillator (XTAL) frequency (2.5. Chip Specification, p. 14)
503 let f_error = ((f64::from(freq_error) * (1i64 << 24) as f64) / f64::from(f_xtal))
504 * (self.get_signal_bandwidth()? as f64 / 500_000.0f64); // p. 37
505 Ok(f_error as i64)
506 }
507
508 fn set_ldo_flag(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
509 let sw = self.get_signal_bandwidth()?;
510 // Section 4.1.1.5
511 let symbol_duration = 1000 / (sw / ((1_i64) << self.get_spreading_factor()?));
512
513 // Section 4.1.1.6
514 let ldo_on = symbol_duration > 16;
515
516 let mut config_3 = self.read_register(Register::RegModemConfig3.addr())?;
517 config_3.set_bit(3, ldo_on);
518 //config_3.set_bit(2, true);
519 self.write_register(Register::RegModemConfig3.addr(), config_3)
520 }
521
522 fn read_register(&mut self, reg: u8) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
523 self.cs.set_low().map_err(CS)?;
524
525 let mut buffer = [reg & 0x7f, 0];
526 let transfer = self.spi.transfer(&mut buffer).map_err(SPI)?;
527 self.cs.set_high().map_err(CS)?;
528 Ok(transfer[1])
529 }
530
531 fn write_register(
532 &mut self,
533 reg: u8,
534 byte: u8,
535 ) -> Result<(), Error<E, CS::Error, RESET::Error>> {
536 self.cs.set_low().map_err(CS)?;
537
538 let buffer = [reg | 0x80, byte];
539 self.spi.write(&buffer).map_err(SPI)?;
540 self.cs.set_high().map_err(CS)?;
541 Ok(())
542 }
543
544 pub fn put_in_fsk_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
545 // Put in FSK mode
546 let mut op_mode = 0;
547 op_mode
548 .set_bit(7, false) // FSK mode
549 .set_bits(5..6, 0x00) // FSK modulation
550 .set_bit(3, false) //Low freq registers
551 .set_bits(0..2, 0b011); // Mode
552
553 self.write_register(Register::RegOpMode as u8, op_mode)
554 }
555
556 pub fn set_fsk_pa_ramp(
557 &mut self,
558 modulation_shaping: FskDataModulationShaping,
559 ramp: FskRampUpRamDown,
560 ) -> Result<(), Error<E, CS::Error, RESET::Error>> {
561 let mut pa_ramp = 0;
562 pa_ramp
563 .set_bits(5..6, modulation_shaping as u8)
564 .set_bits(0..3, ramp as u8);
565
566 self.write_register(Register::RegPaRamp as u8, pa_ramp)
567 }
568
569 pub fn set_lora_pa_ramp(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
570 self.write_register(Register::RegPaRamp as u8, 0b1000)
571 }
572
573 pub fn set_lora_sync_word(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
574 self.write_register(Register::RegSyncWord as u8, 0x34)
575 }
576}
577/// Modes of the radio and their corresponding register values.
578#[derive(Clone, Copy)]
579pub enum RadioMode {
580 LongRangeMode = 0x80,
581 Sleep = 0x00,
582 Stdby = 0x01,
583 Tx = 0x03,
584 RxContinuous = 0x05,
585 RxSingle = 0x06,
586}
587
588impl RadioMode {
589 /// Returns the address of the mode.
590 pub fn addr(self) -> u8 {
591 self as u8
592 }
593}
diff --git a/embassy-lora/src/sx127x/sx127x_lora/register.rs b/embassy-lora/src/sx127x/sx127x_lora/register.rs
new file mode 100644
index 000000000..2445e21b1
--- /dev/null
+++ b/embassy-lora/src/sx127x/sx127x_lora/register.rs
@@ -0,0 +1,107 @@
1// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0
2// license
3//
4// Modifications made to make the driver work with the rust-lorawan link layer.
5#![allow(dead_code, clippy::enum_variant_names)]
6
7#[derive(Clone, Copy)]
8pub enum Register {
9 RegFifo = 0x00,
10 RegOpMode = 0x01,
11 RegFrfMsb = 0x06,
12 RegFrfMid = 0x07,
13 RegFrfLsb = 0x08,
14 RegPaConfig = 0x09,
15 RegPaRamp = 0x0a,
16 RegOcp = 0x0b,
17 RegLna = 0x0c,
18 RegFifoAddrPtr = 0x0d,
19 RegFifoTxBaseAddr = 0x0e,
20 RegFifoRxBaseAddr = 0x0f,
21 RegFifoRxCurrentAddr = 0x10,
22 RegIrqFlagsMask = 0x11,
23 RegIrqFlags = 0x12,
24 RegRxNbBytes = 0x13,
25 RegPktSnrValue = 0x19,
26 RegModemStat = 0x18,
27 RegPktRssiValue = 0x1a,
28 RegModemConfig1 = 0x1d,
29 RegModemConfig2 = 0x1e,
30 RegSymbTimeoutLsb = 0x1f,
31 RegPreambleMsb = 0x20,
32 RegPreambleLsb = 0x21,
33 RegPayloadLength = 0x22,
34 RegMaxPayloadLength = 0x23,
35 RegModemConfig3 = 0x26,
36 RegFreqErrorMsb = 0x28,
37 RegFreqErrorMid = 0x29,
38 RegFreqErrorLsb = 0x2a,
39 RegRssiWideband = 0x2c,
40 RegDetectionOptimize = 0x31,
41 RegInvertiq = 0x33,
42 RegDetectionThreshold = 0x37,
43 RegSyncWord = 0x39,
44 RegInvertiq2 = 0x3b,
45 RegDioMapping1 = 0x40,
46 RegVersion = 0x42,
47 RegTcxo = 0x4b,
48 RegPaDac = 0x4d,
49}
50#[derive(Clone, Copy)]
51pub enum PaConfig {
52 PaBoost = 0x80,
53 PaOutputRfoPin = 0,
54}
55
56#[derive(Clone, Copy)]
57pub enum IRQ {
58 IrqTxDoneMask = 0x08,
59 IrqPayloadCrcErrorMask = 0x20,
60 IrqRxDoneMask = 0x40,
61}
62
63impl Register {
64 pub fn addr(self) -> u8 {
65 self as u8
66 }
67}
68
69impl PaConfig {
70 pub fn addr(self) -> u8 {
71 self as u8
72 }
73}
74
75impl IRQ {
76 pub fn addr(self) -> u8 {
77 self as u8
78 }
79}
80
81#[derive(Clone, Copy)]
82pub enum FskDataModulationShaping {
83 None = 1,
84 GaussianBt1d0 = 2,
85 GaussianBt0d5 = 10,
86 GaussianBt0d3 = 11,
87}
88
89#[derive(Clone, Copy)]
90pub enum FskRampUpRamDown {
91 _3d4ms = 0b000,
92 _2ms = 0b0001,
93 _1ms = 0b0010,
94 _500us = 0b0011,
95 _250us = 0b0100,
96 _125us = 0b0101,
97 _100us = 0b0110,
98 _62us = 0b0111,
99 _50us = 0b1000,
100 _40us = 0b1001,
101 _31us = 0b1010,
102 _25us = 0b1011,
103 _20us = 0b1100,
104 _15us = 0b1101,
105 _12us = 0b1110,
106 _10us = 0b1111,
107}
diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml
index 7dfcdb0fe..371ac68cc 100644
--- a/examples/stm32l0/Cargo.toml
+++ b/examples/stm32l0/Cargo.toml
@@ -23,6 +23,10 @@ embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["
23embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" } 23embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" }
24embassy-macros = { path = "../../embassy-macros" } 24embassy-macros = { path = "../../embassy-macros" }
25 25
26embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx127x", "time"] }
27lorawan-device = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["async"] }
28lorawan-encoding = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["default-crypto"] }
29
26defmt = "0.2.3" 30defmt = "0.2.3"
27defmt-rtt = "0.2.0" 31defmt-rtt = "0.2.0"
28 32
diff --git a/examples/stm32l0/src/bin/lorawan.rs b/examples/stm32l0/src/bin/lorawan.rs
new file mode 100644
index 000000000..5ca69f9a7
--- /dev/null
+++ b/examples/stm32l0/src/bin/lorawan.rs
@@ -0,0 +1,104 @@
1//! This example runs on the STM32 LoRa Discovery board which has a builtin Semtech Sx127x radio
2#![no_std]
3#![no_main]
4#![macro_use]
5#![allow(dead_code)]
6#![feature(generic_associated_types)]
7#![feature(type_alias_impl_trait)]
8
9#[path = "../example_common.rs"]
10mod example_common;
11
12use embassy_lora::{sx127x::*, LoraTimer};
13use embassy_stm32::{
14 dbgmcu::Dbgmcu,
15 dma::NoDma,
16 exti::ExtiInput,
17 gpio::{Input, Level, Output, Pull, Speed},
18 rcc,
19 rng::Rng,
20 spi,
21 time::U32Ext,
22 Peripherals,
23};
24use lorawan_device::async_device::{region, Device, JoinMode};
25use lorawan_encoding::default_crypto::DefaultFactory as Crypto;
26
27fn config() -> embassy_stm32::Config {
28 let mut config = embassy_stm32::Config::default();
29 config.rcc = config.rcc.clock_src(embassy_stm32::rcc::ClockSrc::HSI16);
30 config
31}
32
33#[embassy::main(config = "config()")]
34async fn main(_spawner: embassy::executor::Spawner, mut p: Peripherals) {
35 unsafe {
36 Dbgmcu::enable_all();
37 }
38
39 let mut rcc = rcc::Rcc::new(p.RCC);
40 let _ = rcc.enable_hsi48(&mut p.SYSCFG, p.CRS);
41
42 // SPI for sx127x
43 let spi = spi::Spi::new(
44 p.SPI1,
45 p.PB3,
46 p.PA7,
47 p.PA6,
48 NoDma,
49 NoDma,
50 200_000.hz(),
51 spi::Config::default(),
52 );
53
54 let cs = Output::new(p.PA15, Level::High, Speed::Low);
55 let reset = Output::new(p.PC0, Level::High, Speed::Low);
56 let _ = Input::new(p.PB1, Pull::None);
57
58 let ready = Input::new(p.PB4, Pull::Up);
59 let ready_pin = ExtiInput::new(ready, p.EXTI4);
60
61 let radio = Sx127xRadio::new(
62 spi,
63 cs,
64 reset,
65 ready_pin,
66 DummySwitch,
67 &mut embassy::time::Delay,
68 )
69 .unwrap();
70
71 let region = region::EU868::default().into();
72 let mut radio_buffer = [0; 256];
73 let mut device: Device<'_, _, Crypto, _, _> = Device::new(
74 region,
75 radio,
76 LoraTimer,
77 Rng::new(p.RNG),
78 &mut radio_buffer[..],
79 );
80
81 defmt::info!("Joining LoRaWAN network");
82
83 // TODO: Adjust the EUI and Keys according to your network credentials
84 device
85 .join(&JoinMode::OTAA {
86 deveui: [0, 0, 0, 0, 0, 0, 0, 0],
87 appeui: [0, 0, 0, 0, 0, 0, 0, 0],
88 appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
89 })
90 .await
91 .ok()
92 .unwrap();
93 defmt::info!("LoRaWAN network joined");
94
95 defmt::info!("Sending 'PING'");
96 device.send(b"PING", 1, false).await.ok().unwrap();
97 defmt::info!("Message sent!");
98}
99
100pub struct DummySwitch;
101impl RadioSwitch for DummySwitch {
102 fn set_rx(&mut self) {}
103 fn set_tx(&mut self) {}
104}
diff --git a/examples/stm32wl55/Cargo.toml b/examples/stm32wl55/Cargo.toml
index d0c727862..4688bdad1 100644
--- a/examples/stm32wl55/Cargo.toml
+++ b/examples/stm32wl55/Cargo.toml
@@ -19,8 +19,12 @@ defmt-error = []
19[dependencies] 19[dependencies]
20embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-trace"] } 20embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-trace"] }
21embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] } 21embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] }
22embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "defmt-trace", "stm32wl55jc_cm4", "time-driver-tim2", "memory-x", "subghz"] } 22embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "defmt-trace", "stm32wl55jc_cm4", "time-driver-tim2", "memory-x", "subghz", "unstable-pac"] }
23embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" } 23embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" }
24embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time"] }
25
26lorawan-device = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["async"] }
27lorawan-encoding = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["default-crypto"] }
24 28
25defmt = "0.2.3" 29defmt = "0.2.3"
26defmt-rtt = "0.2.0" 30defmt-rtt = "0.2.0"
diff --git a/examples/stm32wl55/src/bin/lorawan.rs b/examples/stm32wl55/src/bin/lorawan.rs
new file mode 100644
index 000000000..155905ae7
--- /dev/null
+++ b/examples/stm32wl55/src/bin/lorawan.rs
@@ -0,0 +1,79 @@
1#![no_std]
2#![no_main]
3#![macro_use]
4#![allow(dead_code)]
5#![feature(generic_associated_types)]
6#![feature(type_alias_impl_trait)]
7
8#[path = "../example_common.rs"]
9mod example_common;
10
11use embassy_lora::{stm32wl::*, LoraTimer};
12use embassy_stm32::{
13 dbgmcu::Dbgmcu,
14 dma::NoDma,
15 gpio::{Level, Output, Pin, Speed},
16 interrupt, pac, rcc,
17 rng::Rng,
18 subghz::*,
19 Peripherals,
20};
21use lorawan_device::async_device::{region, Device, JoinMode};
22use lorawan_encoding::default_crypto::DefaultFactory as Crypto;
23
24fn config() -> embassy_stm32::Config {
25 let mut config = embassy_stm32::Config::default();
26 config.rcc = config.rcc.clock_src(embassy_stm32::rcc::ClockSrc::HSI16);
27 config
28}
29
30#[embassy::main(config = "config()")]
31async fn main(_spawner: embassy::executor::Spawner, p: Peripherals) {
32 unsafe {
33 Dbgmcu::enable_all();
34 let mut rcc = rcc::Rcc::new(p.RCC);
35 rcc.enable_lsi();
36 pac::RCC.ccipr().modify(|w| {
37 w.set_rngsel(0b01);
38 });
39 }
40
41 let ctrl1 = Output::new(p.PC3.degrade(), Level::High, Speed::High);
42 let ctrl2 = Output::new(p.PC4.degrade(), Level::High, Speed::High);
43 let ctrl3 = Output::new(p.PC5.degrade(), Level::High, Speed::High);
44 let rfs = RadioSwitch::new(ctrl1, ctrl2, ctrl3);
45
46 let radio = SubGhz::new(p.SUBGHZSPI, p.PA5, p.PA7, p.PA6, NoDma, NoDma);
47
48 let irq = interrupt::take!(SUBGHZ_RADIO);
49 static mut RADIO_STATE: SubGhzState<'static> = SubGhzState::new();
50 let radio = unsafe { SubGhzRadio::new(&mut RADIO_STATE, radio, rfs, irq) };
51
52 let region = region::EU868::default().into();
53 let mut radio_buffer = [0; 256];
54 let mut device: Device<'_, _, Crypto, _, _> = Device::new(
55 region,
56 radio,
57 LoraTimer,
58 Rng::new(p.RNG),
59 &mut radio_buffer[..],
60 );
61
62 defmt::info!("Joining LoRaWAN network");
63
64 // TODO: Adjust the EUI and Keys according to your network credentials
65 device
66 .join(&JoinMode::OTAA {
67 deveui: [0, 0, 0, 0, 0, 0, 0, 0],
68 appeui: [0, 0, 0, 0, 0, 0, 0, 0],
69 appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
70 })
71 .await
72 .ok()
73 .unwrap();
74 defmt::info!("LoRaWAN network joined");
75
76 defmt::info!("Sending 'PING'");
77 device.send(b"PING", 1, false).await.ok().unwrap();
78 defmt::info!("Message sent!");
79}