Merge pull request #410 from lulf/embassy-lora

Add embassy-lora crate
This commit is contained in:
Dario Nieuwenhuis 2021-10-10 21:23:02 +02:00 committed by GitHub
commit 1c4c813255
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1742 additions and 1 deletions

33
embassy-lora/Cargo.toml Normal file
View 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
View 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
View 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))
}
}

View 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,
}
}

View 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,
}
}

View 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
}
}

View 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,
}

View file

@ -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"

View 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) {}
}

View file

@ -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"

View 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!");
}