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,8 +1,19 @@
/*
* @file protocol.h
* @brief
* @brief Serial protocol definitions, command structures and dispatch table.
*
* Created: 07.05.2026 10:15:36
* Frame format: [CMD : 2B][LEN : 2B][PAYLOAD : LEN B][CRC16 : 2B]
* - CMD : 16-bit little-endian command identifier.
* - LEN : 16-bit little-endian payload length, excluding CRC.
* - PAYLOAD : LEN bytes, command-specific.
* - CRC16 : CRC16-XMODEM (poly 0x1021, init 0x0000) over CMD+LEN+PAYLOAD.
*
* Response identifiers:
* - 0x414B "AK" : command accepted and executed.
* - 0x4E4B "NK" : valid command but cannot execute (bad params, wrong state).
* - 0x4552 "ER" : malformed frame (bad CRC, bad length).
*
* Created: 07.05.2026
* Author: ThePetrovich
*
* Copyright YKSA - Sakha Aerospace Systems, LLC.
@ -15,294 +26,188 @@
#define PROTOCOL_H
#include "config.h"
#include "lsense.h"
#include <stdint.h>
/*
* @details
* General Protocol Description:
* - All commands are initiated by the host
* - Command format: [CMD (2 bytes)][Length (2 bytes)][Payload (variable)][CRC16 (2 bytes)]
* - CMD: 2 bytes command identifier (e.g., 'HV' for set potentiometers)
* - Length: 2 bytes unsigned integer indicating the length of the payload in bytes
* - Payload: Variable length data specific to the command
* - CRC16: 2 bytes CRC16-XMODEM checksum of the CMD, Length, and Payload fields
* - Response: For commands that require a response, the device will send back a similar structured
* message with the appropriate CMD and payload. Otherwise, it should send an acknowledgment
* (e.g., 'ACK') or an error response if the command is invalid.
* - If a valid command is received, but cannot be executed (e.g. locked state, parameters out of range),
* the device should send a command failure response (e.g., 'NAK') without performing any action.
* - Error Handling: If a command is malformed (e.g., incorrect CRC, invalid length), the device will
* ignore the command and should send an error response (e.g., 'ERR') to the host.
* The device should not perform any action if the command is invalid.
* Example Command: Set Potentiometers
* - CMD: 'HV' (0x48 0x56)
* - Length: 3 (0x00 0x03)
* - Payload: [HV_POT (1 byte)][AMP_POT (1 byte)][DET_POT (1 byte)]
* - CRC16: Calculated over 'HV', Length, and Payload
* Response: 'ACK' if successful, 'NAK' if parameters are out of safe range, 'ERR' if command is malformed
*/
#define PROTOCOL_RESP_ACK 0x414B /* "AK" */
#define PROTOCOL_RESP_NAK 0x4E4B /* "NK" */
#define PROTOCOL_RESP_ERR 0x4552 /* "ER" */
uint16_t protocol_crc16_xmodem(uint8_t *data, uint16_t len);
/**
* @brief Compute CRC16-XMODEM (poly 0x1021, init 0x0000) over a buffer.
* @param data Pointer to input bytes.
* @param len Number of bytes to process.
* @return Final CRC value.
*/
uint16_t protocol_crc16_xmodem(const uint8_t *data, uint16_t len);
/**
* @brief Incrementally update a running CRC16-XMODEM with additional bytes.
* Pass @c crc=0 for the first call, then chain the result through
* further calls. This is the single CRC primitive used by the protocol.
* @param crc Current CRC value.
* @param data Pointer to input bytes.
* @param len Number of bytes to process.
* @return Updated CRC value.
*/
uint16_t protocol_crc16_update(uint16_t crc, const uint8_t *data, uint16_t len);
typedef struct protocol_header_t {
uint16_t cmd;
uint16_t length;
} __attribute__((packed)) protocol_header_t;
void protocol_send_header(uint16_t cmd, uint16_t length);
/**
* @brief Send a complete framed message: header + payload + CRC.
* @param cmd Command identifier.
* @param payload Optional payload pointer (may be NULL when @p length is 0).
* @param length Payload length in bytes.
*/
void protocol_send_message(uint16_t cmd, const void *payload, uint16_t length);
/**
* @brief Send a framed message whose payload is split across two fragments.
* CRC is computed across header + frag1 + frag2 in order.
*/
void protocol_send_message_v2(uint16_t cmd, const void *frag1, uint16_t len1, const void *frag2, uint16_t len2);
/** @brief Send an ACK response (cmd = PROTOCOL_RESP_ACK, no payload). */
void protocol_send_ack(void);
/** @brief Send a NAK response (cmd = PROTOCOL_RESP_NAK, no payload). */
void protocol_send_nak(void);
/** @brief Send an ERR response (cmd = PROTOCOL_RESP_ERR, no payload). */
void protocol_send_error(void);
void protocol_send_message(uint16_t cmd, void *payload, uint16_t length);
/*
* @brief Firmware version command and response structure
* @param[out] major Firmware major version
* @param[out] minor Firmware minor version
* X-macro command dispatch table.
*
* X(EnumName, CmdId, HandlerFn) is the single source of truth for all
* supported commands. It is expanded to:
* - an enumeration of command IDs (e.g. CMD_FIRMWARE_VERSION),
* - prototypes for every handler function,
* - the dispatch switch in sbc_fw.ino.
* The same X-macro can be re-included on the host/master side to generate
* matching send helpers.
*/
#define CMD_FIRMWARE_VERSION 0x7600
#define X_PROTOCOL_handlerS \
X(CMD_FIRMWARE_VERSION, 0x7600, protocol_version_handler) \
X(CMD_LIGHT_SENSOR_PRESENCE, 0x6C00, protocol_light_sensor_presence_handler) \
X(CMD_READ_LIGHT_SENSORS, 0x7200, protocol_read_light_sensors_handler) \
X(CMD_GET_CONFIGURATION, 0x6D00, protocol_get_configuration_handler) \
X(CMD_SET_CONFIGURATION, 0x7D00, protocol_set_configuration_handler) \
X(CMD_SET_POTENTIOMETERS, 0x7D01, protocol_set_potentiometers_handler) \
X(CMD_SET_CALIBRATION, 0x7D02, protocol_set_calibration_handler) \
X(CMD_SET_FLAGS, 0x7D03, protocol_set_flags_handler) \
X(CMD_SET_TEMP_DRIFT_COMPENSATION, 0x7D04, protocol_set_temp_drift_compensation_handler) \
X(CMD_SET_DRIFT_COMPENSATION_ENABLE, 0x7D05, protocol_set_drift_compensation_enable_handler) \
X(CMD_GET_TELEMETRY, 0x7400, protocol_get_telemetry_handler) \
X(CMD_FLUSH_COUNTERS, 0x7401, protocol_flush_counters_handler) \
X(CMD_FLUSH_SPECTRUM, 0x7402, protocol_flush_spectrum_handler) \
X(CMD_RSENSE_ENABLE, 0x7500, protocol_rsense_enable_handler) \
X(CMD_RSENSE_DISABLE, 0x7501, protocol_rsense_disable_handler) \
X(CMD_SPECTRUM_FREEZE, 0x7502, protocol_spectrum_freeze_handler) \
X(CMD_SPECTRUM_UNFREEZE, 0x7503, protocol_spectrum_unfreeze_handler) \
X(CMD_GET_COUNTS, 0x7504, protocol_get_counts_handler) \
X(CMD_READ_SPECTRUM_DATA, 0x7505, protocol_read_spectrum_chunk_handler)
/** @brief Symbolic command IDs generated from #X_PROTOCOL_handlerS. */
enum protocol_cmd_id {
#define X(Enum, Cmd_id, Handler) Enum = Cmd_id,
X_PROTOCOL_handlerS
#undef X
};
/* Command handler prototypes generated from X_PROTOCOL_handlerS. */
#define X(Enum, Cmd_id, Handler) void Handler(void *payload, uint16_t length);
X_PROTOCOL_handlerS
#undef X
/** @brief Response payload for #CMD_FIRMWARE_VERSION. */
typedef struct protocol_firmware_version_response_t {
uint8_t major;
uint8_t minor;
} __attribute__((packed)) protocol_firmware_version_response_t;
void handle_version_command(void *payload, uint16_t length);
/* Light sensor protocol payloads reuse the lsense module types directly
* so handlers can populate them via a single call without field copies. */
/*
* @brief Light sensor presence command and response structure
* @param[out] total_detected Total number of sensors detected across all buses
* @param[out] bus_x_count Number of sensors detected on bus X
* @param[out] bus_x_neg_addr I2C address of negative sensor on bus X (0 if not present)
* @param[out] bus_x_pos_addr I2C address of positive sensor on bus X (0 if not present)
* @param[out] bus_y_count Number of sensors detected on bus Y
* @param[out] bus_y_neg_addr I2C address of negative sensor on bus Y (0 if not present)
* @param[out] bus_y_pos_addr I2C address of positive sensor on bus Y (0 if not present)
* @param[out] bus_z_count Number of sensors detected on bus Z
* @param[out] bus_z_neg_addr I2C address of negative sensor on bus Z (0 if not present)
* @param[out] bus_z_pos_addr I2C address of positive sensor on bus Z (0 if not present)
/** @brief Response payload for #CMD_LIGHT_SENSOR_PRESENCE — alias of #lsense_presence_t. */
typedef lsense_presence_t protocol_light_sensor_presence_response_t;
/**
* @brief Command payload for #CMD_READ_LIGHT_SENSORS.
*
* channel: 0=red 1=green 2=blue 3=IR 4=green2
* 5=visible (R+G+B+G2) 6=all (R+G+B+G2+IR)
*/
#define CMD_LIGHT_SENSOR_PRESENCE 0x6C00
typedef struct protocol_light_sensor_presence_response_t {
uint8_t total_detected;
uint8_t bus_x_count;
uint8_t bus_x_neg_addr;
uint8_t bus_x_pos_addr;
uint8_t bus_y_count;
uint8_t bus_y_neg_addr;
uint8_t bus_y_pos_addr;
uint8_t bus_z_count;
uint8_t bus_z_neg_addr;
uint8_t bus_z_pos_addr;
} __attribute__((packed)) protocol_light_sensor_presence_response_t;
void handle_light_sensor_presence_command(void *payload, uint16_t length);
/*
* @brief Read light sensor data command and response structure
* @param[in] channel Spectral channel (0 = red, 1 = green, 2 = blue, 3 = IR, 4 = green2, 5 = composite visible, 6 =
* composite all)
* @param[out] bus_x_neg_channel_value Channel value for negative sensor on bus X
* @param[out] bus_x_pos_channel_value Channel value for positive sensor on bus X
* @param[out] bus_y_neg_channel_value Channel value for negative sensor on bus Y
* @param[out] bus_y_pos_channel_value Channel value for positive sensor on bus Y
* @param[out] bus_z_neg_channel_value Channel value for negative sensor on bus Z
* @param[out] bus_z_pos_channel_value Channel value for positive sensor on bus Z
*/
#define CMD_READ_LIGHT_SENSORS 0x7200
typedef struct protocol_read_light_sensors_command_t {
typedef struct protocol_read_light_sensors_handler_t {
uint8_t channel;
} __attribute__((packed)) protocol_read_light_sensors_command_t;
} __attribute__((packed)) protocol_read_light_sensors_handler_t;
typedef struct protocol_read_light_sensors_response_t {
uint16_t bus_x_neg_channel_value;
uint16_t bus_x_pos_channel_value;
uint16_t bus_y_neg_channel_value;
uint16_t bus_y_pos_channel_value;
uint16_t bus_z_neg_channel_value;
uint16_t bus_z_pos_channel_value;
} __attribute__((packed)) protocol_read_light_sensors_response_t;
/** @brief Response payload for #CMD_READ_LIGHT_SENSORS — alias of #lsense_channel_values_t. */
typedef lsense_channel_values_t protocol_read_light_sensors_response_t;
void handle_read_light_sensors_command(void *payload, uint16_t length);
/* Configuration commands reuse the persisted-config types from config.h
* directly. The on-the-wire layout is the corresponding struct, so handlers
* can use a single struct assignment in place of field-by-field copies. */
/*
* @brief Get configuration command and response structure
* @param[out] config Current device configuration
*/
#define CMD_GET_CONFIGURATION 0x6D00
typedef struct protocol_get_configuration_response_t {
config_t config;
} __attribute__((packed)) protocol_get_configuration_response_t;
/** @brief Response payload for #CMD_GET_CONFIGURATION — alias of #config_t. */
typedef config_t protocol_get_configuration_response_t;
void handle_get_configuration_command(void *payload, uint16_t length);
/** @brief Command payload for #CMD_SET_CONFIGURATION — alias of #config_t. */
typedef config_t protocol_set_configuration_handler_t;
/*
* @brief Set configuration command and response structure
* @param[in] config New device configuration to apply
*/
#define CMD_SET_CONFIGURATION 0x7D00
typedef struct protocol_set_configuration_command_t {
config_t config;
} __attribute__((packed)) protocol_set_configuration_command_t;
/** @brief Command payload for #CMD_SET_POTENTIOMETERS — alias of #potentiometers_t. */
typedef potentiometers_t protocol_set_potentiometers_handler_t;
void handle_set_configuration_command(void *payload, uint16_t length);
/** @brief Command payload for #CMD_SET_CALIBRATION — alias of #calibration_t. */
typedef calibration_t protocol_set_calibration_handler_t;
/*
* @brief Set potentiometers command and response structure
* @param[in] hv_pot High voltage potentiometer value
* @param[in] amp_pot Amplifier potentiometer value
* @param[in] det_pot Detector potentiometer value
*/
#define CMD_SET_POTENTIOMETERS 0x7D01
typedef struct protocol_set_potentiometers_command_t {
uint8_t hv_pot;
uint8_t amp_pot;
uint8_t det_pot;
} __attribute__((packed)) protocol_set_potentiometers_command_t;
/** @brief Command payload for #CMD_SET_FLAGS — alias of #config_flags_t. */
typedef config_flags_t protocol_set_flags_handler_t;
void handle_set_potentiometers_command(void *payload, uint16_t length);
/** @brief Command payload for #CMD_SET_TEMP_DRIFT_COMPENSATION — alias of #temp_drift_compensation_t. */
typedef temp_drift_compensation_t protocol_set_temp_drift_compensation_handler_t;
/*
* @brief Set calibration data command and response structure
* @param[in] adccal0 ADC offset at GND
* @param[in] adccal3 ADC reading at 3.0V reference
* @param[in] tempcal Temperature calibration value
* @param[in] tempdrift_mvK Temperature drift compensation in mV/K
*/
#define CMD_SET_CALIBRATION 0x7D02
typedef struct protocol_set_calibration_command_t {
uint16_t adccal0;
uint16_t adccal3;
uint16_t tempcal;
uint16_t tempdrift_mvK;
} __attribute__((packed)) protocol_set_calibration_command_t;
/** @brief Command payload for #CMD_SET_DRIFT_COMPENSATION_ENABLE. */
typedef struct protocol_set_drift_compensation_enable_handler_t {
uint8_t enable; /* 1 = enable, 0 = disable */
} __attribute__((packed)) protocol_set_drift_compensation_enable_handler_t;
void handle_set_calibration_command(void *payload, uint16_t length);
/*
* @brief Set flags command and response structure
* @param[in] flags Configuration flags to set (e.g., oversampling, channel mode, temperature compensation)
*/
#define CMD_SET_FLAGS 0x7D03
typedef struct protocol_set_flags_command_t {
uint8_t flags;
} __attribute__((packed)) protocol_set_flags_command_t;
/*
* @brief Set temperature drift compensation ranges command and response structure
* @param[in] tempdrift_pot_hv_low Low threshold for high voltage potentiometer
* @param[in] tempdrift_pot_hv_high High threshold for high voltage potentiometer
* @param[in] tempdrift_pot_amp_low Low threshold for amplifier potentiometer
* @param[in] tempdrift_pot_amp_high High threshold for amplifier potentiometer
* @param[in] tempdrift_pot_det_low Low threshold for detector potentiometer
* @param[in] tempdrift_pot_det_high High threshold for detector potentiometer
*/
#define CMD_SET_TEMP_DRIFT_COMPENSATION_RANGES 0x7D04
typedef struct protocol_set_temp_drift_compensation_ranges_command_t {
uint8_t tempdrift_pot_hv_low;
uint8_t tempdrift_pot_hv_high;
uint8_t tempdrift_pot_amp_low;
uint8_t tempdrift_pot_amp_high;
uint8_t tempdrift_pot_det_low;
uint8_t tempdrift_pot_det_high;
} __attribute__((packed)) protocol_set_temp_drift_compensation_ranges_command_t;
void handle_set_flags_command(void *payload, uint16_t length);
/*
* @brief Get telemetry command and response structure
* @param[out] hv_temp_c High voltage component temperature in 0.1°C
* @param[out] amp_temp_c Amplifier component temperature in 0.1°C
* @param[out] det_temp_c Detector component temperature in 0.1°C
* @param[out] mcu_temp_c MCU temperature in 0.1°C
* @param[out] vbias_mv SiPM bias voltage in mV
* @param[out] hv_pot Current high voltage potentiometer setting
* @param[out] amp_pot Current amplifier potentiometer setting
* @param[out] det_pot Current detector potentiometer setting
* @param[out] cps Current counts per second (CPM/60)
* @param[out] cp10s Counts in the last 10 seconds
* @param[out] total_counts Total counts since last reset
*/
#define CMD_GET_TELEMETRY 0x7400
/** @brief Response payload for #CMD_GET_TELEMETRY. */
typedef struct protocol_get_telemetry_response_t {
uint16_t hv_temp_c;
uint16_t amp_temp_c;
uint16_t det_temp_c;
uint16_t mcu_temp_c;
uint16_t hv_temp_c; /**< 0.1 °C — HV_TEMP_PIN, +28V supply sensor. */
uint16_t amp_temp_c; /**< 0.1 °C — AMP_TEMP_PIN, amplifier sensor. */
uint16_t sipm_temp_c; /**< 0.1 °C — DET_TEMP_PIN, SiPM carrier sensor. */
uint16_t mcu_temp_c; /**< Kelvin — MCU internal temperature sensor. */
uint16_t vbias_mv;
uint8_t hv_pot;
uint8_t amp_pot;
uint8_t det_pot;
uint16_t cps;
uint32_t cp10s;
potentiometers_t pots;
uint16_t cps; /**< counts per second = CPM / 60. */
uint32_t cp10s; /**< counts in the last 10 s window. */
uint32_t total_counts;
uint8_t flags; /**< bit 0: power on ok, bit 1: sensing enabled, bit 2: drift compensation enabled, bit 3: reserved. */
} __attribute__((packed)) protocol_get_telemetry_response_t;
void handle_get_telemetry_command(void *payload, uint16_t length);
/*
* @brief Flush counters command
* @details Resets total counts and 10-second counts to zero
*/
#define CMD_FLUSH_COUNTERS 0x7401
void handle_flush_counters_command(void *payload, uint16_t length);
/*
* @brief Flush spectrum command
* @details Clears all channel data and resets total counts to zero
*/
#define CMD_FLUSH_SPECTRUM 0x7402
void handle_flush_spectrum_command(void *payload, uint16_t length);
/*
* @brief Turn on the spectrometer circutry and enable fast ADC mode for radiation detection
*/
#define CMD_RSENSE_ENABLE 0x7500
void handle_rsense_enable_command(void *payload, uint16_t length);
/*
* @brief Turn off the spectrometer circutry and disable fast ADC mode for radiation detection
*/
#define CMD_RSENSE_DISABLE 0x7501
void handle_rsense_disable_command(void *payload, uint16_t length);
/*
* @brief Freeze spectrum data for reading
*/
#define CMD_SPECTRUM_FREEZE 0x7502
void handle_spectrum_freeze_command(void *payload, uint16_t length);
/*
* @brief Unfreeze spectrum data after reading
*/
#define CMD_SPECTRUM_UNFREEZE 0x7503
void handle_spectrum_unfreeze_command(void *payload, uint16_t length);
/*
* @brief Get counts since last request
*/
#define CMD_GET_COUNTS 0x7504
/** @brief Response payload for #CMD_GET_COUNTS. */
typedef struct protocol_get_counts_response_t {
uint32_t counts;
uint32_t counts;
} __attribute__((packed)) protocol_get_counts_response_t;
void handle_get_counts_command(void *payload, uint16_t length);
/** @brief Command payload for #CMD_READ_SPECTRUM_DATA. */
typedef struct protocol_read_spectrum_chunk_handler_t {
uint16_t offset; /* byte offset into spectrum data */
uint16_t length; /* number of bytes to read */
} __attribute__((packed)) protocol_read_spectrum_chunk_handler_t;
/*
* @brief Read spectrum data (arbitrary chunking for large data)
/**
* @brief Response header for #CMD_READ_SPECTRUM_DATA.
* Followed on the wire by @c length bytes of raw spectrum data.
*/
#define CMD_READ_SPECTRUM_DATA 0x7505
typedef struct protocol_read_spectrum_chunk_command_t {
uint16_t offset;
uint16_t length;
} __attribute__((packed)) protocol_read_spectrum_chunk_command_t;
typedef struct protocol_read_spectrum_chunk_response_t {
uint16_t offset;
uint16_t length;
// Followed by 'length' bytes of spectrum data
uint16_t offset;
uint16_t length;
} __attribute__((packed)) protocol_read_spectrum_chunk_response_t;
void handle_read_spectrum_chunk_command(void *payload, uint16_t length);
#endif /* PROTOCOL_H */