From 0034d2e9e715237f2df8bf24d48251099e0ab311 Mon Sep 17 00:00:00 2001 From: David Lenfesty Date: Wed, 31 May 2023 21:02:15 -0600 Subject: [PATCH] fw: Define simple command protocol --- firmware/src/main.rs | 1 + firmware/src/proto.rs | 229 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 firmware/src/proto.rs diff --git a/firmware/src/main.rs b/firmware/src/main.rs index e05cb28..05d9a8a 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -28,6 +28,7 @@ mod mcp4726; mod uart; mod litex_uart; mod logging; +mod proto; const MAC: [u8; 6] = [0xA0, 0xBB, 0xCC, 0xDD, 0xEE, 0xF0]; diff --git a/firmware/src/proto.rs b/firmware/src/proto.rs new file mode 100644 index 0000000..b8d40bd --- /dev/null +++ b/firmware/src/proto.rs @@ -0,0 +1,229 @@ +//! # Configuration protocol +//! +//! Configuration and runtime management is done over a separate TCP port, to +//! simplify management. It follows a simple binary protocol, described here. +//! +//! The protocol is packet-based, 2 bytes start sequence, 1 byte command, 4 byte +//! data, 1 byte CRC. CRC includes the start sequence, and is MAXIM-DOW. Chosen +//! because the crc python package had it as well. +//! +//! The 4 bytes of data are little-endian. +//! +//! For the settings that can be set, see [Settings]. +//! +//! ## Sending a command +//! +//! The command is simply the index of the setting to read or write, with the +//! top bit set to indicate a write. Set the value to 0 during a read. +//! +//! ## Receiving a response +//! +//! The command is the index of the setting given, with the top bit set if it's +//! an error. The value is either the value saved in the setting, or an error +//! code. Error codes are defined in [ErrorCodes] + +/// Start sequence to indicate the beginning of a packet. +const START_SEQUENCE: [u8; 2] = [0xDE, 0xAD]; + +/// Possible return values from a command +#[derive(Clone, Copy, Debug)] +pub enum ErrorCodes { + /// Command accepted + Success = 0, + /// Value was not valid for the provided setting + ValueOutOfRange = 1, + /// Setting index provided does not exist + InvalidSetting = 2, +} + +/// Setting indices +#[derive(Clone, Copy, Debug)] +pub enum Settings { + /// Trigger threshold (in ADC counts, peak to peak is 1024) + TriggerThreshold = 0, + /// Number of samples that diff must be higher than threshold to trigger + TriggerPeriod = 1, + /// Value to decay difference every DecayPeriod cycles + DecayValue = 2, + /// The number of cycles between every decay + DecayPeriod = 3, + /// Gain setting of preprocessor (value in dB) + Gain = 4, + /// Center frequency to set preprocessor filter to (value in Hz) + CenterFreq = 5, + /// Sampling enabled, 1 to enable, 0 to disable + SamplingEnabled = 6, +} + +#[derive(Clone, Copy, Debug)] +pub enum Error { + /// Start sequence incorrect, no packet + BadStartSequence, + /// CRC was bad + BadCrc, + /// State machine error + StateMachineError, + /// No packet found in data + NoPacket, +} + +impl TryFrom for Settings { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::TriggerThreshold), + 1 => Ok(Self::TriggerPeriod), + 2 => Ok(Self::DecayValue), + 3 => Ok(Self::DecayPeriod), + 4 => Ok(Self::Gain), + 5 => Ok(Self::CenterFreq), + 6 => Ok(Self::SamplingEnabled), + + _ => Err(()), + } + } +} + +pub struct CommandPacket { + pub is_write: bool, + pub setting: u8, + pub value: u32, +} + +pub struct ResponsePacket { + pub is_error: bool, + pub setting: u8, + pub value: u32, +} + +/// Serialize a valid response +pub fn serialize_response_value(setting: Settings, value: u32) -> [u8; 8] { + serialize_response(setting as u8, value) +} + +/// Serialize an error response +pub fn serialize_response_error(setting: u8, error: ErrorCodes) -> [u8; 8] { + serialize_response(0x80 | setting, error as u32) +} + +fn serialize_response(command: u8, value: u32) -> [u8; 8] { + let mut crc = crc_any::CRCu8::crc8maxim(); + let mut buf = [0u8; 8]; + + // Construct packet data + &buf[0..2].copy_from_slice(&START_SEQUENCE); + buf[2] = command; + for i in 0..4 { + buf[3 + i] = (value >> (8 * i)) as u8; + } + + // Run CRC and append value + crc.digest(&buf[0..7]); + buf[7] = crc.get_crc(); + + buf +} + +pub struct PacketParser { + packet_index: u8, + crc: crc_any::CRCu8, + + is_write: bool, + setting: u8, + value: u32, +} + +impl PacketParser { + pub fn new() -> Self { + Self { + packet_index: 0, + crc: crc_any::CRCu8::crc8maxim(), + + is_write: false, + setting: 0, + value: 0, + } + } + + /// Try and parse a packet from a received buffer. Returns Some(packet) if a + /// packet was found, and the number of bytes processed from the buffer. Bytes + /// processed may be smaller than the buffer length if a packet was found. + pub fn try_parse(&mut self, buf: &[u8]) -> (Result, usize) { + let mut bytes_processed = 0; + for byte in buf { + bytes_processed += 1; + match self.process_byte(*byte) { + Ok(packet) => return (Ok(packet), bytes_processed), + + // We don't care to pass errors to the user + // TODO log + _ => (), + } + } + + (Err(Error::NoPacket), bytes_processed) + } + + /// Process incoming byte using a simple state machine. + pub fn process_byte(&mut self, byte: u8) -> Result { + match self.packet_index { + // Check the start sequence + 0 => { + if byte != START_SEQUENCE[0] { + self.reset(); + return Err(Error::BadStartSequence); + } + } + 1 => { + if byte != START_SEQUENCE[1] { + self.reset(); + return Err(Error::BadStartSequence); + } + } + + // Save data + 2 => { + self.is_write = byte & 0x80 != 0; + self.setting = byte & 0x7F; + } + 3..=6 => { + self.value |= (byte << (8 * (self.packet_index - 3))) as u32; + } + + // Check CRC + 7 => { + if self.crc.get_crc() == byte { + return Ok(CommandPacket { + is_write: self.is_write, + setting: self.setting, + value: self.value, + }); + } else { + self.reset(); + return Err(Error::BadCrc); + } + } + + // Should be unreachable, just reset + _ => { + self.reset(); + return Err(Error::StateMachineError); + } + } + + // Increment index + self.packet_index += 1; + // Process CRC (sucks I can't just give it one byte) + self.crc.digest(&[byte]); + + Err(Error::NoPacket) + } + + /// Reset values to start parsing a packet again + fn reset(&mut self) { + self.packet_index = 0; + self.crc = crc_any::CRCu8::crc8maxim(); + self.value = 0; + } +}