feat: adopt ELRS USB CRSF frame format, add skeleton modules

Switch CRC from CCITT (0x07) to DVB-S2 (0xD5) to match ELRS.
Adopt ELRS USB frame format: [addr][length][type][payload][crc].
Update frame type constants to match current ELRS protocol values.

New skeleton modules (stub implementations with TODO comments):
  - crsf_telemetry.h/.c: telemetry decoders (GPS, battery, link..)
  - crsf_stream.h/.c: incremental streaming frame reader
  - crsf_param.h/.c: parameter protocol (read/write/set_power)

Retained old frame format as *_legacy functions for backward
compatibility with existing telemetry tool and tests.

Add cel_serial_find_elrs_port() and cel_serial_open_probe()
stubs to serial module for port auto-detection and baud probing.
This commit is contained in:
2026-06-14 20:47:56 +02:00
parent c42ec407da
commit df3d399610
16 changed files with 919 additions and 156 deletions
+104 -39
View File
@@ -13,32 +13,106 @@
#define CEL_CRSF_ADDRESS_RC_DEVICE 0xDD
#define CEL_CRSF_ADDRESS_GPS 0xEC
#define CEL_CRSF_ADDRESS_FLIGHT_CONTROLLER 0xED
#define CEL_CRSF_ADDRESS_MODULE 0xEE
#define CEL_CRSF_ADDRESS_LUA 0xEF
/* CRSF frame types */
/* CRSF frame types (ELRS) */
typedef enum {
CEL_CRSF_FRAMETYPE_PACKET_LINK_TELEMETRY = 0x02,
CEL_CRSF_FRAMETYPE_RC_CHANNELS_PACKED = 0x01,
CEL_CRSF_FRAMETYPE_GPS = 0x02,
CEL_CRSF_FRAMETYPE_HEARTBEAT = 0x03,
CEL_CRSF_FRAMETYPE_VERSION = 0x04,
CEL_CRSF_FRAMETYPE_PARAMETER_SETTINGS_ENTRY = 0x05,
CEL_CRSF_FRAMETYPE_PARAMETER_READ = 0x06,
CEL_CRSF_FRAMETYPE_PARAMETER_WRITE = 0x07,
CEL_CRSF_FRAMETYPE_DEVICE_INFO = 0x08,
CEL_CRSF_FRAMETYPE_PARAMETER_LIST = 0x09,
CEL_CRSF_FRAMETYPE_RC_CHANNELS_RAW = 0x16,
CEL_CRSF_FRAMETYPE_MSP_READ = 0x17,
CEL_CRSF_FRAMETYPE_MSP_WRITE = 0x18,
CEL_CRSF_FRAMETYPE_CURR_VOLTAGE_TEMP = 0x1E,
CEL_CRSF_FRAMETYPE_BATTERY_SENSOR = 0x1F,
CEL_CRSF_FRAMETYPE_COMPRESSED_SENSORS = 0x28,
CEL_CRSF_FRAMETYPE_ARM = 0x0D,
CEL_CRSF_FRAMETYPE_SETTING = 0x9E,
CEL_CRSF_FRAMETYPE_SUPERBOX = 0xA0,
CEL_CRSF_FRAMETYPE_DEVICE_SUPERBOX = 0xA1,
} cel_crsf_frame_type;
CEL_CRSF_TYPE_RC_CHANNELS_PACKED = 0x01,
CEL_CRSF_TYPE_GPS = 0x02,
CEL_CRSF_TYPE_VARIO = 0x07,
CEL_CRSF_TYPE_BATTERY = 0x08,
CEL_CRSF_TYPE_BARO_ALT = 0x09,
CEL_CRSF_TYPE_AIRSPEED = 0x0A,
CEL_CRSF_TYPE_HEARTBEAT = 0x0B,
CEL_CRSF_TYPE_RPM = 0x0C,
CEL_CRSF_TYPE_TEMP = 0x0D,
CEL_CRSF_TYPE_VOLTAGES = 0x0E,
CEL_CRSF_TYPE_ESC_SENSOR = 0x10,
CEL_CRSF_TYPE_LINK_STATS = 0x14,
CEL_CRSF_TYPE_RC_CHANNELS = 0x16,
CEL_CRSF_TYPE_ATTITUDE = 0x1E,
CEL_CRSF_TYPE_FLIGHT_MODE = 0x21,
CEL_CRSF_TYPE_DEVICE_PING = 0x28,
CEL_CRSF_TYPE_DEVICE_INFO = 0x29,
CEL_CRSF_TYPE_PARAM_ENTRY = 0x2B,
CEL_CRSF_TYPE_PARAM_READ = 0x2C,
CEL_CRSF_TYPE_PARAM_WRITE = 0x2D,
CEL_CRSF_TYPE_ELRS_STATUS = 0x2E,
} cel_crsf_type;
/* Parsed CRSF frame */
/* ELRS parameter types */
typedef enum {
CEL_PARAM_UINT8 = 0,
CEL_PARAM_INT8 = 1,
CEL_PARAM_UINT16 = 2,
CEL_PARAM_INT16 = 3,
CEL_PARAM_UINT32 = 4,
CEL_PARAM_INT32 = 5,
CEL_PARAM_FLOAT = 8,
CEL_PARAM_TEXT_SELECT = 9,
CEL_PARAM_STRING = 10,
CEL_PARAM_FOLDER = 11,
CEL_PARAM_INFO = 12,
CEL_PARAM_COMMAND = 13,
} cel_param_type;
/* Channel value mapping (11-bit) */
#define CEL_CRSF_CH_MIN 172 /* 988 us */
#define CEL_CRSF_CH_MID 992 /* 1500 us */
#define CEL_CRSF_CH_MAX 1811 /* 2012 us */
/* Parsed CRSF frame (ELRS USB format: [addr][length][type][payload][crc]) */
typedef struct {
uint8_t addr;
uint8_t length;
uint8_t type;
uint8_t payload[255];
uint8_t payload_len;
uint8_t crc;
} cel_crsf_frame;
/* CRC8/DVB-S2 (poly 0xD5, init 0x00) */
uint8_t cel_crsf_crc(uint8_t const* data, size_t len);
/* Build an RC channels frame (16 channels, 11-bit each).
Returns total bytes written into dst (min 28 bytes needed). */
size_t cel_crsf_build_rc_frame(uint8_t* dst, int16_t const channels[16]);
/* Build a device ping frame. Returns bytes written (min 7 bytes needed). */
size_t cel_crsf_build_ping_frame(uint8_t* dst);
/* Build a parameter read frame. Returns bytes written (min 9 bytes needed). */
size_t cel_crsf_build_param_read_frame(uint8_t* dst, uint8_t index,
uint8_t chunk);
/* Build a parameter write frame. Returns bytes written (min 9 bytes needed). */
size_t cel_crsf_build_param_write_frame(uint8_t* dst, uint8_t index,
uint8_t value);
/* Parse a single CRSF frame from buf. Returns 0 on success, -1 on error.
buf should start with the address byte (0xC8, 0xEE, etc.). */
int cel_crsf_frame_parse(cel_crsf_frame* frame, uint8_t const* buf,
size_t len);
/* Channel helpers */
int16_t cel_crsf_channel_clamp(int16_t value);
int16_t cel_crsf_channel_us_to_val(uint16_t us);
uint16_t cel_crsf_channel_val_to_us(int16_t value);
void cel_crsf_channel_default(int16_t channels[16]);
/* Telemetry parsing — see crsf_telemetry.h */
#include "celrs/crsf_telemetry.h"
/* Streaming reader — see crsf_stream.h */
#include "celrs/crsf_stream.h"
/* Parameter protocol — see crsf_param.h */
#include "celrs/crsf_param.h"
/* --- Deprecated (old frame format: [0xC8][dest][src][type][size][payload][crc]) --- */
/* Deprecated frame structure (old format). Use cel_crsf_frame instead. */
typedef struct {
uint8_t destination;
uint8_t source;
@@ -46,20 +120,11 @@ typedef struct {
uint8_t size;
uint8_t payload[255];
uint8_t crc;
} cel_crsf_frame;
} cel_crsf_frame_legacy;
/* CRC8 calculation over CRSF frame data (CCITT poly 0x07) */
uint8_t cel_crsf_crc(uint8_t const* data, size_t len);
/* Validate CRC of a CRSF frame (header already stripped, starts at dest addr) */
int cel_crsf_frame_validate(cel_crsf_frame const* frame);
/* Parse a raw buffer into a cel_crsf_frame. Returns 0 on success, -1 on error.
buf should start with 0xC8 header. */
int cel_crsf_frame_parse(cel_crsf_frame* frame, uint8_t const* buf, size_t len);
/* Build a CRSF frame into dst buffer. Returns total bytes written.
dst must have space for at least 5 + size bytes (header, addr, src, type,
size byte, payload, crc). */
size_t cel_crsf_frame_build(uint8_t* dst, uint8_t destination, uint8_t source,
uint8_t type, uint8_t const* payload, uint8_t size);
int cel_crsf_frame_validate(cel_crsf_frame_legacy const* frame);
int cel_crsf_frame_parse_legacy(cel_crsf_frame_legacy* frame, uint8_t const* buf,
size_t len);
size_t cel_crsf_frame_build_legacy(uint8_t* dst, uint8_t destination,
uint8_t source, uint8_t type,
uint8_t const* payload, uint8_t size);