Skip to content Link Search Menu Expand Document
ModalAI DOCS
Store

voxl-elrs

Project badge Project badge Project badge

Table of contents

  1. Overview
    1. Supported hardware
    2. Transmitter compatibility
    3. Installation
  2. Ports and Transports
  3. Verify the Link
  4. Binding
  5. Firmware Updates
    1. Recovering a bricked ESP receiver
    2. M0220 firmware (SMP)
  6. PWM Outputs
  7. SKU Presets — Automatic Configuration
  8. Wiring
    1. BetaFPV Nano RX
  9. Tips

Overview

ExpressLRS (ELRS) is a completely open, high-refresh, long-range radio control link using the CRSF protocol. voxl-elrs is ModalAI’s tool for managing ELRS radios attached to VOXL: verifying the link, binding, flashing firmware, reading and writing receiver parameters, mapping PWM outputs, and applying whole configurations automatically by SKU.

voxl-elrs was rewritten as a compiled CLI in version 1.0.0. The old Python tool and its interactive wizard (voxl-elrs -w) no longer exist, and several old flags changed meaning (-b is now --baud, not --bind; -p is now --port, not --setid). If you are following instructions that mention the wizard, --scan, or /etc/modalai/voxl-elrs.conf, they predate the rewrite — the current equivalents are all on this page.

Supported hardware

Firmware bundled with voxl-elrs (currently ELRS 3.5.3.x) covers:

DeviceRoleNotes
ModalAI M0184Receiversee M0184
ModalAI M0193Transmitter (a receiver build is also bundled) 
BetaFPV Nano 900MHzReceiver 
ModalAI M0220Transceivernot in the bundled catalog; updated over SMP from an MRC bundle you supply. See M0220 and M0220 firmware below

In theory any ELRS-compatible receiver can be talked to over CRSF, but bind/PWM/preset functionality depends on ModalAI firmware modifications. To see exactly what firmware your installed version bundles, run voxl-elrs list-firmware on target.

Transmitter compatibility

The ELRS version the transmitter must run depends on which ModalAI radio is installed on the vehicle:

  • M0184 receiver: the bundled receiver firmware is ELRS 3.x, so the transmitter module must run ELRS v3. Transmitters on ELRS 4.x cannot bind to the M0184 and must be downgraded to the latest 3.x release using the standard ExpressLRS transmitter update process. The transmitter must also be a 900 MHz or dual-band module operating in the low band. 2.4 GHz-only modules, for example the internal ELRS module of the Radiomaster Boxer, cannot bind to the M0184. See M0184 transmitter compatibility.
  • M0220 transceiver: compatible with ELRS v4+ devices by default; operation with ELRS v3 devices is experimental. See the M0220 page.

Installation

voxl-elrs ships with the VOXL SDK, and can be installed on VOXL or any Debian/Ubuntu machine with the ModalAI package repository configured:

sudo apt install voxl-elrs

Bash completion is included; voxl-elrs version prints the git version and which transports/APIs were compiled in — useful first diagnostic when reporting issues.

Ports and Transports

By default voxl-elrs uses --port auto: when voxl-px4 is running it talks over the MPA pipe (mpa:crsf_raw) so it can coexist with the autopilot, otherwise it opens the radio UART (qup7) directly. Override with -p:

voxl-elrs ping                      # auto port selection
voxl-elrs -p qup7 ping              # explicit VOXL 2 radio UART
voxl-elrs -p /dev/ttyUSB0 ping      # USB serial adapter on a host PC
voxl-elrs -p mpa:crsf_raw ping      # explicit MPA pipe

From SDK 1.8.0 (voxl-elrs 1.2.0 and newer), a host PC can reach a radio attached to a VOXL over USB with -p adb:SERIAL. Install the Android platform tools on the host first, and install voxl-elrs with the same major RPC protocol version on both the host and the VOXL. The host forwards a local TCP port to an abstract Unix socket on the VOXL and starts voxl-elrs raw --socket NAME there through adb; the target side picks its own auto port. Find the serial with adb devices -l:

