Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/scripts/check-pg-versions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,9 @@ while IFS= read -r file; do
fi

# Determine companion file (.c <-> .h)
local companion=""
# NOTE: this loop runs in the main script body, not inside a function, so
# `local` is invalid here and aborts the script under `set -e`.
companion=""
if [[ "$file" == *.c ]]; then
companion="${file%.c}.h"
elif [[ "$file" == *.h ]]; then
Expand Down
112 changes: 112 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
name: Build tagged release

# Manually-triggered release build: builds all targets via ci.yml and
# publishes a (pre)release on this repository with the firmware hexes
# and SITL resources attached.
on:
push:
tags:
- 'v*-srxl2-*'
branches:
- 'release/srxl2-*'
workflow_dispatch:
inputs:
tag:
description: 'Release tag to create (e.g. v9.1.0-srxl2-beta1)'
required: true
default: 'v9.1.0-srxl2-beta1'
prerelease:
description: 'Mark as pre-release'
required: false
default: true
type: boolean

permissions:
contents: write

jobs:
build:
name: build
uses: ./.github/workflows/ci.yml

release:
name: Release
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get version
id: version
run: |
VERSION=$(grep project CMakeLists.txt|awk -F VERSION '{ gsub(/[ \t\)]/, "", $2); print $2 }')
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Compute tag
id: tagname
run: |
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
echo "tag=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
else
echo "tag=v${{ steps.version.outputs.version }}-$(basename "${GITHUB_REF_NAME}")" >> $GITHUB_OUTPUT
fi
- name: download artifacts
uses: actions/download-artifact@v4
with:
path: hexes
pattern: matrix-inav-*
merge-multiple: true
- name: download sitl linux
uses: actions/download-artifact@v4
with:
path: resources/sitl/linux
pattern: inav-*SITL-Linux
merge-multiple: true
- name: download sitl linux aarch64
uses: actions/download-artifact@v4
with:
path: resources/sitl/linux/arm64
pattern: inav-*SITL-Linux-aarch64
merge-multiple: true
- name: download sitl windows
uses: actions/download-artifact@v4
with:
path: resources/sitl/windows
pattern: inav-*SITL-WIN
merge-multiple: true
- name: download sitl mac
uses: actions/download-artifact@v4
with:
path: resources/sitl/macos
pattern: inav-*SITL-MacOS
merge-multiple: true
- name: Consolidate sitl files
run: |
zip -r -9 sitl-resources.zip resources/
- name: Upload release artifacts
uses: softprops/action-gh-release@v2
with:
name: inav-${{ steps.version.outputs.version }} ${{ github.event.inputs.tag || steps.tagname.outputs.tag }}
tag_name: ${{ github.event.inputs.tag || steps.tagname.outputs.tag }}
prerelease: true
draft: false
make_latest: false
files: |
hexes/*.hex
sitl-resources.zip
body: |
### Spektrum SRXL2 ESC beta

Firmware build with Spektrum SRXL2 Smart ESC support: direct
single-wire control of a Smart ESC from a UART TX pad, with ESC
telemetry. See `docs/SRXL2_ESC.md` for wiring, configuration and
debugging.

Flash with **Full chip erase**.

### Repository:
${{ github.repository }}

### Branch:
${{ github.ref_name }}

### Commit:
${{ github.sha }}
59 changes: 59 additions & 0 deletions PR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Add Spektrum SRXL2 Smart ESC support

This adds support for driving a Spektrum Smart ESC directly from a UART using
the SRXL2 protocol, instead of PWM. The ESC signal wire goes to the TX pad of a
free hardware UART and the same wire carries telemetry back (voltage, current,
RPM, FET temperature), so you get Smart ESC telemetry without needing a
Spektrum receiver in between.

The flight controller takes the bus master role, following the state machine of
the official SpektrumRC/SRXL2 reference code: addressed handshake polls, a
reply to the ESC's unprompted handshake at power-up, the final broadcast
handshake, then channel data at 100Hz with a telemetry poll every 10th frame.
Channel values use the normal Spektrum ±100% range (0x2AA0..0xD554) — sending
raw 0 as throttle makes the ESC reject it as invalid and keep beeping, that one
took a while to figure out on the bench.

What's included:

- `SRXL2` motor protocol (`motor_pwm_protocol = SRXL2`), no hardware timer used
- "Spektrum SRXL2 ESC" serial port function for the Ports tab
- ESC telemetry wired into the esc_sensor backend, so `vbat_meter_type = ESC`,
`current_meter_type = ESC`, OSD ESC RPM/temperature etc. all work as usual.
RPM is converted from electrical RPM using `motor_poles`.
- A MOTOR REVERSE mode for reversible ESCs. It drives the SRXL2 reverse
channel; when the direction change is actually applied is up to the ESC
firmware (it only reverses through zero throttle), so the switch is safe to
flip in advance, e.g. for reverse-thrust landings.
- `debug_mode = SRXL2` with counters (frames, CRC errors, handshake state)
that make wiring problems easy to spot from the Sensors tab
- `docs/SRXL2_ESC.md` with wiring, configuration and troubleshooting notes

Bench tested on a MATEKF405SE with an Avian Lite 85A (SPMXAE85A) and a 14 pole
motor, fixed wing configuration with a single motor: handshake, throttle,
voltage/current/RPM telemetry and the reverse switch all working. Not flight
tested yet.

Current limitations:

- single ESC on motor 1 only (see below)
- needs a hardware UART (no SoftSerial), and targets with >512KB flash

About the single motor limitation: it's not as arbitrary as it looks. Two ESCs
can't share the wire — Spektrum ESCs all ship with the same fixed device ID
(0x40) and there's no way to change it, so on a shared bus they'd both answer
handshakes and telemetry polls at the same time. And even if the IDs were
different, SRXL2 carries one set of channel data for the whole bus, so every
ESC on it would read the same throttle channel — no differential thrust, which
kills the main reason to have per-motor control in the first place. The real
path to twins is one UART per ESC, each with its own bus and its own throttle
from the mixer. That needs the driver state to become per-instance instead of
the current single global one; it's a reasonable follow-up, but I'd rather land
the single-motor case first since that's what I can actually test on my bench.

A note for review: I picked serial function bit 28 and permanentId 70 for the
new mode box, which were the next free ones at the time of writing. Happy to
renumber if they're already spoken for by another PR.

Companion configurator PR adds the SRXL2 protocol entry, the port function and
the mode name.
127 changes: 127 additions & 0 deletions docs/SRXL2_ESC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Spektrum SRXL2 ESC support

INAV can drive a Spektrum Smart ESC (Avian/Firma "Smart" line) directly over its
SRXL2 throttle wire, replacing the PWM signal with a digital half-duplex link
that also carries ESC telemetry (RPM, voltage, current, FET temperature) back
to the flight controller. The flight controller acts as the SRXL2 bus master
(device ID `0x21`, the role a Spektrum receiver normally plays) and the ESC is
the polled device (ID `0x40`).

## Wiring

- Connect the ESC signal wire to the **TX pad** of a free **hardware UART**
(not SoftSerial). SRXL2 is a single-wire half-duplex bus: the UART runs in
bidirectional mode on the TX pin and the RX pad of that UART stays unused.
- Common ground between FC and ESC as usual.
- On F4 targets the pin runs open-drain with the internal pull-up
(the same proven configuration Betaflight uses for SRXL2), so the line
idles high with no external components.
- Mind target pin sharing: e.g. on MATEKF405SE the TX2 pad (PA2) doubles as
SoftSerial1 — do not enable SoftSerial on the pad used for the ESC.

## Configuration

Ports tab: set the chosen UART's peripheral function to **Spektrum SRXL2 ESC**
(115200 baud). Outputs/Mixer: set the ESC protocol to **SRXL2**.

CLI equivalent:

```
serial <port> 268435456 115200 115200 0 115200 # function bit 28 = SRXL2_ESC
set motor_pwm_protocol = SRXL2
save
```

Telemetry consumers work as with other ESC telemetry sources, e.g.
`set vbat_meter_type = ESC` to read pack voltage from the ESC.

Current limitations:

- Single ESC (motor index 0). Multi-ESC SRXL2 is not supported.
- RPM is converted from the ESC's electrical RPM using `motor_poles`
(default 14) — set it to match the attached motor.
- Available on targets with more than 512KB flash (`USE_SRXL2_ESC`).

### Reversible ESCs

When the motor protocol is SRXL2 a **MOTOR REVERSE** mode appears in the
Modes tab. Assign it to a switch to drive the ESC's reverse channel
(channel 6): switch active = reverse requested. The ESC's own firmware
decides when the direction change is applied (typically only through zero
throttle), so the switch can be armed safely in advance — e.g. for
reverse-thrust landings. INAV's throttle handling is unchanged; this is a
plain pass-through of the direction request.

## Protocol details

The driver (`src/main/drivers/srxl2_esc.c`) implements the master side of the
official Spektrum SRXL2 state machine (reference: SpektrumRC/SRXL2
`spm_srxl.c`):

- **Handshake**: an addressed handshake (`src 0x21 → dst 0x40`) is sent every
50ms. The ESC's own unprompted handshake (`dst 0x00`, sent after its 50ms
power-up listen window) is answered immediately. When the ESC replies with a
handshake addressed back to us, the driver sends the **final broadcast
handshake (`dst 0xFF`)** — required by the official device state machine to
enter its Running state — and switches to normal operation.
- **Baud**: the handshake advertises `baudSupported = 0` (115200 only). The
driver never switches baud, so it must never advertise 400k capability:
the ESC would switch on the broadcast handshake and drop off the bus.
- **Control frames** (type `0xCD`) are sent at 100Hz with channel mask `0x41`:
channel 0 = throttle, channel 6 = reverse. Every 10th frame polls the ESC
for telemetry (`replyID = 0x40`); the ESC answers with a telemetry frame
(type `0x80`) carrying the standard XBUS ESC telemetry block.
- **Channel values** use the Spektrum convention: the full 16-bit range spans
±150% servo travel with 32768 = center. The driver maps
`mincommand..max_throttle` onto the standard ±100% band
`0x2AA0..0xD554`. Raw 0 must never be sent as throttle — it decodes as
−150% (a sub-900µs pulse equivalent) and Smart ESCs reject it as an
invalid throttle, keeping their no-signal beep even with the bus link up.
- **Half-duplex turnaround**: the driver never starts transmitting within
300µs of the last received byte, so ESC replies are not stomped. All frames
use CRC16-CCITT (seed 0) transmitted big-endian.

## Debugging

Set `debug_mode = SRXL2` and watch the debug values (Configurator Sensors tab
or blackbox):

| debug | Meaning |
|-------|---------|
| 0 | Link state: 0 = handshaking, 1 = running |
| 1 | Handshake frames transmitted |
| 2 | Bytes received (includes our own half-duplex echo) |
| 3 | Valid SRXL2 frames received |
| 4 | CRC failures (non-zero = electrical/wiring problem) |
| 5 | Handshake replies received from the ESC |
| 6 | Telemetry frames received from the ESC |
| 7 | Current throttle channel value (~10912 at idle) |

Interpretation guide: debug 1 growing with debug 2 stuck at 0 means nothing is
coming back (wiring); debug 4 growing means a noisy line; debug 5 > 0 with
debug 0 = 0 means a handshake logic problem; debug 6 growing proves the ESC is
in its running state and answering polls.

## Development notes

Issues found and fixed while bringing the driver up on real hardware
(MATEKF405SE + Spektrum Smart ESC), kept here so future work doesn't
rediscover them:

1. **CRC byte order**: the CRC is big-endian on the wire; the RX validator
must compare without byte-swapping the computed value.
2. **Current units**: the XBUS telemetry block reports current in 10mA units,
which is already the centiampere unit `escSensorData_t` expects — no
scaling.
3. **Handshake state machine**: the ESC's unprompted handshake (`dst 0`) is a
request, not a confirmation — it must be answered, and only the addressed
reply plus our final `0xFF` broadcast completes the sequence.
4. **Line drive**: plain `SERIAL_BIDIR` (open-drain + pull-up). `SERIAL_BIDIR_PP`
must not be used: the push-pull pin has no pull-up, so the line floats
whenever the half-duplex UART releases the driver between frames.
5. **Throttle encoding**: see Channel values above — the ±100% band, never
raw 0. The symptom of a wrong band is subtle: handshake and telemetry work
but the ESC keeps its no-throttle beep.
6. **Build system**: `PWM_TYPE_SRXL2` uses no hardware timer
(`usesHwTimer = false`) and `pwmCompleteMotorUpdate()` must exist on
non-DSHOT targets too (`USE_DSHOT || USE_SRXL2_ESC` guard).
Loading
Loading