Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Long Range Streaming #2113

wants to merge 9 commits into
base: master
Choose a base branch

Long Range Streaming #2113

wants to merge 9 commits into from


Copy link

@qqqlab qqqlab commented Mar 10, 2023

ExpressLRS appears to be focused on the "Express" part of the name, i.e. low latency racers. This PR focuses on the "LRS" part: Long Range Streaming.

  • Improve bidirectional data throughput by streaming data
  • Add second serial port in addition to the RC link
  • Backwards compatible with V3.2
  • Create a fundament for future expansion such as higher/adaptive data rates and encryption

Technical Details

Streaming Data

The new OTA stream packages that replace the MSP and TLM packages have the following structure:

Main header (one byte):

packetType: 2 bits - the ELRS packet type, to stay compatible with current ERLS implementation
ack: 1 bit - acknowledge the receipt of the previous package, should be added to all package types.
seq: 1 bit - a one bit sequence counter, used to differentiate re-transmits from new transmits.
type: 3 bits - type of the next part of serial data stream (Types are: Command, CRSF, and SERIAL, plus 5 types for future expansion)
more: 1 bit - to indicate that the package contains more parts

Each additional part in the steam package has the following additional one byte header:

len: 4 bits - the length of the part (1-16 bytes)
type: 3 bits - type of the next part
more: 1 bit - more parts follow

To simplify the code, the same headers are used for OTA4 and OTA8. The RC and SYNC packages remain unchanged.

Additional Serial Port

With the defines GPIO_PIN_STREAM_RX, GPIO_PIN_STREAM_TX and STREAM_BAUD an additional serial port can be opened on both the TX and RX. Data send to this port is transmitted to the port on the other device. Serial data, RC channel data and CRSF bidirectional telemetry data are transmitted simultaneously.

Backwards Compatible With V3.2

A handshake is used to set both TX and RX to stream mode, if both sides have this capability. The TX send stream packets as TLM (type 3), the RX sends stream packets as MSP (type 1). This flow is the opposite direction as used in the V3.2 implementation, and stream packets are completely ignored by V3.2 devices. After connection a V3.3 device sends a couple stream packets; if the other device is also V3.3 they both switch to stream mode.

Future Expansion Options

  • High data rate mode. The streaming data structure allows any packet size. A new data rate could be added for higher throughput. For example, by changing the Coding Rate from 4/8 to 4/5 for 333HZ, the payload throughput almost doubles from 3330 to 6330 B/s.
  • Encryption. Once the packet size is at least 16 bytes, it becomes relatively easy to add AES-128 ECB encryption.
  • UI (hardware.html) to set stream serial port pins, baud rate.
  • Protocol for stream serial port. If ELRS has knowledge of the serial protocol (MavLink, CRSF, NMEA, etc.) it can better use the available bandwidth by only transmitting complete packages.
  • Serial hardware flow control RTS/CTS.
  • Retransmission. The ack and seq flags are in place, need to add code to manage the retries. Add lost connection check so that the transmitter doesn't keep re-transmitting everything when no replies are received. Need to have a close look at non-1:2 telemetry ratio operation.
  • Include Airport Airport #1904 functionality in the main firmware.


Below the test results when sending concurrent rc data, max CRSF data uplink (ul) and max CRSF data downlink (dl).

Link capacity

 50           rc: 13 Hz                    ul/dl:  62/ 125 B/s
100F          rc: 25 Hz                    ul/dl: 250/ 500 B/s
333F          rc: 83 Hz                    ul/dl: 833/1666 B/s
500           rc:125 Hz                    ul/dl: 625/1250 B/s

Results with this PR (len is the frame length of the CRSF packages)

 50   len:10  rc: 12 Hz  ul/dl:  6/  9 Hz  ul/dl:  60/  90 B/s
 50   len:22  rc: 11 Hz  ul/dl:  3/  5 Hz  ul/dl:  66/ 110 B/s
100F  len:10  rc: 25 Hz  ul/dl: 25/ 32 Hz  ul/dl: 250/ 320 B/s
100F  len:22  rc: 26 Hz  ul/dl: 12/ 21 Hz  ul/dl: 264/ 462 B/s
333F  len:10  rc: 85 Hz  ul/dl: 85/118 Hz  ul/dl: 850/1180 B/s
333F  len:22  rc: 83 Hz  ul/dl: 38/ 66 Hz  ul/dl: 836/1452 B/s
500   len:10  rc:126 Hz  ul/dl: 62/ 82 Hz  ul/dl: 620/ 820 B/s
500   len:22  rc:122 Hz  ul/dl: 28/ 50 Hz  ul/dl: 616/1100 B/s

