Skip to content Link Search Menu Expand Document
ModalAI DOCS
Store

M0184 Software Guide

Table of contents

  1. Summary
    1. Components
    2. Software Block Diagram
  2. Use Cases
    1. The voxl-elrs Utility
    2. Firmware Updates
    3. Presets and SKU Configuration
    4. Setting Up PWM Pins
    5. Driving PWM Over IP
      1. Transmitter setup
      2. Example Usage
      3. Failsafe

Summary

Components

Component/RepoNotes
elrs-build-dockerDocker container that can clone/build FW with ease
VOXL SDKUsed to deploy all required software/firmware components to VOXL / ELRS hardware
ExpressLRSFork of Express LRS supporting ELRSv1 (goal is to mainline) that runs on ELRS v1 (M0184)
crsf_rcPX4 driver running in voxl-px4 that communicates over CRSF to ELRS v1 hardware
voxl-elrsVOXL2 command line utility, used to update FW/config on ELRS v1 from VOXL2, part of the SDK
FW bootloaderOpen source ExpressLRS bootloader

Software Block Diagram

Use Cases

The voxl-elrs Utility

voxl-elrs is the on-target command line tool for talking to the receiver over CRSF. It ships with VOXL SDK (1.4.1 or newer) as part of the voxl-elrs package.

With no arguments it picks the transport automatically: the crsf_raw MPA pipe when voxl-px4 is running, otherwise the qup7 serial backend. A serial device path (/dev/ttyUSB0), qup*, or mpa:* value can be passed explicitly with -p, and -b overrides the default 420000 baud.

voxl-elrs ping                # verify the receiver responds
voxl-elrs enumerate           # list every CRSF parameter
voxl-elrs get-param 0         # read a parameter by index
voxl-elrs set-param 1 1       # write a parameter
voxl-elrs get-rc              # current RC channel values
voxl-elrs get-link-stats      # live RSSI / link quality / SNR
voxl-elrs failsafe            # report failsafe state
voxl-elrs bind                # put the receiver in bind mode
voxl-elrs version             # build info

Command names that aren’t built in are matched against the receiver’s own CRSF COMMAND parameters, so commands like bind and unbind execute whatever the connected firmware exposes. save-json / load-json export and import the full receiver configuration as JSON (live, read-only, and command parameters are skipped).

Tab completion for all commands and flags is available by sourcing bash_completions/voxl-elrs from the repo (or dropping it into /etc/bash_completion.d).

Firmware Updates

Firmware binaries ship on the VOXL under /usr/share/modalai/libelrs/firmware (override with the ELRS_FIRMWARE_DIR environment variable); the same catalog lives in the repo under data/firmware.

voxl-elrs list-firmware                  # every binary in the local catalog
voxl-elrs list-firmware modalai_m0184    # filter by target (case-insensitive)
voxl-elrs update                         # auto-detect receiver, flash if newer
voxl-elrs update --force                 # reflash even if already current
voxl-elrs upload-firmware fw.bin         # flash a specific file (XMODEM)
voxl-elrs upload-firmware fw.bin esp     # ...or via ESPTool

update auto-detects the connected receiver, compares its installed version against the newest catalog binary, and flashes over XMODEM for MODALAI targets (ESPTool for others).

From a host PC, the -B (Betaflight) and -E (EdgeTX) flags run a serial passthrough first — for example, updating a ModalAI TX module through an EdgeTX radio connected over USB:

voxl-elrs -E -p /dev/ttyACM0 -b 921600 update MODALAI_M0193_TX

Presets and SKU Configuration

Receiver settings are managed as JSON presets under /usr/share/modalai/voxl-elrs/config on target (ELRS_CONFIG_ROOT overrides; sources in data/config). Each preset declares which vehicles it applies to (a supported_skus regex list) and a priority; every preset matching the vehicle’s SKU is merged in priority order before being applied.

voxl-elrs list-presets            # every preset in the catalog
voxl-elrs list-presets-for-sku    # presets matching this vehicle's SKU
voxl-elrs configure-sku           # apply the merged preset for this SKU
voxl-elrs configure               # firmware update + preset apply in one step
voxl-elrs configure --force       # ...even when firmware is already current

configure is the one-stop provisioning command: it upgrades the receiver firmware from the local catalog if needed (waiting for the receiver to reboot), then applies the merged preset for the SKU in /data/modalai/sku.txt (or one passed explicitly).

Setting Up PWM Pins

The receiver’s four PWM pins (J4 pads P0-P3) are driven by per-pin mapping curves that translate an RC channel’s value into a PWM output. Pins are normally configured through the SKU presets above — for example stinger_leds.json drives Stinger’s LED bar from RC channel 7 — but the maps can also be inspected and tuned directly:

voxl-elrs pwm-map 0                    # decode pin 0's map and plot it
voxl-elrs pwm-map-set 0 2 0 1500 --mode interp --yes   # move one control point
voxl-elrs pwm-map-tune 1               # interactive tuning shell

