RC Transmitter to USB Gamepad Using Arduino




Why should I do this?

When it comes to flying RC aircraft using a simulator can be a great way to hone your skills during the off season or even prepare for your first flight. The major disadvantage to using a simulator is that using a mouse, keyboard, touchscreen device, or standard video game controller, can be quite a different experience than the bulky transmitter with gimbals. There are commercial solutions available, depending on your interface type, but if you have an arduino ( and who doesn't? ) you can very easily connect a transmitter that supports PPM trainer / buddy box connections.

I can't say that it works with any transmitter, but I can say that it's compatible with the Spektrum DX6

But what software can I use it with?

I wanted to practice in my favorite simulator RC-AirSim by Fabricated Reality. It's an amazingly simple and accurate simulation (Not Game!) for electric RC airplanes. It's very inexpensive and if you are satisfied with the trainer plane, it has an unlimited use free demo. For less than $10 (the price fluctuates from 5 - 9 US dollars throughout the year) you can unlock all the plane models. They each fly different and realist, so you can learn the basics of a different type of plane before you try it in real life. This tutorial creates a game controller compatible with RC-AirSim with no configuration in the game. It just works.

How does it work?

Many RC transmitters allow you to share control of a plane with a second transmitter, so an inexperienced pilot can try taking over flight without having to hand over the transmitter in the event they loose control of the plane. This is a great feature supported by many transmitters. The setup can be complicated but it depends on the transmitter.

Basically with some transmitters, the data sent though the buddy cable (this tutorial will show you how to interface with a 2 wire cable such as a mono 3.5mm audio cable) is identical to the data that the receiver in the plane receives. They call this PPM, it's a series of pulses that can be turned into numerical values for percentage movement of servos. In Transmitters like the Spektrum DX6. The data can easily be deciphered using an arduino interrupt pin. Mike McCauley released an arduino library called RcTrainer that allows for capturing Spektrum PPM trainer communication in just this manner. I've used this library for the signal decoding.

Creating HID USB devices on the arduino can be accomplished in server different ways. The newer arduinos come with this ability built into the serial adapter chip, and some of the older arduinos can use a firmware hack on their serial adapters to do the same. But I wanted a solution that didn't depend on the arduino form factor and would work with just an atmega (eventually making it into a custom board) The solution here is the VUSB library for Atmel avr micro controllers. It's a software USB HID 1.1 emulation library. it has been ported to arduino as a library multiple times by multiple people. I created a HID gamepad descriptor that appears to the system as a standard gamepad, Works on windows, linux, and mac with no extra drivers needed, and matches the standard pattern for twin joystick controllers to little configuration is typically needed.

Teacher Notes

Teachers! Did you use this instructable in your classroom?
Add a Teacher Note to share how you incorporated it into your lesson.

Step 1: Required Materials

  • Arduino (I'll be using an uno, but you can use any atmega328p based arduino)
  • A Spektrum compatible transmitter ( they all speak the same language, but the cables are different and the channel order may differ from brand to brand. If you have another brand you can can read the values of each channel out to the serial port and determine what settings need to be changed. The code is dirt simple)
  • For Spektrum you'll need a mono 3.5mm audio cable
  • Either a 3.5mm mono receptacle, or you can just cut the cable as they are cheap
  • a usb cable you can part with (you'll have to cut it up!)
  • 2 - 1N5227 or similar 3.6V biased zener diodes.
  • 2 - 68 ohm resistors
  • 1 - 2.2k ohm resistor
  • breadboard and breadboard wires
  • for optional calibration button you'll need a10K resistor, a tact switch, an led, and a current limiting resistor (value depends on led but 120 ohm - 200 ohm usually works fine with any led and 5v power) this is completely optional as you can just code the values that work into the arduino sketch.

Of course you'll need a computer, the arduino software, and some basic arduino knowledge.

This tutorial is not geared toward someone who has never used an arduino before so if you find any of these steps hard to follow please refer back to a tutorial on basic arduino.

Step 2: Assemble the Circuit on the Breadboard

The schematic included is for a standalone version, that only requires the minimal parts. You can purchase an atmega328p for $4 and use the schematic to make a permanent version. But for simplicity this tutorial will follow a breadboard and full arduino approach.


The usb specification expects 3.3 volt logic, but provides 5 volts of power. Fortunately the arduino runs on 5v and 3.3v is enough to trigger a logic high on a 5v logic. BUT you don't want to throw 5v into the usb host and possibly damage a cheaper usb host like an inexpensive budget laptop with little usb protection. This is why there are 3.6V zener diodes. They are bridged from logic to ground, when the voltage output by the arduino digital pin is over the 3.6v, the diode conducts backwards, pulling the signal voltage down to 3.6v (3.6 is a more common zener value that 3.3 and works just fine)

Also, in order to a usb host that the device is a 1.1 lowspeed device the usb specification says the d- (white wire) line should be pulled high to 3.3v. This is required for the host to see the device. That's where the 2.2k resistor comes in. We don't want the zener diode pulling the full current of the 5v signal down to 3.6v (and exploding into flames) so the 2.2k resistor provides the necessary pullup without letting too much current though.

This design comes straight from the recommended schematic on the VUSB website. You'll notice they use a 1.5k resistor, but 2.2k works better. All we want is the voltage, not the current.


Incorrectly connecting write to your USB port can cause your USB controller to fail. Shorting the Vcc and Gnd connections WILL cause your usb host controller to fail. Be-careful to not short any wires together while they are plugged into your USB port. I take no responsibility for damage to your computer or if these instructions do not work for you. Proceed at your own risk!


Red = 5v
Black = Ground
Green = D+
White = D-

These are the common wire colors used for usb, any respectable quality cable will use this color scheme.

Get connecting!

  1. Red USB wire to the red power bus on the breadboard.
  2. Black USB wire to the blue gnd bus on the breadboard.
  3. Connect the red power bus to "vin" on the arduino.
  4. Connect the blue gnd bus to the "gnd" on the arduino.
  5. Connect the Green D+ USB wire to pin row 1 on the breadboard.
  6. Connect the White D- USB wire to pin row 2 on the breadboard.
  7. Bridge breadboard row pins 1 and 5 together with a 68 ohm resistor.
  8. Bridge breadboard row pins 2 and 6 together with a 68 ohm resistor.
  9. Bridge breadboard row pins 5 to blue gnd bus with a zener diode, making sure that the polarity is flowing from gnd to pin 5 (this means that the black stripe on the diode is on the OPPOSITE side that connects to gnd).
  10. Bridge breadboard row pins 6 to blue gnd bus with a zener dioide, making sure that the polarity is flowing from gnd to pin 6 (this means that the black stripe on the diode is on the OPPOSITE side that connects to gnd).
  11. Bridge breadboard row pins 6 to the red power bus using the 2.2k resistor.
  12. Connect breadboard row pins 5 to the arduino digital pin 2.
  13. Connect breadboard row pins 6 to the arduino digital pin 7.
  14. Connect the center connector of the 3.5mm mono cable to the breadboard row pins 12.
  15. Connect the outer connector of the 3.5mm mono cable to the breadboard row pins 13.
  16. Connect breadboard row pins 12 to the arduino digital pin 3. (connecting the center audio cable pole to the digital pin 3 on the arduino)
  17. Connect the breadboard row pins 13 to the breadboard blue gnd bus. (connecting the outer gnd terminal of the mono audio cable to the gnd bus)

This is all that is technically required! but I'll go ahead and describe the optional calibration button connections.

  1. Plug the tact switch into the breadboard with the long side bridging the gap in the center of the breadboard starting at row pins 16.
  2. Bridge breadboard row pins 16 and breadboard red power bus with the 10k resistor.
  3. Connect breadboard row pins 18 (the bottom tact switch pin) to the breadboard blue gnd bus.
  4. Connect breadboard row pins 16 to the arduino digital pin 10.
  5. Plug the led into the breadboard with the long positive lead into breadboard row pins 22 and the shorter negative lead into breadboard row pins 23.
  6. Bridge the breadboard row pins 23 to the breadboard blue gnd bus using the 150~200 ohm current limiting resistor. (note if you need help calculating the resistor value for your led just search online, there are hundred of online calculators, but it's not the focus of this tutorial)
  7. Connect the breadboard row pins 22 to the arduino digital pin 9.

That's all the connections! You're ready to program the arduino!

Step 3: Programming the Arduino

The libraries

As I mentioned in the intro, two libraries were used and modified to make this project a reality.

VUSB and RCTrainer.

But in order to get the project working, you'll need to download the files I've included here. As the libraries have been modified for this particular purpose, as well as a special class for this game controller case has been developed into it's own isolated VUSB library I've called HIDJoy specifically for this purpose.

I'm giving credit to the original developers, but please in order for this project to work, use the files I've provided here.

Open your arduino user library folder (for help installing libraries look here)

and copy in the RCTrainer and HIDJOY library folders.

Copy the USBtx and USBtxEx sketch folders to your local arduino sketch folder.

!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

There is a gotcha to using VUSB. It consumes INT0_VECTOR which is the definition of the arduino's interrupt 0. If you call attachinterrupt() from any other code in the project (which RCTrainer must do) then the arduino core libraries try to redefine INT0_VECTOR and it will not compile. I've included a workaround for this. If you skip this step, you will get an error on compile that says __vector1_ is already defined.

Included in the VUSB library folder is a file called "WInterrupts.c" This file is a modified version of a core arduino file that will not conflict with any existing arduino project. All that is changed is it has a preprocessor directive for compile that says if INT0_VECTOR is already define, don't try to redefine it.

You must copy this "WInterrrupts.c" file and overwrite the arduino software one in order for this project to work. Find your arduino installation directory. If you used the default path on windows its C:\program files\arduino or C:\program files(x86)\arduino. If you are using another operating system or another install path, find it. (search the web it's easy enough to find) and then navigate to this directory


in that folder exists the previous version of WInterrupts.c, replace it with the one from the HIDJoy library folder.

Now lets get going!

Open the arduino software and open either the sketch USBtx for no calibration, or USBtxEx for optional calibration (if you added the button and led)

Since we have done all the setup work already, you should only have to compile and send the code to your arduino.

note: there are default values calibrated for my SpektrumDX6 transmitter. If you are using the Spektrum DX series you should be able to just upload and use with no calibration or code changes.

you can play with the default range values in the code to get your specific controller working. The code is documented well enough to see where to change default values.

Step 4: Plug and Play!

At this point you are free to unplug the arduino's usb cable and plug in the usb cable you connected to the breadboard, though it's not necessary as both can be plugged in at the same time. This is useful if you want to use the arduino's serial output for debugging or getting an understanding of how the system is working, or more importantly getting this to work with your particular set up. When you plug in your new adapter, you'll likely see something about installing drivers, and then finally in the devices and printers panel you will be able to see your USB game controller, it will be named "Twin USB Joystick" this name was chosen as it's a common gamepad name and will allow for auto configuration in RCAirSim or any standard xbox controller type game platform such as steam.

You'll need to set up your Transmitter to trainer mode. This isn't part of the tutorial as it's specific to many different platforms. But on the spektrum, create a new model with all the default settings, and then enter the trainer menu under system setup, make sure your wired mode settings say "programmable master" and set the throttle, aileron, rudder, and elevator to "master" and not slave. this will ensure that your signal is sent out over the trainer port.


The arduino sketch is set up for the Spektrum DX6. Your transmitter may be compatible, but have the channels in a different order, or need different values. If anyone figures out the settings and configuration for other transmitters, please post them in the comments.

You'll see in the main loop, (for USBtxEx in mode 0) where there are get channel calls, the 3,0,2,1 mapping to lx, ly, rx, ry respectively. The order may differ in another transmitters ppm signal, but otherwise be compatible. This is something to note if the sticks seem to be doing the wrong things but still working. Also the lxul, lxuh ect values are the numbers generated by lowest and highest positions. these must be adjusted for different transmitters as they are surely different.

If a transmitter sends out PPM on a single cable, this system should need only simple code changes to work with that transmitter, and if people with other transmitters figure out those changes, I'll be happy to update the code to work.

There are adapter cables that will convert futaba and hitec round ports into 2 wire spektrum compatible ports. Its something to consider!

Calibration Mode:

If you built the version with the calibration button and used the USBtxEx sketch, anytime the system is plugged into the usb port with the transmitter connected. Click the button and the led should start blinking. Move both sticks to the top left position and hold them there then click the button to confirm the max top left from both sticks, then the led will blink faster, pull both sticks to the bottom right and hold them there, then click the button. The led should go back out. This confirms the max value for the bottom right. This is useful if you're not sure what values your transmitter are sending out, but you know that you aren't getting the full range. (also check rates and expo setting on transmitter as those affect data sent out over the ppm port)

Now that you're set up and plugged in

your device should be working. Simply open up RCAirSim the desktop version not app version as it's the only one that supports external controllers. And take off! Remember that the transmitter is only the joysticks, you'll still need to use the keyboard or mouse to make selections in menus.

Step 5: Final Thoughts

I am a computer engineering student at the University of South Carolina. I love to build things, and I recently picked up the hobby of building and flying electric model planes from foam board. Simulation helped me a lot, especially RCAirSim since I could practice on my iPhone between classes when I knew I'd be trying to fly the coming weekend.

This instructable was an attempt to share this wonderful device with as many people as possible. I actually designed a PCB that can be etched and used to make a stand alone version, though the one pictured in the title of this instructable was my prototype, using a perfboard.

This instructable was not an attempt to win a contest, or to provide a end all solution. I was just hoping to share some of usefulness of the project to other model pilots who have arduinos.


Feel free to post any questions or improvements in the comments. I'm very busy and I don't know when I'll be able to respond. But I will try.



Rickey Ward

3 People Made This Project!


  • Made with Math Contest

    Made with Math Contest
  • Multi-Discipline Contest

    Multi-Discipline Contest
  • Robotics Contest

    Robotics Contest

26 Discussions

Iqbal Samin

Question 1 year ago

Please add more channels, and is it compatible with RealFlight and Aerofly?

1 answer

Question 1 year ago on Step 4

i have a question..
i have a FlySky TH-9X programmed with OpenTX..
i want to be able to use atleast 6 channels out of the radio... is this possible..?
or is it strictly locked to only 4 channels

1 answer

Answer 1 year ago

the USB descriptor I wrote for this project only has 2 channels, but yes. you can add buttons to the descriptor and updated the payload if you so choose. It's not very difficult. If I find some time I'll add a version that has support for more channels


2 years ago

Found these days that if you feel the gamepad/joystick is kind of slow respective has some delay in reacting to your movements, you can try to change USB_CFG_INTR_POLL_INTERVAL from 100 to 10 (ms) in libraries/HIDJoy/usbconfig.h


2 years ago

Hi. Thx for this. I am trying to find a way to connect my JR Tx to a Android Smartphone. Other Projekts are using the FMS Pic Protocol for conecting to a Pc Sim, but I need the Hid Protocol, so I think Im right here... Im using a Arduino Nano and ARDUINO IDE 1.8.2 from the Webside. When compiling I get this Error:

C:\Users\kolluk\Documents\Arduino\libraries\HIDJoy\HIDJoy.cpp:48:1: warning: narrowing conversion of '161' from 'int' to 'const char' inside { } [-Wnarrowing]



C:\Users\kolluk\Documents\Arduino\libraries\HIDJoy\HIDJoy.cpp:48:1: warning: narrowing conversion of '161' from 'int' to 'const char' inside { } [-Wnarrowing]

C:\Users\kolluk\Documents\Arduino\libraries\HIDJoy\HIDJoy.cpp:48:1: warning: narrowing conversion of '255' from 'int' to 'const char' inside { } [-Wnarrowing]

C:\Users\kolluk\Documents\Arduino\libraries\HIDJoy\HIDJoy.cpp:48:1: warning: narrowing conversion of '255' from 'int' to 'const char' inside { } [-Wnarrowing]

C:\Users\kolluk\Documents\Arduino\libraries\HIDJoy\HIDJoy.cpp:48:1: warning: narrowing conversion of '149' from 'int' to 'const char' inside { } [-Wnarrowing]

C:\Users\kolluk\Documents\Arduino\libraries\HIDJoy\HIDJoy.cpp:48:1: warning: narrowing conversion of '129' from 'int' to 'const char' inside { } [-Wnarrowing]

C:\Users\kolluk\Documents\Arduino\libraries\HIDJoy\HIDJoy.cpp:48:1: warning: narrowing conversion of '192' from 'int' to 'const char' inside { } [-Wnarrowing]

C:\Users\kolluk\Documents\Arduino\libraries\HIDJoy\HIDJoy.cpp:48:1: warning: narrowing conversion of '192' from 'int' to 'const char' inside { } [-Wnarrowing]

Hope someone can help, Im new in Arduino programming



2 years ago

Ok figured this out. Basically everything was there but needed a bit bug fixing. I had to enable to send bigger data packages for gamepad_report_t struct (9 bytes) and thus use "write" instead of "usbSetInterrupt". See the gist mentioned before to get an 8 axes and 8 buttons gamepad.

I have 2 hardware related questions:

1. According to the instructions VIN has to be connected to USB 5V. This means that +5V on the board will only be about 4V. While this might be fine for usual operation, what happens when connecting the USB on the board too, e.g. for serial debugging? This USBs 5V is connected to the boards +5V. Wouldn't it make more sense to connect our USBs 5V to +5V instead of VIN?

2. I miss resistors between the arduino digital pins and USB D+, D- lines as they would be needed for the voltage drop down to 3.6V given by the zener diodes. As this should not be a big issue with USB 5V connected to VIN, it might become one when connecting to +5V (and increasing +5V from about 4V to 5V). Is there a deep reason for your design? Is this a 68 ohms USB termination or impedance thing? Or is it possible that you accidentally swapped USB D+,D- pins with the arduino digital pins when writing down these instructions?

2 replies

Reply 2 years ago

Thank DrTrigon!
1) yes it definitely should be to +5v pin if you're going to use serial debugging (I personally use this project on a headless chip (just an atmega328p on perfboard or sometimes on a digispark with different firmware). So I didn't overthink the power connection. +5V pin is the best choice for the usb power input.

2) In the design there are 68 OHM resistors in series with the D- and D+, this is so the zener/controller can't load down the usb interface and fry your USB ports. It would technically work without it but it's not recommended. I need to amend this instructable with a schematic.

The circuit this is based from is the example config on the VUSB official website. https://www.obdev.at/Images/vusb/circuit-zoomed.gif


Reply 2 years ago

Thanks a lot for your answers!

2) All you said is true! The PC USB port is safe! My question was more in the other direction, here I am more concerned about the arduino, since currently there are 3.6V zener diodes connected directly to the atmega chip pins, which output up to 5V.

A schematic for this instructable would be perfect! :)

In your link the zener diodes are missing, so I found this: https://www.insidegadgets.com/wp-content/uploads/... (the zener diodes are in between USB and 68 ohm resistors and not in between 68 ohm resistors and atmega)


2 years ago

Thanks a lot for this nice tutorial. It was very useful to me! I Made it! :)

