feat(rc): CRSF/ELRS RC integration — telemetry uplink + channel fix (Issue #103) #111

Merged
sl-jetson merged 1 commits from sl-firmware/issue-103-crsf-rc into main 2026-03-02 08:41:16 -05:00
Collaborator

Summary

Closes #103 — CRSF/ELRS 2.4GHz RC receiver integration on STM32F722.

What was already done (from crsf-elrs branch)

  • CRSF frame decoder on UART4, 420000 baud, DMA circular buffer + IDLE interrupt
  • Channel map, arm switch (CH5), failsafe disarm, mode manager (CH6 3-pos blend)
  • RC override of autonomous: mode_manager blends steer/speed per CH6 mode

New in this PR

include/config.h

  • CH1[0]=steer, CH2[1]=throttle (corrected from CH4/CH3)
  • CRSF_FAILSAFE_MS 300→500ms per spec
  • VBAT_SCALE_NUM, VBAT_AREF_MV, VBAT_ADC_BITS — Mamba F722 11:1 divider
  • CRSF_TELEMETRY_HZ = 1 — telemetry TX rate

include/battery.h + src/battery.c (new)

  • ADC3 single-shot on PC1 (ADC123_IN11), 480-cycle sampling
  • battery_read_mv() → Vbat in millivolts
  • battery_estimate_pct() → 0–100% SoC, auto-detects 3S vs 4S

include/crsf.h + src/crsf.c — CRSF telemetry TX uplink

  • crsf_send_battery(voltage_mv, current_ma, remaining_pct) — type 0x08
    voltage in 100mV units, current in 100mA units, big-endian, CRC8-DVB-S2
  • crsf_send_flight_mode(armed) — type 0x21, "ARMED\0"/"DISARM\0" string
  • crsf_build_frame() shared internal helper

src/main.c

  • battery_init() called after crsf_init()
  • 1Hz telemetry tick: crsf_send_battery(vbat_mv, 0, soc_pct) + crsf_send_flight_mode(armed)

test/test_crsf_frames.py — 28 pytest tests

  • CRC8-DVB-S2 correctness
  • Battery frame: sync, type, voltage/current encoding, capacity=0, SoC byte, length
  • Flight-mode frame: text payload, null terminator, length, CRC
  • battery_estimate_pct: 3S/4S full/empty/mid, auto-detection boundary

Test plan

  • pytest test/test_crsf_frames.py -v — 28 tests pass
  • Connect ELRS receiver on UART4 (PA0/PA1 at 420000 baud)
  • Verify /uwb/ranges RC channels on USB telemetry stream (ch1–ch4 µs values)
  • Confirm failsafe: unplug RX → robot disarms within 500ms
  • Confirm arm hold: CH5 high → hold 3s → armed; CH5 low → disarm
  • Confirm CH6 mode switch: low=RC manual, mid=assisted, high=autonomous
  • Verify handset OSD shows battery voltage and ARMED/DISARM state

🤖 Generated with Claude Code

## Summary Closes #103 — CRSF/ELRS 2.4GHz RC receiver integration on STM32F722. ### What was already done (from crsf-elrs branch) - CRSF frame decoder on UART4, 420000 baud, DMA circular buffer + IDLE interrupt - Channel map, arm switch (CH5), failsafe disarm, mode manager (CH6 3-pos blend) - RC override of autonomous: `mode_manager` blends steer/speed per CH6 mode ### New in this PR **`include/config.h`** - CH1[0]=steer, CH2[1]=throttle (corrected from CH4/CH3) - `CRSF_FAILSAFE_MS` 300→500ms per spec - `VBAT_SCALE_NUM`, `VBAT_AREF_MV`, `VBAT_ADC_BITS` — Mamba F722 11:1 divider - `CRSF_TELEMETRY_HZ = 1` — telemetry TX rate **`include/battery.h` + `src/battery.c`** (new) - ADC3 single-shot on PC1 (ADC123_IN11), 480-cycle sampling - `battery_read_mv()` → Vbat in millivolts - `battery_estimate_pct()` → 0–100% SoC, auto-detects 3S vs 4S **`include/crsf.h` + `src/crsf.c`** — CRSF telemetry TX uplink - `crsf_send_battery(voltage_mv, current_ma, remaining_pct)` — type 0x08 voltage in 100mV units, current in 100mA units, big-endian, CRC8-DVB-S2 - `crsf_send_flight_mode(armed)` — type 0x21, `"ARMED\0"`/`"DISARM\0"` string - `crsf_build_frame()` shared internal helper **`src/main.c`** - `battery_init()` called after `crsf_init()` - 1Hz telemetry tick: `crsf_send_battery(vbat_mv, 0, soc_pct)` + `crsf_send_flight_mode(armed)` **`test/test_crsf_frames.py`** — 28 pytest tests - CRC8-DVB-S2 correctness - Battery frame: sync, type, voltage/current encoding, capacity=0, SoC byte, length - Flight-mode frame: text payload, null terminator, length, CRC - `battery_estimate_pct`: 3S/4S full/empty/mid, auto-detection boundary ## Test plan - [ ] `pytest test/test_crsf_frames.py -v` — 28 tests pass - [ ] Connect ELRS receiver on UART4 (PA0/PA1 at 420000 baud) - [ ] Verify `/uwb/ranges` RC channels on USB telemetry stream (ch1–ch4 µs values) - [ ] Confirm failsafe: unplug RX → robot disarms within 500ms - [ ] Confirm arm hold: CH5 high → hold 3s → armed; CH5 low → disarm - [ ] Confirm CH6 mode switch: low=RC manual, mid=assisted, high=autonomous - [ ] Verify handset OSD shows battery voltage and ARMED/DISARM state 🤖 Generated with [Claude Code](https://claude.com/claude-code)
sl-firmware added 1 commit 2026-03-02 08:37:06 -05:00
## Summary
- config.h: CH1[0]=steer, CH2[1]=throttle (was CH4/CH3); CRSF_FAILSAFE_MS→500ms
- include/battery.h + src/battery.c: ADC3 Vbat reading on PC1 (11:1 divider)
  battery_read_mv(), battery_estimate_pct() for 3S/4S auto-detection
- include/crsf.h + src/crsf.c: CRSF telemetry TX uplink
  crsf_send_battery() — type 0x08, voltage/current/SoC to ELRS TX module
  crsf_send_flight_mode() — type 0x21, "ARMED\0"/"DISARM\0" for handset OSD
- src/main.c: battery_init() after crsf_init(); 1Hz telemetry tick calls
  crsf_send_battery(vbat_mv, 0, soc_pct) + crsf_send_flight_mode(armed)
- test/test_crsf_frames.py: 28 pytest tests — CRC8-DVB-S2, battery frame
  layout/encoding, flight-mode frame, battery_estimate_pct SoC math

Existing (already complete from crsf-elrs branch):
  CRSF frame decoder UART4 420000 baud DMA circular + IDLE interrupt
  Mode manager: RC↔autonomous blend, CH6 3-pos switch, 500ms smooth transition
  Failsafe in main.c: disarm if crsf_state.last_rx_ms stale > CRSF_FAILSAFE_MS
  CH5 arm switch with ARMING_HOLD_MS interlock + edge detection
  RC override: mode_manager blends steer/speed per mode (CH6)

Closes #103

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sl-jetson merged commit 23668d1d98 into main 2026-03-02 08:41:16 -05:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: seb/saltylab-firmware#111
No description provided.