pwm-map prints the control points plus an ASCII plot honoring the pin’s map mode (off, step, or interp); pwm-map-set edits a single point with a confirmation preview; pwm-map-tune opens an interactive shell to adjust, plot, and save or discard changes.

Driving PWM Over IP

The PWM pins can also be driven with no RC transmitter at all: a ground station sends button states over IP as MAVLink MANUAL_CONTROL, voxl-mavlink-server forwards them to PX4, and the crsf_rc driver translates configured buttons into RC channel values sent to the receiver — where the PWM maps above turn them into outputs.

Transmitter setup

If using a normal RC transmitter as a USB Joystick such as a RadioMaster Zorro there is a little bit of manual setup required in order for QGC and other applications to recognize the buttons correctly. If using a Logitech USB controller or other where buttons enumerate automatically this section can be skipped.

The following instructions were compiled using a RadioMaster Zorro connected via USB-C to the host PC.

edgeTX v2.10+ is required, in order to validate FW version:

  • Press the SYS button, which will bring you to the TOOLS page
  • Press the PAGE < button to navigate to the VERSION page
  • Confirm edgeTX version is >= v2.10

In order to upgrade FW version, follow the instructions here: https://manual.edgetx.org/installing-and-updating-edgetx/update-from-opentx-to-edgetx-1 https://manual.edgetx.org/edgetx-how-to/access-dfu-and-bootloader-mode

In order to translate the RadioMaster Zorro’s channel mappings to buttons. do the following:

  • Press the MDL button
  • Press the PAGE > button once to navigate to the SETUP page
  • Scroll all the way to the bottom until you see the USB Joystick section
  • In the USB Joystick section, set Mode to Advanced
  • Set If.mode to Joystick
  • Set Circ. cut to None
  • Navigate to [Channel Settings] and press enter
  • Set CH1 - CH4 to X, Y, Z, rotX (NOTE: The channels used for switches/buttons need to be on channel 9 or above for them to populate in QGC)

  • In the [Channel Settings] menu, navigate to the channel you’d like to setup, i.e. CH9
  • Set Mode to Btn
  • Set Positions to either 2POS or 3POS depending on the physical switch
  • Set Button no. to be the button numbers in QGC that you’d like this switch to populate (NOTE: A 2POS switch requires two buttons (0,1), whereas a 3POS switch requires 3 buttons (0,1,2))
  • Return to the home screen
  • Press the MDL button
  • Press the PAGE > button 5 times to navigate to the MIXES page
  • Scroll down to the corresponding channel which was setup, i.e. CH9
  • Select each channel to be used (hold down button) and set the source as the physical switch you’d like to link to the channel
  • In order to finalize changes you may need to disconnect and reconnect the joystick or restart QGC

Example Usage

Let’s say a LED bar was soldered to PWM pin P0, and configured for RC channel 7 (for example Stinger Vision FPV).

A PWM value of 1000 turns it off, a value of 1500 turns it to IR, and a value of 2000 turns it to visible light.

On an RC transmitter, we configure a 3-pos switch to map to buttons 1, 2 and 3.

In voxl-px4, we set PX4 params using params/v1.14/radio_helpers/pwm_via_elrs.params:

  • RC_CRSF_BUTTON1 to 1, RC_CRSF_PWMCHN1 to 7, RC_CRSF_PWMVAL1 to 191* (This maps to 1000)
  • RC_CRSF_BUTTON2 to 2, RC_CRSF_PWMCHN2 to 7, RC_CRSF_PWMVAL2 to 992* (This maps to 1500)
  • RC_CRSF_BUTTON3 to 3, RC_CRSF_PWMCHN3 to 7, RC_CRSF_PWMVAL3 to 1792* (This maps to 2000)

*See PWM value mappings here

Failsafe

RC failsafe behavior is ultimately the autopilot’s own (e.g. PX4’s RC-loss failsafe parameters) — the VOXL services never synthesize or zero out RC toward the flight controller. What the RC mux in voxl-mavlink-server (en_elrs_rc_mux in /etc/modalai/voxl-mavlink-server.conf, enabled on FPV SKUs via voxl-configure-mavlink-server factory_enable_rc_mux) decides is which source is published on the rc_active MPA pipe for onboard consumers:

  • A live ELRS link always wins. While the receiver is delivering RC frames, rc_active carries the receiver’s channels — even if RC-over-IP (MANUAL_CONTROL from a ground station) is also arriving.
  • ELRS lost while IP RC is connected: the receiver is considered failsafed after 500 ms without an RC frame, and rc_active switches to the IP stream on the next message.
  • ELRS regained: switches back to the receiver as soon as live frames resume.
  • Both lost: IP RC times out after 5 seconds without a MANUAL_CONTROL message. With the ELRS link also down, rc_active deliberately goes silent rather than publishing stale values — consumers see no RC data, and the autopilot’s own RC-loss failsafe governs the vehicle.

Note that MANUAL_CONTROL from a ground station is always forwarded to the flight controller regardless of the mux — the mux only selects what onboard services see on rc_active.