aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2021-10-10 21:23:02 +0200
committerGitHub <[email protected]>2021-10-10 21:23:02 +0200
commit1c4c813255d39c9b517a0b6c5cc905df1c6aa565 (patch)
tree80f0a6620171759f57a5d2420f73a801cc1bf953
parent009b77c1b9874cccb9b2f81876f41e9c3d53f3a5 (diff)
parent16a47a0ad9eb5f40aa1f202d3fbaa03c7b77b836 (diff)
Merge pull request #410 from lulf/embassy-lora
Add embassy-lora crate
-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}