Inital commit
CI / macOS (push) Has been cancelled
CI / Windows / Clang (push) Has been cancelled

This commit is contained in:
2026-06-14 19:48:37 +02:00
commit cd7d411332
30 changed files with 1645 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
add_library(celrs_crsf STATIC crsf.c)
target_include_directories(celrs_crsf PUBLIC "${CMAKE_SOURCE_DIR}")
target_compile_features(celrs_crsf PRIVATE c_std_23)
add_library(celrs_serial STATIC serial.c)
target_include_directories(celrs_serial PUBLIC "${CMAKE_SOURCE_DIR}")
target_compile_features(celrs_serial PRIVATE c_std_23)
# Level-filtering logger — calls log_write(); symbol resolved by the final binary
add_library(celrs_logger STATIC logger.c)
target_include_directories(celrs_logger PUBLIC "${CMAKE_SOURCE_DIR}")
target_compile_features(celrs_logger PRIVATE c_std_23)
# Real log_write implementation — linked into production binaries only
add_library(celrs_log_write STATIC log_write.c)
target_include_directories(celrs_log_write PUBLIC "${CMAKE_SOURCE_DIR}")
target_compile_features(celrs_log_write PRIVATE c_std_23)
+74
View File
@@ -0,0 +1,74 @@
#include "celrs/crsf.h"
#include <string.h>
/* CRC8-CCITT with polynomial 0x07, init 0x00 (used by CRSF/ELRS) */
uint8_t cel_crsf_crc(uint8_t const* data, size_t len) {
uint8_t crc = 0x00;
for (size_t i = 0; i < len; i++) {
crc ^= data[i];
for (int j = 0; j < 8; j++) {
crc = (crc & 0x80) ? (crc << 1) ^ 0x07 : (crc << 1);
}
}
return crc;
}
int cel_crsf_frame_validate(cel_crsf_frame const* frame) {
/* Rebuild the data that was CRC'd: dest + src + type + size + payload */
uint8_t data[260];
size_t offset = 0;
data[offset++] = frame->destination;
data[offset++] = frame->source;
data[offset++] = frame->type;
data[offset++] = frame->size;
memcpy(data + offset, frame->payload, frame->size);
offset += frame->size;
uint8_t calc_crc = cel_crsf_crc(data, offset);
return calc_crc == frame->crc ? 0 : -1;
}
int cel_crsf_frame_parse(cel_crsf_frame* frame, uint8_t const* buf, size_t len) {
if (frame == NULL || buf == NULL) return -1;
/* Minimum: header(1) + dest(1) + src(1) + type(1) + size(1) = 5 bytes */
if (len < 5) return -1;
/* Verify header */
if (buf[0] != CEL_CRSF_FRAME_HEADER) return -1;
frame->destination = buf[1];
frame->source = buf[2];
frame->type = buf[3];
frame->size = buf[4];
uint8_t size = buf[4];
/* Total: header(1) + dest(1) + src(1) + type(1) + size(1) + payload(N) + crc(1) */
size_t total = 6 + size;
if (len < total) return -1;
memcpy(frame->payload, buf + 5, size);
frame->crc = buf[5 + size];
return cel_crsf_frame_validate(frame);
}
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) {
if (dst == NULL) return 0;
dst[0] = CEL_CRSF_FRAME_HEADER;
dst[1] = destination;
dst[2] = source;
dst[3] = type;
dst[4] = size;
if (payload != NULL && size > 0) {
memcpy(dst + 5, payload, size);
}
/* CRC over dest + src + type + size + payload */
uint8_t crc = cel_crsf_crc(dst + 1, 3 + 1 + size);
dst[5 + size] = crc;
return 1 + 3 + 1 + size + 1; /* header + 3 fields + size byte + payload + crc */
}
+65
View File
@@ -0,0 +1,65 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
/* CRSF frame header byte */
#define CEL_CRSF_FRAME_HEADER 0xC8
/* CRSF device addresses */
#define CEL_CRSF_ADDRESS_FC_BROADCAST 0x00
#define CEL_CRSF_ADDRESS_FC 0x10
#define CEL_CRSF_ADDRESS_TBS_GROUND_STATION 0x80
#define CEL_CRSF_ADDRESS_CUSTOM_MODULE 0xEA
#define CEL_CRSF_ADDRESS_RC_DEVICE 0xDD
#define CEL_CRSF_ADDRESS_GPS 0xEC
#define CEL_CRSF_ADDRESS_FLIGHT_CONTROLLER 0xED
/* CRSF frame types */
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;
/* Parsed CRSF frame */
typedef struct {
uint8_t destination;
uint8_t source;
uint8_t type;
uint8_t size;
uint8_t payload[255];
uint8_t crc;
} cel_crsf_frame;
/* 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);
+6
View File
@@ -0,0 +1,6 @@
#include "celrs/log_write.h"
#include <stdio.h>
void cel_log_write(char const* msg) {
printf("%s\n", msg);
}
+3
View File
@@ -0,0 +1,3 @@
#pragma once
void cel_log_write(char const* msg);
+18
View File
@@ -0,0 +1,18 @@
#include "celrs/logger.h"
#include "celrs/log_write.h"
#include <stdio.h>
static cel_log_level s_level = CEL_LOG_DEBUG;
void cel_logger_set_level(cel_log_level level) { s_level = level; }
static void emit(char const* prefix, char const* msg) {
char buf[512];
snprintf(buf, sizeof(buf), "[%s] %s", prefix, msg);
cel_log_write(buf);
}
void cel_log_debug(char const* msg) { if (s_level <= CEL_LOG_DEBUG) emit("DEBUG", msg); }
void cel_log_info(char const* msg) { if (s_level <= CEL_LOG_INFO) emit("INFO", msg); }
void cel_log_warn(char const* msg) { if (s_level <= CEL_LOG_WARN) emit("WARN", msg); }
void cel_log_err(char const* msg) { if (s_level <= CEL_LOG_ERROR) emit("ERROR", msg); }
+9
View File
@@ -0,0 +1,9 @@
#pragma once
typedef enum { CEL_LOG_DEBUG = 0, CEL_LOG_INFO, CEL_LOG_WARN, CEL_LOG_ERROR, CEL_LOG_NONE } cel_log_level;
void cel_logger_set_level(cel_log_level level);
void cel_log_debug(char const* msg);
void cel_log_info(char const* msg);
void cel_log_warn(char const* msg);
void cel_log_err(char const* msg);
+69
View File
@@ -0,0 +1,69 @@
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "celrs/serial.h"
/*
* Platform-agnostic serial port implementation.
*
* Windows uses Win32 CreateFile/ReadFile/WriteFile.
* POSIX uses open/read/write with termios.
*
* This is a stub implementation that compiles but does nothing.
* Real platform-specific code will be added when TDD tests drive it.
*/
#include <stdlib.h>
#include <string.h>
struct cel_serial_port {
char path[256];
int baud_rate;
int fd; /* platform-specific handle (HANDLE on Win, int on POSIX) */
};
cel_serial_port* cel_serial_open(char const* path, int baud_rate) {
if (path == NULL) return NULL;
cel_serial_port* port = (cel_serial_port*)calloc(1, sizeof(cel_serial_port));
if (port == NULL) return NULL;
strncpy(port->path, path, sizeof(port->path) - 1);
port->path[sizeof(port->path) - 1] = '\0';
port->baud_rate = baud_rate;
port->fd = -1;
/* TODO: platform-specific open (CreateFile on Win, open+termios on POSIX) */
(void)baud_rate;
return port;
}
void cel_serial_close(cel_serial_port* port) {
if (port == NULL) return;
/* TODO: platform-specific close */
free(port);
}
size_t cel_serial_read(cel_serial_port* port, unsigned char* buf, size_t len, int timeout_ms) {
(void)port;
(void)buf;
(void)len;
(void)timeout_ms;
/* TODO: platform-specific read */
return 0;
}
size_t cel_serial_write(cel_serial_port* port, unsigned char const* buf, size_t len) {
(void)port;
(void)buf;
(void)len;
/* TODO: platform-specific write */
return 0;
}
void cel_serial_flush(cel_serial_port* port) {
(void)port;
/* TODO: platform-specific flush */
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include <stddef.h>
/* Opaque serial port handle */
typedef struct cel_serial_port cel_serial_port;
/* Open a serial port. path is platform-specific:
* Windows: "COM3"
* Linux/macOS: "/dev/ttyUSB0"
* baud_rate: 400000 is standard for ELRS CRSF
* Returns NULL on failure. */
cel_serial_port* cel_serial_open(char const* path, int baud_rate);
/* Close and free the serial port */
void cel_serial_close(cel_serial_port* port);
/* Read up to len bytes. Returns bytes read, 0 on timeout/error. */
size_t cel_serial_read(cel_serial_port* port, unsigned char* buf, size_t len, int timeout_ms);
/* Write data. Returns bytes written, 0 on error. */
size_t cel_serial_write(cel_serial_port* port, unsigned char const* buf, size_t len);
/* Flush output buffer */
void cel_serial_flush(cel_serial_port* port);