RC Hobby Controllers and Arduino


Remote Control Arduino

Robots, cars, robotcars, and all sorts of prank devices require some degree of remote control. Most of the time, it's tempting to implement this yourself using XBee or some other wireless technology. Sometimes it's not a bad idea, but more often than not it's an over-powered and somewhat frustrating way to go. You find yourself thinking, "I remember the good old days when I just put batteries in the RC car and pushed the stick and it moved."

Well, welcome back to the good old days.

RC transmitter/receiver combos range from the simple and inexpensive to the seriously tricked-out, but the nice thing about them is that they all stick to a standard which makes them largely interchangeable. It turns out that connecting an RC receiver to your Arduino project is about the same as connecting a servo, and the code is just as simple. In this tutorial, I'll take you through the basics of using your Arduino to interpret commands from an inexpensive RC remote, so you can control anything, from a simple four-wheeled robot to your favorite processing sketch!

That Sounds Great but I've Never Touched an RC Transmitter...

It's no big deal, I'll walk you through it. Radio-control transmitters and receivers are usually used to drive model cars or planes. A typical transmitter will have a few control surfaces, like wheels or joysticks, as well as some switches or dials. Each degree of freedom that the controller gives you is assigned a channel. In other words, a joystick covers two channels (x and y), whereas a dial or switch will cover one. RC transmitters generally have somewhere between four and six of these channels. 

Since most RC models can be generalized as a fancy box of servos, that's exactly what the receiver is set up to control. Although they come in various shapes and sizes, they all share a common feature: a row of servo headers. These headers are lined up so that the servos in your model can be plugged directly into the receiver. This is handy because it allows us to plug the receiver into the Arduino which can interpret the "servo language" and decide how to use it.

Let's Hook it Up!

Alright, that's the spirit! We'll hook up a few channels from the RC receiver to get a feel for what the input from the transmitter looks like. The RC transmitter/receiver pair that I have is six channels, but we'll just hook up three for now. This means we need three digital pins to read the input, as well as 5V power to the receiver. Here's a diagram of how I hooked mine up:

Diagram

Notice that the RC receiver is upside-down, I flipped it to make it easier to trace my wires. You'll need to use male-female jumpers if you have them. If not, you can use some male and female jumpers stuck together. The digital pins that I chose are pretty much arbitrary; you should be able to use any digital input on the Arduino that you like, but the above should correspond to the code below.

Now let's upload a sketch and see what's coming in on those pins. The "servo" language that the RC receiver is pushing out is really PWM, or Pulse Width Modulation. Arduino has a handy function built in for reading these pulses and returning their length in milliseconds. This function is called pulseIn(). Let's take a look at a simple sketch I've written to print the raw input of the receiver to the serial monitor:

 

     
/*
 RC PulseIn Serial Read out
 By: Nick Poole
 SparkFun Electronics
 Date: 5
 License: CC-BY SA 3.0 - Creative commons share-alike 3.0
 use this code however you'd like, just keep this license and
 attribute. Let me know if you make hugely, awesome, great changes.
 */
 
int ch1; // Here's where we'll keep our channel values
int ch2;
int ch3;

void setup() {

  pinMode(5, INPUT); // Set our input pins as such
  pinMode(6, INPUT);
  pinMode(7, INPUT);

  Serial.begin(9600); // Pour a bowl of Serial

}

void loop() {

  ch1 = pulseIn(5, HIGH, 25000); // Read the pulse width of 
  ch2 = pulseIn(6, HIGH, 25000); // each channel
  ch3 = pulseIn(7, HIGH, 25000);

  Serial.print("Channel 1:"); // Print the value of 
  Serial.println(ch1);        // each channel

  Serial.print("Channel 2:");
  Serial.println(ch2);

  Serial.print("Channel 3:");
  Serial.println(ch3);

  delay(100); // I put this here just to make the terminal 
              // window happier
}

 

