refractor into proper driver with abstractions and Radio struct, LoRa TX and RX working
This commit is contained in:
@@ -5,4 +5,4 @@ runner = "probe-rs run --chip STM32WLE5JC"
|
||||
target = "thumbv7em-none-eabi"
|
||||
|
||||
[env]
|
||||
DEFMT_LOG = "trace"
|
||||
DEFMT_LOG = "debug"
|
||||
|
||||
19
src/error.rs
Normal file
19
src/error.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
#[derive(Debug, Clone, Copy, defmt::Format)]
|
||||
pub enum RadioError {
|
||||
/// SPI comms failed
|
||||
Spi,
|
||||
/// Radio busy (polling rfbusys)
|
||||
Busy,
|
||||
/// Timeout of tx or rx
|
||||
Timeout,
|
||||
/// Rx packet had bad CRC
|
||||
CrcInvalid,
|
||||
/// Header was invalid
|
||||
HeaderInvalid,
|
||||
/// Payload too large
|
||||
PayloadTooLarge,
|
||||
/// Invalid configuration (e.g. power out of range for selected PA)
|
||||
InvalidConfig,
|
||||
/// Command not allowed in the current radio state
|
||||
InvalidState,
|
||||
}
|
||||
10
src/lib.rs
Normal file
10
src/lib.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
#![no_std]
|
||||
#![allow(async_fn_in_trait)]
|
||||
|
||||
pub mod error;
|
||||
pub mod modulations;
|
||||
pub mod radio;
|
||||
pub mod spi;
|
||||
pub mod traits;
|
||||
|
||||
pub use error::RadioError;
|
||||
178
src/main.rs
178
src/main.rs
@@ -1,67 +1,24 @@
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use defmt::{debug, trace};
|
||||
use defmt::{error, info, warn};
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_stm32::{Config, gpio::{Level, Output, Speed}, pac, rcc::{MSIRange, Sysclk, mux}, spi::Spi};
|
||||
use embassy_stm32::{
|
||||
Config,
|
||||
gpio::{Level, Output, Speed},
|
||||
rcc::{MSIRange, Sysclk, mux},
|
||||
spi::Spi,
|
||||
};
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_hal_async::spi::{ErrorType, Operation, SpiBus, SpiDevice};
|
||||
use stm32wle5jc_radio::{
|
||||
RadioError,
|
||||
modulations::lora::{Bandwidth, LoraConfig, LoraRadio, SpreadingFactor},
|
||||
radio::{PaSelection, Radio},
|
||||
spi::SubGhzSpiDevice,
|
||||
traits::{Configure, Receive, Transmit},
|
||||
};
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
/// Wrapper for the sub-GHz SPI device
|
||||
struct SubGhzSpiDevice<T>(T);
|
||||
|
||||
impl<T: SpiBus> ErrorType for SubGhzSpiDevice<T> {
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
/// This works as a translation layer between normal SPI transactions and sub-GHz device SPI
|
||||
/// transactions. Everything above this layer sees it like a normal SPI device!
|
||||
impl<T: SpiBus> SpiDevice for SubGhzSpiDevice<T> {
|
||||
/// Perform a transaction on the sub-GHz device
|
||||
async fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> {
|
||||
// Pull NSS low to allow SPI comms
|
||||
pac::PWR.subghzspicr().modify(|w| w.set_nss(false));
|
||||
trace!("NSS low");
|
||||
for operation in operations {
|
||||
match operation {
|
||||
Operation::Read(buf) => {
|
||||
self.0.read(buf).await?;
|
||||
trace!("Read {:x}", buf);
|
||||
},
|
||||
Operation::Write(buf) => {
|
||||
self.0.write(buf).await?;
|
||||
trace!("Wrote {:x}", buf);
|
||||
},
|
||||
Operation::Transfer(read, write) => {
|
||||
self.0.transfer(read, write).await?;
|
||||
trace!("Read {:x} wrote {:x}", read, write);
|
||||
},
|
||||
Operation::TransferInPlace(buf) => {
|
||||
self.0.transfer_in_place(buf).await?;
|
||||
trace!("Read+wrote {:x}", buf);
|
||||
},
|
||||
Operation::DelayNs(_) => {}
|
||||
}
|
||||
}
|
||||
// Pull NSS high
|
||||
pac::PWR.subghzspicr().modify(|w| w.set_nss(true));
|
||||
trace!("NSS high");
|
||||
// Poll BUSY flag until it's done
|
||||
while pac::PWR.sr2().read().rfbusys() {}
|
||||
trace!("BUSY flag clear");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn reset_radio() {
|
||||
debug!("Resetting the radio");
|
||||
pac::RCC.csr().modify(|w| w.set_rfrst(true));
|
||||
pac::RCC.csr().modify(|w| w.set_rfrst(false));
|
||||
Timer::after_millis(1).await;
|
||||
debug!("Radio reset finished");
|
||||
}
|
||||
|
||||
#[embassy_executor::main]
|
||||
async fn main(_spawner: Spawner) {
|
||||
let mut config = Config::default();
|
||||
@@ -73,87 +30,38 @@ async fn main(_spawner: Spawner) {
|
||||
}
|
||||
let p = embassy_stm32::init(config);
|
||||
|
||||
let mut spi = SubGhzSpiDevice(Spi::new_subghz(p.SUBGHZSPI, p.DMA1_CH1, p.DMA1_CH2));
|
||||
reset_radio().await;
|
||||
let spi = SubGhzSpiDevice(Spi::new_subghz(p.SUBGHZSPI, p.DMA1_CH1, p.DMA1_CH2));
|
||||
let rf_tx = Output::new(p.PC4, Level::Low, Speed::High);
|
||||
let rf_rx = Output::new(p.PC5, Level::Low, Speed::High);
|
||||
let rf_en = Output::new(p.PC3, Level::Low, Speed::High);
|
||||
let mut radio = Radio::new(spi, rf_tx, rf_rx, rf_en);
|
||||
radio.init().await.unwrap();
|
||||
|
||||
debug!("Writing SetStandby");
|
||||
let _ = spi.write(&[0x80, 0x00]).await;
|
||||
debug!("Radio in standby!");
|
||||
let mut lora = LoraRadio::new(&mut radio);
|
||||
lora.configure(&LoraConfig {
|
||||
frequency: 868_100_000,
|
||||
sf: SpreadingFactor::SF9,
|
||||
bw: Bandwidth::Bw7_8kHz,
|
||||
pa: PaSelection::HighPower,
|
||||
power_dbm: 22,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// SetDIO3AsTCXOCtrl - 1.7V, 5ms timeout
|
||||
debug!("SetDIO3AsTCXOCtrl");
|
||||
let _ = spi.write(&[0x97, 0x01, 0x00, 0x01, 0x45]).await;
|
||||
// Calibrate - all blocks (RC64k, RC13M, PLL, ADC pulse, ADC bulk N, ADC bulk P, image)
|
||||
debug!("Calibrate");
|
||||
let _ = spi.write(&[0x89, 0x7F]).await;
|
||||
// CalibrateImage - for 863-870 MHz band
|
||||
debug!("CalibrateImage");
|
||||
let _ = spi.write(&[0x98, 0xD7, 0xDB]).await;
|
||||
// SetBufferBaseAddress
|
||||
debug!("SetBufferBaseAddress");
|
||||
let _ = spi.write(&[0x8f, 0x00, 0x00]).await;
|
||||
// WriteBuffer (max 255 bytes, wraps around after that)
|
||||
debug!("WriteBuffer");
|
||||
let _ = spi.write(&[0x0e, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05]).await;
|
||||
// SetPacketType to LoRa
|
||||
debug!("LoRa SetPacketType");
|
||||
let _ = spi.write(&[0x8a, 0x01]).await;
|
||||
// SetPacketParam - 8 preamble length, explicit header, 5-byte payload,
|
||||
// CRC enabled, standard IQ setup
|
||||
debug!("SetPacketParam");
|
||||
let _ = spi.write(&[0x8c, 0x00, 0x08, 0x00, 0x05, 0x01, 0x00]).await;
|
||||
// Probably redundant, but:
|
||||
// Defining LoRa sync word (public network) with WriteRegister to SUBGHZ_LSYNCR (0x740)
|
||||
debug!("WriteRegister to SUBGHZ_LSYNCR");
|
||||
let _ = spi.write(&[0x0D, 0x07, 0x40, 0x14, 0x24]).await;
|
||||
// SetRfFrequency: rffreq = (rf_frequency * 2^25) / f_xtal
|
||||
// (868_100_000 * 33_554_432) / 32_000_000 = 910_268_825
|
||||
// hex(910_268_825) = 0x36419999
|
||||
debug!("SetRfFrequency");
|
||||
let _ = spi.write(&[0x86, 0x36, 0x41, 0x99, 0x99]).await;
|
||||
// SetPaConfig - +14dBm for SX1262
|
||||
debug!("SetPaConfig");
|
||||
let _ = spi.write(&[0x95, 0x02, 0x02, 0x00, 0x01]).await;
|
||||
// SetTxParams - +22dBm (as written in the docs), 40µs ramp time
|
||||
debug!("SetTxParams");
|
||||
let _ = spi.write(&[0x8e, 0x16, 0x02]).await;
|
||||
// SetModulationParams - sf12, 15.63kHz bandwidth, CR 4/5, ldro off
|
||||
debug!("SetModulationParams");
|
||||
let _ = spi.write(&[0x8b, 0x0c, 0x01, 0x01, 0x00]).await;
|
||||
// SetDioIrqParams - set TxDone and Timeout IRQs
|
||||
debug!("SetDioIrqParams");
|
||||
let _ = spi.write(&[0x08,
|
||||
0b00000000, 0b00000001, // IrqMask: bit 0 (TxDone)
|
||||
0b00000000, 0b00000001, // DIO1Mask: same as IrqMask
|
||||
0b00000000, 0b00000000, // DIO2Mask: none
|
||||
0b00000000, 0b00000000, // DIO3Mask: none
|
||||
]).await;
|
||||
// Enable RF switch before tx
|
||||
let _ctrl1 = Output::new(p.PC4, Level::High, Speed::High); // TX
|
||||
let _ctrl2 = Output::new(p.PC5, Level::Low, Speed::High); // RX
|
||||
let _ctrl3 = Output::new(p.PC3, Level::High, Speed::High); // EN
|
||||
// SetTx - no timeout
|
||||
debug!("SetTx - transmitting now");
|
||||
let _ = spi.write(&[0x83, 0x00, 0x00, 0x00]).await;
|
||||
// GetIrqStatus loop to poll when the command finishes
|
||||
loop {
|
||||
debug!("GetIrqStatus");
|
||||
// Send 0x12 opcode + 4 nops (extra NOP because SPI is full-duplex, response is shifted)
|
||||
let mut buf = [0x12, 0x00, 0x00, 0x00, 0x00];
|
||||
let _ = spi.transfer_in_place(&mut buf).await;
|
||||
trace!("IRQ raw: {:x}", buf);
|
||||
|
||||
// buf[0] = garbage, buf[1] = status, buf[2] = NOP, buf[3-4] = IrqStatus
|
||||
if buf[3] & 0x01 != 0 {
|
||||
debug!("GetIrqStatus - tx done");
|
||||
break;
|
||||
}
|
||||
|
||||
Timer::after_millis(1).await;
|
||||
info!("sending stuffs");
|
||||
match lora.tx(b"hiiiiiII!").await {
|
||||
Ok(_) => info!("yay :3"),
|
||||
Err(e) => error!("tx error: {:?}", e),
|
||||
}
|
||||
|
||||
info!("waiting for packet...");
|
||||
let mut buf = [0u8; 255];
|
||||
match lora.rx(&mut buf, 1_000).await {
|
||||
Ok(len) => info!("rx {} bytes: {:x}", len, &buf[..len]),
|
||||
Err(RadioError::Timeout) => warn!("no packet received (timeout)"),
|
||||
Err(e) => error!("rx error: {:?}", e),
|
||||
}
|
||||
// ClearIrqStatus - all IRQs
|
||||
debug!("ClearIrqStatus");
|
||||
let _ = spi.write(&[0x02, 0xff, 0xff]).await;
|
||||
|
||||
loop {
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
|
||||
275
src/modulations/lora.rs
Normal file
275
src/modulations/lora.rs
Normal file
@@ -0,0 +1,275 @@
|
||||
use defmt::{debug, trace};
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal_async::spi::SpiDevice;
|
||||
|
||||
use crate::error::RadioError;
|
||||
use crate::radio::{PacketType, PaSelection, Radio, RampTime, irq};
|
||||
use crate::traits::{Configure, Receive, Transmit};
|
||||
|
||||
#[derive(Clone, Copy, defmt::Format)]
|
||||
#[repr(u8)]
|
||||
pub enum SpreadingFactor {
|
||||
SF5 = 0x05,
|
||||
SF6 = 0x06,
|
||||
SF7 = 0x07,
|
||||
SF8 = 0x08,
|
||||
SF9 = 0x09,
|
||||
SF10 = 0x0a,
|
||||
SF11 = 0x0b,
|
||||
SF12 = 0x0c,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, defmt::Format)]
|
||||
#[repr(u8)]
|
||||
pub enum Bandwidth {
|
||||
Bw7_8kHz = 0x00,
|
||||
Bw10_42kHz = 0x08,
|
||||
Bw15_63kHz = 0x01,
|
||||
Bw20_83kHz = 0x09,
|
||||
Bw31_25kHz = 0x02,
|
||||
Bw41_67kHz = 0x0a,
|
||||
Bw62_50kHz = 0x03,
|
||||
Bw125kHz = 0x04,
|
||||
Bw250kHz = 0x05,
|
||||
Bw500kHz = 0x06,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, defmt::Format)]
|
||||
#[repr(u8)]
|
||||
pub enum CodingRate {
|
||||
/// No forward error correction coding
|
||||
Cr44 = 0x00,
|
||||
Cr45 = 0x01,
|
||||
Cr46 = 0x02,
|
||||
Cr47 = 0x03,
|
||||
Cr48 = 0x04,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, defmt::Format)]
|
||||
pub struct LoraConfig {
|
||||
pub frequency: u32,
|
||||
pub sf: SpreadingFactor,
|
||||
pub bw: Bandwidth,
|
||||
pub cr: CodingRate,
|
||||
pub ldro: bool,
|
||||
pub preamble_len: u16,
|
||||
pub explicit_header: bool,
|
||||
pub crc_on: bool,
|
||||
pub iq_inverted: bool,
|
||||
pub sync_word: u16,
|
||||
pub pa: PaSelection,
|
||||
pub power_dbm: i8,
|
||||
pub ramp: RampTime,
|
||||
}
|
||||
|
||||
impl Default for LoraConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
frequency: 868_100_000,
|
||||
sf: SpreadingFactor::SF7,
|
||||
bw: Bandwidth::Bw125kHz,
|
||||
cr: CodingRate::Cr45,
|
||||
ldro: false,
|
||||
preamble_len: 8,
|
||||
explicit_header: true,
|
||||
crc_on: true,
|
||||
iq_inverted: false,
|
||||
sync_word: 0x1424, // private LoRa network
|
||||
pa: PaSelection::LowPower,
|
||||
power_dbm: 14,
|
||||
ramp: RampTime::Us40,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// LoRa modulation - borrows a Radio, implements Configure + Transmit + Receive
|
||||
pub struct LoraRadio<'a, SPI: SpiDevice, TX: OutputPin, RX: OutputPin, EN: OutputPin> {
|
||||
radio: &'a mut Radio<SPI, TX, RX, EN>,
|
||||
payload_len: u8,
|
||||
config: LoraConfig,
|
||||
}
|
||||
|
||||
impl<'a, SPI: SpiDevice, TX: OutputPin, RX: OutputPin, EN: OutputPin>
|
||||
LoraRadio<'a, SPI, TX, RX, EN>
|
||||
{
|
||||
pub fn new(radio: &'a mut Radio<SPI, TX, RX, EN>) -> Self {
|
||||
Self {
|
||||
radio,
|
||||
payload_len: 0,
|
||||
config: LoraConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-send SetPacketParams with updated payload length (called before each tx/rx)
|
||||
async fn update_payload_len(&mut self, len: u8) -> Result<(), RadioError> {
|
||||
debug!("Updating payload length to {}", len);
|
||||
if len == self.payload_len {
|
||||
return Ok(());
|
||||
}
|
||||
self.payload_len = len;
|
||||
self.send_packet_params(len).await
|
||||
}
|
||||
|
||||
/// Send the full SetPacketParams command with the given payload length
|
||||
async fn send_packet_params(&mut self, payload_len: u8) -> Result<(), RadioError> {
|
||||
self.radio
|
||||
.set_packet_params(&[
|
||||
(self.config.preamble_len >> 8) as u8,
|
||||
self.config.preamble_len as u8,
|
||||
!self.config.explicit_header as u8,
|
||||
payload_len,
|
||||
self.config.crc_on as u8,
|
||||
self.config.iq_inverted as u8,
|
||||
])
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI: SpiDevice, TX: OutputPin, RX: OutputPin, EN: OutputPin> Configure
|
||||
for LoraRadio<'_, SPI, TX, RX, EN>
|
||||
{
|
||||
type Config = LoraConfig;
|
||||
|
||||
async fn configure(&mut self, config: &LoraConfig) -> Result<(), RadioError> {
|
||||
self.config = *config;
|
||||
|
||||
// Select LoRa packet type
|
||||
self.radio.set_packet_type(PacketType::LoRa).await?;
|
||||
|
||||
// Calibrate image for this frequency band
|
||||
let band = Radio::<SPI, TX, RX, EN>::image_cal_for_freq(config.frequency);
|
||||
self.radio.calibrate_image(band).await?;
|
||||
|
||||
// RF frequency
|
||||
self.radio.set_rf_frequency(config.frequency).await?;
|
||||
|
||||
// Modulation: SF, BW, CR, LDRO
|
||||
self.radio
|
||||
.set_modulation_params(&[
|
||||
config.sf as u8,
|
||||
config.bw as u8,
|
||||
config.cr as u8,
|
||||
config.ldro as u8,
|
||||
])
|
||||
.await?;
|
||||
|
||||
// Packet params (payload length 0 for now, updated per tx/rx)
|
||||
self.send_packet_params(0).await?;
|
||||
self.payload_len = 0;
|
||||
|
||||
// Fix IQ polarity for non-inverted IQ (set bit 2 of register 0x0736)
|
||||
if !config.iq_inverted {
|
||||
let mut iqpol = [0u8; 1];
|
||||
self.radio.read_register(0x0736, &mut iqpol).await?;
|
||||
trace!("Got data {:x}", iqpol);
|
||||
self.radio.write_register(0x0736, &[iqpol[0] | 0x04]).await?;
|
||||
}
|
||||
|
||||
// Sync word at SUBGHZ_LSYNCR (0x0740)
|
||||
self.radio.set_lora_sync_word(config.sync_word).await?;
|
||||
|
||||
// PA config + TX power (uses optimal settings from the datasheet)
|
||||
self.radio
|
||||
.set_output_power(config.pa, config.power_dbm, config.ramp)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI: SpiDevice, TX: OutputPin, RX: OutputPin, EN: OutputPin> Transmit
|
||||
for LoraRadio<'_, SPI, TX, RX, EN>
|
||||
{
|
||||
async fn tx(&mut self, data: &[u8]) -> Result<(), RadioError> {
|
||||
if data.len() > 255 {
|
||||
return Err(RadioError::PayloadTooLarge);
|
||||
}
|
||||
|
||||
// Write payload to radio buffer
|
||||
self.radio.set_buffer_base(0x00, 0x80).await?;
|
||||
self.radio.write_buffer(0x00, data).await?;
|
||||
|
||||
// Update packet params with actual payload length
|
||||
self.update_payload_len(data.len() as u8).await?;
|
||||
|
||||
// Clear any stale IRQ flags before starting TX
|
||||
self.radio.clear_irq(irq::ALL).await?;
|
||||
|
||||
// Enable TxDone IRQ on DIO1
|
||||
self.radio.set_dio1_irq(irq::TX_DONE | irq::TIMEOUT).await?;
|
||||
|
||||
// Start TX
|
||||
self.radio.set_tx(0).await?;
|
||||
|
||||
// Wait until it's done or until timeout
|
||||
let status = self.radio.poll_irq(irq::TX_DONE | irq::TIMEOUT).await?;
|
||||
if status & irq::TIMEOUT != 0 {
|
||||
return Err(RadioError::Timeout);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI: SpiDevice, TX: OutputPin, RX: OutputPin, EN: OutputPin> Receive
|
||||
for LoraRadio<'_, SPI, TX, RX, EN>
|
||||
{
|
||||
async fn rx(&mut self, buf: &mut [u8], timeout_ms: u32) -> Result<usize, RadioError> {
|
||||
// Set max payload length we can accept
|
||||
let max_len = buf.len().min(255) as u8;
|
||||
self.update_payload_len(max_len).await?;
|
||||
|
||||
// Set buffer base addresses (both at 0, same as in lora-rs)
|
||||
self.radio.set_buffer_base(0x00, 0x00).await?;
|
||||
|
||||
// Clear any stale IRQ flags before starting RX
|
||||
self.radio.clear_irq(irq::ALL).await?;
|
||||
|
||||
// Enable RX-related IRQs on DIO1
|
||||
self.radio
|
||||
.set_dio1_irq(irq::RX_DONE | irq::TIMEOUT | irq::CRC_ERR | irq::HEADER_ERR)
|
||||
.await?;
|
||||
|
||||
// Stop RX timer on preamble detection (required for proper RX behavior)
|
||||
self.radio.set_stop_rx_timer_on_preamble(true).await?;
|
||||
|
||||
// Set RX gain (0x94 = normal, 0x96 = boosted)
|
||||
self.radio.write_register(0x08AC, &[0x94]).await?;
|
||||
|
||||
// Convert ms to 15.625µs steps (ms * 64), 0 = single mode, 0xFFFFFF = continuous
|
||||
let timeout_steps = if timeout_ms == 0 {
|
||||
0
|
||||
} else {
|
||||
timeout_ms.saturating_mul(64).min(0xFFFFFF)
|
||||
};
|
||||
self.radio.set_rx(timeout_steps).await?;
|
||||
|
||||
// Wait for something to happen
|
||||
let status = self
|
||||
.radio
|
||||
.poll_irq(irq::RX_DONE | irq::TIMEOUT | irq::CRC_ERR | irq::HEADER_ERR)
|
||||
.await?;
|
||||
|
||||
// Check what happened
|
||||
if status & irq::TIMEOUT != 0 {
|
||||
return Err(RadioError::Timeout);
|
||||
}
|
||||
if status & irq::CRC_ERR != 0 {
|
||||
return Err(RadioError::CrcInvalid);
|
||||
}
|
||||
if status & irq::HEADER_ERR != 0 {
|
||||
return Err(RadioError::HeaderInvalid);
|
||||
}
|
||||
|
||||
// Read received data from the radio buffer
|
||||
let (len, offset) = self.radio.get_rx_buffer_status().await?;
|
||||
let read_len = len.min(buf.len() as u8);
|
||||
self.radio
|
||||
.read_buffer(offset, &mut buf[..read_len as usize])
|
||||
.await?;
|
||||
|
||||
trace!("Got data {:x}", &mut buf[..read_len as usize]);
|
||||
|
||||
Ok(read_len as usize)
|
||||
}
|
||||
}
|
||||
1
src/modulations/mod.rs
Normal file
1
src/modulations/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod lora;
|
||||
1049
src/radio.rs
Normal file
1049
src/radio.rs
Normal file
File diff suppressed because it is too large
Load Diff
54
src/spi.rs
Normal file
54
src/spi.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use defmt::trace;
|
||||
use embassy_stm32::pac;
|
||||
use embedded_hal_async::spi::{ErrorType, Operation, SpiBus, SpiDevice};
|
||||
|
||||
/// Wrapper for the sub-GHz SPI device
|
||||
pub struct SubGhzSpiDevice<T>(pub T);
|
||||
|
||||
impl<T: SpiBus> ErrorType for SubGhzSpiDevice<T> {
|
||||
type Error = T::Error;
|
||||
}
|
||||
|
||||
/// This works as a translation layer between normal SPI transactions and sub-GHz device SPI
|
||||
/// transactions. Everything above this layer sees it like a normal SPI device!
|
||||
impl<T: SpiBus> SpiDevice for SubGhzSpiDevice<T> {
|
||||
/// Perform a transaction on the sub-GHz device
|
||||
async fn transaction(
|
||||
&mut self,
|
||||
operations: &mut [Operation<'_, u8>],
|
||||
) -> Result<(), Self::Error> {
|
||||
// Pull NSS low to allow SPI comms
|
||||
pac::PWR.subghzspicr().modify(|w| w.set_nss(false));
|
||||
trace!("NSS low");
|
||||
for operation in operations {
|
||||
match operation {
|
||||
Operation::Read(buf) => {
|
||||
self.0.read(buf).await?;
|
||||
trace!("Read {:x}", buf);
|
||||
}
|
||||
Operation::Write(buf) => {
|
||||
self.0.write(buf).await?;
|
||||
trace!("Wrote {:x}", buf);
|
||||
}
|
||||
Operation::Transfer(read, write) => {
|
||||
self.0.transfer(read, write).await?;
|
||||
trace!("Read {:x} wrote {:x}", read, write);
|
||||
}
|
||||
Operation::TransferInPlace(buf) => {
|
||||
self.0.transfer_in_place(buf).await?;
|
||||
trace!("Read+wrote {:x}", buf);
|
||||
}
|
||||
Operation::DelayNs(_) => {}
|
||||
}
|
||||
}
|
||||
// Pull NSS high
|
||||
pac::PWR.subghzspicr().modify(|w| w.set_nss(true));
|
||||
trace!("NSS high");
|
||||
// Small delay for the radio to assert BUSY after NSS goes high
|
||||
cortex_m::asm::delay(500);
|
||||
// Poll BUSY flag until it's done
|
||||
while pac::PWR.sr2().read().rfbusys() {}
|
||||
trace!("BUSY flag clear");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
20
src/traits.rs
Normal file
20
src/traits.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use crate::error::RadioError;
|
||||
|
||||
/// Can configure the radio
|
||||
pub trait Configure {
|
||||
/// Each modulation has its own `Config` struct
|
||||
type Config;
|
||||
|
||||
async fn configure(&mut self, config: &Self::Config) -> Result<(), RadioError>;
|
||||
}
|
||||
|
||||
/// Can send data
|
||||
pub trait Transmit {
|
||||
async fn tx(&mut self, data: &[u8]) -> Result<(), RadioError>;
|
||||
}
|
||||
|
||||
/// Can receive data
|
||||
pub trait Receive {
|
||||
/// Returns the number of bytes received
|
||||
async fn rx(&mut self, buf: &mut [u8], timeout_ms: u32) -> Result<usize, RadioError>;
|
||||
}
|
||||
Reference in New Issue
Block a user