fw: Define simple command protocol
This commit is contained in:
parent
aebb3a58f0
commit
0034d2e9e7
@ -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];
|
||||
|
||||
|
229
firmware/src/proto.rs
Normal file
229
firmware/src/proto.rs
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user