diff --git a/src/modulations/bpsk.rs b/src/modulations/bpsk.rs index 1da5161..4066478 100644 --- a/src/modulations/bpsk.rs +++ b/src/modulations/bpsk.rs @@ -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 { + 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)] pub struct BpskConfig { pub frequency: u32, @@ -39,6 +233,7 @@ pub struct BpskConfig { pub pa: PaSelection, pub power_dbm: i8, pub ramp: RampTime, + pub packet: BpskPacket, } impl Default for BpskConfig { @@ -49,6 +244,7 @@ impl Default for BpskConfig { pa: PaSelection::LowPower, power_dbm: 14, ramp: RampTime::Us40, + packet: BpskPacket::default(), } } } @@ -123,16 +319,16 @@ 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); - } + let mut buf = [0u8; 255]; + // Convert buffer to packet with chosen framing + let len = self.config.packet.to_bytes(data, &mut buf)?; // Write payload to radio buffer 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 - self.update_payload_len(data.len() as u8).await?; + self.update_payload_len(len).await?; // Clear any stale IRQ flags before starting TX self.radio.clear_irq(irq::ALL).await?;