Elrs 4.0 support - #20
Merged
Merged
Conversation
Cast pitch, roll, and yaw to int16_t instead of uint16_t in the attitude telemetry example to properly support negative angle values.
Adds the newer CRSF telemetry frame types used by the ELRS 3.4+/4.x ecosystem, with getters for each sensor. RPM, temp, and cells frames are variable length, so their value counts are derived from the frame length. Telemetry parsing is shared between the flight controller and backpack directions, so the backpack path now receives all telemetry types instead of only attitude.
ELRS 4.0 handsets running EdgeTX 2.11+ may append a status byte after the packed channels in RC_CHANNELS_PACKED frames, carrying the commanded arm state for Arm using Switch mode. Detect it by frame length, expose the raw byte, and add isArmed() mirroring the ELRS arming logic (status bit in switch mode, channel 5 position otherwise). Frames without the status byte behave exactly as before.
ELRS TX modules send this extended-header frame to the handset with good/ bad packet counts, warning flags, and a warning message string. Decode it into crsf_elrs_status_t, exposed via getElrsStatus(). The message is variable length on the wire and is copied with a bounded length and guaranteed null termination.
Demonstrates sending CRSF_FRAMETYPE_GPS_TIME so a connected handset can set its clock (used by ELRS 4.1+ with EdgeTX 2.11+; older versions ignore the packet).
writeChannels(addr, channels) sends a standard 22-byte packed channels frame and works with any CRSF device. The three-argument overload appends the ELRS 4.0 channels status byte (CRSF_CHANNELS_STATUS_* bits) so a sketch acting as a handset can command arm state in Arm using Switch mode. ELRS 3.x TX modules do not understand the longer frame, so the status byte overload must only be used with 4.0+ modules. The forwardChannelsToFC example now uses the plain helper; frames sent toward a flight controller never carry the status byte.
Byte 0 of a CRSF frame is a sync byte, not a destination address, so dispatch is now purely type-based: channels and telemetry frames are decoded regardless of the leading byte, and extended header frames (0x28-0x96) are routed by the destination/origin bytes they carry. Adds crsf_ext_header_t and a device address to begin() (default flight controller) for extended frames addressed to a specific device. Behavior change from 1.x: frames that the old address-based dispatch dropped (e.g. telemetry with an unexpected leading byte) are now parsed. Wire compatible with both ELRS 3.x and 4.x.
Opt-in support for appearing as a device on an ELRS 4.0 CRSF network: sendHeartbeat() announces this device's address for router discovery, and setDeviceName() enables answering DEVICE_PING (0x28) with a DEVICE_INFO (0x29) response addressed to the requester. Adds writeExtPacket() for building extended header frames with this device's address as origin. All of it is inert unless the sketch opts in, and the frames themselves predate ELRS 4.0, so 3.x links are unaffected.
TX modules send this extended-header frame to tell the handset the requested channels packet interval and a phase offset correction (both in 0.1us units). Decoded into crsf_handset_timing_t via getHandsetTiming() so handset-emulation sketches can pace their channels frames. The frame was named RADIO_ID in older firmwares with the same wire format, so this works with both ELRS 3.x and 4.x modules.
The spec-compliant dispatch rework changes observable parsing behavior for multi-instance bridge sketches (frames previously dropped by address-based dispatch are now parsed), so this line is a major version.
elrs4SelfTest: loopback functional test needing no radio hardware - two cross-wired UARTs on one board run a handset instance against a TX module instance, exercising channels round-trip, the arm status byte and isArmed logic, variable-length sensor decoding (cells/rpm/temp incl. 24-bit sign extension), GPS time, ELRS_STATUS, handset timing sync, device discovery ping/response, and heartbeat, printing PASS/FAIL per check. handsetEmulator: drives a real TX module - paces channels frames from the module's timing sync, optional 4.0 arm status byte, prints ELRS status. elrs4ReceiverTest: FC-side bench test for a 4.x receiver - prints channels, link stats, and the new sensors (millivolt cells, temps, rpm, airspeed, GPS time), sends heartbeats and answers discovery pings so the sketch appears in the ExpressLRS Lua.
Heading is degrees * 100 on the wire (EdgeTX parses it with 2 decimal places), but the example multiplied by 1000. That made every reading ten times too large and overflowed the uint16 field above 65.5 degrees, which is the 'overflow issue in EdgeTX' the README noted: the bug was ours, not EdgeTX's. 0-360 degrees now maps to 0-36000 and fits comfortably.
The battery example packed capacity with htobe16 shifted left by 8, which is correct big endian but caps capacity at 65535mAh. EdgeTX reads the field as three big endian bytes, so add an htobe24 macro (matching the one in ELRS 4.x) and use it: capacity now goes up to 16777215mAh. Verified the byte order lands as 12 D6 87 for 1234567mAh, which is what EdgeTX's getCrossfireTelemetryValue<3> decodes back to 1234567.
EdgeTX picks what a BaroAltitude packet contains from its declared length: a 2 byte payload is altitude only, 5 bytes adds TBS vertical speed, and 6 or more adds ELRS style int16 vertical speed. The example was truncating the payload to altitude only and sending a separate Vario packet to work around what looked like an EdgeTX parsing problem. Sending the full 4 byte payload works, so altitude and vertical speed now travel in one packet. The standalone Vario send is kept as sendVario() for vertical speed that does not come from a barometer. Also drops the callbacks TODO (not planned; the getter API stays) and notes the 3.x/4.x compatibility story in the README intro.
The README was mostly a CRSF wire format dump, which buried what the library actually does. It now covers features, hardware requirements and wiring, installation, a quick start, a full API reference, a guide to the examples, compatibility gotchas, and references. The protocol specification moves to CRSF_PROTOCOL.md, reorganised into tables and updated to match what this library now implements: byte 0 documented as a sync byte rather than a destination address, extended header frames and their routing explained, and the frame types added since ELRS 3.x filled in. Also documents the model match trap, since a receiver that binds but sends nothing looks exactly like a wiring fault.
Convert Features and Examples sections to use comparison tables showing ELRS 3.x vs 4.x compatibility. Remove Wiring and detailed API sections. This makes it immediately clear which features and examples work on each firmware version, improving usability for users working with different ELRS versions.
Implements the CRSF model select command (0x32) to allow emulating handset behavior when connecting to transmitter modules. This addresses the common issue of mismatched model IDs causing receivers to remain silent. Includes: - New sendModelId() method to transmit model ID to TX module - CRC8 polynomial calculation support for command frame payloads - COMMAND frame protocol constants and support - Updated examples to initialize model ID at startup - Enhanced documentation explaining silent receiver issues and fixes - Library description improvements
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds ExpressLRS 4.0 support while keeping full compatibility with 3.x, and
reworks packet dispatch to follow the CRSF specification.
Added
Frame types decoded
GPS_TIMEAIRSPEEDHEARTBEATRPMTEMPCELLSDEVICE_PING/DEVICE_INFOELRS_STATUSCOMMANDHANDSETRADIO_IDin 3.xProtocol mechanics handled along the way:
from the frame length. RPM values are sign extended from 24 bits.
optional arming status byte, detected by frame length.
destination and origin bytes they carry, via the new
crsf_ext_header_t.COMMANDframes carry an extra CRC over thepayload using polynomial
0xBAbefore the normal0xD5frame CRC.Examples
elrs4SelfTestelrs4ReceiverTesthandsetEmulatorChanged
Packet dispatch is now specification compliant. Byte 0 of a CRSF frame
is a sync byte, not a destination address. Dispatch is purely type based,
and extended header frames are routed by their own destination and origin
bytes.
begin()takes an optional device address, defaulting toCRSF_ADDRESS_FLIGHT_CONTROLLER.Migration: existing sketches compile and run unchanged. Frames that the
old address based dispatch silently dropped are now parsed, which is
observable for sketches running several instances to bridge links.
BaroAltitudesends vertical speed in the same packet. The examplepreviously truncated the frame and sent a separate
Variopacket to workaround an EdgeTX limitation that no longer exists.
sendVario()remains forvertical speed that does not come from a barometer.
forwardChannelsToFCuses the newwriteChannels()helper.Documentation. The README is now about features, requirements, examples
and compatibility, with ELRS 3.x and 4.x support marked per feature. The
wire format moved to
CRSF_PROTOCOL.md, corrected to document byte 0 as async byte and to explain extended header routing.
Fixed
crsf_sensor_attitude_tuseduint16_twhere the specification is signed, so negative pitch, roll and yaw were
misread. The telemetry example also cast negative floats to
uint16_t,which is undefined behaviour.
degrees times 100. Readings were ten times too large and overflowed the
16 bit field above 65.5 degrees. This was the "EdgeTX overflow issue" noted
in the old README TODO; the bug was ours.
65535 mAh. Now 16777215 mAh.
Compatibility
Works with both ELRS 3.x and 4.x from the same sketch. Frames that do not
exist on an older link never arrive, and their getters stay zeroed.
Features needing a specific firmware:
Validation
Self test 26 of 26. Verified on ELRS 3.x hardware (channels, link statistics,
arm state, and newer telemetry correctly absent) and on ELRS 4.x hardware
across all examples.
handsetEmulatorwas confirmed end to end, with emulatedchannel data arriving through the RF link on a second board.