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!
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.
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:
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:
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.
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!
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:
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:
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.
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!