adb devices -l
voxl-elrs -p adb:0123456789ABCDEF ping

Do not combine an adb: port with -B or -E passthrough.

-b/--baud overrides the serial baud rate (default 420000). Three passthrough modes exist for radios that aren’t wired directly to VOXL, all requiring an explicit /dev/... port:

  • -B/--betaflight — receiver behind a Betaflight flight controller: sets up serial passthrough (the Betaflight configurator must be disconnected). Passthrough persists until the FC power cycles.
  • -E/--edgetx — internal TX module in an EdgeTX handset connected via USB (Serial/VCP mode).
  • -T/--tx — talk directly to a TX module’s CRSF UART (TX addressing and TX default baud).

For flashing across a bootloader that runs at a different baud than the app, add --boot-baud <baud> (e.g. -b 420000 --boot-baud 921600).

Always start with ping — it prints the device name, serial, hardware/firmware ID, and parameter count:

voxl2:/$ voxl-elrs ping

If the device doesn’t respond, check the wiring and the ELRS LED status of the receiver.

From SDK 1.8.0 (voxl-elrs 1.2.3), commands that look up a parameter by name (bind, unbind, arm, and so on) use a per-boot cache of parameter indexes in /run/voxl-elrs (or $XDG_RUNTIME_DIR/voxl-elrs without access to /run). After the first full enumeration a lookup costs one ping and one read, and the name at the cached index is checked before it is used; a mismatch re-enumerates. The cache is keyed by device name, IDs, firmware version, and parameter count. Firmware updates clear it, as does the first device info after a ModalAI receiver reboots. Set ELRS_PARAM_CACHE_DIR to move it, or set it to an empty value to disable it.

Binding

voxl-elrs bind        # put the connected receiver into bind mode
voxl-elrs unbind      # unbind a bound transmitter

Receiver and transmitter must be on the same major ELRS version to bind (3.x with current firmware). A binding phrase can instead be set through the SKU preset system or voxl-portal, which converts the phrase to a UID and writes it to the receiver.

bind and unbind aren’t special cases: any command parameter the receiver exposes over CRSF can be invoked by name the same way. Use enumerate to see everything the device offers, and get-param <idx> / set-param <idx> <val> to read and write individual parameters. get-rc, get-link-stats, and failsafe are also available for checking RC input, link quality, and failsafe behavior.

Firmware Updates

For M0184 / M0193 / BetaFPV devices, firmware ships on-target under /usr/share/modalai/voxl-elrs/firmware:

voxl-elrs list-firmware              # what's bundled, per target
voxl-elrs update                     # auto-detect device, flash if out of date
voxl-elrs update --target MODALAI_M0184_RX    # skip device detection

update stops voxl-px4 if it’s running (to free the UART), flashes over XMODEM (ModalAI targets) or esptool (ESP targets), and restarts the autopilot after. The --target form is also the recovery path for a receiver that no longer responds to detection.

upload-firmware <file> [xmodem|esp|smp] flashes an arbitrary firmware file; the protocol is detected when omitted (mrc selects the legacy M0220 MAVLink FTP path). Use it for firmware built yourself with the ExpressLRS Configurator.

Recovering a bricked ESP receiver

Manually enter the bootloader: power off, hold the boot button, power on while holding it for a second. Then flash directly with esptool:

python3 -m esptool --port /dev/slpi-uart-7 --chip ESP8266 --baud 115200 \
    --before no_reset --after soft_reset write_flash 0x00 <firmware.bin>

From SDK 1.8.0 (voxl-esptool 0.2.2), esptool accepts /dev/slpi-uart-2, /dev/slpi-uart-6, and /dev/slpi-uart-7. On these SLPI ports esptool resets the receiver into its bootloader with the CRSF bl command, so a receiver that still runs ELRS does not need the boot button (for example, esptool --port /dev/slpi-uart-7 chip_id). With --before no_reset, the first attempt is a plain sync, for a receiver already held in the ROM bootloader as above.

Bootloader button

M0220 firmware (SMP)