If you want to add a schematic, I did it in fritzing and can share it.

4 replies

Reply 2 years ago

I'm glad you liked the tutorial, Feel free to add the image in a comment, everyone will be able to see it.


Reply 2 years ago

Sure I'll do that.

Meanwhile I also tested it with my old Futaba FC-28 and that works too! Very nice! ;)

I do have the issue that it does not work with https://fpv-freerider.itch.io/fpv-freerider (source engine based I guess). I assume it is related to how libudev handles the device. I found I need a joystick with at least 1 button in order for the software/game to assign and use it. Can you give me a hint how to emulate a button?

Another thing I noticed by using ("udevadm info -q all -n /dev/input/js0") was that it reports "ID_INPUT_ACCELEROMETER=1" but "ID_INPUT_JOYSTICK" is missing. However I guess this is not relevant.


Reply 2 years ago

That's very common, some input driver packages look for minimal configurations. Its really simple to add an arbitrary button to the device descriptor. There is a usnHidReportDescriptor array of bytes written in hex that tells the HID device interface on the computer what type of data, and what the data will look like, that is being sent. I don't currently have the time to play with it but when I do I'll modify the HID to have a couple buttons for digital channels. If I don't get to it first, just google v-usb hid descriptor for joystick and look at the part they have for a button that is missing from the one I included. (it's a little more complicated than that but its fun to learn!) if you can't get it by the time I have a chance, I'll updated this and eventually host it on git hub too.


Reply 2 years ago

Thanks for your hints. I had to modify libraries/HIDJoy/HIDJoy.cpp, libraries/HIDJoy/HIDJoy.h and libraries/HIDJoy/usbconfig.h according to e.g. http://eleccelerator.com/tutorial-about-usb-hid-r... . It works very nicely with 1 button (and is trivially expandable up to 8) and also with 6 axes. What I was not able to get running yet is 8 axes. Even though the gamepad does get recognized by the OS, the values don't change. What is the size limit on the gameReport struct? I would guess 64 byte, but it seams that 9 does not work either... Or I used the wrong axes ("Slider")?

For convenience I put it on gist: https://gist.github.com/drtrigon/0458d5d3bb55c841...


2 years ago

Thanks for this great instrubtable!

But I have two questions:

1. Is it possible to transmit more than four channels?

2. Do you know why it doesn't work on an Arduino Pro Mini (clone) with Atmega328? Under Win10 I get the message that my hardware isn't recognized correctly...

1 reply

Reply 2 years ago

Yes you can transmit more than 2 channels, but you'll have to modify the HID descriptor in the controller library. Not very difficult but will take hours of fiddling. Over-all scheme of things it's not difficult


3 years ago

So, which version of Arduino did you modify, exactly? I just installed v1.6.8 and WInterrupts.c is considerably different than your modified one. I suspect Arduino itself has been updated with important changes since your post. This means that using your WInterrupts.c could be out of sync and break something else for Arduino.

So... what did you modify, exactly? That way, folks here will be armed with the info needed to keep their latest Arduino up to date by changing WInterrupts.c for HIDJoy, and not downgrade things and get out of sync.

2 replies

Reply 3 years ago

It looks like all I need to do in Arduino v1.6.8 (or v1.6.x?) WInterrupts.c is replace this:


with this:

#ifndef INT0_vect



Basically, wrap the INT0_vect initialization in a compiler if statement. Does that look right? Anything else? BTW, Arduino does let you _compile_ without this modification, but I'm not sure if it runs right on the device (haven't tried it). Also, I don't see where RCTrainer refers to INT0_vect, but maybe I'm not good at my string searches through the files.


Reply 3 years ago

Also that string doesn't come up, you won't see where it's used. The functions called in the user code calls that interrupt vector in the Arduino code base. There's a lot of code you just don't see, such as the winterrups.c file. You have the right idea though. Just that compile directive. But if they already fixed it in the platform then you won't have to change it.