The pulseIn() function takes three arguments: the first is the pin that your pulse is coming in on; the second is what type of pulse you're looking for; and the third takes a time-out number, which is how long you're willing to wait for a pulse. What this function returns is the length of the pulse in microseconds, and this is how we're going to read the incoming PWM as if we were a servo. When you run this code, you should get some numbers spitting out onto the terminal. The numbers probably won't mean much to you but they should be somewhere between 1000 and 2000. What really matters is that when you move the control surface associated with that number, it should change. If you don't know what channel each part of your transmitter is on, this is a good way to find out: just wiggle sticks, push buttons, turn knobs and flip switches, and take note of which channel is affected.

Now that we have these values, we can code with them and do all kinds of things. The sketch below is my sketch after it's been modified to put these pulseIn() values into context.

     
/*
 RC PulseIn Joystick
 By: Nick Poole
 SparkFun Electronics
 Date: 5
 License: CC-BY SA 3.0 - Creative commons share-alike 3.0
 use this code however you'd like, just keep this license and
 attribute. Let me know if you make hugely, awesome, great changes.
 */

int ch1; // Here's where we'll keep our channel values
int ch2;
int ch3;

void setup() {

pinMode(5, INPUT); // Set our input pins as such
pinMode(6, INPUT);
pinMode(7, INPUT);

Serial.begin(9600); // Pour a bowl of Serial

}

void loop() {
  
  ch1 = pulseIn(5, HIGH, 25000); // Read the pulse width of 
  ch2 = pulseIn(6, HIGH, 25000); // each channel
  ch3 = pulseIn(7, HIGH, 25000);
  
  if(ch1>1000){Serial.println("Left Switch: Engaged");} 
  if(ch1<1000){Serial.println("Left Switch: Disengaged");}
  /* I found that Ch1 was my left switch and that it 
  floats around 900 in the off position and jumps to 
  around 1100 in the on position */

Serial.print("Right Stick X:"); // Ch3 was x-axis 
Serial.println(map(ch3, 1000,2000,-500,500)); // center at 0

Serial.print("Right Stick Y:"); // Ch2 was y-axis
Serial.println(map(ch2, 1000,2000,-500,500)); // center at 0

Serial.println(); //make some room

delay(100);// I put this here just to make the terminal 
           // window happier
}

Here you can see that I've figured out which control surface on my transmitter is controlling which channel, and I've written a few print statements that will reflect my actions on the transmitter in plain English. You could expand this out to every channel on the receiver, you could also replace this sketch with Firmata and control a Processing sketch with your RC transmitter, but I think the best demonstration I can give is to slap this thing on a robot and drive it around. So let's get to it!

Radio Receiver Robot Rig?

I've chosen the Magician chassis and Ardumoto motor driver shield to be my robot platform for this project. Since it only has two wheels, it's a good way to demonstrate simple differential steering and how to mix everything to make it work. The first thing we're going to do is modify the Ardumoto shield to create an impromptu "RCMoto" shield by adding some headers to the prototyping header that we can plug our receiver into:

 

RCShieldRCback

 

I made six rows of three, since my transmitter has six channels, and you shouldn't need to connect to the battery header on the receiver as long as you're running power and ground to the servo headers. When you solder these in, keep in mind that your receiver is going to go upside down on this rig, and wire it accordingly. You should be able to see how I wired this one by inspecting the pictures above. The ground row is all bridged together and plugged into ground -- same with the power row -- then each signal pin is broken out to a digital pin. This isn't the cleanest mod, but it works out pretty well. Here's what the robot chassis looks like with our modified Ardumoto Shield and RC receiver connected:

 

RCBot

 

Not bad, right? Make sure that your motors are appropriately connected and add a power supply (9v battery worked fine for me), because it's almost time to drive this thing. Only step left is to write some code to translate servo steering commands into left/right motor commands. I'll give you the code now and explain it below.

 

     
/*
 RC PulseIn Joystick Servo Control
 By: Nick Poole
 SparkFun Electronics
 Date: 5
 License: CC-BY SA 3.0 - Creative commons share-alike 3.0
 use this code however you'd like, just keep this license and
 attribute. Let me know if you make hugely, awesome, great changes.
 */

