commit
1c4c813255
11 changed files with 1742 additions and 1 deletions
33
embassy-lora/Cargo.toml
Normal file
33
embassy-lora/Cargo.toml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
[package]
|
||||||
|
name = "embassy-lora"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Ulf Lilleengen <lulf@redhat.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
sx127x = []
|
||||||
|
stm32wl = ["embassy-stm32", "embassy-stm32/subghz"]
|
||||||
|
time = []
|
||||||
|
|
||||||
|
defmt-trace = []
|
||||||
|
defmt-debug = []
|
||||||
|
defmt-info = []
|
||||||
|
defmt-warn = []
|
||||||
|
defmt-error = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
defmt = { version = "0.2.3", optional = true }
|
||||||
|
log = { version = "0.4.14", optional = true }
|
||||||
|
|
||||||
|
embassy = { version = "0.1.0", path = "../embassy", default-features = false }
|
||||||
|
embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true }
|
||||||
|
embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common", default-features = false }
|
||||||
|
futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
|
||||||
|
embedded-hal = { version = "0.2", features = ["unproven"] }
|
||||||
|
bit_field = { version = "0.10" }
|
||||||
|
|
||||||
|
lorawan-device = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["async"] }
|
||||||
|
lorawan-encoding = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false }
|
225
embassy-lora/src/fmt.rs
Normal file
225
embassy-lora/src/fmt.rs
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
#![macro_use]
|
||||||
|
#![allow(unused_macros)]
|
||||||
|
|
||||||
|
#[cfg(all(feature = "defmt", feature = "log"))]
|
||||||
|
compile_error!("You may not enable both `defmt` and `log` features.");
|
||||||
|
|
||||||
|
macro_rules! assert {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::assert!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::assert!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_eq {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::assert_eq!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::assert_eq!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! assert_ne {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::assert_ne!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::assert_ne!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! debug_assert {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::debug_assert!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::debug_assert!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! debug_assert_eq {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::debug_assert_eq!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::debug_assert_eq!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! debug_assert_ne {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::debug_assert_ne!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::debug_assert_ne!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! todo {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::todo!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::todo!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! unreachable {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::unreachable!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::unreachable!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! panic {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
{
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
::core::panic!($($x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::panic!($($x)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! trace {
|
||||||
|
($s:literal $(, $x:expr)* $(,)?) => {
|
||||||
|
{
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
::log::trace!($s $(, $x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::trace!($s $(, $x)*);
|
||||||
|
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||||
|
let _ = ($( & $x ),*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! debug {
|
||||||
|
($s:literal $(, $x:expr)* $(,)?) => {
|
||||||
|
{
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
::log::debug!($s $(, $x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::debug!($s $(, $x)*);
|
||||||
|
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||||
|
let _ = ($( & $x ),*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! info {
|
||||||
|
($s:literal $(, $x:expr)* $(,)?) => {
|
||||||
|
{
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
::log::info!($s $(, $x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::info!($s $(, $x)*);
|
||||||
|
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||||
|
let _ = ($( & $x ),*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! warn {
|
||||||
|
($s:literal $(, $x:expr)* $(,)?) => {
|
||||||
|
{
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
::log::warn!($s $(, $x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::warn!($s $(, $x)*);
|
||||||
|
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||||
|
let _ = ($( & $x ),*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! error {
|
||||||
|
($s:literal $(, $x:expr)* $(,)?) => {
|
||||||
|
{
|
||||||
|
#[cfg(feature = "log")]
|
||||||
|
::log::error!($s $(, $x)*);
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
::defmt::error!($s $(, $x)*);
|
||||||
|
#[cfg(not(any(feature = "log", feature="defmt")))]
|
||||||
|
let _ = ($( & $x ),*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "defmt")]
|
||||||
|
macro_rules! unwrap {
|
||||||
|
($($x:tt)*) => {
|
||||||
|
::defmt::unwrap!($($x)*)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "defmt"))]
|
||||||
|
macro_rules! unwrap {
|
||||||
|
($arg:expr) => {
|
||||||
|
match $crate::fmt::Try::into_result($arg) {
|
||||||
|
::core::result::Result::Ok(t) => t,
|
||||||
|
::core::result::Result::Err(e) => {
|
||||||
|
::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($arg:expr, $($msg:expr),+ $(,)? ) => {
|
||||||
|
match $crate::fmt::Try::into_result($arg) {
|
||||||
|
::core::result::Result::Ok(t) => t,
|
||||||
|
::core::result::Result::Err(e) => {
|
||||||
|
::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub struct NoneError;
|
||||||
|
|
||||||
|
pub trait Try {
|
||||||
|
type Ok;
|
||||||
|
type Error;
|
||||||
|
fn into_result(self) -> Result<Self::Ok, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Try for Option<T> {
|
||||||
|
type Ok = T;
|
||||||
|
type Error = NoneError;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn into_result(self) -> Result<T, NoneError> {
|
||||||
|
self.ok_or(NoneError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> Try for Result<T, E> {
|
||||||
|
type Ok = T;
|
||||||
|
type Error = E;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn into_result(self) -> Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
23
embassy-lora/src/lib.rs
Normal file
23
embassy-lora/src/lib.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#![no_std]
|
||||||
|
#![feature(type_alias_impl_trait)]
|
||||||
|
#![feature(generic_associated_types)]
|
||||||
|
//! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device
|
||||||
|
//! crate's async LoRaWAN MAC implementation.
|
||||||
|
|
||||||
|
pub(crate) mod fmt;
|
||||||
|
|
||||||
|
#[cfg(feature = "stm32wl")]
|
||||||
|
pub mod stm32wl;
|
||||||
|
#[cfg(feature = "sx127x")]
|
||||||
|
pub mod sx127x;
|
||||||
|
|
||||||
|
/// A convenience timer to use with the LoRaWAN crate
|
||||||
|
pub struct LoraTimer;
|
||||||
|
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
impl lorawan_device::async_device::radio::Timer for LoraTimer {
|
||||||
|
type DelayFuture<'m> = impl core::future::Future<Output = ()> + 'm;
|
||||||
|
fn delay_ms<'m>(&'m mut self, millis: u64) -> Self::DelayFuture<'m> {
|
||||||
|
embassy::time::Timer::after(embassy::time::Duration::from_millis(millis))
|
||||||
|
}
|
||||||
|
}
|
368
embassy-lora/src/stm32wl/mod.rs
Normal file
368
embassy-lora/src/stm32wl/mod.rs
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
//! A radio driver integration for the radio found on STM32WL family devices.
|
||||||
|
use core::future::Future;
|
||||||
|
use core::mem::MaybeUninit;
|
||||||
|
use embassy::channel::signal::Signal;
|
||||||
|
use embassy::interrupt::InterruptExt;
|
||||||
|
use embassy::util::Unborrow;
|
||||||
|
use embassy_hal_common::unborrow;
|
||||||
|
use embassy_stm32::{
|
||||||
|
dma::NoDma,
|
||||||
|
gpio::{AnyPin, Output},
|
||||||
|
interrupt::SUBGHZ_RADIO,
|
||||||
|
subghz::{
|
||||||
|
CalibrateImage, CfgIrq, CodingRate, HeaderType, Irq, LoRaBandwidth, LoRaModParams,
|
||||||
|
LoRaPacketParams, LoRaSyncWord, Ocp, PaConfig, PaSel, PacketType, RampTime, RegMode,
|
||||||
|
RfFreq, SpreadingFactor as SF, StandbyClk, Status, SubGhz, TcxoMode, TcxoTrim, Timeout,
|
||||||
|
TxParams,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use embedded_hal::digital::v2::OutputPin;
|
||||||
|
use lorawan_device::async_device::{
|
||||||
|
radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig},
|
||||||
|
Timings,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum State {
|
||||||
|
Idle,
|
||||||
|
Txing,
|
||||||
|
Rxing,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct RadioError;
|
||||||
|
|
||||||
|
static IRQ: Signal<(Status, u16)> = Signal::new();
|
||||||
|
|
||||||
|
struct StateInner<'a> {
|
||||||
|
radio: SubGhz<'a, NoDma, NoDma>,
|
||||||
|
switch: RadioSwitch<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// External state storage for the radio state
|
||||||
|
pub struct SubGhzState<'a>(MaybeUninit<StateInner<'a>>);
|
||||||
|
impl<'a> SubGhzState<'a> {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self(MaybeUninit::uninit())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The radio peripheral keeping the radio state and owning the radio IRQ.
|
||||||
|
pub struct SubGhzRadio<'a> {
|
||||||
|
state: *mut StateInner<'a>,
|
||||||
|
_irq: SUBGHZ_RADIO,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SubGhzRadio<'a> {
|
||||||
|
/// Create a new instance of a SubGhz radio for LoRaWAN.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Do not leak self or futures
|
||||||
|
pub unsafe fn new(
|
||||||
|
state: &'a mut SubGhzState<'a>,
|
||||||
|
radio: SubGhz<'a, NoDma, NoDma>,
|
||||||
|
switch: RadioSwitch<'a>,
|
||||||
|
irq: impl Unborrow<Target = SUBGHZ_RADIO>,
|
||||||
|
) -> Self {
|
||||||
|
unborrow!(irq);
|
||||||
|
|
||||||
|
let mut inner = StateInner { radio, switch };
|
||||||
|
inner.radio.reset();
|
||||||
|
|
||||||
|
let state_ptr = state.0.as_mut_ptr();
|
||||||
|
state_ptr.write(inner);
|
||||||
|
|
||||||
|
irq.disable();
|
||||||
|
irq.set_handler(|p| {
|
||||||
|
// This is safe because we only get interrupts when configured for, so
|
||||||
|
// the radio will be awaiting on the signal at this point. If not, the ISR will
|
||||||
|
// anyway only adjust the state in the IRQ signal state.
|
||||||
|
let state = unsafe { &mut *(p as *mut StateInner<'a>) };
|
||||||
|
state.on_interrupt();
|
||||||
|
});
|
||||||
|
irq.set_handler_context(state_ptr as *mut ());
|
||||||
|
irq.enable();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
state: state_ptr,
|
||||||
|
_irq: irq,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> StateInner<'a> {
|
||||||
|
/// Configure radio settings in preparation for TX or RX
|
||||||
|
pub(crate) fn configure(&mut self) -> Result<(), RadioError> {
|
||||||
|
trace!("Configuring STM32WL SUBGHZ radio");
|
||||||
|
self.radio.set_standby(StandbyClk::Rc)?;
|
||||||
|
let tcxo_mode = TcxoMode::new()
|
||||||
|
.set_txco_trim(TcxoTrim::Volts1pt7)
|
||||||
|
.set_timeout(Timeout::from_duration_sat(
|
||||||
|
core::time::Duration::from_millis(40),
|
||||||
|
));
|
||||||
|
|
||||||
|
self.radio.set_tcxo_mode(&tcxo_mode)?;
|
||||||
|
self.radio.set_regulator_mode(RegMode::Ldo)?;
|
||||||
|
|
||||||
|
self.radio.calibrate_image(CalibrateImage::ISM_863_870)?;
|
||||||
|
|
||||||
|
self.radio.set_buffer_base_address(0, 0)?;
|
||||||
|
|
||||||
|
self.radio.set_pa_config(
|
||||||
|
&PaConfig::new()
|
||||||
|
.set_pa_duty_cycle(0x1)
|
||||||
|
.set_hp_max(0x0)
|
||||||
|
.set_pa(PaSel::Lp),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.radio.set_pa_ocp(Ocp::Max140m)?;
|
||||||
|
|
||||||
|
// let tx_params = TxParams::LP_14.set_ramp_time(RampTime::Micros40);
|
||||||
|
self.radio.set_tx_params(
|
||||||
|
&TxParams::new()
|
||||||
|
.set_ramp_time(RampTime::Micros40)
|
||||||
|
.set_power(0x0A),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.radio.set_packet_type(PacketType::LoRa)?;
|
||||||
|
self.radio.set_lora_sync_word(LoRaSyncWord::Public)?;
|
||||||
|
trace!("Done initializing STM32WL SUBGHZ radio");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form
|
||||||
|
/// the upcoming RX window start.
|
||||||
|
async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result<u32, RadioError> {
|
||||||
|
//trace!("TX Request: {}", config);
|
||||||
|
trace!("TX START");
|
||||||
|
self.switch.set_tx_lp();
|
||||||
|
self.configure()?;
|
||||||
|
|
||||||
|
self.radio
|
||||||
|
.set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?;
|
||||||
|
|
||||||
|
let mod_params = LoRaModParams::new()
|
||||||
|
.set_sf(convert_spreading_factor(config.rf.spreading_factor))
|
||||||
|
.set_bw(convert_bandwidth(config.rf.bandwidth))
|
||||||
|
.set_cr(CodingRate::Cr45)
|
||||||
|
.set_ldro_en(true);
|
||||||
|
self.radio.set_lora_mod_params(&mod_params)?;
|
||||||
|
|
||||||
|
let packet_params = LoRaPacketParams::new()
|
||||||
|
.set_preamble_len(8)
|
||||||
|
.set_header_type(HeaderType::Variable)
|
||||||
|
.set_payload_len(buf.len() as u8)
|
||||||
|
.set_crc_en(true)
|
||||||
|
.set_invert_iq(false);
|
||||||
|
|
||||||
|
self.radio.set_lora_packet_params(&packet_params)?;
|
||||||
|
|
||||||
|
let irq_cfg = CfgIrq::new()
|
||||||
|
.irq_enable_all(Irq::TxDone)
|
||||||
|
.irq_enable_all(Irq::RxDone)
|
||||||
|
.irq_enable_all(Irq::Timeout);
|
||||||
|
self.radio.set_irq_cfg(&irq_cfg)?;
|
||||||
|
|
||||||
|
self.radio.set_buffer_base_address(0, 0)?;
|
||||||
|
self.radio.write_buffer(0, buf)?;
|
||||||
|
|
||||||
|
self.radio.set_tx(Timeout::DISABLED)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (_status, irq_status) = IRQ.wait().await;
|
||||||
|
IRQ.reset();
|
||||||
|
|
||||||
|
if irq_status & Irq::TxDone.mask() != 0 {
|
||||||
|
let stats = self.radio.lora_stats()?;
|
||||||
|
let (status, error_mask) = self.radio.op_error()?;
|
||||||
|
trace!(
|
||||||
|
"TX done. Stats: {:?}. OP error: {:?}, mask {:?}",
|
||||||
|
stats,
|
||||||
|
status,
|
||||||
|
error_mask
|
||||||
|
);
|
||||||
|
|
||||||
|
return Ok(0);
|
||||||
|
} else if irq_status & Irq::Timeout.mask() != 0 {
|
||||||
|
trace!("TX timeout");
|
||||||
|
return Err(RadioError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Perform a radio receive operation with the radio config and receive buffer. The receive buffer must
|
||||||
|
/// be able to hold a single LoRaWAN packet.
|
||||||
|
async fn do_rx(
|
||||||
|
&mut self,
|
||||||
|
config: RfConfig,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> Result<(usize, RxQuality), RadioError> {
|
||||||
|
assert!(buf.len() >= 255);
|
||||||
|
trace!("RX START");
|
||||||
|
// trace!("Starting RX: {}", config);
|
||||||
|
self.switch.set_rx();
|
||||||
|
self.configure()?;
|
||||||
|
|
||||||
|
self.radio
|
||||||
|
.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?;
|
||||||
|
|
||||||
|
let mod_params = LoRaModParams::new()
|
||||||
|
.set_sf(convert_spreading_factor(config.spreading_factor))
|
||||||
|
.set_bw(convert_bandwidth(config.bandwidth))
|
||||||
|
.set_cr(CodingRate::Cr45)
|
||||||
|
.set_ldro_en(true);
|
||||||
|
self.radio.set_lora_mod_params(&mod_params)?;
|
||||||
|
|
||||||
|
let packet_params = LoRaPacketParams::new()
|
||||||
|
.set_preamble_len(8)
|
||||||
|
.set_header_type(HeaderType::Variable)
|
||||||
|
.set_payload_len(0xFF)
|
||||||
|
.set_crc_en(true)
|
||||||
|
.set_invert_iq(true);
|
||||||
|
self.radio.set_lora_packet_params(&packet_params)?;
|
||||||
|
|
||||||
|
let irq_cfg = CfgIrq::new()
|
||||||
|
.irq_enable_all(Irq::RxDone)
|
||||||
|
.irq_enable_all(Irq::PreambleDetected)
|
||||||
|
.irq_enable_all(Irq::HeaderErr)
|
||||||
|
.irq_enable_all(Irq::Timeout)
|
||||||
|
.irq_enable_all(Irq::Err);
|
||||||
|
self.radio.set_irq_cfg(&irq_cfg)?;
|
||||||
|
|
||||||
|
self.radio.set_rx(Timeout::DISABLED)?;
|
||||||
|
trace!("RX started");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let (status, irq_status) = IRQ.wait().await;
|
||||||
|
IRQ.reset();
|
||||||
|
trace!("RX IRQ {:?}, {:?}", status, irq_status);
|
||||||
|
if irq_status & Irq::RxDone.mask() != 0 {
|
||||||
|
let (status, len, ptr) = self.radio.rx_buffer_status()?;
|
||||||
|
|
||||||
|
let packet_status = self.radio.lora_packet_status()?;
|
||||||
|
let rssi = packet_status.rssi_pkt().to_integer();
|
||||||
|
let snr = packet_status.snr_pkt().to_integer();
|
||||||
|
trace!(
|
||||||
|
"RX done. Received {} bytes. RX status: {:?}. Pkt status: {:?}",
|
||||||
|
len,
|
||||||
|
status.cmd(),
|
||||||
|
packet_status,
|
||||||
|
);
|
||||||
|
self.radio.read_buffer(ptr, &mut buf[..len as usize])?;
|
||||||
|
self.radio.set_standby(StandbyClk::Rc)?;
|
||||||
|
return Ok((len as usize, RxQuality::new(rssi, snr as i8)));
|
||||||
|
} else if irq_status & (Irq::Timeout.mask() | Irq::TxDone.mask()) != 0 {
|
||||||
|
return Err(RadioError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read interrupt status and store in global signal
|
||||||
|
fn on_interrupt(&mut self) {
|
||||||
|
let (status, irq_status) = self.radio.irq_status().expect("error getting irq status");
|
||||||
|
self.radio
|
||||||
|
.clear_irq_status(irq_status)
|
||||||
|
.expect("error clearing irq status");
|
||||||
|
if irq_status & Irq::PreambleDetected.mask() != 0 {
|
||||||
|
trace!("Preamble detected, ignoring");
|
||||||
|
} else {
|
||||||
|
IRQ.signal((status, irq_status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PhyRxTx for SubGhzRadio<'static> {
|
||||||
|
type PhyError = RadioError;
|
||||||
|
|
||||||
|
type TxFuture<'m> = impl Future<Output = Result<u32, Self::PhyError>> + 'm;
|
||||||
|
fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
|
||||||
|
async move {
|
||||||
|
let inner = unsafe { &mut *self.state };
|
||||||
|
inner.do_tx(config, buf).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RxFuture<'m> = impl Future<Output = Result<(usize, RxQuality), Self::PhyError>> + 'm;
|
||||||
|
fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
|
||||||
|
async move {
|
||||||
|
let inner = unsafe { &mut *self.state };
|
||||||
|
inner.do_rx(config, buf).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<embassy_stm32::spi::Error> for RadioError {
|
||||||
|
fn from(_: embassy_stm32::spi::Error) -> Self {
|
||||||
|
RadioError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Timings for SubGhzRadio<'a> {
|
||||||
|
fn get_rx_window_offset_ms(&self) -> i32 {
|
||||||
|
-200
|
||||||
|
}
|
||||||
|
fn get_rx_window_duration_ms(&self) -> u32 {
|
||||||
|
800
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the radio switch found on STM32WL based boards, used to control the radio for transmission or reception.
|
||||||
|
pub struct RadioSwitch<'a> {
|
||||||
|
ctrl1: Output<'a, AnyPin>,
|
||||||
|
ctrl2: Output<'a, AnyPin>,
|
||||||
|
ctrl3: Output<'a, AnyPin>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RadioSwitch<'a> {
|
||||||
|
pub fn new(
|
||||||
|
ctrl1: Output<'a, AnyPin>,
|
||||||
|
ctrl2: Output<'a, AnyPin>,
|
||||||
|
ctrl3: Output<'a, AnyPin>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
ctrl1,
|
||||||
|
ctrl2,
|
||||||
|
ctrl3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_rx(&mut self) {
|
||||||
|
self.ctrl1.set_high().unwrap();
|
||||||
|
self.ctrl2.set_low().unwrap();
|
||||||
|
self.ctrl3.set_high().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_tx_lp(&mut self) {
|
||||||
|
self.ctrl1.set_high().unwrap();
|
||||||
|
self.ctrl2.set_high().unwrap();
|
||||||
|
self.ctrl3.set_high().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn set_tx_hp(&mut self) {
|
||||||
|
self.ctrl2.set_high().unwrap();
|
||||||
|
self.ctrl1.set_low().unwrap();
|
||||||
|
self.ctrl3.set_high().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_spreading_factor(sf: SpreadingFactor) -> SF {
|
||||||
|
match sf {
|
||||||
|
SpreadingFactor::_7 => SF::Sf7,
|
||||||
|
SpreadingFactor::_8 => SF::Sf8,
|
||||||
|
SpreadingFactor::_9 => SF::Sf9,
|
||||||
|
SpreadingFactor::_10 => SF::Sf10,
|
||||||
|
SpreadingFactor::_11 => SF::Sf11,
|
||||||
|
SpreadingFactor::_12 => SF::Sf12,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_bandwidth(bw: Bandwidth) -> LoRaBandwidth {
|
||||||
|
match bw {
|
||||||
|
Bandwidth::_125KHz => LoRaBandwidth::Bw125,
|
||||||
|
Bandwidth::_250KHz => LoRaBandwidth::Bw250,
|
||||||
|
Bandwidth::_500KHz => LoRaBandwidth::Bw500,
|
||||||
|
}
|
||||||
|
}
|
201
embassy-lora/src/sx127x/mod.rs
Normal file
201
embassy-lora/src/sx127x/mod.rs
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
use core::future::Future;
|
||||||
|
use embassy::traits::gpio::WaitForRisingEdge;
|
||||||
|
use embedded_hal::blocking::delay::DelayMs;
|
||||||
|
use embedded_hal::blocking::spi::{Transfer, Write};
|
||||||
|
use embedded_hal::digital::v2::OutputPin;
|
||||||
|
use lorawan_device::async_device::{
|
||||||
|
radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig},
|
||||||
|
Timings,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod sx127x_lora;
|
||||||
|
use sx127x_lora::{Error as RadioError, LoRa, RadioMode, IRQ};
|
||||||
|
|
||||||
|
/// Trait representing a radio switch for boards using the Sx127x radio. One some
|
||||||
|
/// boards, this will be a dummy implementation that does nothing.
|
||||||
|
pub trait RadioSwitch {
|
||||||
|
fn set_tx(&mut self);
|
||||||
|
fn set_rx(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Semtech Sx127x radio peripheral
|
||||||
|
pub struct Sx127xRadio<SPI, CS, RESET, E, I, RFS>
|
||||||
|
where
|
||||||
|
SPI: Transfer<u8, Error = E> + Write<u8, Error = E> + 'static,
|
||||||
|
E: 'static,
|
||||||
|
CS: OutputPin + 'static,
|
||||||
|
RESET: OutputPin + 'static,
|
||||||
|
I: WaitForRisingEdge + 'static,
|
||||||
|
RFS: RadioSwitch + 'static,
|
||||||
|
{
|
||||||
|
radio: LoRa<SPI, CS, RESET>,
|
||||||
|
rfs: RFS,
|
||||||
|
irq: I,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum State {
|
||||||
|
Idle,
|
||||||
|
Txing,
|
||||||
|
Rxing,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, CS, RESET, E, I, RFS> Sx127xRadio<SPI, CS, RESET, E, I, RFS>
|
||||||
|
where
|
||||||
|
SPI: Transfer<u8, Error = E> + Write<u8, Error = E> + 'static,
|
||||||
|
CS: OutputPin + 'static,
|
||||||
|
RESET: OutputPin + 'static,
|
||||||
|
I: WaitForRisingEdge + 'static,
|
||||||
|
RFS: RadioSwitch + 'static,
|
||||||
|
{
|
||||||
|
pub fn new<D: DelayMs<u32>>(
|
||||||
|
spi: SPI,
|
||||||
|
cs: CS,
|
||||||
|
reset: RESET,
|
||||||
|
irq: I,
|
||||||
|
rfs: RFS,
|
||||||
|
d: &mut D,
|
||||||
|
) -> Result<Self, RadioError<E, CS::Error, RESET::Error>> {
|
||||||
|
let mut radio = LoRa::new(spi, cs, reset);
|
||||||
|
radio.reset(d)?;
|
||||||
|
Ok(Self { radio, irq, rfs })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, CS, RESET, E, I, RFS> Timings for Sx127xRadio<SPI, CS, RESET, E, I, RFS>
|
||||||
|
where
|
||||||
|
SPI: Transfer<u8, Error = E> + Write<u8, Error = E> + 'static,
|
||||||
|
CS: OutputPin + 'static,
|
||||||
|
RESET: OutputPin + 'static,
|
||||||
|
I: WaitForRisingEdge + 'static,
|
||||||
|
RFS: RadioSwitch + 'static,
|
||||||
|
{
|
||||||
|
fn get_rx_window_offset_ms(&self) -> i32 {
|
||||||
|
-500
|
||||||
|
}
|
||||||
|
fn get_rx_window_duration_ms(&self) -> u32 {
|
||||||
|
800
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<SPI, CS, RESET, E, I, RFS> PhyRxTx for Sx127xRadio<SPI, CS, RESET, E, I, RFS>
|
||||||
|
where
|
||||||
|
SPI: Transfer<u8, Error = E> + Write<u8, Error = E> + 'static,
|
||||||
|
CS: OutputPin + 'static,
|
||||||
|
E: 'static,
|
||||||
|
RESET: OutputPin + 'static,
|
||||||
|
I: WaitForRisingEdge + 'static,
|
||||||
|
RFS: RadioSwitch + 'static,
|
||||||
|
{
|
||||||
|
type PhyError = Sx127xError;
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
type TxFuture<'m> where SPI: 'm, CS: 'm, RESET: 'm, E: 'm, I: 'm, RFS: 'm = impl Future<Output = Result<u32, Self::PhyError>> + 'm;
|
||||||
|
|
||||||
|
fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> {
|
||||||
|
trace!("TX START");
|
||||||
|
async move {
|
||||||
|
self.rfs.set_tx();
|
||||||
|
self.radio.set_tx_power(14, 0)?;
|
||||||
|
self.radio.set_frequency(config.rf.frequency)?;
|
||||||
|
// TODO: Modify radio to support other coding rates
|
||||||
|
self.radio.set_coding_rate_4(5)?;
|
||||||
|
self.radio
|
||||||
|
.set_signal_bandwidth(bandwidth_to_i64(config.rf.bandwidth))?;
|
||||||
|
self.radio
|
||||||
|
.set_spreading_factor(spreading_factor_to_u8(config.rf.spreading_factor))?;
|
||||||
|
|
||||||
|
self.radio.set_preamble_length(8)?;
|
||||||
|
self.radio.set_lora_pa_ramp()?;
|
||||||
|
self.radio.set_lora_sync_word()?;
|
||||||
|
self.radio.set_invert_iq(false)?;
|
||||||
|
self.radio.set_crc(true)?;
|
||||||
|
|
||||||
|
self.radio.set_dio0_tx_done()?;
|
||||||
|
self.radio.transmit_payload(buf)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
self.irq.wait_for_rising_edge().await;
|
||||||
|
self.radio.set_mode(RadioMode::Stdby).ok().unwrap();
|
||||||
|
let irq = self.radio.clear_irq().ok().unwrap();
|
||||||
|
if (irq & IRQ::IrqTxDoneMask.addr()) != 0 {
|
||||||
|
trace!("TX DONE");
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
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;
|
||||||
|
|
||||||
|
fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> {
|
||||||
|
trace!("RX START");
|
||||||
|
async move {
|
||||||
|
self.rfs.set_rx();
|
||||||
|
self.radio.reset_payload_length()?;
|
||||||
|
self.radio.set_frequency(config.frequency)?;
|
||||||
|
// TODO: Modify radio to support other coding rates
|
||||||
|
self.radio.set_coding_rate_4(5)?;
|
||||||
|
self.radio
|
||||||
|
.set_signal_bandwidth(bandwidth_to_i64(config.bandwidth))?;
|
||||||
|
self.radio
|
||||||
|
.set_spreading_factor(spreading_factor_to_u8(config.spreading_factor))?;
|
||||||
|
|
||||||
|
self.radio.set_preamble_length(8)?;
|
||||||
|
self.radio.set_lora_sync_word()?;
|
||||||
|
self.radio.set_invert_iq(true)?;
|
||||||
|
self.radio.set_crc(true)?;
|
||||||
|
|
||||||
|
self.radio.set_dio0_rx_done()?;
|
||||||
|
self.radio.set_mode(RadioMode::RxContinuous)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
self.irq.wait_for_rising_edge().await;
|
||||||
|
self.radio.set_mode(RadioMode::Stdby).ok().unwrap();
|
||||||
|
let irq = self.radio.clear_irq().ok().unwrap();
|
||||||
|
if (irq & IRQ::IrqRxDoneMask.addr()) != 0 {
|
||||||
|
let rssi = self.radio.get_packet_rssi().unwrap_or(0) as i16;
|
||||||
|
let snr = self.radio.get_packet_snr().unwrap_or(0.0) as i8;
|
||||||
|
let response = if let Ok(size) = self.radio.read_packet_size() {
|
||||||
|
self.radio.read_packet(buf)?;
|
||||||
|
Ok((size, RxQuality::new(rssi, snr)))
|
||||||
|
} else {
|
||||||
|
Ok((0, RxQuality::new(rssi, snr)))
|
||||||
|
};
|
||||||
|
trace!("RX DONE");
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub struct Sx127xError;
|
||||||
|
|
||||||
|
impl<A, B, C> From<sx127x_lora::Error<A, B, C>> for Sx127xError {
|
||||||
|
fn from(_: sx127x_lora::Error<A, B, C>) -> Self {
|
||||||
|
Sx127xError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spreading_factor_to_u8(sf: SpreadingFactor) -> u8 {
|
||||||
|
match sf {
|
||||||
|
SpreadingFactor::_7 => 7,
|
||||||
|
SpreadingFactor::_8 => 8,
|
||||||
|
SpreadingFactor::_9 => 9,
|
||||||
|
SpreadingFactor::_10 => 10,
|
||||||
|
SpreadingFactor::_11 => 11,
|
||||||
|
SpreadingFactor::_12 => 12,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bandwidth_to_i64(bw: Bandwidth) -> i64 {
|
||||||
|
match bw {
|
||||||
|
Bandwidth::_125KHz => 125_000,
|
||||||
|
Bandwidth::_250KHz => 250_000,
|
||||||
|
Bandwidth::_500KHz => 500_000,
|
||||||
|
}
|
||||||
|
}
|
593
embassy-lora/src/sx127x/sx127x_lora/mod.rs
Normal file
593
embassy-lora/src/sx127x/sx127x_lora/mod.rs
Normal file
|
@ -0,0 +1,593 @@
|
||||||
|
// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0
|
||||||
|
// license
|
||||||
|
//
|
||||||
|
// Modifications made to make the driver work with the rust-lorawan link layer.
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use bit_field::BitField;
|
||||||
|
use embedded_hal::blocking::{
|
||||||
|
delay::DelayMs,
|
||||||
|
spi::{Transfer, Write},
|
||||||
|
};
|
||||||
|
use embedded_hal::digital::v2::OutputPin;
|
||||||
|
use embedded_hal::spi::{Mode, Phase, Polarity};
|
||||||
|
|
||||||
|
mod register;
|
||||||
|
use self::register::PaConfig;
|
||||||
|
use self::register::Register;
|
||||||
|
pub use self::register::IRQ;
|
||||||
|
|
||||||
|
/// Provides the necessary SPI mode configuration for the radio
|
||||||
|
pub const MODE: Mode = Mode {
|
||||||
|
phase: Phase::CaptureOnSecondTransition,
|
||||||
|
polarity: Polarity::IdleHigh,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Provides high-level access to Semtech SX1276/77/78/79 based boards connected to a Raspberry Pi
|
||||||
|
pub struct LoRa<SPI, CS, RESET> {
|
||||||
|
spi: SPI,
|
||||||
|
cs: CS,
|
||||||
|
reset: RESET,
|
||||||
|
pub explicit_header: bool,
|
||||||
|
pub mode: RadioMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
pub enum Error<SPI, CS, RESET> {
|
||||||
|
Uninformative,
|
||||||
|
VersionMismatch(u8),
|
||||||
|
CS(CS),
|
||||||
|
Reset(RESET),
|
||||||
|
SPI(SPI),
|
||||||
|
Transmitting,
|
||||||
|
}
|
||||||
|
|
||||||
|
use super::sx127x_lora::register::{FskDataModulationShaping, FskRampUpRamDown};
|
||||||
|
use Error::*;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "version_0x09"))]
|
||||||
|
const VERSION_CHECK: u8 = 0x12;
|
||||||
|
|
||||||
|
#[cfg(feature = "version_0x09")]
|
||||||
|
const VERSION_CHECK: u8 = 0x09;
|
||||||
|
|
||||||
|
impl<SPI, CS, RESET, E> LoRa<SPI, CS, RESET>
|
||||||
|
where
|
||||||
|
SPI: Transfer<u8, Error = E> + Write<u8, Error = E>,
|
||||||
|
CS: OutputPin,
|
||||||
|
RESET: OutputPin,
|
||||||
|
{
|
||||||
|
/// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time.
|
||||||
|
/// This also preforms a hardware reset of the module and then puts it in standby.
|
||||||
|
pub fn new(spi: SPI, cs: CS, reset: RESET) -> Self {
|
||||||
|
Self {
|
||||||
|
spi,
|
||||||
|
cs,
|
||||||
|
reset,
|
||||||
|
explicit_header: true,
|
||||||
|
mode: RadioMode::Sleep,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset<D: DelayMs<u32>>(
|
||||||
|
&mut self,
|
||||||
|
d: &mut D,
|
||||||
|
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
self.reset.set_low().map_err(Reset)?;
|
||||||
|
d.delay_ms(10_u32);
|
||||||
|
self.reset.set_high().map_err(Reset)?;
|
||||||
|
d.delay_ms(10_u32);
|
||||||
|
let version = self.read_register(Register::RegVersion.addr())?;
|
||||||
|
if version == VERSION_CHECK {
|
||||||
|
self.set_mode(RadioMode::Sleep)?;
|
||||||
|
self.write_register(Register::RegFifoTxBaseAddr.addr(), 0)?;
|
||||||
|
self.write_register(Register::RegFifoRxBaseAddr.addr(), 0)?;
|
||||||
|
let lna = self.read_register(Register::RegLna.addr())?;
|
||||||
|
self.write_register(Register::RegLna.addr(), lna | 0x03)?;
|
||||||
|
self.write_register(Register::RegModemConfig3.addr(), 0x04)?;
|
||||||
|
self.set_tcxo(true)?;
|
||||||
|
self.set_mode(RadioMode::Stdby)?;
|
||||||
|
self.cs.set_high().map_err(CS)?;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::VersionMismatch(version))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transmits up to 255 bytes of data. To avoid the use of an allocator, this takes a fixed 255 u8
|
||||||
|
/// array and a payload size and returns the number of bytes sent if successful.
|
||||||
|
pub fn transmit_payload_busy(
|
||||||
|
&mut self,
|
||||||
|
buffer: [u8; 255],
|
||||||
|
payload_size: usize,
|
||||||
|
) -> Result<usize, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
if self.transmitting()? {
|
||||||
|
Err(Transmitting)
|
||||||
|
} else {
|
||||||
|
self.set_mode(RadioMode::Stdby)?;
|
||||||
|
if self.explicit_header {
|
||||||
|
self.set_explicit_header_mode()?;
|
||||||
|
} else {
|
||||||
|
self.set_implicit_header_mode()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.write_register(Register::RegIrqFlags.addr(), 0)?;
|
||||||
|
self.write_register(Register::RegFifoAddrPtr.addr(), 0)?;
|
||||||
|
self.write_register(Register::RegPayloadLength.addr(), 0)?;
|
||||||
|
for byte in buffer.iter().take(payload_size) {
|
||||||
|
self.write_register(Register::RegFifo.addr(), *byte)?;
|
||||||
|
}
|
||||||
|
self.write_register(Register::RegPayloadLength.addr(), payload_size as u8)?;
|
||||||
|
self.set_mode(RadioMode::Tx)?;
|
||||||
|
while self.transmitting()? {}
|
||||||
|
Ok(payload_size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_dio0_tx_done(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
self.write_register(Register::RegIrqFlagsMask.addr(), 0b1111_0111)?;
|
||||||
|
let mapping = self.read_register(Register::RegDioMapping1.addr())?;
|
||||||
|
self.write_register(Register::RegDioMapping1.addr(), (mapping & 0x3F) | 0x40)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_dio0_rx_done(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
self.write_register(Register::RegIrqFlagsMask.addr(), 0b0001_1111)?;
|
||||||
|
let mapping = self.read_register(Register::RegDioMapping1.addr())?;
|
||||||
|
self.write_register(Register::RegDioMapping1.addr(), mapping & 0x3F)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transmit_payload(
|
||||||
|
&mut self,
|
||||||
|
buffer: &[u8],
|
||||||
|
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
assert!(buffer.len() < 255);
|
||||||
|
if self.transmitting()? {
|
||||||
|
Err(Transmitting)
|
||||||
|
} else {
|
||||||
|
self.set_mode(RadioMode::Stdby)?;
|
||||||
|
if self.explicit_header {
|
||||||
|
self.set_explicit_header_mode()?;
|
||||||
|
} else {
|
||||||
|
self.set_implicit_header_mode()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.write_register(Register::RegIrqFlags.addr(), 0)?;
|
||||||
|
self.write_register(Register::RegFifoAddrPtr.addr(), 0)?;
|
||||||
|
self.write_register(Register::RegPayloadLength.addr(), 0)?;
|
||||||
|
for byte in buffer.iter() {
|
||||||
|
self.write_register(Register::RegFifo.addr(), *byte)?;
|
||||||
|
}
|
||||||
|
self.write_register(Register::RegPayloadLength.addr(), buffer.len() as u8)?;
|
||||||
|
self.set_mode(RadioMode::Tx)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn packet_ready(&mut self) -> Result<bool, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
Ok(self.read_register(Register::RegIrqFlags.addr())?.get_bit(6))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn irq_flags_mask(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
Ok(self.read_register(Register::RegIrqFlagsMask.addr())? as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn irq_flags(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
Ok(self.read_register(Register::RegIrqFlags.addr())? as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_packet_size(&mut self) -> Result<usize, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
let size = self.read_register(Register::RegRxNbBytes.addr())?;
|
||||||
|
Ok(size as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the contents of the fifo as a fixed 255 u8 array. This should only be called is there is a
|
||||||
|
/// new packet ready to be read.
|
||||||
|
pub fn read_packet(
|
||||||
|
&mut self,
|
||||||
|
buffer: &mut [u8],
|
||||||
|
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
self.clear_irq()?;
|
||||||
|
let size = self.read_register(Register::RegRxNbBytes.addr())?;
|
||||||
|
assert!(size as usize <= buffer.len());
|
||||||
|
let fifo_addr = self.read_register(Register::RegFifoRxCurrentAddr.addr())?;
|
||||||
|
self.write_register(Register::RegFifoAddrPtr.addr(), fifo_addr)?;
|
||||||
|
for i in 0..size {
|
||||||
|
let byte = self.read_register(Register::RegFifo.addr())?;
|
||||||
|
buffer[i as usize] = byte;
|
||||||
|
}
|
||||||
|
self.write_register(Register::RegFifoAddrPtr.addr(), 0)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the radio is currently transmitting a packet.
|
||||||
|
pub fn transmitting(&mut self) -> Result<bool, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
if (self.read_register(Register::RegOpMode.addr())? & RadioMode::Tx.addr())
|
||||||
|
== RadioMode::Tx.addr()
|
||||||
|
{
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
if (self.read_register(Register::RegIrqFlags.addr())? & IRQ::IrqTxDoneMask.addr()) == 1
|
||||||
|
{
|
||||||
|
self.write_register(Register::RegIrqFlags.addr(), IRQ::IrqTxDoneMask.addr())?;
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the radio's IRQ registers.
|
||||||
|
pub fn clear_irq(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
let irq_flags = self.read_register(Register::RegIrqFlags.addr())?;
|
||||||
|
self.write_register(Register::RegIrqFlags.addr(), 0xFF)?;
|
||||||
|
Ok(irq_flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the transmit power and pin. Levels can range from 0-14 when the output
|
||||||
|
/// pin = 0(RFO), and form 0-20 when output pin = 1(PaBoost). Power is in dB.
|
||||||
|
/// Default value is `17`.
|
||||||
|
pub fn set_tx_power(
|
||||||
|
&mut self,
|
||||||
|
mut level: i32,
|
||||||
|
output_pin: u8,
|
||||||
|
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
if PaConfig::PaOutputRfoPin.addr() == output_pin {
|
||||||
|
// RFO
|
||||||
|
if level < 0 {
|
||||||
|
level = 0;
|
||||||
|
} else if level > 14 {
|
||||||
|
level = 14;
|
||||||
|
}
|
||||||
|
self.write_register(Register::RegPaConfig.addr(), (0x70 | level) as u8)
|
||||||
|
} else {
|
||||||
|
// PA BOOST
|
||||||
|
if level > 17 {
|
||||||
|
if level > 20 {
|
||||||
|
level = 20;
|
||||||
|
}
|
||||||
|
// subtract 3 from level, so 18 - 20 maps to 15 - 17
|
||||||
|
level -= 3;
|
||||||
|
|
||||||
|
// High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.)
|
||||||
|
self.write_register(Register::RegPaDac.addr(), 0x87)?;
|
||||||
|
self.set_ocp(140)?;
|
||||||
|
} else {
|
||||||
|
if level < 2 {
|
||||||
|
level = 2;
|
||||||
|
}
|
||||||
|
//Default value PA_HF/LF or +17dBm
|
||||||
|
self.write_register(Register::RegPaDac.addr(), 0x84)?;
|
||||||
|
self.set_ocp(100)?;
|
||||||
|
}
|
||||||
|
level -= 2;
|
||||||
|
self.write_register(
|
||||||
|
Register::RegPaConfig.addr(),
|
||||||
|
PaConfig::PaBoost.addr() | level as u8,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_modem_stat(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
Ok(self.read_register(Register::RegModemStat.addr())? as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the over current protection on the radio(mA).
|
||||||
|
pub fn set_ocp(&mut self, ma: u8) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
let mut ocp_trim: u8 = 27;
|
||||||
|
|
||||||
|
if ma <= 120 {
|
||||||
|
ocp_trim = (ma - 45) / 5;
|
||||||
|
} else if ma <= 240 {
|
||||||
|
ocp_trim = (ma + 30) / 10;
|
||||||
|
}
|
||||||
|
self.write_register(Register::RegOcp.addr(), 0x20 | (0x1F & ocp_trim))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the state of the radio. Default mode after initiation is `Standby`.
|
||||||
|
pub fn set_mode(&mut self, mode: RadioMode) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
if self.explicit_header {
|
||||||
|
self.set_explicit_header_mode()?;
|
||||||
|
} else {
|
||||||
|
self.set_implicit_header_mode()?;
|
||||||
|
}
|
||||||
|
self.write_register(
|
||||||
|
Register::RegOpMode.addr(),
|
||||||
|
RadioMode::LongRangeMode.addr() | mode.addr(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.mode = mode;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_payload_length(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
self.write_register(Register::RegPayloadLength.addr(), 0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the frequency of the radio. Values are in megahertz.
|
||||||
|
/// I.E. 915 MHz must be used for North America. Check regulation for your area.
|
||||||
|
pub fn set_frequency(&mut self, freq: u32) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
const FREQ_STEP: f64 = 61.03515625;
|
||||||
|
// calculate register values
|
||||||
|
let frf = (freq as f64 / FREQ_STEP) as u32;
|
||||||
|
// write registers
|
||||||
|
self.write_register(
|
||||||
|
Register::RegFrfMsb.addr(),
|
||||||
|
((frf & 0x00FF_0000) >> 16) as u8,
|
||||||
|
)?;
|
||||||
|
self.write_register(Register::RegFrfMid.addr(), ((frf & 0x0000_FF00) >> 8) as u8)?;
|
||||||
|
self.write_register(Register::RegFrfLsb.addr(), (frf & 0x0000_00FF) as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the radio to use an explicit header. Default state is `ON`.
|
||||||
|
fn set_explicit_header_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr())?;
|
||||||
|
self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0xfe)?;
|
||||||
|
self.explicit_header = true;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the radio to use an implicit header. Default state is `OFF`.
|
||||||
|
fn set_implicit_header_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr())?;
|
||||||
|
self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0x01)?;
|
||||||
|
self.explicit_header = false;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the spreading factor of the radio. Supported values are between 6 and 12.
|
||||||
|
/// If a spreading factor of 6 is set, implicit header mode must be used to transmit
|
||||||
|
/// and receive packets. Default value is `7`.
|
||||||
|
pub fn set_spreading_factor(
|
||||||
|
&mut self,
|
||||||
|
mut sf: u8,
|
||||||
|
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
if sf < 6 {
|
||||||
|
sf = 6;
|
||||||
|
} else if sf > 12 {
|
||||||
|
sf = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
if sf == 6 {
|
||||||
|
self.write_register(Register::RegDetectionOptimize.addr(), 0xc5)?;
|
||||||
|
self.write_register(Register::RegDetectionThreshold.addr(), 0x0c)?;
|
||||||
|
} else {
|
||||||
|
self.write_register(Register::RegDetectionOptimize.addr(), 0xc3)?;
|
||||||
|
self.write_register(Register::RegDetectionThreshold.addr(), 0x0a)?;
|
||||||
|
}
|
||||||
|
let modem_config_2 = self.read_register(Register::RegModemConfig2.addr())?;
|
||||||
|
self.write_register(
|
||||||
|
Register::RegModemConfig2.addr(),
|
||||||
|
(modem_config_2 & 0x0f) | ((sf << 4) & 0xf0),
|
||||||
|
)?;
|
||||||
|
self.set_ldo_flag()?;
|
||||||
|
|
||||||
|
self.write_register(Register::RegSymbTimeoutLsb.addr(), 0x05)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_tcxo(&mut self, external: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
if external {
|
||||||
|
self.write_register(Register::RegTcxo.addr(), 0x10)
|
||||||
|
} else {
|
||||||
|
self.write_register(Register::RegTcxo.addr(), 0x00)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the signal bandwidth of the radio. Supported values are: `7800 Hz`, `10400 Hz`,
|
||||||
|
/// `15600 Hz`, `20800 Hz`, `31250 Hz`,`41700 Hz` ,`62500 Hz`,`125000 Hz` and `250000 Hz`
|
||||||
|
/// Default value is `125000 Hz`
|
||||||
|
pub fn set_signal_bandwidth(
|
||||||
|
&mut self,
|
||||||
|
sbw: i64,
|
||||||
|
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
let bw: i64 = match sbw {
|
||||||
|
7_800 => 0,
|
||||||
|
10_400 => 1,
|
||||||
|
15_600 => 2,
|
||||||
|
20_800 => 3,
|
||||||
|
31_250 => 4,
|
||||||
|
41_700 => 5,
|
||||||
|
62_500 => 6,
|
||||||
|
125_000 => 7,
|
||||||
|
250_000 => 8,
|
||||||
|
_ => 9,
|
||||||
|
};
|
||||||
|
let modem_config_1 = self.read_register(Register::RegModemConfig1.addr())?;
|
||||||
|
self.write_register(
|
||||||
|
Register::RegModemConfig1.addr(),
|
||||||
|
(modem_config_1 & 0x0f) | ((bw << 4) as u8),
|
||||||
|
)?;
|
||||||
|
self.set_ldo_flag()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the coding rate of the radio with the numerator fixed at 4. Supported values
|
||||||
|
/// are between `5` and `8`, these correspond to coding rates of `4/5` and `4/8`.
|
||||||
|
/// Default value is `5`.
|
||||||
|
pub fn set_coding_rate_4(
|
||||||
|
&mut self,
|
||||||
|
mut denominator: u8,
|
||||||
|
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
if denominator < 5 {
|
||||||
|
denominator = 5;
|
||||||
|
} else if denominator > 8 {
|
||||||
|
denominator = 8;
|
||||||
|
}
|
||||||
|
let cr = denominator - 4;
|
||||||
|
let modem_config_1 = self.read_register(Register::RegModemConfig1.addr())?;
|
||||||
|
self.write_register(
|
||||||
|
Register::RegModemConfig1.addr(),
|
||||||
|
(modem_config_1 & 0xf1) | (cr << 1),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the preamble length of the radio. Values are between 6 and 65535.
|
||||||
|
/// Default value is `8`.
|
||||||
|
pub fn set_preamble_length(
|
||||||
|
&mut self,
|
||||||
|
length: i64,
|
||||||
|
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
self.write_register(Register::RegPreambleMsb.addr(), (length >> 8) as u8)?;
|
||||||
|
self.write_register(Register::RegPreambleLsb.addr(), length as u8)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enables are disables the radio's CRC check. Default value is `false`.
|
||||||
|
pub fn set_crc(&mut self, value: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
let modem_config_2 = self.read_register(Register::RegModemConfig2.addr())?;
|
||||||
|
if value {
|
||||||
|
self.write_register(Register::RegModemConfig2.addr(), modem_config_2 | 0x04)
|
||||||
|
} else {
|
||||||
|
self.write_register(Register::RegModemConfig2.addr(), modem_config_2 & 0xfb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inverts the radio's IQ signals. Default value is `false`.
|
||||||
|
pub fn set_invert_iq(&mut self, value: bool) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
if value {
|
||||||
|
self.write_register(Register::RegInvertiq.addr(), 0x66)?;
|
||||||
|
self.write_register(Register::RegInvertiq2.addr(), 0x19)
|
||||||
|
} else {
|
||||||
|
self.write_register(Register::RegInvertiq.addr(), 0x27)?;
|
||||||
|
self.write_register(Register::RegInvertiq2.addr(), 0x1d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the spreading factor of the radio.
|
||||||
|
pub fn get_spreading_factor(&mut self) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
Ok(self.read_register(Register::RegModemConfig2.addr())? >> 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the signal bandwidth of the radio.
|
||||||
|
pub fn get_signal_bandwidth(&mut self) -> Result<i64, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
let bw = self.read_register(Register::RegModemConfig1.addr())? >> 4;
|
||||||
|
let bw = match bw {
|
||||||
|
0 => 7_800,
|
||||||
|
1 => 10_400,
|
||||||
|
2 => 15_600,
|
||||||
|
3 => 20_800,
|
||||||
|
4 => 31_250,
|
||||||
|
5 => 41_700,
|
||||||
|
6 => 62_500,
|
||||||
|
7 => 125_000,
|
||||||
|
8 => 250_000,
|
||||||
|
9 => 500_000,
|
||||||
|
_ => -1,
|
||||||
|
};
|
||||||
|
Ok(bw)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the RSSI of the last received packet.
|
||||||
|
pub fn get_packet_rssi(&mut self) -> Result<i32, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
Ok(i32::from(self.read_register(Register::RegPktRssiValue.addr())?) - 157)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the signal to noise radio of the the last received packet.
|
||||||
|
pub fn get_packet_snr(&mut self) -> Result<f64, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
Ok(f64::from(
|
||||||
|
self.read_register(Register::RegPktSnrValue.addr())?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the frequency error of the last received packet in Hz.
|
||||||
|
pub fn get_packet_frequency_error(&mut self) -> Result<i64, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
let mut freq_error: i32;
|
||||||
|
freq_error = i32::from(self.read_register(Register::RegFreqErrorMsb.addr())? & 0x7);
|
||||||
|
freq_error <<= 8i64;
|
||||||
|
freq_error += i32::from(self.read_register(Register::RegFreqErrorMid.addr())?);
|
||||||
|
freq_error <<= 8i64;
|
||||||
|
freq_error += i32::from(self.read_register(Register::RegFreqErrorLsb.addr())?);
|
||||||
|
|
||||||
|
let f_xtal = 32_000_000; // FXOSC: crystal oscillator (XTAL) frequency (2.5. Chip Specification, p. 14)
|
||||||
|
let f_error = ((f64::from(freq_error) * (1i64 << 24) as f64) / f64::from(f_xtal))
|
||||||
|
* (self.get_signal_bandwidth()? as f64 / 500_000.0f64); // p. 37
|
||||||
|
Ok(f_error as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_ldo_flag(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
let sw = self.get_signal_bandwidth()?;
|
||||||
|
// Section 4.1.1.5
|
||||||
|
let symbol_duration = 1000 / (sw / ((1_i64) << self.get_spreading_factor()?));
|
||||||
|
|
||||||
|
// Section 4.1.1.6
|
||||||
|
let ldo_on = symbol_duration > 16;
|
||||||
|
|
||||||
|
let mut config_3 = self.read_register(Register::RegModemConfig3.addr())?;
|
||||||
|
config_3.set_bit(3, ldo_on);
|
||||||
|
//config_3.set_bit(2, true);
|
||||||
|
self.write_register(Register::RegModemConfig3.addr(), config_3)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_register(&mut self, reg: u8) -> Result<u8, Error<E, CS::Error, RESET::Error>> {
|
||||||
|
self.cs.set_low().map_err(CS)?;
|
||||||
|
|
||||||
|
let mut buffer = [reg & 0x7f, 0];
|
||||||
|
let transfer = self.spi.transfer(&mut buffer).map_err(SPI)?;
|
||||||
|
self.cs.set_high().map_err(CS)?;
|
||||||
|
Ok(transfer[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_register(
|
||||||
|
&mut self,
|
||||||
|
reg: u8,
|
||||||
|
byte: u8,
|
||||||
|
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
self.cs.set_low().map_err(CS)?;
|
||||||
|
|
||||||
|
let buffer = [reg | 0x80, byte];
|
||||||
|
self.spi.write(&buffer).map_err(SPI)?;
|
||||||
|
self.cs.set_high().map_err(CS)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put_in_fsk_mode(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
// Put in FSK mode
|
||||||
|
let mut op_mode = 0;
|
||||||
|
op_mode
|
||||||
|
.set_bit(7, false) // FSK mode
|
||||||
|
.set_bits(5..6, 0x00) // FSK modulation
|
||||||
|
.set_bit(3, false) //Low freq registers
|
||||||
|
.set_bits(0..2, 0b011); // Mode
|
||||||
|
|
||||||
|
self.write_register(Register::RegOpMode as u8, op_mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_fsk_pa_ramp(
|
||||||
|
&mut self,
|
||||||
|
modulation_shaping: FskDataModulationShaping,
|
||||||
|
ramp: FskRampUpRamDown,
|
||||||
|
) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
let mut pa_ramp = 0;
|
||||||
|
pa_ramp
|
||||||
|
.set_bits(5..6, modulation_shaping as u8)
|
||||||
|
.set_bits(0..3, ramp as u8);
|
||||||
|
|
||||||
|
self.write_register(Register::RegPaRamp as u8, pa_ramp)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_lora_pa_ramp(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
self.write_register(Register::RegPaRamp as u8, 0b1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_lora_sync_word(&mut self) -> Result<(), Error<E, CS::Error, RESET::Error>> {
|
||||||
|
self.write_register(Register::RegSyncWord as u8, 0x34)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Modes of the radio and their corresponding register values.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum RadioMode {
|
||||||
|
LongRangeMode = 0x80,
|
||||||
|
Sleep = 0x00,
|
||||||
|
Stdby = 0x01,
|
||||||
|
Tx = 0x03,
|
||||||
|
RxContinuous = 0x05,
|
||||||
|
RxSingle = 0x06,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RadioMode {
|
||||||
|
/// Returns the address of the mode.
|
||||||
|
pub fn addr(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
107
embassy-lora/src/sx127x/sx127x_lora/register.rs
Normal file
107
embassy-lora/src/sx127x/sx127x_lora/register.rs
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0
|
||||||
|
// license
|
||||||
|
//
|
||||||
|
// Modifications made to make the driver work with the rust-lorawan link layer.
|
||||||
|
#![allow(dead_code, clippy::enum_variant_names)]
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum Register {
|
||||||
|
RegFifo = 0x00,
|
||||||
|
RegOpMode = 0x01,
|
||||||
|
RegFrfMsb = 0x06,
|
||||||
|
RegFrfMid = 0x07,
|
||||||
|
RegFrfLsb = 0x08,
|
||||||
|
RegPaConfig = 0x09,
|
||||||
|
RegPaRamp = 0x0a,
|
||||||
|
RegOcp = 0x0b,
|
||||||
|
RegLna = 0x0c,
|
||||||
|
RegFifoAddrPtr = 0x0d,
|
||||||
|
RegFifoTxBaseAddr = 0x0e,
|
||||||
|
RegFifoRxBaseAddr = 0x0f,
|
||||||
|
RegFifoRxCurrentAddr = 0x10,
|
||||||
|
RegIrqFlagsMask = 0x11,
|
||||||
|
RegIrqFlags = 0x12,
|
||||||
|
RegRxNbBytes = 0x13,
|
||||||
|
RegPktSnrValue = 0x19,
|
||||||
|
RegModemStat = 0x18,
|
||||||
|
RegPktRssiValue = 0x1a,
|
||||||
|
RegModemConfig1 = 0x1d,
|
||||||
|
RegModemConfig2 = 0x1e,
|
||||||
|
RegSymbTimeoutLsb = 0x1f,
|
||||||
|
RegPreambleMsb = 0x20,
|
||||||
|
RegPreambleLsb = 0x21,
|
||||||
|
RegPayloadLength = 0x22,
|
||||||
|
RegMaxPayloadLength = 0x23,
|
||||||
|
RegModemConfig3 = 0x26,
|
||||||
|
RegFreqErrorMsb = 0x28,
|
||||||
|
RegFreqErrorMid = 0x29,
|
||||||
|
RegFreqErrorLsb = 0x2a,
|
||||||
|
RegRssiWideband = 0x2c,
|
||||||
|
RegDetectionOptimize = 0x31,
|
||||||
|
RegInvertiq = 0x33,
|
||||||
|
RegDetectionThreshold = 0x37,
|
||||||
|
RegSyncWord = 0x39,
|
||||||
|
RegInvertiq2 = 0x3b,
|
||||||
|
RegDioMapping1 = 0x40,
|
||||||
|
RegVersion = 0x42,
|
||||||
|
RegTcxo = 0x4b,
|
||||||
|
RegPaDac = 0x4d,
|
||||||
|
}
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum PaConfig {
|
||||||
|
PaBoost = 0x80,
|
||||||
|
PaOutputRfoPin = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum IRQ {
|
||||||
|
IrqTxDoneMask = 0x08,
|
||||||
|
IrqPayloadCrcErrorMask = 0x20,
|
||||||
|
IrqRxDoneMask = 0x40,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Register {
|
||||||
|
pub fn addr(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PaConfig {
|
||||||
|
pub fn addr(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IRQ {
|
||||||
|
pub fn addr(self) -> u8 {
|
||||||
|
self as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum FskDataModulationShaping {
|
||||||
|
None = 1,
|
||||||
|
GaussianBt1d0 = 2,
|
||||||
|
GaussianBt0d5 = 10,
|
||||||
|
GaussianBt0d3 = 11,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum FskRampUpRamDown {
|
||||||
|
_3d4ms = 0b000,
|
||||||
|
_2ms = 0b0001,
|
||||||
|
_1ms = 0b0010,
|
||||||
|
_500us = 0b0011,
|
||||||
|
_250us = 0b0100,
|
||||||
|
_125us = 0b0101,
|
||||||
|
_100us = 0b0110,
|
||||||
|
_62us = 0b0111,
|
||||||
|
_50us = 0b1000,
|
||||||
|
_40us = 0b1001,
|
||||||
|
_31us = 0b1010,
|
||||||
|
_25us = 0b1011,
|
||||||
|
_20us = 0b1100,
|
||||||
|
_15us = 0b1101,
|
||||||
|
_12us = 0b1110,
|
||||||
|
_10us = 0b1111,
|
||||||
|
}
|
|
@ -23,6 +23,10 @@ embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["
|
||||||
embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" }
|
embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" }
|
||||||
embassy-macros = { path = "../../embassy-macros" }
|
embassy-macros = { path = "../../embassy-macros" }
|
||||||
|
|
||||||
|
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx127x", "time"] }
|
||||||
|
lorawan-device = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["async"] }
|
||||||
|
lorawan-encoding = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["default-crypto"] }
|
||||||
|
|
||||||
defmt = "0.2.3"
|
defmt = "0.2.3"
|
||||||
defmt-rtt = "0.2.0"
|
defmt-rtt = "0.2.0"
|
||||||
|
|
||||||
|
|
104
examples/stm32l0/src/bin/lorawan.rs
Normal file
104
examples/stm32l0/src/bin/lorawan.rs
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
//! This example runs on the STM32 LoRa Discovery board which has a builtin Semtech Sx127x radio
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![macro_use]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
#![feature(generic_associated_types)]
|
||||||
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
|
#[path = "../example_common.rs"]
|
||||||
|
mod example_common;
|
||||||
|
|
||||||
|
use embassy_lora::{sx127x::*, LoraTimer};
|
||||||
|
use embassy_stm32::{
|
||||||
|
dbgmcu::Dbgmcu,
|
||||||
|
dma::NoDma,
|
||||||
|
exti::ExtiInput,
|
||||||
|
gpio::{Input, Level, Output, Pull, Speed},
|
||||||
|
rcc,
|
||||||
|
rng::Rng,
|
||||||
|
spi,
|
||||||
|
time::U32Ext,
|
||||||
|
Peripherals,
|
||||||
|
};
|
||||||
|
use lorawan_device::async_device::{region, Device, JoinMode};
|
||||||
|
use lorawan_encoding::default_crypto::DefaultFactory as Crypto;
|
||||||
|
|
||||||
|
fn config() -> embassy_stm32::Config {
|
||||||
|
let mut config = embassy_stm32::Config::default();
|
||||||
|
config.rcc = config.rcc.clock_src(embassy_stm32::rcc::ClockSrc::HSI16);
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy::main(config = "config()")]
|
||||||
|
async fn main(_spawner: embassy::executor::Spawner, mut p: Peripherals) {
|
||||||
|
unsafe {
|
||||||
|
Dbgmcu::enable_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rcc = rcc::Rcc::new(p.RCC);
|
||||||
|
let _ = rcc.enable_hsi48(&mut p.SYSCFG, p.CRS);
|
||||||
|
|
||||||
|
// SPI for sx127x
|
||||||
|
let spi = spi::Spi::new(
|
||||||
|
p.SPI1,
|
||||||
|
p.PB3,
|
||||||
|
p.PA7,
|
||||||
|
p.PA6,
|
||||||
|
NoDma,
|
||||||
|
NoDma,
|
||||||
|
200_000.hz(),
|
||||||
|
spi::Config::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let cs = Output::new(p.PA15, Level::High, Speed::Low);
|
||||||
|
let reset = Output::new(p.PC0, Level::High, Speed::Low);
|
||||||
|
let _ = Input::new(p.PB1, Pull::None);
|
||||||
|
|
||||||
|
let ready = Input::new(p.PB4, Pull::Up);
|
||||||
|
let ready_pin = ExtiInput::new(ready, p.EXTI4);
|
||||||
|
|
||||||
|
let radio = Sx127xRadio::new(
|
||||||
|
spi,
|
||||||
|
cs,
|
||||||
|
reset,
|
||||||
|
ready_pin,
|
||||||
|
DummySwitch,
|
||||||
|
&mut embassy::time::Delay,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let region = region::EU868::default().into();
|
||||||
|
let mut radio_buffer = [0; 256];
|
||||||
|
let mut device: Device<'_, _, Crypto, _, _> = Device::new(
|
||||||
|
region,
|
||||||
|
radio,
|
||||||
|
LoraTimer,
|
||||||
|
Rng::new(p.RNG),
|
||||||
|
&mut radio_buffer[..],
|
||||||
|
);
|
||||||
|
|
||||||
|
defmt::info!("Joining LoRaWAN network");
|
||||||
|
|
||||||
|
// TODO: Adjust the EUI and Keys according to your network credentials
|
||||||
|
device
|
||||||
|
.join(&JoinMode::OTAA {
|
||||||
|
deveui: [0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
appeui: [0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
defmt::info!("LoRaWAN network joined");
|
||||||
|
|
||||||
|
defmt::info!("Sending 'PING'");
|
||||||
|
device.send(b"PING", 1, false).await.ok().unwrap();
|
||||||
|
defmt::info!("Message sent!");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DummySwitch;
|
||||||
|
impl RadioSwitch for DummySwitch {
|
||||||
|
fn set_rx(&mut self) {}
|
||||||
|
fn set_tx(&mut self) {}
|
||||||
|
}
|
|
@ -19,8 +19,12 @@ defmt-error = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-trace"] }
|
embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt", "defmt-trace"] }
|
||||||
embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] }
|
embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] }
|
||||||
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "defmt-trace", "stm32wl55jc_cm4", "time-driver-tim2", "memory-x", "subghz"] }
|
embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "defmt-trace", "stm32wl55jc_cm4", "time-driver-tim2", "memory-x", "subghz", "unstable-pac"] }
|
||||||
embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" }
|
embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" }
|
||||||
|
embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time"] }
|
||||||
|
|
||||||
|
lorawan-device = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["async"] }
|
||||||
|
lorawan-encoding = { git = "https://github.com/lulf/rust-lorawan.git", rev = "a373d06fa8858d251bc70d5789cebcd9a638ec42", default-features = false, features = ["default-crypto"] }
|
||||||
|
|
||||||
defmt = "0.2.3"
|
defmt = "0.2.3"
|
||||||
defmt-rtt = "0.2.0"
|
defmt-rtt = "0.2.0"
|
||||||
|
|
79
examples/stm32wl55/src/bin/lorawan.rs
Normal file
79
examples/stm32wl55/src/bin/lorawan.rs
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
#![macro_use]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
#![feature(generic_associated_types)]
|
||||||
|
#![feature(type_alias_impl_trait)]
|
||||||
|
|
||||||
|
#[path = "../example_common.rs"]
|
||||||
|
mod example_common;
|
||||||
|
|
||||||
|
use embassy_lora::{stm32wl::*, LoraTimer};
|
||||||
|
use embassy_stm32::{
|
||||||
|
dbgmcu::Dbgmcu,
|
||||||
|
dma::NoDma,
|
||||||
|
gpio::{Level, Output, Pin, Speed},
|
||||||
|
interrupt, pac, rcc,
|
||||||
|
rng::Rng,
|
||||||
|
subghz::*,
|
||||||
|
Peripherals,
|
||||||
|
};
|
||||||
|
use lorawan_device::async_device::{region, Device, JoinMode};
|
||||||
|
use lorawan_encoding::default_crypto::DefaultFactory as Crypto;
|
||||||
|
|
||||||
|
fn config() -> embassy_stm32::Config {
|
||||||
|
let mut config = embassy_stm32::Config::default();
|
||||||
|
config.rcc = config.rcc.clock_src(embassy_stm32::rcc::ClockSrc::HSI16);
|
||||||
|
config
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy::main(config = "config()")]
|
||||||
|
async fn main(_spawner: embassy::executor::Spawner, p: Peripherals) {
|
||||||
|
unsafe {
|
||||||
|
Dbgmcu::enable_all();
|
||||||
|
let mut rcc = rcc::Rcc::new(p.RCC);
|
||||||
|
rcc.enable_lsi();
|
||||||
|
pac::RCC.ccipr().modify(|w| {
|
||||||
|
w.set_rngsel(0b01);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctrl1 = Output::new(p.PC3.degrade(), Level::High, Speed::High);
|
||||||
|
let ctrl2 = Output::new(p.PC4.degrade(), Level::High, Speed::High);
|
||||||
|
let ctrl3 = Output::new(p.PC5.degrade(), Level::High, Speed::High);
|
||||||
|
let rfs = RadioSwitch::new(ctrl1, ctrl2, ctrl3);
|
||||||
|
|
||||||
|
let radio = SubGhz::new(p.SUBGHZSPI, p.PA5, p.PA7, p.PA6, NoDma, NoDma);
|
||||||
|
|
||||||
|
let irq = interrupt::take!(SUBGHZ_RADIO);
|
||||||
|
static mut RADIO_STATE: SubGhzState<'static> = SubGhzState::new();
|
||||||
|
let radio = unsafe { SubGhzRadio::new(&mut RADIO_STATE, radio, rfs, irq) };
|
||||||
|
|
||||||
|
let region = region::EU868::default().into();
|
||||||
|
let mut radio_buffer = [0; 256];
|
||||||
|
let mut device: Device<'_, _, Crypto, _, _> = Device::new(
|
||||||
|
region,
|
||||||
|
radio,
|
||||||
|
LoraTimer,
|
||||||
|
Rng::new(p.RNG),
|
||||||
|
&mut radio_buffer[..],
|
||||||
|
);
|
||||||
|
|
||||||
|
defmt::info!("Joining LoRaWAN network");
|
||||||
|
|
||||||
|
// TODO: Adjust the EUI and Keys according to your network credentials
|
||||||
|
device
|
||||||
|
.join(&JoinMode::OTAA {
|
||||||
|
deveui: [0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
appeui: [0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
defmt::info!("LoRaWAN network joined");
|
||||||
|
|
||||||
|
defmt::info!("Sending 'PING'");
|
||||||
|
device.send(b"PING", 1, false).await.ok().unwrap();
|
||||||
|
defmt::info!("Message sent!");
|
||||||
|
}
|
Loading…
Reference in a new issue