Protocol rework

This commit is contained in:
ThePetrovich 2026-05-10 15:33:08 +08:00
parent 3e77d34ccc
commit 2b713a3e3a
15 changed files with 1347 additions and 1155 deletions

View file

@ -1,6 +1,6 @@
/*
* @file sbc_fw.ino
* @brief Main firmware for sensor board controller, YKSA PL/EDU16 RSENSE
* @brief Main firmware for sensor board controller, YKSA PL/EDU16 RSENSE.
*
* Created: 21.09.2025
* Author: ThePetrovich
@ -15,15 +15,126 @@
#include <SPI.h>
#include <SoftwareI2C.h>
#include <stdint.h>
#include <string.h>
#include "config.h"
#include "eeprom.h"
#include "iodefs.h"
#include "lsense.h"
#include "protocol.h"
#include "rsense.h"
/*
* Frame format: [CMD 2B][LEN 2B][PAYLOAD LEN bytes][CRC16 2B].
* Maximum payload size chosen to fit the largest command (config_t ~22 bytes);
* 256 bytes leaves headroom for future commands.
*/
#define PROTOCOL_MAX_PAYLOAD 256U
#define PROTOCOL_HEADER_SIZE ((uint16_t)sizeof(protocol_header_t))
#define PROTOCOL_CRC_SIZE 2U
#define PROTOCOL_BUF_SIZE (PROTOCOL_HEADER_SIZE + PROTOCOL_MAX_PAYLOAD + PROTOCOL_CRC_SIZE)
static uint8_t rx_buf[PROTOCOL_BUF_SIZE];
static uint16_t rx_pos = 0;
/**
* @brief Dispatch a fully-validated frame to its registered handler.
* Generated from the X-macro table in protocol.h.
*/
static void dispatch_handler(uint16_t cmd, void *payload, uint16_t length)
{
switch (cmd) {
#define X(Enum, Cmd_id, Handler) \
case Cmd_id: \
Handler(payload, length); \
break;
X_PROTOCOL_handlerS
#undef X
default : protocol_send_error();
break;
}
}
/**
* @brief Drain the serial RX buffer and assemble/dispatch any complete frames.
* On bad CRC, oversized length, or buffer overflow, an ERR is emitted
* and the receive state is reset.
*/
static void protocol_receive(void)
{
while (Serial.available()) {
uint8_t byte = (uint8_t)Serial.read();
if (rx_pos < PROTOCOL_BUF_SIZE) {
rx_buf[rx_pos++] = byte;
} else {
/* Buffer overflow — discard and reset. */
protocol_send_error();
rx_pos = 0;
continue;
}
/* Need a full header before total frame length is known. */
if (rx_pos < PROTOCOL_HEADER_SIZE) {
continue;
}
protocol_header_t hdr;
memcpy(&hdr, rx_buf, sizeof(hdr));
if (hdr.length > PROTOCOL_MAX_PAYLOAD) {
protocol_send_error();
rx_pos = 0;
continue;
}
uint16_t total = PROTOCOL_HEADER_SIZE + hdr.length + PROTOCOL_CRC_SIZE;
if (rx_pos < total) {
continue; /* not enough bytes yet */
}
uint16_t rx_crc = (uint16_t)rx_buf[total - 2] | ((uint16_t)rx_buf[total - 1] << 8);
uint16_t calc_crc = protocol_crc16_xmodem(rx_buf, total - PROTOCOL_CRC_SIZE);
if (calc_crc != rx_crc) {
protocol_send_error();
} else {
dispatch_handler(hdr.cmd, &rx_buf[PROTOCOL_HEADER_SIZE], hdr.length);
}
/* Shift any back-to-back trailing bytes to the front of the buffer. */
uint16_t extra = rx_pos - total;
if (extra > 0) {
memmove(rx_buf, rx_buf + total, extra);
}
rx_pos = extra;
}
}
static uint32_t status_led_last_blink = 0;
/**
* @brief Toggle the status LED at #STATUS_LED_BLINK_PERIOD ms intervals.
*/
static inline void update_status_led(void)
{
if (millis() - status_led_last_blink > STATUS_LED_BLINK_PERIOD) {
digitalWrite(STATUS_LED, !digitalRead(STATUS_LED));
status_led_last_blink = millis();
}
}
/**
* @brief Bring up the SPI bus shared by the AD5160 digital potentiometers.
*/
static void initialize_spi(void)
{
SPI.begin();
SPI.setClockDivider(SPI_CLOCK_DIV128); /* 125 kHz @ 16 MHz */
SPI.setDataMode(SPI_MODE0); /* AD5160 (Rev C.) */
SPI.setBitOrder(MSBFIRST); /* AD5160 (Rev C.), p. 13, fig. 37 */
}
void setup()
{
pinMode(STATUS_LED, OUTPUT);
@ -38,102 +149,9 @@ void setup()
rsense_init();
}
/**
* @brief Initialize SPI interface for potentiometer control
*/
void initialize_spi(void)
{
SPI.begin();
SPI.setClockDivider(SPI_CLOCK_DIV128); // 125 kHz @ 16 MHz
SPI.setDataMode(SPI_MODE0); // AD5160 (Rev C.)
SPI.setBitOrder(MSBFIRST); // AD5160 (Rev C.), p. 13, fig. 37
}
/**
* @brief Handle incoming serial commands
* @param command Command character received
*/
void handle_serial_command(char command)
{
switch (command) {
case 'v': // Firmware version
handle_version_command();
break;
case 'd': // Detect/ping
handle_detect_command();
break;
case 'l': // Light sensor presence
lsense_cmd_presence();
break;
case 'r': // Read sensors
lsense_cmd_read();
rsense_cmd_get_cpm();
break;
case 'c': // Dump 128 bytes of channel data
rsense_cmd_dump_channels();
break;
case 'f': // Flush counters
rsense_cmd_flush();
break;
case 'e': // Enable radiation detection
rsense_cmd_enable();
break;
case 's': // Disable radiation detection
rsense_cmd_disable();
break;
case 'p': // Set potentiometers
rsense_cmd_set_potentiometers();
break;
case 't': // Telemetry
rsense_cmd_telemetry();
break;
case 'm': // Set spectrum mode (16-bit or 32-bit)
rsense_cmd_set_configuration();
break;
default:
// Unknown command - ignore
break;
}
}
/**
* @brief Handle firmware version command
*/
void handle_version_command(void)
{
Serial.write(FIRMWARE_VERSION_MAJOR);
Serial.write(FIRMWARE_VERSION_MINOR);
Serial.flush();
SERIAL_BUFFER_CLEAR();
}
/**
* @brief Handle detect/ping command
*/
void handle_detect_command(void) { SERIAL_SEND_OK(); }
/**
* @brief Update status LED (blink)
*/
inline void update_status_led(void)
{
if (millis() - status_led_last_blink > STATUS_LED_BLINK_PERIOD) {
digitalWrite(STATUS_LED, !digitalRead(STATUS_LED));
status_led_last_blink = millis();
}
}
/**
* @brief Main program loop
*/
void loop()
{
// Process incoming serial commands
while (Serial.available()) {
char command = Serial.read();
handle_serial_command(command);
}
protocol_receive();
update_status_led();
rsense_periodic();
}