int ch1; // Here's where we'll keep our channel values
int ch2;
int ch3;

int move; // Forward/Back speed
int turn; // Turning Factor

int pwm_a = 3;  //PWM control for motor outputs
int pwm_b = 11;  //PWM control for motor outputs
int dir_a = 12;  //direction control for motor outputs
int dir_b = 13;  //direction control for motor outputs

void setup() {

pinMode(5, INPUT); // Set our input pins as such
pinMode(6, INPUT);
pinMode(7, INPUT);

Serial.begin(9600); // Pour a bowl of Serial (for debugging)

  pinMode(pwm_a, OUTPUT);  //Set control pins to be outputs
  pinMode(pwm_b, OUTPUT);
  pinMode(dir_a, OUTPUT);
  pinMode(dir_b, OUTPUT);

  analogWrite(pwm_a, 0);  
  analogWrite(pwm_b, 0);
}

void loop() {
  
  ch1 = pulseIn(4, HIGH, 25000); // Read the pulse width of  
  ch2 = pulseIn(5, HIGH, 25000); // each channel
  ch3 = pulseIn(6, HIGH, 25000);
  /*
  if(ch1>1000){Serial.println("Left Switch: Engaged");}
  if(ch1<1000){Serial.println("Left Switch: Disengaged");}

Serial.print("Right Stick X:");
Serial.println(map(ch3, 1000,2000,-500,500));

Serial.print("Right Stick Y:");
Serial.println(map(ch2, 1000,2000,-500,500));

Serial.println();

delay(100);

clearAndHome();
*/

move = map(ch2, 1000,2000, -500, 500); //center over zero
move = constrain(move, -255, 255); //only pass values whose absolutes are
                                   //valid pwm values

/*What we're doing here is determining whether we want to move
forward or backward*/
if(move>0){digitalWrite(dir_a, 1);digitalWrite(dir_b, 1);};
if(move<0){digitalWrite(dir_a, 0);digitalWrite(dir_b, 0); move=abs(move);};

/*Here we're determining whether a left or a right turn is being 
executed*/
turn = map(ch1,1000,2000,-500,500);
turn = constrain(turn, -255, 255);

/*This is where we do some mixing, by subtracting our "turn" 
variable from the appropriate motor's speed we can execute
a turn in either direction*/
if(turn>0){analogWrite(pwm_b, move-turn); analogWrite(pwm_a, move);};
if(turn<0){turn=abs(turn); analogWrite(pwm_a, move-turn); analogWrite(pwm_b, move);};


Serial.print("move:"); //Serial debugging stuff
Serial.println(move);

Serial.print("turn:"); //Serial debugging stuff
Serial.println(turn);

Serial.print("move-turn:"); //Serial debugging stuff
Serial.println(move-turn);

Serial.println(); //Serial debugging stuff
Serial.println();
Serial.println();

}


 

Okay, let me see if I can explain how the steering happens in the above sketch. What I've done is basically mapped the forward/back direction of my control stick to a number between -255 and 255, with the former representing backward at full speed and the latter representing full forward. To communicate this to the Ardumoto shield, you have to break that out into two different numbers: direction and speed. To do that, I simply check to see if my number is positive. If so, I change the direction of both motors to 1. If the number is negative, then I just set both motor directions to 0. All I have to do after that is get the absolute value of the number and use it as my basic PWM value for both motors. I say "basic PWM value" because it's not actually what gets sent to both motors, instead a turn-rate is subtracted from one side.

To find my turn-rate and apply it, I map the side-to-side direction of the control stick to a number between -255 and 255, with the former representing a full turn to the left and the latter representing a full turn to the right. After I have that number, I find out which motor to apply it to. The way this works is by slowing down the motor on the side that we want to turn toward. This method will never produce a zero-radius turn, but it works reasonably well. Again I check to see whether my number is negative. If so, I subtract the absolute value of the turn-rate from the left motor's speed. If it's positive, I subtract the absolute value of the turn-rate from the right motor's speed.

