refractor into library crate with examples
This commit is contained in:
17
stm32wl-subghz/Cargo.toml
Normal file
17
stm32wl-subghz/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "stm32wl-subghz"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.lusia.moe/lukrecja/stm32wl-subghz"
|
||||
categories = [ "no-std", "embedded", "asynchronous" ]
|
||||
keywords = [ "stm32wl", "stm32wle5jc", "lora", "fsk", "gfsk", "msk", "gmsk", "bpsk", "radio", "embedded-hal-async" ]
|
||||
description = "Sub-GHz radio driver for STM32WL-series microcontrollers"
|
||||
|
||||
[dependencies]
|
||||
embassy-stm32 = { version = "0.5.0", features = ["unstable-pac"] }
|
||||
embassy-time = "0.5.0"
|
||||
defmt = "1.0.1"
|
||||
cortex-m = { version = "0.7.6", features = ["inline-asm"] }
|
||||
embedded-hal = "1.0.0"
|
||||
embedded-hal-async = "1.0.0"
|
||||
7
stm32wl-subghz/LICENSE
Normal file
7
stm32wl-subghz/LICENSE
Normal file
@@ -0,0 +1,7 @@
|
||||
Copyright © 2026 Lukrecja Pleskaczyńska
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
20
stm32wl-subghz/README.md
Normal file
20
stm32wl-subghz/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# stm32wl-subghz
|
||||
|
||||
[![Latest Version]][crates.io]
|
||||
|
||||
Sub-GHZ SPI device radio driver for STM32WL-series microcontrollers, currently supporting LoRa and BPSK.
|
||||
|
||||
Built on `embedded-hal-async` and `embassy-stm32`.
|
||||
|
||||
## Supported hardware
|
||||
- STM32WLE5JC (single-core) - tested
|
||||
- Possibly all the other variants in STM32WL5x and STM32WLEx families
|
||||
|
||||
## Usage
|
||||
See the [examples](../examples/) directory.
|
||||
|
||||
## License
|
||||
MIT
|
||||
|
||||
[Latest Version]: https://img.shields.io/crates/v/stm32wl-subghz.svg
|
||||
[crates.io]: https://crates.io/crates/stm32wl-subghz
|
||||
19
stm32wl-subghz/src/error.rs
Normal file
19
stm32wl-subghz/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,
|
||||
}
|
||||
13
stm32wl-subghz/src/lib.rs
Normal file
13
stm32wl-subghz/src/lib.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
#![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;
|
||||
pub use radio::{PaSelection, Radio};
|
||||
pub use spi::SubGhzSpiDevice;
|
||||
pub use traits::{Configure, Receive, Transmit};
|
||||
154
stm32wl-subghz/src/modulations/bpsk.rs
Normal file
154
stm32wl-subghz/src/modulations/bpsk.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use defmt::debug;
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal_async::spi::SpiDevice;
|
||||
|
||||
use crate::{
|
||||
RadioError,
|
||||
radio::{PaSelection, PacketType, Radio, RampTime, irq},
|
||||
traits::{Configure, Transmit},
|
||||
};
|
||||
|
||||
/// BPSK bitrate
|
||||
/// Formula: register = (32 * 32_000_000) / bps
|
||||
#[derive(Clone, Copy, defmt::Format)]
|
||||
pub enum Bitrate {
|
||||
/// 100 bits per second
|
||||
Bps100,
|
||||
/// 600 bits per second
|
||||
Bps600,
|
||||
/// Arbitrary bitrate in bits per second
|
||||
Custom(u32),
|
||||
}
|
||||
|
||||
impl Bitrate {
|
||||
/// Get the 3-byte register value for this bitrate
|
||||
fn to_bytes(self) -> [u8; 3] {
|
||||
let val = match self {
|
||||
Bitrate::Bps100 => 0x9C4000,
|
||||
Bitrate::Bps600 => 0x1A0AAA,
|
||||
Bitrate::Custom(bps) => (32 * 32_000_000) / bps,
|
||||
};
|
||||
[(val >> 16) as u8, (val >> 8) as u8, val as u8]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, defmt::Format)]
|
||||
pub struct BpskConfig {
|
||||
pub frequency: u32,
|
||||
pub bitrate: Bitrate,
|
||||
pub pa: PaSelection,
|
||||
pub power_dbm: i8,
|
||||
pub ramp: RampTime,
|
||||
}
|
||||
|
||||
impl Default for BpskConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
frequency: 868_100_000,
|
||||
bitrate: Bitrate::Bps600,
|
||||
pa: PaSelection::LowPower,
|
||||
power_dbm: 14,
|
||||
ramp: RampTime::Us40,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// BPSK modulation - borrows a Radio, implements Configure + Transmit
|
||||
pub struct BpskRadio<'a, SPI: SpiDevice, TX: OutputPin, RX: OutputPin, EN: OutputPin> {
|
||||
radio: &'a mut Radio<SPI, TX, RX, EN>,
|
||||
payload_len: u8,
|
||||
config: BpskConfig,
|
||||
}
|
||||
|
||||
impl<'a, SPI: SpiDevice, TX: OutputPin, RX: OutputPin, EN: OutputPin>
|
||||
BpskRadio<'a, SPI, TX, RX, EN>
|
||||
{
|
||||
pub fn new(radio: &'a mut Radio<SPI, TX, RX, EN>) -> Self {
|
||||
Self {
|
||||
radio,
|
||||
payload_len: 0,
|
||||
config: BpskConfig::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(&[payload_len]).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<SPI: SpiDevice, TX: OutputPin, RX: OutputPin, EN: OutputPin> Configure
|
||||
for BpskRadio<'_, SPI, TX, RX, EN>
|
||||
{
|
||||
type Config = BpskConfig;
|
||||
|
||||
async fn configure(&mut self, config: &Self::Config) -> Result<(), RadioError> {
|
||||
self.config = *config;
|
||||
|
||||
// Select BPSK packet type
|
||||
self.radio.set_packet_type(PacketType::Bpsk).await?;
|
||||
|
||||
// Payload length updated per tx
|
||||
self.send_packet_params(0).await?;
|
||||
|
||||
// RF frequency
|
||||
self.radio.set_rf_frequency(config.frequency).await?;
|
||||
|
||||
// Set modulation params (bitrate + Gaussian BT 0.5 pulse shape)
|
||||
let br = config.bitrate.to_bytes();
|
||||
self.radio
|
||||
.set_modulation_params(&[br[0], br[1], br[2], 0x16])
|
||||
.await?;
|
||||
|
||||
// PA config + TX power
|
||||
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 BpskRadio<'_, 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, 0x00).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(())
|
||||
}
|
||||
}
|
||||
277
stm32wl-subghz/src/modulations/lora.rs
Normal file
277
stm32wl-subghz/src/modulations/lora.rs
Normal file
@@ -0,0 +1,277 @@
|
||||
use defmt::{debug, trace};
|
||||
use embedded_hal::digital::OutputPin;
|
||||
use embedded_hal_async::spi::SpiDevice;
|
||||
|
||||
use crate::error::RadioError;
|
||||
use crate::radio::{PaSelection, PacketType, 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)
|
||||
}
|
||||
}
|
||||
2
stm32wl-subghz/src/modulations/mod.rs
Normal file
2
stm32wl-subghz/src/modulations/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod bpsk;
|
||||
pub mod lora;
|
||||
1049
stm32wl-subghz/src/radio.rs
Normal file
1049
stm32wl-subghz/src/radio.rs
Normal file
File diff suppressed because it is too large
Load Diff
54
stm32wl-subghz/src/spi.rs
Normal file
54
stm32wl-subghz/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
stm32wl-subghz/src/traits.rs
Normal file
20
stm32wl-subghz/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