206 lines
5.3 KiB
Markdown
206 lines
5.3 KiB
Markdown
# 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 <stdint.h>
|
|
|
|
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.
|