There are obviously other ways to implement differential steering; with a little more code you could reverse the appropriate motor and turn in place. Also, this code doesn't steer the way you might expect when driving in reverse. At any rate, this should get you started with Radio Control robots, and I hope that you've been inspired to grab a hobby transmitter for your next robot project!

Happy hacking!

Comments 24 comments

  • Use the interrupt luke:

    (well, my code was easier but too long to be here so…)

    http://code.google.com/p/aeroquad/source/browse/tags/AeroQuad_v2.4.3/Receiver.h

    • can you help me can you program me on 4 channel 1 channel is to manual en automation en 1 channel for gas servo 1 channel for rudder can you help mee jcantuba@gmail.com

      • Here my code: https://github.com/lestofante/arduinoSketch/tree/master/ClassPPM The code read the PPM on pin 2,4,5,6 and put in in a array. The value can be retrived with inputs.getDuration(X) where X is the POSITION IN THE ARRAY, witch is set in the ISR in InputPin.cpp

  • I think it would be useful to mention in this tutorial how to read the rc receiver using interrupts on the arduino. Reading them through the pulsein function works but is really not the quickest method.

  • How would one reverse this? Is it possible to have just the receiver connected to the device and remotely sending the commands to it via RF or Bluetooth or something along those lines?

  • Ok maybe i am just stupid or something but were can i find/buy the radio receiver using in this project i can’t find a link anywhere and have spent hours on spark fun website

  • hi could you help me adapt this to the rover 5 and rover 5 motor controller with only two motors…tryed using it but got no movement from bot .. may be im wiring it wrong

  • is it possible to control a wheeled robot..actually a wall climbing robot based on suction mechanism…using this one..??? it got to move in all four directions..!!

  • What does “You’ll need to use male-female jumpers if you have them. If not, you can use some male and female jumpers stuck together” mean?

  • This is such a great project. Someone was asking about a similar project on TechXchange last week: http://www.digikey.com/techxchange/thread/4150

  • I’d like to share with you a little mod on this code.

    Connected to the Throttle / brake channel of the reciever, it will detect when you hold the brakes on your transmitter. Then it will turn ON the brake lights!

    (a bit too simple, I know, but it was my first approach to Arduino)

    / ************************************************************ Modificación del código “RC PulseIn Serial Read out” By: Nick Poole (SparkFun Electronics) License: CC-BY SA 3.0 - Creative commons share-alike 3.0 / // lectura de entrada desde receptor RC, para activar luz de freno. // // By Pentiumcadiz // // http://www.youtube.com/watch?v=JdMYpq1Mus8 //

    int pin_entrada = 7; // pin en el leemos la señal que viene del receptor unsigned long pulso; // variable en la que almacenaremos la lectura int salida = 13; // pin de salida para LED(s)

    void setup() { pinMode(pin_entrada, INPUT); // configuramos el pin D7 como entrada pinMode(salida, OUTPUT); // configuramos el pin D13 como salida Serial.begin(9600); // habilitamos puerto serie }

    void loop() { pulso = pulseIn(pin_entrada, HIGH); // leemos el pulso

    //Si está frenando if(pulso < 1400) //´Teóricamente es 1500, pero doy 100 de margen. { // turn LED on: digitalWrite(salida, HIGH); // Luz de frenos encendida } else { // turn LED off: digitalWrite(salida, LOW); // Luz de frenos apagada }

    // Sólo durante las pruebas, muestro la lectura del Rx, para comprobar los valores. Serial.println(pulso); // mandamos el dato al monitor serie delay(300); // pausa para no volver loco al monitor serie }

  • how i can set x and y for all channels ?

  • how i can make that the serial shows me when i move the right button ??

  • Hi, The code presented here has a number of problems, I understand that it is simply an introductory example however for a solution that addresses the problems see here -

    http://rcarduino.blogspot.com/2012/05/interfacing-rc-channels-to-l293d-motor.html

    For those that mention it, it also uses interrupts although in the case of a slow moving robot pulseIn could work just as well - until you start adding additional inputs, encoders etc.

    The link also allows correct proportional speed and steering in forwards and reverse and also allows rotation on the spot.

    Duane B

    rcarduino.blogspot.com

  • Using interrupts is better ! I made the same thing few month ago, in order to diagnose receivers outputs : http://www.youtube.com/watch?v=oY1-du-HGQY Then, I replaced the serial bridge in the 8u2 with an HID joystick one, so I can use my remote control as a joystick on any computer for virtual racing. It just requires some software flow control in the serial link between the 328p and the 8u2 to avoid buffer overflow.

  • I didn’t see any mention of where to purchase or which reciever was used. (If it was mentioned in the code sections, I can’t read them as they don’t show completely in android.)

    • basically, i think they are refering to any hobby grade tx/rx system. but, as an avid RC'er, i would like to point out that even the the industry standard for the frequency is in the 2.4ghz range, pretty much EVERY radio manufacturer has their own proprietary system: i.e. Spektrum has DSM, Futaba has the FAAST and FHSS. this can make receiver buying a pricey ordeal, since there are very few aftermarket receivers for either the Spektrum or Futaba radios. your best bet for a decent 2.4 radio with pretty inexpensive receivers is to get a FlySky system. these are readily available through either hobbypartz.com or on hobbyking.com (on HK they are branded as hobbyking brand.)

  • ArduPPM is a quite mature RC PWM decoder system (developed as part of the ArduPilot project) and the source (in the ardupilot source repository) is quite useful to have a look at.

    Also, if your RC receiver has a PPM output (and there are tutorials around on how to hack one in if it doesn’t) that’s even easier to read in, and you can get every channel using just one input pin on the arduino.

  • I can’t get the sketches to compile.

    sketch_may23a.cpp: In function ‘void clearAndHome()’: sketch_may23a.cpp:46:18: error: ‘BYTE’ was not declared in this scope

    • check the tutorial again. it’s an error because you’re using arduino 1.0. you have to set the variable as a byte first, then print it. removing that line allows it to work.

  • I don’t know if the cost of two xbees and another Arduino would be more or less than buying an RC transmitter and receiver. (If you already HAVE an elcheapo RC TX and RX left over from a cheap toy that got smashed then no. But if you had to buy just the transmitter and receiver it might be a wash.) In my case I started with two Arduinos and two xbees. I tried to hook a joystick up to the analog inputs of one xbee and receive the output on the second but there was a HUGE time lag between moving the joystick and seeing the corresponding change on the receiver. One or both xbees were buffering a lot of data from the A/D converter. So I used one Arduino to perform the A/D on two channels and send the data (in binary) to the xbee to transmit. On the receiving end I did pretty much the same thing done here to control two motors except I didn’t need to analyze pulse widths as the required number was figured on the transmit end. I was using the 900mhz xbee’s BTW, we just thought we might get more range on the lower frequency and we didn’t need the extra BW.

    • A 2.4GHz TX/RX package on another hobby website is ~$25. You don’t need anything else to get wireless in to your project. I’m going to figure that the cost of two xbee’s is going to be more than that, but I am not even going to check I am that confident.

      XB has its uses, but for super-simple and cheap wireless control (not for getting any data back) you absolutely cannot beat hobby TX/RX for price and range.

    • with the xbee just try to force the flush of the output. There should be a command, if not, the buffer size should be 32 char… :)

      a 6channel 2.4GHz RX/TX can be bought at 25$ (see hobbyking), it cannot carry data, but it is fast, with a long range, anti-collision, and steal low bandwidth.

  • Great tutorial, Do you have a movie to show how much is faster the communication between Arduino and the remote control? I was building something similar but over Wifi, I had some problems with my driver controller.