From d624b927044e89eb0072ea2de7777202ea4d8f4c Mon Sep 17 00:00:00 2001 From: Lukrecja Date: Thu, 26 Feb 2026 01:46:49 +0100 Subject: [PATCH] implement BPSK modulation (tx-only with arbitrary freq setting) --- src/main.rs | 28 +++----- src/modulations/bpsk.rs | 152 ++++++++++++++++++++++++++++++++++++++++ src/modulations/mod.rs | 1 + 3 files changed, 163 insertions(+), 18 deletions(-) create mode 100644 src/modulations/bpsk.rs diff --git a/src/main.rs b/src/main.rs index f2d893b..2e0867c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ #![no_std] #![no_main] -use defmt::{error, info, warn}; +use defmt::{error, info}; use embassy_executor::Spawner; use embassy_stm32::{ Config, @@ -11,11 +11,12 @@ use embassy_stm32::{ }; use embassy_time::{Duration, Timer}; use stm32wle5jc_radio::{ - RadioError, - modulations::lora::{Bandwidth, LoraConfig, LoraRadio, SpreadingFactor}, + modulations:: + bpsk::{Bitrate, BpskConfig, BpskRadio} + , radio::{PaSelection, Radio}, spi::SubGhzSpiDevice, - traits::{Configure, Receive, Transmit}, + traits::{Configure, Transmit}, }; use {defmt_rtt as _, panic_probe as _}; @@ -37,32 +38,23 @@ async fn main(_spawner: Spawner) { let mut radio = Radio::new(spi, rf_tx, rf_rx, rf_en); radio.init().await.unwrap(); - let mut lora = LoraRadio::new(&mut radio); - lora.configure(&LoraConfig { + let mut bpsk = BpskRadio::new(&mut radio); + bpsk.configure(&BpskConfig { frequency: 868_100_000, - sf: SpreadingFactor::SF9, - bw: Bandwidth::Bw7_8kHz, + bitrate: Bitrate::Bps600, pa: PaSelection::HighPower, - power_dbm: 22, + power_dbm: 17, ..Default::default() }) .await .unwrap(); info!("sending stuffs"); - match lora.tx(b"hiiiiiII!").await { + match bpsk.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), - } - loop { Timer::after(Duration::from_secs(1)).await; } diff --git a/src/modulations/bpsk.rs b/src/modulations/bpsk.rs new file mode 100644 index 0000000..5241043 --- /dev/null +++ b/src/modulations/bpsk.rs @@ -0,0 +1,152 @@ +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, + 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) -> 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 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 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(()) + } +} diff --git a/src/modulations/mod.rs b/src/modulations/mod.rs index 4c6fa93..5f92c6a 100644 --- a/src/modulations/mod.rs +++ b/src/modulations/mod.rs @@ -1 +1,2 @@ +pub mod bpsk; pub mod lora;