fw: Define simple command protocol

This commit is contained in:
David Lenfesty 2023-05-31 21:02:15 -06:00
parent aebb3a58f0
commit 0034d2e9e7
2 changed files with 230 additions and 0 deletions

View File

@ -28,6 +28,7 @@ mod mcp4726;
mod uart; mod uart;
mod litex_uart; mod litex_uart;
mod logging; mod logging;
mod proto;
const MAC: [u8; 6] = [0xA0, 0xBB, 0xCC, 0xDD, 0xEE, 0xF0]; const MAC: [u8; 6] = [0xA0, 0xBB, 0xCC, 0xDD, 0xEE, 0xF0];

229
firmware/src/proto.rs Normal file
View File

@ -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<u8> for Settings {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
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<CommandPacket, Error>, 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<CommandPacket, Error> {
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;
}
}