implement software framing for BPSK modulation
This commit is contained in:
@@ -32,6 +32,200 @@ impl Bitrate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, defmt::Format)]
|
||||||
|
pub enum CrcType {
|
||||||
|
None,
|
||||||
|
/// Using a common 0x07 polynomial
|
||||||
|
Crc8,
|
||||||
|
/// CRC-16 CCITT using 0x1021 polynomial
|
||||||
|
Crc16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CrcType {
|
||||||
|
fn compute(self, data: &[u8]) -> (u16, usize) {
|
||||||
|
match self {
|
||||||
|
CrcType::None => (0, 0),
|
||||||
|
CrcType::Crc8 => {
|
||||||
|
let mut crc: u8 = 0x00;
|
||||||
|
for &byte in data {
|
||||||
|
crc ^= byte;
|
||||||
|
for _ in 0..8 {
|
||||||
|
if crc & 0x80 != 0 {
|
||||||
|
crc = (crc << 1) ^ 0x07;
|
||||||
|
} else {
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(crc as u16, 1)
|
||||||
|
}
|
||||||
|
CrcType::Crc16 => {
|
||||||
|
let mut crc: u16 = 0xFFFF;
|
||||||
|
for &byte in data {
|
||||||
|
crc ^= (byte as u16) << 8;
|
||||||
|
for _ in 0..8 {
|
||||||
|
if crc & 0x8000 != 0 {
|
||||||
|
crc = (crc << 1) ^ 0x1021;
|
||||||
|
} else {
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(crc, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(self, crc: u16, buf: &mut [u8]) {
|
||||||
|
match self {
|
||||||
|
CrcType::None => {}
|
||||||
|
CrcType::Crc8 => {
|
||||||
|
buf[0] = crc as u8;
|
||||||
|
}
|
||||||
|
CrcType::Crc16 => {
|
||||||
|
buf[0] = (crc >> 8) as u8;
|
||||||
|
buf[1] = crc as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, defmt::Format)]
|
||||||
|
pub enum Whitening {
|
||||||
|
None,
|
||||||
|
Ccitt,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Whitening {
|
||||||
|
fn apply(self, seed: u16, data: &mut [u8]) {
|
||||||
|
match self {
|
||||||
|
Whitening::None => return,
|
||||||
|
Whitening::Ccitt => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate CCITT whitening using x^9 + x^4 + 1 polynomial and LFSR
|
||||||
|
let mut lfsr: u16 = seed & 0x1FF;
|
||||||
|
for byte in data.iter_mut() {
|
||||||
|
let mut mask = 0u8;
|
||||||
|
for bit in 0..8 {
|
||||||
|
let feedback = ((lfsr >> 8) ^ (lfsr >> 3)) & 1;
|
||||||
|
lfsr = ((lfsr << 1) | feedback) & 0x1FF;
|
||||||
|
mask |= (feedback as u8) << (7 - bit);
|
||||||
|
}
|
||||||
|
*byte ^= mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, defmt::Format)]
|
||||||
|
pub enum BpskPacket {
|
||||||
|
/// No framing, just send raw data
|
||||||
|
Raw,
|
||||||
|
/// Use a configurable framing
|
||||||
|
Framing {
|
||||||
|
/// Length of the preamble (0xAA) in bytes
|
||||||
|
preamble_len: usize,
|
||||||
|
/// Synchronization word (max 32 bytes)
|
||||||
|
sync_word: [u8; 32],
|
||||||
|
/// Sync word length
|
||||||
|
sync_word_len: usize,
|
||||||
|
/// Enable/disable reporting length in the packet
|
||||||
|
include_len: bool,
|
||||||
|
/// CRC size (0, 1 or 2 bytes)
|
||||||
|
crc_type: CrcType,
|
||||||
|
/// Whitening algorithm
|
||||||
|
whitening: Whitening,
|
||||||
|
/// Whitening LFSR seed (9-bit, 0x000..0x1FF)
|
||||||
|
whitening_seed: u16,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BpskPacket {
|
||||||
|
pub fn default() -> Self {
|
||||||
|
Self::Framing {
|
||||||
|
preamble_len: 32,
|
||||||
|
// Baker-13 code (2 bytes)
|
||||||
|
sync_word: [0x1F, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||||||
|
sync_word_len: 2,
|
||||||
|
include_len: true,
|
||||||
|
crc_type: CrcType::Crc16,
|
||||||
|
whitening: Whitening::Ccitt,
|
||||||
|
whitening_seed: 0x1FF,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_bytes(self, payload: &[u8], buf: &mut [u8]) -> Result<u8, RadioError> {
|
||||||
|
match self {
|
||||||
|
BpskPacket::Raw => {
|
||||||
|
// Simple copy operation, no modifications made
|
||||||
|
buf[..payload.len()].copy_from_slice(payload);
|
||||||
|
payload.len().try_into().map_err(|_| RadioError::PayloadTooLarge)
|
||||||
|
}
|
||||||
|
BpskPacket::Framing {
|
||||||
|
preamble_len,
|
||||||
|
sync_word,
|
||||||
|
sync_word_len,
|
||||||
|
include_len,
|
||||||
|
crc_type,
|
||||||
|
whitening,
|
||||||
|
whitening_seed,
|
||||||
|
} => {
|
||||||
|
let len_field_size = if include_len { 1 } else { 0 };
|
||||||
|
let crc_size = match crc_type {
|
||||||
|
CrcType::None => 0,
|
||||||
|
CrcType::Crc8 => 1,
|
||||||
|
CrcType::Crc16 => 2,
|
||||||
|
};
|
||||||
|
// Validate packet length
|
||||||
|
let total = preamble_len + sync_word_len + len_field_size + payload.len() + crc_size;
|
||||||
|
|
||||||
|
if total > buf.len() {
|
||||||
|
return Err(RadioError::PayloadTooLarge);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keeps track of the current position in the buffer
|
||||||
|
let mut pos = 0;
|
||||||
|
|
||||||
|
// Write preamble which consists of 0xAA symbols
|
||||||
|
buf[pos..pos + preamble_len].fill(0xAA);
|
||||||
|
pos += preamble_len;
|
||||||
|
|
||||||
|
// Write sync word
|
||||||
|
buf[pos..pos + sync_word_len].copy_from_slice(&sync_word[..sync_word_len]);
|
||||||
|
pos += sync_word_len;
|
||||||
|
|
||||||
|
// Actual payload starts here
|
||||||
|
let data_start = pos;
|
||||||
|
|
||||||
|
// If enabled in the config, write length info
|
||||||
|
if include_len {
|
||||||
|
let payload_len: u8 = payload.len().try_into().map_err(|_| RadioError::PayloadTooLarge)?;
|
||||||
|
buf[pos] = payload_len;
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the original payload itself
|
||||||
|
buf[pos..pos + payload.len()].copy_from_slice(payload);
|
||||||
|
pos += payload.len();
|
||||||
|
|
||||||
|
// Compute CRC before the whitening
|
||||||
|
let (crc, crc_len) = crc_type.compute(&buf[data_start..pos]);
|
||||||
|
crc_type.write(crc, &mut buf[pos..]);
|
||||||
|
pos += crc_len;
|
||||||
|
|
||||||
|
// Apply whitening
|
||||||
|
whitening.apply(whitening_seed, &mut buf[data_start..pos]);
|
||||||
|
|
||||||
|
// Additional validation - if buffer position can't fit in u8, it's invalid
|
||||||
|
pos.try_into().map_err(|_| RadioError::PayloadTooLarge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, defmt::Format)]
|
#[derive(Clone, Copy, defmt::Format)]
|
||||||
pub struct BpskConfig {
|
pub struct BpskConfig {
|
||||||
pub frequency: u32,
|
pub frequency: u32,
|
||||||
@@ -39,6 +233,7 @@ pub struct BpskConfig {
|
|||||||
pub pa: PaSelection,
|
pub pa: PaSelection,
|
||||||
pub power_dbm: i8,
|
pub power_dbm: i8,
|
||||||
pub ramp: RampTime,
|
pub ramp: RampTime,
|
||||||
|
pub packet: BpskPacket,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BpskConfig {
|
impl Default for BpskConfig {
|
||||||
@@ -49,6 +244,7 @@ impl Default for BpskConfig {
|
|||||||
pa: PaSelection::LowPower,
|
pa: PaSelection::LowPower,
|
||||||
power_dbm: 14,
|
power_dbm: 14,
|
||||||
ramp: RampTime::Us40,
|
ramp: RampTime::Us40,
|
||||||
|
packet: BpskPacket::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,16 +319,16 @@ impl<SPI: SpiDevice, TX: OutputPin, RX: OutputPin, EN: OutputPin> Transmit
|
|||||||
for BpskRadio<'_, SPI, TX, RX, EN>
|
for BpskRadio<'_, SPI, TX, RX, EN>
|
||||||
{
|
{
|
||||||
async fn tx(&mut self, data: &[u8]) -> Result<(), RadioError> {
|
async fn tx(&mut self, data: &[u8]) -> Result<(), RadioError> {
|
||||||
if data.len() > 255 {
|
let mut buf = [0u8; 255];
|
||||||
return Err(RadioError::PayloadTooLarge);
|
// Convert buffer to packet with chosen framing
|
||||||
}
|
let len = self.config.packet.to_bytes(data, &mut buf)?;
|
||||||
|
|
||||||
// Write payload to radio buffer
|
// Write payload to radio buffer
|
||||||
self.radio.set_buffer_base(0x00, 0x00).await?;
|
self.radio.set_buffer_base(0x00, 0x00).await?;
|
||||||
self.radio.write_buffer(0x00, data).await?;
|
self.radio.write_buffer(0x00, &buf[..len as usize]).await?;
|
||||||
|
|
||||||
// Update packet params with actual payload length
|
// Update packet params with actual payload length
|
||||||
self.update_payload_len(data.len() as u8).await?;
|
self.update_payload_len(len).await?;
|
||||||
|
|
||||||
// Clear any stale IRQ flags before starting TX
|
// Clear any stale IRQ flags before starting TX
|
||||||
self.radio.clear_irq(irq::ALL).await?;
|
self.radio.clear_irq(irq::ALL).await?;
|
||||||
|
|||||||
Reference in New Issue
Block a user