ESP32-S3-based joystick emulation that reads ExpressLRS CRSF protocol from an RC receiver and presents as a USB HID gamepad (8 axes, 16-bit). Built with Rust, esp-hal, and embassy.
| Attribute | Value |
|---|---|
| Usage | Game Pad (Generic Desktop) |
| Axes | 8: X, Y, Z, Rx, Ry, Rz, Slider, Slider |
| Axis resolution | 16-bit, logical range 0–65535 |
| Center (neutral) | 32767 |
| Report size | 16 bytes (8 × uint16 LE) |
| IN endpoint | Interrupt, 16 bytes, 1 ms interval |
| VID:PID | 0x303A:0x4001 (Espressif) |
| USB speed | Full Speed (12 Mbps) |
| Polling rate | Up to 1 kHz (matches CRSF frame rate) |
# 1. Install Rust
curl --proto '=https' -sSf https://sh.rustup.rs | sh
# 2. Install ESP tooling
cargo install espup espflash
# 3. Install Xtensa Rust target + LLVM
espup install
# 4. Source environment (every new terminal)
. "$HOME/.cargo/env"
. "$HOME/export-esp.sh"# 1. Install Rust
# Download and run rustup-init.exe from https://rustup.rs
# 2. Install ESP tooling
cargo install espup espflash
# 3. Install Xtensa Rust target + LLVM
espup install
# 4. Source environment (every new terminal)
. $env:USERPROFILE\.cargo\env.ps1
. $env:USERPROFILE\export-esp.ps1
export-esp.sh/export-esp.ps1is generated byespup installin your home directory. It setsLIBCLANG_PATHand Xtensa toolchainPATHvariables.
On Windows you can alternatively use WSL2 and follow the Linux instructions
above. USB passthrough with usbipd may be needed for flashing.
Open the project folder in VS Code and install these extensions:
- rust-analyzer
- VsCodeTaskButtons (optional — adds toolbar buttons for build/flash/test)
The repo includes pre-configured files under .vscode/:
settings.json— toolbar buttons bound to Build (ESP), Flash & Monitor, and Test (host) tasks.tasks.json— three tasks:- Build (ESP) —
cargo esp-build(bound toCtrl+Shift+B) - Test (host) —
cargo test --lib - Flash & Monitor —
espflash flash --monitor(depends on Build)
- Build (ESP) —
Before building, source the ESP environment in the VS Code terminal first
(e.g. . $HOME/export-esp.sh on Linux or
. $env:USERPROFILE\export-esp.ps1 on Windows).
# Build for ESP32-S3
cargo esp-build
# Flash and monitor
espflash flash --monitor target/xtensa-esp32s3-none-elf/release/rc-joystick
# Run unit tests on host
cargo test --lib| GPIO | Function |
|---|---|
| 4 | UART1 RX — CRSF from RC receiver |
| 5 | UART1 TX — CRSF to RC receiver (reserved) |
| 43 | UART0 TX — debug console (115200 baud) |
| 20 | USB OTG D+ |
| 19 | USB OTG D- |
Connect a UART adapter to GPIO43 (TX) at 115200 baud. Boot messages confirm firmware startup. The USB OTG port is dedicated to the HID gamepad, not serial.
Connect an ExpressLRS RC receiver to GPIO4 (RX) and GPIO5 (TX). The firmware automatically parses CRSF frames and sends HID axis reports.
Verify on the host:
- Linux:
evtest/jstest/lsusb -v - Windows: Game Controllers control panel (
joy.cpl) - macOS: System Report → USB → Game Controller
rc-joystick/
├── Cargo.toml
├── .cargo/config.toml
├── src/
│ ├── main.rs # embassy entry, main loop
│ ├── lib.rs # library root (host-testable)
│ ├── crsf.rs # CRSF parser + unit tests
│ ├── hid.rs # HID gamepad descriptor
│ ├── pins.rs # GPIO constant definitions
│ └── config.rs # Project metadata
| Layer | Crate |
|---|---|
| HAL | esp-hal |
| USB framework | embassy-usb |
| HID descriptor | usbd-hid |
| Async executor | embassy-executor |
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only
Copyright (c) 2026 Richard C. L. Li
This project is dual-licensed under the GNU General Public License, version 2 or version 3. You may choose either license when using, modifying, or distributing this software.