JoyCam — bridge your joystick, receiver, or transmitter to the CRSF
(Crossfire), IBUS (FlySky) and SBUS (Futaba) protocols on Linux.
Receive RC channels and link telemetry from an ELRS / TBS Crossfire receiver,
generate IBUS or SBUS control packets, or read a USB joystick and map its axes
to channel values — all in one toolbox.
Optimised for embedded Linux and OpenIPC-compatible devices (IP cameras, NVRs
based on Sigmastar, Goke, Hisilicon SoCs). Run crsf_rx, ibus_rx or sbus_rx
directly on the camera's UART — no separate computer needed.
| Tool | Description | OpenIPC |
|---|---|---|
crsf_rx |
Reads CRSF frames from a serial port or RFC 2217 endpoint, parses 16 RC channels (11-bit) and link statistics (RSSI, LQ, RF power). | ✅ |
crsf_tx |
Generates CRSF RC channel packets at 100 Hz with a sawtooth sweep. Supports RFC 2217 (tcp:host:port). |
✅ |
ibus_rx |
Reads IBUS (FlySky) frames from a serial port or RFC 2217 endpoint, parses 14 RC channels. | ✅ |
ibus_tx |
Generates IBUS RC channel packets at 50 Hz with a sawtooth sweep. Supports RFC 2217 (tcp:host:port). |
✅ |
sbus_rx |
Reads SBUS (Futaba) frames from a serial port or RFC 2217 endpoint, parses 16 RC channels. USB-UART needs hardware inverter. | ✅ |
sbus_tx |
Generates SBUS RC channel packets at 100 Hz with a sawtooth sweep. Supports RFC 2217 (tcp:host:port). USB-UART needs hardware inverter. |
✅ |
joystick |
Reads a USB joystick/gamepad via evdev and maps axes/buttons to RC channels over CRSF, SBUS, or IBUS. Output to UART, RFC 2217, or screen. |
- Linux (tested on Ubuntu 22.04 / Debian 12, OpenIPC firmware)
- gcc with GNU99 support (or cross-compiler for target SoC)
- No external libraries. Pure POSIX + Linux ioctl (with POSIX sockets for RFC 2217).
Only depends on the C standard library and kernel headers
(
linux/input.hfor evdev support).
CROSS=arm-linux-gnueabihf- makemakeSeven binaries are produced: crsf_rx, crsf_tx, ibus_rx, ibus_tx, sbus_rx, sbus_tx, joystick.
# Local serial port
./crsf_rx /dev/ttyUSB0 [-d]
# Remote via RFC 2217
./crsf_rx tcp:192.168.1.5:2217 [-d]Output:
Listening for CRSF data on /dev/ttyUSB0...
Channels: 0:992 1:1020 2:988 3:1700 4:992 5:992 6:992 7:992 8:992 9:992 10:992 11:992 12:992 13:992 14:992 15:992
Link Quality: 99% RSSI1: -45 RSSI2: -48 Power: 100
Use -d to hex dump every raw CRSF frame alongside the decoded channels.
On OpenIPC cameras the ELRS receiver is typically connected to the camera's
UART1 or UART2 (e.g. /dev/ttyAMA1).
# Local serial port
./ibus_rx /dev/ttyUSB0 [-d]
# Remote via RFC 2217
./ibus_rx tcp:192.168.1.5:2217 [-d]Output:
Listening for IBUS data on /dev/ttyUSB0...
Channels: 0:1500 1:1500 2:1500 3:1500 4:1500 5:1500 6:1500 7:1500 | 8:1500 9:1500 10:1500 11:1500 12:1500 13:1500
IBUS uses 115200 baud, 14 channels in the 1000–2000 range.
# Local serial port
./sbus_rx /dev/ttyAMA0 [-d]
# Remote via RFC 2217
./sbus_rx tcp:192.168.1.5:2217 [-d]SBUS uses 100000 baud 8E2 with an inverted signal. On most SoC UARTs the inversion is handled in hardware; USB-UART adapters typically need an external inverter circuit.
Output:
Listening for SBUS data on /dev/ttyAMA0...
Channels: 0:992 1:992 2:992 3:992 4:992 5:992 6:992 7:992 8:992 9:992 10:992 11:992 12:992 13:992 14:992 15:992
CRSF (100 Hz):
# Local serial port
./crsf_tx /dev/ttyACM0 [-a <ch>]
# Remote via RFC 2217
./crsf_tx tcp:192.168.1.5:2217 [-a <ch>]IBUS (50 Hz):
./ibus_tx /dev/ttyACM0 [-a <ch>]
./ibus_tx tcp:192.168.1.5:2217 [-a <ch>]SBUS (100 Hz):
./sbus_tx /dev/ttyAMA0 [-a <ch>]
./sbus_tx tcp:192.168.1.5:2217 [-a <ch>]Channel 0 sweeps by default. Use -a <ch> to sweep a different channel.
joystick reads a USB joystick via evdev and maps its axes/buttons to
channel values. It supports CRSF (-p crsf), IBUS (-p ibus),
and SBUS (-p sbus). Five display modes are available:
| Mode | Command | Behaviour |
|---|---|---|
| Status line | ./joystick -p crsf|ibus|sbus <evdev> |
Show all channels every frame |
| Verbose | ./joystick -p crsf|ibus|sbus <evdev> -v |
Print every axis/button event + HEX dump |
| Transmit | ./joystick -p crsf|ibus|sbus <evdev> <port> |
Send frames silently |
| Tx + status | ./joystick -p crsf|ibus|sbus <evdev> <port> -d |
Send + status line |
| Tx + verbose | ./joystick -p crsf|ibus|sbus <evdev> <port> -v |
Send + raw events |
<port> can be a local serial device (/dev/ttyS0) or an RFC 2217 TCP
endpoint (tcp:host:port).
Find your evdev device with:
ls -l /dev/input/by-id/*-joystickExamples:
# Debug — see axis/button events (CRSF)
./joystick -p crsf /dev/input/by-id/usb-045e_028e-event-joystick
# Silent transmit — IBUS over local UART
./joystick -p ibus /dev/input/by-id/usb-045e_028e-event-joystick /dev/ttyS0
# Silent transmit — SBUS over RFC 2217
./joystick -p sbus /dev/input/by-id/usb-045e_028e-event-joystick tcp:192.168.1.5:2217
# Debug + transmit over RFC 2217 (CRSF)
./joystick -p crsf /dev/input/by-id/usb-045e_028e-event-joystick tcp:192.168.1.5:2217 -dCreate a virtual serial pair with socat:
socat -d -d pty,raw,echo=0,link=/tmp/ttyV0 \
pty,raw,echo=0,link=/tmp/ttyV1In terminal A — transmit test frames:
# CRSF (100 Hz)
./crsf_tx /tmp/ttyV0
# or IBUS (50 Hz)
./ibus_tx /tmp/ttyV0
# or SBUS (100 Hz) — uses custom 100000 baud via termios2
./sbus_tx /tmp/ttyV0In terminal B — receive and decode:
# CRSF
./crsf_rx /tmp/ttyV1
# or IBUS
./ibus_rx /tmp/ttyV1
# or SBUS
./sbus_rx /tmp/ttyV1Example loopback test using a virtual serial pair:
- Create a virtual serial pair:
socat -d -d pty,raw,echo=0,link=/tmp/ttyV0 \
pty,raw,echo=0,link=/tmp/ttyV1- In terminal A — run the joystick bridge:
# CRSF
./joystick -p crsf /dev/input/by-id/usb-045e_028e-event-joystick /tmp/ttyV0 -d
# or IBUS
./joystick -p ibus /dev/input/by-id/usb-045e_028e-event-joystick /tmp/ttyV0 -d
# or SBUS
./joystick -p sbus /dev/input/by-id/usb-045e_028e-event-joystick /tmp/ttyV0 -d- In terminal B — receive frames:
# CRSF
./crsf_rx /tmp/ttyV1
# or IBUS
./ibus_rx /tmp/ttyV1
# or SBUS
./sbus_rx /tmp/ttyV1Move joystick sticks — the receiver shows live channel values.
Bridge a local serial port to TCP, then connect from another machine:
# On the machine with the serial device
socat TCP-LISTEN:2217,reuseaddr,fork FILE:/dev/ttyS0,raw,nonblock,waitlock=/tmp/s0.lock
# On the remote machine
./crsf_rx tcp:192.168.1.5:2217
./crsf_tx tcp:192.168.1.5:2217
./joystick -p crsf /dev/input/event0 tcp:192.168.1.5:2217 -d| evdev code | Meaning | CRSF channel | IBUS channel | SBUS channel |
|---|---|---|---|---|
| 0 | Left stick X (LX) | ch0 | ch0 | ch0 |
| 1 | Left stick Y (LY) | ch1 | ch1 | ch1 |
| 2 | Right stick X (RX) | ch2 | ch2 | ch2 |
| 5 | Right stick Y (RY) | ch3 | ch3 | ch3 |
| 9 | Left trigger (LT) | ch4 | ch4 | ch4 |
| 10 | D-pad X axis | ch5 | ch5 | ch5 |
| 16 | D-pad Y axis | ch6 | ch6 | ch6 |
| 17 | Extra axis | ch7 | ch7 | ch7 |
| evdev code | Linux name | CRSF channel | IBUS channel | SBUS channel |
|---|---|---|---|---|
| 304 | BTN_SOUTH (A / cross) |
ch8 | ch8 | ch8 |
| 305 | BTN_EAST (B / circle) |
ch9 | ch9 | ch9 |
| 306 | BTN_NORTH (X / triangle) |
ch10 | ch10 | ch10 |
| 307 | BTN_WEST (Y / square) |
ch11 | ch11 | ch11 |
| 308 | BTN_TL (left bumper) |
ch12 | ch12 | ch12 |
| 309 | BTN_TR (right bumper) |
ch13 | ch13 | ch13 |
| 310 | BTN_TL2 (left trigger) |
ch14 | ch14 | ch14 |
| 311 | BTN_TR2 (right trigger) |
ch15 | ch15 | ch15 |
| 312 | BTN_SELECT (back) |
ch16 | ch16 | ch16 |
| 313 | BTN_START (start) |
ch17 | ch17 | ch17 |
| 314 | BTN_THUMBL (left stick click) |
ch18 | ch18 | ch18 |
| 315 | BTN_THUMBR (right stick click) |
ch19 | ch19 | ch19 |
All other buttons are ignored.
All tools can connect to a remote serial port via RFC 2217 (Telnet COM Port
Control Option) — just use a tcp:host:port URI instead of a device path:
# Connect to an RFC 2217 server instead of a local UART
./crsf_rx tcp:192.168.1.100:2217
./ibus_tx tcp:192.168.1.100:2217
./joystick -p crsf /dev/input/event0 tcp:192.168.1.100:2217 -dThe client automatically negotiates baud rate, data size, parity, and stop bits
using the Telnet COM-PORT-OPTION protocol. The 0xFF byte is transparently
escaped/unescaped in the data stream. Modem and line state notifications
are logged at debug level.
Create a virtual RFC 2217 server that bridges to a local serial port:
socat TCP-LISTEN:2217,reuseaddr,fork FILE:/dev/ttyS0,raw,nonblock,waitlock=/tmp/s0.lockThen connect from another machine:
./crsf_rx tcp:192.168.1.200:2217# Terminal A — RFC 2217 server (dummy)
socat -d -d TCP-LISTEN:2217,reuseaddr pty,raw,echo=0,link=/tmp/ttyV0 &
# Terminal B — generate CRSF frames
./crsf_tx /tmp/ttyV0
# Terminal C — receive via RFC 2217
./crsf_rx tcp:127.0.0.1:2217.
├── Makefile — Build system
├── joycam.h — Master header: CRSF + IBUS constants, structures, prototypes
├── joycam.c — Shared utils: serial I/O, channel display, axis/button mapping
├── joycrsf.c — CRSF protocol: CRC8, packet parser FSM, packet generator
├── joyibus.c — IBUS protocol: checksum, packet parser FSM, packet generator
├── joyrfc2217.c — RFC 2217 (Telnet COM Port Control): TCP client, IAC FSM, data escaping
├── joysbus.c — SBUS protocol: termios2 baudrate, packet parser FSM, packet generator
├── crsf_rx.c — CRSF receiver
├── crsf_tx.c — CRSF transmitter
├── ibus_rx.c — IBUS receiver
├── ibus_tx.c — IBUS transmitter
├── sbus_rx.c — SBUS receiver
├── sbus_tx.c — SBUS transmitter
├── joystick.c — evdev joystick reader with CRSF / IBUS / SBUS output
└── README.md — This file
Copyright (c) OpenIPC https://openipc.org The Prosperity Public License 3.0.0