Results with V3.2.0

 50   len:10  rc: 12 Hz  ul/dl:  6/  0 Hz  ul/dl:  60/   0 B/s
 50   len:22  rc: 13 Hz  ul/dl:  2/  1 Hz  ul/dl:  44/  22 B/s
100F  len:10  rc: 25 Hz  ul/dl: 12/  1 Hz  ul/dl: 120/  10 B/s
100F  len:22  rc: 25 Hz  ul/dl:  8/  0 Hz  ul/dl: 176/   0 B/s
333F  len:10  rc: 81 Hz  ul/dl: 41/  0 Hz  ul/dl: 410/   0 B/s
333F  len:22  rc: 82 Hz  ul/dl: 28/  0 Hz  ul/dl: 616/   0 B/s
500   len:10  rc:130 Hz  ul/dl: 65/ 20 Hz  ul/dl: 650/ 200 B/s
500   len:22  rc:124 Hz  ul/dl: 25/  4 Hz  ul/dl: 550/  88 B/s

Tools for testing: connected to receiver UART running on EdgeTx Radio

Additional Serial Port

               +----+           +----+
RC/MSP/TLM <-->| TX |    air    | RX |<--> RC/MSP/TLM
               |    |< ~ ~ ~ ~ >|    |
serial data <->|    |           |    |<-> serial data
               +----+           +----+

To enable the optional serial data port a second UART is needed both on the TX and RX side. For DIY hardware this is easy to achieve, for commercially available hardware a bit more difficult.

Most commercial TX devices have a debug/backpack UART which could be used for serial data.

For commercial RX it is not so easy as most devices only break out a single UART. I see the following options:

  • ESP32 receiver: will work fine, but there are not many available with accessible (PWM) pins.
  • ESP8285 receiver: the ESP8285 has one full hardware UART, and a second TX-only UART. So not really an option, unless we use a low baud rate software UART on a receiver with PWM pins.
  • Use ESP32 TX as receiver: the ESP32 has 3 hardware UARTs which can be connected to almost any GPIO with the IO-MUX. Most existing TX use GPIO13 as half duplex (one pin) UART to the handset, and UART0 on 1,3 as debug/backpack UART. Could use a TX module as receiver, only problem is to get access to second UART pins. Thanks to the IO-MUX, we could use for example the I2C lines of a display, or test pins if available. Using a TX as receiver has the added feature of higher transmit power for telemetry.

Seeing the rate at which new commercial hardware enters the market, I expect updated hardware soon after new software capabilities are available.

Analysis of ELRS V3.2

The TX sends 3 types of OTA packages: RC, SYNC and MSP. The RX only sends TLM type packages. The RC and SYNC packages are single package messages, the MSP and TLM are multi package messages. The MSP and TLM messages have the following properties:

  • The OTA data is the CRSF telemetry message, sent as multiple OTA packets, or an internal ELRS command such as BIND.
  • Each packet has a packageIndex header which is used to reassemble the multiple OTA packages into a single message on the receiving end.
  • Each message is sent as two OTA packages minimum.
  • Both transmitter and receiver keep track of a TelemetryStatus bit. This bit is used to acknowledge receipt of packages, and to facilitate re-transmits.
  • The TelemetryStatus bit is transmitted in the RC package and in the LINKSTATUS TLM package.
  • If the receiver and transmitter get out of sync, a resync package is sent to synchronize the TelemetryStatus bits on the transmitter and receiver.


  • On average half a OTA payload is wasted with every CSRF message because of packing/unpacking the data.
  • Small messages use two packages, even if they would fit in one.
  • Synchronization mechanism uses additional bandwidth and makes the system more complex.
  • The TLM TelemetryStatus bit is sent as part of LINKSTATUS, so 32 bits are used to transmit 1 bit of payload. Adding the TelemetryStatus to the header without breaking compatibility appears not feasible.
  • MSP packages do not have TelemetryStatus bit, slowing the link down when sending simultaneous up and downlink telemetry data. Adding the TelemetryStatus to the header without breaking compatibility appears not feasible.

The current setup works fine for sending occasional telemetry back and forth. However when trying to fully load the link, it sometimes collapses so badly that nothing gets through anymore, see #2088. I've submitted a couple of PRs to patch the current setup, but some issues can't be patched without making incompatible changes. This being the case, I thought it might be a good idea to try a new design.

