How to connect your RC receiver to your computer via USB
Index
I love to fly my drone, but at the moment I have to wait for new parts to arrive. So my goal was to connect my RC remote to my computer to fly in HELI-X8, one of my favorite simulation games.
Here I want to show you how to connect your RC receiver to your computer.
All the code of this tutorial can also be found on my GitHub page.
What Microcontroller to choose?
ESP8266, Arduino pro mini?
At first, I had to decide what microcontroller to choose. I had a few different options. My ESP8266s and Arduino pro minis couldn’t accomplish the task because they can only talk to the computer via Serial communication. But for a game controller, we need to simulate a USB HID Device (human interface device).
The Advantage of a HID device is that you don’t need to install any drivers, and every computer can recognize the controller correctly.
So what microcontrollers had I left?
STM32?
At the moment, I am trying to learn to program the STM32F1, and I had a few Blue Pill boards lying around. They are plenty powerful but not very easy to program. On top of that, they have a wrong resistor on the board, so your computer might not correctly recognize the device when plugged in.
Therefore I did choose a different MCU this time but will revisit this project with the STM32 in the future.
Arduino Uno?
Then I remembered I tried something like that quite some time ago. Back then, I used the Arduino Uno. Even though the Arduino Uno has precisely the same Microcontroller as the Arduino pro mini, the Uno has a second, smaller microcontroller for the USB Serial communication. This MCU can be programmed to act as a game controller. The software project I used for this ist the UNOJoy project by Alan Chatham on GitHub.
It worked but wasn’t fast enough for all eight channels on the receiver. There will be a project using this technique, but for now, I chose something different.
Another Arduino?
There are a few different Arduino models which can be used as an USB device, you can see them in the List down below, but I don’t have any of them at the moment.
- Leonardo
- (Pro)Micro
- Any other 8u2/16u2/at90usb8/162/32u2/32u4 compatible board
- Due
- Zero
Teensy 3.6!
The MCU I settled with is Teensy 3.6. This is my first project using this Microcontroller. Soon I will have an in-depth look at this not so widely known but very powerful Microcontroller. Especially the USB connection, a topic that is usually quite hard to master, is added in a very convenient way. The teensy can also be programmed using the Arduino IDE. A short tutorial on how to get started can be found here.
How to read an RC Servo signal?
Standard servo signal
At first we have to take a look at the signal we receive form our remote.
In the picture, you can see that we have a high pulse for a given time every 20 ms. And when we measure the length of the pulse, we know the position the servo should be set to. This is the information we want to send to the computer to control our virtual plane.
5V vs 3.3V
There is a problem. An RC Receiver outputs a 5V Signal, but the Teensy 3.6 only supports 3.3V logic levels! So be sure to add a logic level shifter to your signal lines. You get them very cheap on eBay, Aliexpress, Amazon, … Otherwise, it is pretty likely to kill your Teensy!
The connections
But how do we connect the RC receiver to the Teensy?
At first we have to connect ground. There you can choose any ground pin of the tennsy and directly connect it to any ground pin of the receiver.
We take the 5V, needed for the receiver, can be taken from USB. The Teensy doesn’t have a dedicated VUsb pin but the 5V from USB can be found on the V-IN pin.
The only pin you have to be careful with is the data pin. Due to the way the ppm library works only a few pins can be used as inputs. The possible pins are 5, 6, 9, 10, 20, 21, 22 or 23. I decided to use pin 10.
As already said, this is the connection we have to include the logic level shifter. Otherwise, we destroy our Teensy!
Pulse In
These signals can be read easily using the pulseIn command.
All the code of this tutorial can also be found on my GitHub page.
void setup() { Serial.begin(9600); //Starts the Serial communication at a baud rate of 9600 bps. Ignored by Teensy (https://www.pjrc.com/teensy/td_serial.html) pinMode(10, INPUT); //initiate pin 10 as input } void loop() { int val = pulseIn(10, HIGH); //wait for a HIGH pulse on pin 10 and set "val" to the pulse duration in Microseconds Serial.println(val); //print the pulse duration to the Serial monitor }
This code reads the RC Signal on pin 10 and sends the microsecond Value over Serial to the computer.
But if you want to read multiple signals, you need quite a lot of pins, wire and logic level shifters. But there is another option.
PulsePositionModulated signals
A PPM (Pulse Position Modulated) signal combines many RC channels into a single signal. Therefore we only need a single logic level shifter and a single pin as an input. The only problem with this signal is that not every remote can be programmed to output a PPM signal. If and how you can tell your remote and receiver to output the signal can be found in the manual.
A PPM signal has a high Pulse with a duration of 0.5ms for every channel transmitted. The values are transmitted by varying the duration of the pause between each pulse. These pauses can have a duration between 0.5ms and 1.5ms. To signal all channels are sent, as a reset pulse, there will be another pulse with a pause longer than 2ms.
Another great reason to use PPM signals is the library included with the Teensyduino installer. Therefore receiving these values is quite easy.
A simple script for checking if the stream is read and transmitted correctly can be found on my GitHub may look like this:
#include <PulsePosition.h> //include PPM library PulsePositionInput rcInput; //create PPM input object void setup() { rcInput.begin(10) //start PPM listener on pin 10 (possible pins: 5, 6, 9, 10, 20, 21, 22, 23) } void loop() { int cnt = rcInput.available(); //how many values are availbale? if(cnt > 0) { //are values available? for(int i = 1; i <= cnt; i++){ //cycle trough all input channels float val = rcInput.read(i); //set val to the value of the current input channel Serial.print(val); //Srite the value to the Serial Terminal Serial.print("\t"); //add a tab for even spacing } Serial.println(); //write a new line when all values are read. } }
Teensy as a game controller
Now we know how to receive the values from the receiver. But now we have to find a way to send these values to the computer.
USB communication
As nearly every single game controller produced in the last years, a USB connection is the way to go. Why? Because when implemented correctly, the controller can be used on every computer and most game consoles without any driver installation, or configuration.
To achieve this, the controller registers with the computer as an HID (human interface device). Your mouse and keyboard also register as an HID.
Because USB is not that easy to master, I decided to go with the Teensy because pjrc, the company behind the Teensy, provides everything you need to build a Mouse, Keyboard or Joystick.
The Joystick part is the one we are interested in.
The Teensy is capable of being a Joystick with 16 or 32 Buttons (depending on the Teensy model), 6 analog axis, and one Hat.
We won’t need everything for this project but if you want to know more or you need more buttons or axis for your project, there is more information here.
Putting it all together
Now we have all the parts we need and only have to put them together.
I decided I want six analog axis and two buttons. But you should be able to look at my code and modify it to suit your needs.
#include <PulsePosition.h> //include PPM lib for reading rc signal #define CHNUM 8 //How many channles to receive PulsePositionInput myIn; //generate ppm input unsigned int ch[CHNUM]; //place for saving rc values void setup() { myIn.begin(10); //start to litsen on pin ten for ppm signal (possible pins: 5, 6, 9, 10, 20, 21, 22, 23) Joystick.useManualSend(true); //Only send Joysick values when all of them are ready } void loop() { int num = myIn.available(); //How many RC channels are available at the moment if (num == CHNUM) { //are all channels received for(int i = 0; i < CHNUM; i++){ //cycle trough all received channels ch[i] = map(myIn.read(i+1), 1000, 2000, 0, 1023); //convert input from 1000µs - 2000µs to 0 - 1023, the 10 bit vlaue needed for the joystick ouput Serial.print(ch[i]); //print value to Serial terminal to check for errors Serial.print("\t"); //print tab for alingment } Serial.println(";"); //print new line for new data Joystick.X(ch[0]); //set Joystick X axis to value of channel 0 Joystick.Y(ch[1]); //set Joystick Y axis to value of channel 1 Joystick.Z(ch[2]); //set Joystick Z axis to value of channel 2 Joystick.Zrotate(ch[3]); //set Joystick Z rotation axis to value of channel 3 Joystick.sliderLeft(ch[4]); //set Joystick slider left to value of channel 4 Joystick.sliderRight(ch[5]); //set Joystick slider right to value of channel 5 ch[6] = (ch[6] > 512)? 1 : 0; //for the buttons we need a value of 1 (pressed) or 0 (unpressed) ch[7] = (ch[7] > 512)? 1 : 0; //another way of writing if(ch[7]>512){ch[7]=1}else{ch[7]=0} Joystick.button(3, ch[6]); //set button 3 on or off depending on channel 6 Joystick.button(4, ch[7]); //set button 4 on or off depending on channel 7 Joystick.send_now(); //now send the updated Joystick positions to the computer } }
The setup()
Some of the code you recognize from the ppm receive test. Again we have a ppm input on pin 10, which gets started in the setup part. But additionally, we initiate an array with one value for each channel to store the values we want to send to the computer. In the setup part, we also have to tell the Joystick software not to send to the computer every time a value gets changed but wait till all channels have been changed. Then we want to send all new values at once.
The loop()
Analog Axis
In the loop, we wait till all channels are received. Then we write the values to the array we use to store the channel values. There we have to be careful because our array starts at index 0 but the Joystick input read starts at channel 1. After that, we also have to convert the received values to usable values. The ppm library gives an output between 1000 and 2000 depending on the value set by the RC remote. Using the map function we convert this to a ten-bit value (from 0 to 1023), which is needed for the joystick library as an analog axis input.
Furthermore, I send all values to the Serial monitor to check if the Teensy receives the RC signal correctly.
After that, I update the different Axis with the new values. But those are not directly sent to the computer.
Buttons
The next task is to convert the ten-bit value to a single on or off value for the buttons. So I decided, when I receive a value greater than 50% (512) then the button is pressed, otherwise, it counts as released. Then the Joystick buttons are updated.
Now the last thing to do in the loop is to send the now updated Joystick values to the computer.
Arduino IDE settings
Now we only have to set up the Arduino IDE propperly.
At first, if not already done, we have to select the right board.
Under Tools -> Boards you have to select your teensy model. In my case I used the Teensy 3.6
The next important thing us the USB Type. If those setting is not correct, the code won’t compile.
The USB Type we need is Tools -> USB Type -> Serial+Keyboard+Mouse+Joystick
The last thing is to select the Port of your board under Tools -> Port
Done
We did it!
Just upload and you should be good to go!
HELI-X8 recognized my controller without any problems. You might want do disconnect and reconnect the teensy if there are any problems.
Now you know how it is possible to connect your RC receiver to your computer.
All the code of this tutorial can also be found on my GitHub page.
One Comment
Johannes
I love the way you make you’re posts. You don’t write to this then that and so on but you explain and show different options. Really inspiring! Thanks a lot.