Flying a browser flight sim with a real RC transmitter: Pico 2 W as a PPM-to-USB joystick
Cheap RC transmitters have a “trainer” jack that outputs the stick positions as a PPM signal. I wanted to use one to fly GeoFS (a free browser flight sim) instead of mouse and keyboard. Total hardware: a Raspberry Pi Pico 2 W, one resistor and a 3.5 mm cable.
The signal
The transmitter is a Reely HT-6, a six-channel budget model. Its trainer port gives an inverted PPM train at 3.3 V logic level, which surprised me: most budget senders put out 5 V. The frame looks like this:
- HIGH periods carry the channel data, 600 to 1600 µs each
- LOW periods are fixed ~400 µs separator pulses
- one long HIGH of about 10.5 ms is the sync gap between frames
- six channels, about 50 frames per second
Aileron, elevator, throttle and rudder are channels 1 to 4; channels 5 and 6 are the two switches.
The wiring mistake I made first
I started with a resistor voltage divider, the standard “be safe with unknown levels” move. That dragged the already-3.3 V signal below the Pico’s input threshold and nothing was read at all. The right circuit is a single 10 kΩ series resistor from the jack tip to GPIO 16, sleeve to ground. Nothing else.
Firmware
CircuitPython, not MicroPython. The MicroPython build for the Pico 2 W did not include the usb.device module at the time, so it could not present itself as a gamepad. CircuitPython does it in two files:
boot.pyenables a gamepad HID device next to the default keyboard and mousecode.pyreads the PPM edges withpulseio.PulseIn, which captures pulse lengths in hardware via DMA, so the Python loop never misses an edge, then maps four channels to X, Y, Z and Rz of anadafruit_hidgamepad
After calibration the channel values run from 600 to 1600 with centre around 1100. Windows sees a normal 4-axis joystick, GeoFS picks it up as a controller, and the sticks feel like sticks instead of a keyboard.
What I would tell someone doing the same
- Measure the trainer output voltage before you build anything. 3.3 V versus 5 V decides whether you need a divider, and a divider on a 3.3 V signal silently kills it.
- Hardware pulse capture matters. A polling loop in Python drops edges at 50 frames of 6 channels per second; DMA capture does not.
- Map the switches to gamepad buttons while you are at it (I have not yet). Flaps and gear on real switches would be nice.
Happy to share the exact code if anyone has the same transmitter or a similar one.
Write a comment