The new design is similar to the radio design. It streams data, instead of packetizing data. As the OTA data is CRSF, it can be streamed, no need to re-package it. Additional synchronization is not needed, as the CRSF stream will self-synchonize: CRSF has 18 'sync' bits: 8 bit sync byte, 2 zero bits in length, 8 bit crc, so in a random byte stream there is only a 1/250000 chance of a mismatch.

The internal ELRS commands have to be converted to CRSF messages, or handled separately. I chose the second option, such that an ELRS command is always sent as single OTA package and it jumps the CRSF queue.

@qqqlab qqqlab marked this pull request as draft March 10, 2023 15:18
Copy link

pkendall64 commented Apr 4, 2023

@qqqlab I like the idea behind this PR, can you fix the conflicts with current master. I like the idea of a flag for switching stream mode on if both TX and RX agree.

Copy link
Contributor Author

qqqlab commented Apr 13, 2023

@pkendall64 Thanks for the feedback. I've fixed the conflicts with the current master.

Copy link
Contributor Author

qqqlab commented Apr 15, 2023

I've improved the structure of the Stream OTA packet to make it more flexible and future proof, see updated PR description.

The only ESP32 receiver with PWM pins I could find in targets.json is the Radiomaster ER6-GV, which is apparently not released yet. So, I'm soldering together a DIY ESP32 receiver now, to do further testing with 2 serial ports on a receiver.

@qqqlab qqqlab changed the title WIP: Stream MSP/TLM Long Range Data Apr 20, 2023
@qqqlab qqqlab marked this pull request as ready for review April 20, 2023 14:18
Copy link
Contributor Author

qqqlab commented Apr 20, 2023

@pkendall64 I think this PR is ready for an initial review, see updated PR comment. I removed PR Draft status. Unfortunately some STM32 targets are failing due to code size. I'm looking forward to your feedback.

@qqqlab qqqlab changed the title Long Range Data Long Range Streaming Apr 21, 2023
Copy link

flux242 commented Jul 26, 2023

why do you want to split incoming data on the RX side into 2 UARTs? If you transfer data OTA serially (halve duplex even) then it should be possible to feed the data consumer serially using only one UART, shouldn't it?

you want an additional UART on the TX be hardwired with a computer to send serial data? BLE is the way, imo

Copy link
Contributor Author

qqqlab commented Jul 26, 2023

@flux242 Thanks for your feedback. For my use case I wanted to have a second RX UART to communicate directly with a companion computer on the drone. Of course there are other ways to achieve this, but to me this appeared to be the cleanest solution without needing additional hardware or flight controller modifications.

The second UART on the TX (or RX) can for example be used for BLE or WIFI, either externally via a physical UART link or internally by including BLE/WIFI in the ELRS firmware. Adding a physical second UART keeps all options open, at the cost of two additional solder pads.

A setup which should work out the box, is this:

  • Laptop/Tablet running ground control software with WIFI Mavlink
  • RC transmitter with ELRS TX module and ELRS backpack flashed as Mavlink to WIFI bridge
  • ELRS RX module with UART1 for CRSF RC data and UART2 for Mavlink, both UARTs going to the flight controller

(I didn't find the time yet to try this out.)

Copy link

flux242 commented Jul 26, 2023

@qqqlab that's the point -- you need an additional HW on the RX side, namely an additional UART which is not always easy to get on the FC side. I understand your point to use existing SW framework on the FC side (inav supports it this way I suppose), so splitting the data was the easiest way to implement the feature. The only question of mine is - is this really necessary? Can inav parse incoming data and split it in SW? If yes then no HW changes would be necessary

Edit: but anyway - your solution is an improvement of the Airport implementation where 2 radio links are necessary which is obviously an overkill

Copy link
Contributor Author

qqqlab commented Jul 26, 2023

@flux242 Yes, the FC could do the splitting and that would save the additional hardware interface. However, I don't think any current FC firmware supports this, so for each FC firmware (inav, ardupilot, px4, betaflight, kiss, dji, you name it) additional programming would be needed. Not to mention that somebody has to dream up a splitting specification, and enforce it. Decent FC hardware has 5+ UARTs, so sacrificing a FC UART and soldering two additional wires appears to me the quicker/easier/more reliable solution.

Copy link

cbf123 commented Dec 12, 2023

with the work being done on the mavlink-rc branch, is this still useful?

Copy link

should not be this PR closed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
None yet
None yet

Successfully merging this pull request may close these issues.

None yet

5 participants