M0220 firmware is not part of the bundled catalog; you supply the MRC bundle. From SDK 1.8.0 (voxl-elrs 1.2.2 and newer), upload-firmware writes it over SMP (the MCUboot serial protocol): the app and XIP images go to the primary slots, and both MCUboot hashes are checked before the device is reset. Add -T for an M0220 TX.

voxl-elrs -p /dev/ttyUSB0 upload-firmware <bundle>
voxl-elrs -T -p /dev/ttyUSB0 upload-firmware <bundle>

SMP needs the updated M0220 bootloader. voxl-elrs talks to the bootloader at 420000 baud for HWID 0x01 and 921600 otherwise; older bootloaders always run at 921600, so a receiver update at 420000 needs the updated bootloader. An interrupted upload or a hash mismatch leaves the device in its bootloader. Recover it with an explicit smp, which does not need CRSF discovery (add -T for a TX):

voxl-elrs -p /dev/ttyUSB0 upload-firmware <bundle> smp

SMP uploads also work over an adb: port. update-mrc remains available for the legacy MAVLink FTP upload. Full details are on the M0220 page.

PWM Outputs

For receivers with PWM output pins (M0184 and M0193: 4 pins), three commands manage the channel mapping:

voxl-elrs pwm-map 0                       # decode and plot pin 0's current mapping
voxl-elrs pwm-map-set 0 1 50 1500         # set pin 0 map point 1: 50% input -> 1500us out
voxl-elrs pwm-map-tune 0                  # interactive editor for pin 0

pwm-map-set accepts --mode off|step|interp and --yes to skip confirmation. Outputs flagged as arm-gated in the receiver firmware only drive once armed — voxl-elrs arm, disarm, and get-arm-status control that gate.

SKU Presets — Automatic Configuration

Receiver configuration (PWM maps, binding phrase, LED behavior, and other parameters) is managed as JSON presets under /usr/share/modalai/voxl-elrs/config, matched by regex against the vehicle’s SKU:

voxl-elrs list-presets                    # everything on disk
voxl-elrs list-presets-for-sku <sku>      # what applies to a given SKU
voxl-elrs get-merged-preset <sku>         # the fully merged result
voxl-elrs configure-sku                   # apply presets matching this vehicle's SKU
voxl-elrs configure                       # update firmware first, then configure-sku

voxl-elrs configure is what voxl-configure-mpa runs during SDK installation on ELRS-equipped SKUs, so a freshly configured vehicle comes up with the right receiver firmware and settings automatically. Individual presets can be inspected and applied with load-preset <name> and preset-get, and whole device configurations exported/imported with save-json / load-json.

From SDK 1.8.0 (voxl-elrs 1.2.1 and newer), presets use a compact format: folders are nested objects and parameters are "name": value pairs, matched by folder path and name (case-insensitive) instead of by parameter index, so they survive index changes between firmware builds. A runtime ` (…) suffix on a folder name is ignored, so “TX Power” matches TX Power (100mW). Verbose save-json files still load. configure` checks every preset entry against the device before writing anything: an unknown name or a value of the wrong type fails the whole apply instead of leaving a partial configuration.

The Custom Domain preset enables the ELRS custom domain at 863-983 MHz with 80 channels. It has priority 0, so it never applies by SKU and is only loaded by hand. It works on the M0184 RX and the M0193 TX (add -T for the TX):

voxl-elrs load-preset "Custom Domain"
voxl-elrs -T -p /dev/ttyUSB0 load-preset "Custom Domain"

Wiring

BetaFPV Nano RX

BetaFPV Nano RX Wiring Diagram

For ModalAI radios, see the wiring sections of the M0184 and M0220 pages.

Tips

  • Receiver and transmitter must share the same ELRS major version to bind.
  • The receiver’s LED blink pattern tells you its state — see the ELRS LED status page.
  • voxl-elrs update compares versions before flashing; if you run custom receiver firmware, avoid plain update (it will overwrite you) and use upload-firmware or update --target deliberately.
  • General ELRS background lives at the ELRS quickstart.
  • To drive an ELRS transmitter from a joystick or MAVLink RC stream, see voxl-rc-server.

Table of contents