Protocol choice: implemented from spec (CRSFforArduino needs Arduino
framework; Betaflight extraction has deep scheduler dependencies).
Protocol verified against Betaflight src/main/rx/crsf.c + CRSF spec.
crsf.c:
- UART4 PA0=TX/PA1=RX (GPIO_AF8_UART4), 420000 baud 8N1, oversampling×8
APB1=54MHz → BRR=0x101 → 418604 baud (0.33% error, within spec)
- DMA1 Stream2 Channel4, circular 64-byte buffer, IDLE interrupt
DMA half/complete callbacks drain buffer; IDLE fires at frame boundary
- CRC8 DVB-S2 (polynomial 0xD5) validated on every frame
- Parser state machine: SYNC(0xC8)→LEN→DATA with length sanity check
- 11-bit channel unpack for all 16 channels from 22-byte payload
- RC channels frame (0x16): unpacks 16ch, updates last_rx_ms + armed
- Link stats frame (0x14): captures RSSI dBm, LQ%, SNR dB
crsf.h: added rssi_dbm, link_quality, snr fields to CRSFState
config.h: CRSF_ARM_THRESHOLD=1750, CRSF_STEER_MAX=400, CRSF_FAILSAFE_MS=300
main.c:
- crsf_init() called after motor_driver_init()
- RC failsafe: disarm if (now - last_rx_ms) > CRSF_FAILSAFE_MS, but only
after RC was first seen (last_rx_ms != 0) — USB-only mode unaffected
- RC arm: CH5 rising edge → safety_arm_start(); falling edge → disarm
Same ARMING_HOLD_MS interlock as USB arm command
- RC steer: CH1 → crsf_to_range() → ±CRSF_STEER_MAX → motor_driver steer
- RSSI/LQ: appended to JSON when safety_rc_alive() ("rssi","lq" fields)
ui/index.html: hidden RC RSSI row revealed on first packet with rssi/lq
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
46 lines
1.4 KiB
C
46 lines
1.4 KiB
C
#ifndef CRSF_H
|
||
#define CRSF_H
|
||
|
||
#include <stdint.h>
|
||
#include <stdbool.h>
|
||
|
||
/*
|
||
* CRSF/ExpressLRS RC receiver state.
|
||
*
|
||
* Updated from ISR context on every valid frame.
|
||
* Read from main loop — values are naturally atomic (8/16-bit on Cortex-M).
|
||
* last_rx_ms == 0 means no frame received yet (USB-only mode).
|
||
*/
|
||
typedef struct {
|
||
uint16_t channels[16]; /* Raw CRSF values, 172 (988µs) – 1811 (2012µs) */
|
||
uint32_t last_rx_ms; /* HAL_GetTick() at last valid RC frame */
|
||
bool armed; /* CH5 arm switch: true when channels[4] > CRSF_ARM_THRESHOLD */
|
||
|
||
/* Link statistics (from 0x14 frames, optional) */
|
||
int8_t rssi_dbm; /* Uplink RSSI in dBm (negative, e.g. -85) */
|
||
uint8_t link_quality; /* Uplink link quality 0–100 % */
|
||
int8_t snr; /* Uplink SNR in dB */
|
||
} CRSFState;
|
||
|
||
/*
|
||
* crsf_init() — configure UART4 (PA0=TX, PA1=RX) at 420000 baud with
|
||
* DMA1 circular RX and IDLE interrupt. Call once before safety_init().
|
||
*/
|
||
void crsf_init(void);
|
||
|
||
/*
|
||
* crsf_parse_byte() — feed one byte into the frame parser.
|
||
* Called automatically from DMA/IDLE ISR. Available for unit tests.
|
||
*/
|
||
void crsf_parse_byte(uint8_t byte);
|
||
|
||
/*
|
||
* crsf_to_range() — map raw CRSF value (172–1811) linearly to [min, max].
|
||
* Clamps at boundaries. Midpoint 992 → (min+max)/2.
|
||
*/
|
||
int16_t crsf_to_range(uint16_t val, int16_t min, int16_t max);
|
||
|
||
extern volatile CRSFState crsf_state;
|
||
|
||
#endif /* CRSF_H */
|