# celrs A C23 project for interfacing with ELRS TX modules (e.g., BAYCK Nano Dual Band) via serial USB using the CRSF (Crossfire Serial) protocol. Built on the same TDD foundation as [ctdd](https://github.com/PorterSky/ctdd) using [Unity](https://github.com/ThrowTheSwitch/Unity) and [CMock](https://github.com/ThrowTheSwitch/CMock). All dependencies are fetched automatically via CMake `FetchContent` — no manual installation required beyond the tools listed below. ## Requirements | Tool | Purpose | | ------------ | ----------------------------------------------------- | | CMake ≥ 3.21 | Build system | | Ninja | Build backend | | C23 compiler | GCC 14+, Clang 18+ | | Ruby ≥ 3.0 | CMock mock generation | | gcovr ≥ 6.0 | Coverage reports — optional (`uv tool install gcovr`) | ## Build Configure: ```sh cmake -S . -B build -G Ninja ``` Build: ```sh ninja -C build ``` ## Test Full Unity output with colors: ```sh ninja -C build check ``` CTest summary only: ```sh ninja -C build test ``` `check` builds all suites and runs CTest with `--output-on-failure`, so assertion-level detail appears on any failure without running binaries by hand. ## Coverage Configure with coverage instrumentation: ```sh cmake -S . -B build-cov -G Ninja -DENABLE_COVERAGE=ON ``` Generate the HTML report: ```sh ninja -C build-cov coverage ``` Open `build-cov/coverage/index.html` in a browser to view results. Only `celrs/` source files are measured. Unity, CMock, and generated mock files are excluded. Requires GCC or Clang with gcov support, and `gcovr` on `PATH`. > **Windows note:** requires GCC (e.g. `scoop install gcc`) or a Clang > build that includes compiler-rt. A custom Clang without compiler-rt > will fail at link time. ## Run Connect your ELRS TX module via USB, then run: Windows: ```sh ./build/main.exe COM3 ``` Linux / macOS: ```sh ./build/main /dev/ttyUSB0 ``` ## Architecture ``` celrs/ crsf.h / crsf.c CRSF protocol: CRC8, frame parse/build serial.h / serial.c Serial port abstraction (Win/POSIX) logger.h / logger.c Level-filtering logger log_write.h/.c stdout log sink main.c Entry point — demo heartbeat + read tests/ test_crsf.c CRSF CRC, parse, build tests test_serial.c Serial open/close/stub tests test_logger.c Logger level-filtering tests deps/ FindUnity.cmake Fetches Unity v2.6.1 via ZIP FindCMock.cmake Fetches CMock v2.6.0 via ZIP ``` ## CRSF Protocol CRSF (Crossfire Serial Protocol) is the serial protocol used by ELRS for communication between ground station and TX/RX modules. ### Frame format ``` +------+------+------+------+-------+-------+ | 0xC8 | dest | src | type | size | ... | CRC | +------+------+------+------+-------+-------+ 1 byte 1B 1B 1B 1B N B 1 byte ``` - **Header:** Always `0xC8` - **Destination:** Target device address - **Source:** Sender device address - **Type:** Frame type (heartbeat, RC channels, telemetry, etc.) - **Size:** Payload length in bytes - **Payload:** Frame-specific data - **CRC:** CRC8-CCITT over dest+src+type+size+payload ### Common device addresses | Address | Device | | ------- | ----------------------------- | | 0x00 | FC Broadcast | | 0x10 | Flight Controller | | 0x80 | TBS Ground Station | | 0xEA | Custom Module | | 0xDD | RC Device | ### Common frame types | Type | Name | | ------ | --------------------------- | | 0x01 | RC Channels Packed | | 0x02 | Packet Link Telemetry | | 0x03 | Heartbeat | | 0x08 | Device Info | | 0x09 | Parameter List | | 0x17 | MSP Read | | 0x18 | MSP Write | ## Adding a new module (TDD workflow) ### 1. Write the header ```c // celrs/telemetry.h #pragma once #include typedef struct { int16_t rssi; uint8_t link_quality; int16_t tx_power; } telemetry_data; int telemetry_parse(telemetry_data* out, uint8_t const* buf, size_t len); ``` ### 2. Write a failing test ```c // tests/test_telemetry.c #include "unity.h" #include "celrs/telemetry.h" void setUp(void) {} void tearDown(void) {} void test_parse_rssi(void) { telemetry_data data; uint8_t buf[8] = { /* ... */ }; TEST_ASSERT_EQUAL_INT(0, telemetry_parse(&data, buf, sizeof(buf))); TEST_ASSERT_EQUAL_INT16(-42, data.rssi); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_parse_rssi); return UNITY_END(); } ``` ### 3. Register the test in `tests/CMakeLists.txt` ```cmake add_executable(test_telemetry test_telemetry.c) target_include_directories(test_telemetry PRIVATE "${CMAKE_SOURCE_DIR}") target_link_libraries(test_telemetry PRIVATE celrs_telemetry Unity::Unity) target_compile_features(test_telemetry PRIVATE c_std_23) add_test(NAME test_telemetry COMMAND test_telemetry) list(APPEND TEST_TARGETS test_telemetry) ``` ### 4. Add a stub, confirm RED, then implement GREEN Stub `celrs/telemetry.c` with `return -1;`, run tests to see the failure, then implement the real logic and confirm they pass.