Introduction: RC Control and Arduino: a Complete Works

Picture of RC Control and Arduino: a Complete Works

In this instructable I will bring you along on my journey of learning how to add RC control using an Arduino microcontroller. Unlike other Instructables or blogs, I will include all the information I can even if it isn't directly related to the project.  I'm doing this for two reasons, first is that when I was learning the basics I would always stumble across these really cool projects that I couldn't do because of lacking introduction information.  Second, is when someone is showcasing their project instead of educating us and sharing their wealth of knowledge.  This project is quite involved and I will only be posting my introduction to RC control with the Arduino platform in this Instructable.  As I continue with other phases of the RC Tank project I will post more Instructables as I continue.

If you like my work here please vote for this instructable in either the Arduino or Remote Control Contests!!! Thanks

Lets start with what I'm using for hardware:

Radio Control System:
FlySky TH9X Transmitter & R8B Receiver
http://www.hobbypartz.com/79p-th9x-r9b-9channel-radio.html

Microcontrollers:
Arduino Uno
http://arduino.cc/en/Main/ArduinoBoardUno
Arduino Mega2560
http://arduino.cc/en/Main/ArduinoBoardMega2560


Servos
TowardPro(knockoff) MG996R Analog Servo, Futaba Compatable - Modded for continuous rotation
http://cgi.ebay.com/ws/eBayISAPI.dll?ViewItem&item=251232756641

Batteries:
6V Lantern Battery - Reciever & Servos
9V Battery - Arduino
1.2V NIMH Rechargable AA Batteries - Transmitter

Materials:
Proto-Shield
Dupont jumper wires
Cardboard
Elbow Grease
Sense of Humor - Trust Me

If anything is confusing feel free to ask questions and I will do my best to answer them and update the instructable accordingly.

Step 1: Bind/Pairing Receiver to Transmitter

Picture of Bind/Pairing Receiver to Transmitter

I bought the TH9X as my first radio system and have had never used one that didn't come with a car attached. There were a few hurdles I went through mainly because I didn't double check what I was doing.

First, the label of the R8B receiver the BIND and BATT locations are switched! You can plug the battery in anywhere since the voltage and ground pins are on common rails. However, if you connect the battery to a channel you then wouldn't be able to use than channel for output unless feeding the signal into a microcontroller. See Second Picture.

Second, was learning how to pair the receiver with the transmitter as two receivers went up in smoke because I wired the plug on a wall wort backwards! Stupid mistake but I did say a sense of humor is needed.

Third, I was getting the remaining two receivers to pair with the transmitter but wasn't getting any output on any channel. Come to find out you have to turn off both the receiver and transmitter to complete pairing.  

Before I give my method to pair the receiver to the transmitter there is one difference if you are using an RC car/plane! In an RC vehicle you will plug in your LiPo battery into and Electronic Speed Controller (ESC) and use the ESC's Battery Elimination Circuit (BEC) to power the receiver. I'm not using an ESC and will use a lantern battery but any battery pack should work if the voltage isn't above 6.5V.

Required Materials:
Transmitter
Receiver
Bind Plug
Batteries/ESC
Servo

How to Pair/Bind your Receiver to your Transmitter:
1) Install batteries into Transmitter. Do not turn on yet!
2) Install bind plug into receiver.
3) Connect battery/BEC to receiver.
4) On the R8B a red led blinks rapidly.
5) Press and hold the bind button on the Transmitter as you turn it on.  
6) Once you see the main screen on the transmitter release the bind button.  The TH9X's bind button is located on the radio module on the back of the transmitter. See third picture.
7) The red led on the receiver should stop blinking and remain on within five seconds.  See fourth picture.
8) Remove the bind plug.
9) Disconnect battery/BEC from receiver.
10) Turn off transmitter.

Step 2: Getting to Know Your Radio System

Picture of Getting to Know Your Radio System

Here is where things may get muddy because of the way I'm approaching and using the radio system.  I'm not going to name the channels by what they control and will be using the channel numbers.  See the following link for an initial understanding.
http://www.rc-airplane-world.com/rc-transmitter-modes.html

I decided to go with Mode 3 because it's easier to remember where the channels are mapped on the transmitter.  I didn't have to change any defaults on the transmitter other than mapping channels 5 to 8.  Go ahead and map these channels how ever you like and if you only have a 4 channel radio don't worry about it as I don't make great use of the auxiliary channels.  Feel free to explore rc-airplane-world.com as it has a lot of great information to learn to fly a rc plane or heli.  I'm also not using any dual/rate settings and will be using stock linear curves.

Mode 3:
Left Stick:
Channel 1: Ailerons
Channel 2: Elevator
Right Stick:
Channel 3: Throttle
Channel 4: Rudder

If all else fails just hook up servos to the receiver and manually find out how your transmitter is set up.  

We need to understand the signals that the receiver will be giving us and how we can use them.  Servos come in a wide variety from sizes, motor styles, and internal control.  Analog servos are the most common but digital servos are gaining popularity because they offer more staying power, are quicker to react but consume more power.  If you are unsure what to buy let your wallet decide because both analog and digital servo require the same signal but just have different internals.  If you have patience you can buy knockoff servos cheap on ebay but will have to wait a month for delivery if you buy them from China.  Keep in mind that continous rotation servos are handy to have but aren't cheap.  I bought two standard servos and modified them to work as continous rotation servos.

Most RC receivers output a 50Hz pulse width signal to servos.  The minimum signal relating to zero degrees (if you have a 180 degree servo) is 1ms followed by 19ms of dead space.  A maximum command for 180 degrees is a pulse of 2ms followed by 18ms of dead space.  If you don't understand this immediately it is not a problem as you can keep reading until this knowledge is put into practice later on.  In the mean time if you Google "hobby servo fundamentals" you should see a pdf from Princeton that I learned from!  

Step 3: Quick Arduino Tuitorial

Picture of Quick Arduino Tuitorial

If you have experience with Arduino great but if not please work through a few tutorials.  You first have to download the Arduino IDE and follow the various installation instructions for your system.  I recommend working through the book "Getting Started with Arduino" or starting with Blink, Serial, and Servo Knob tutorials.   I also recommend looking for a quick reference of the various Arduino function commands or a programming notebook.  Remember that Arduino's are great at integer math but not floating point, I use the map function for most math.

Here I will only cover the functions I will be using later on and will do my best to add best practices along the way.
; - declares end of line, use at end of every line of code except for after curly braces and after conditional statements,
ex:  if (x==5)
          {
              y=5;
          }

// - line comment, used at beginning of line for commenting

/*  comment  */ - block comment, use for multiple line comments like the help-file at the beginning of your code.  The help-file explains what the file of code is for and made by who.

int - declare integer type variable, ex: int variable; or int variable-5;

const - use before variable declaration to ensure variable isn't change in program.  Useful to store information for conditional statements. ex: const int variable=5;

#include <?> - included external files like libraries, ex: #include  <Servo.h>    **no ; is needed 

Servo - declare variable linked to servo control, only used with Servo.h library. ex: Servo steer;

attach(pin) - attach servo to a pin location, used inside setup loop, ex: Servo.attach(5)

void setup(​) - where all your setup data is to declare input and output pins as well as serial speed.

Serial.begin(rate- initilizes serial connection at given baud rates, expected baud rates 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, or 115200.  ex: Serial.begin(115200)

pinMode(pin,mode) - sets whether a pin is an input or output, used inside setup loop, ex: pinMode(5,INPUT); or   pinMode(chA,OUTPUT);  here chA is a constant storing the value for the referenced pin.  

void loop() - main program to be looped.

for(initialization; condition; increment) - repeat a block of statements, ex:  for(int i=0; i<=8; i++);  First, the variable "i" will only be used inside the for loop and "i" can be used for multiple for loops.  Second is the conditional statement and if you want to use equals you must use two!  Third, is if you are subtracting or adding a value at the beginning or end of the loop.  --i subtract one at the beginning, ++i add one at beginning, i-- subtract one at the end, i++ add one at the end.

if(condition) - Will run containing code if the condition is met, ex: if (x==5)  or  if(x<=5)   If you have multiple conditions or complex conditions you can use logical AND(&&), OR(||), NOT(!) with single ended conditional statements.
AND ex: if(x>0 && x<5)   True only if both expressions are true
OR ex: if(x>0 || y>0)   True is either expressions are true
NOT ex: if(!x>0)  True only if expression is false

elseif(condition) - Follows an if statement for separate logic using separate conditions. This works great when reading a sensor and for different sensor readings you are going to do different things.  
ex:
if(sensor<10){do this}
elseif(sensor>10){do that}

else - Gives either/or function to if or elseif statements.  This allows an operation to be performed regardless if any preceding if or elesif statements aren't met.
ex:
if(sensor<10){do thing 1}
elseif(sensor>10){do thing 2}
else{do thing 3}

write(var) - Used with Servo.h library to pass a new position or value to a servo.  ex: steer.write(position);

Serial.print(data- Used with Serial input and will print a variable's value or a character string on the same line in the terminal window.
var ex: Serial.print(x);
string ex: Serial.print("x=");

Serial.println(data) - Used with Serial input and will print data on the same line followed by an automatic return creating a new line.  Follows the same syntax as Serial.print().

map(var, fromLow, fromHigh, toLow, toHigh) - Uses integer math to re scale a variable or value from one range to another.  Extremely useful for reading a sensor and then outputing to a servo. ex: x=map(x,0,1027,0,255);  I highly recommend that instead of manually entering values that you set constant variables to dictate ranges, ex: x=map(x,analogLo,analogHi,servoLo,servoHi); 

Side Note:
Once you get to the point of making a project there are a few things to consider.  When you connect a wire from your sensor output directly to the Arduino board you have a "floating" signal, which works great for testing but not for actual implementation.  If you add a resistor from the signal to ground it helps ensure you have a clean signal.  The higher the frequency the more reason you have to use a pull down resistor.  I generally will use at least a 1k Ohm resistor but depending on the length of wire, proximity to other signal wires, and if the wire is looped.  A good way to look at the effects of this is using an oscilloscope and vary the resistor used or use a potentiometer.  To properly size the resistor for your individual case is an instructable or engineering lesson on its own.  The short version is to figure out what the output of the sensor is rated for and use a series resistor and pot to achieve the best signal varying the RC(Resistor Capacitor transient analysis) characteristics.  

Full link paths:
http://arduino.cc/en/Guide/HomePage
www.makershed.com/Getting_Started_with_Arduino_2nd_Edition_p/mbk1.htm
http://arduino.cc/en/Tutorial/HomePage
http://arduino.cc/en/Reference/HomePage

Step 4: First Program: Reading the Receiver

Picture of First Program: Reading the Receiver

With an understanding of the basic operations of a RC transmitter and basic programming knowledge lets write our first program!  I started with the Basics>BareMinimum sketch to build a program because it has the basic program structure to start with.  

First, write your help file letting you know what the program is for, what board you are using, keep a small change log, and everyday or major addition/change create a new dated file.  I save a file every day I work on the software and keep a separate change log.  This seems like a pain but when you need help it makes easy to just submit your files and ask.  

Second, create your constant pin location variables and their pinMode state in the setup.

Third, create the variables and the pulseIn functions to read and store the RC Receiver values.

Fourth, comment your code if you haven't been!

Fifth, now setup the serial baud rate in the setup block and add your print functions to display in the terminal window.

Sixth, Save it! 

Now lets hook up the hardware, I was using a Mega2560 for this initial testing as it was new and I wanted to test it out a bit.  If you have an Uno just change the pin location variables.  All 8 channels were connected via female/male dupont cables to the digital pins on the Arduino. Then I connected the 5V and ground to their respected locations. The receiver was already bound to the transmitter so after turning on the transmitter, Arduino, upload the sketch, and we are ready to see values in the terminal window. See my code below.  I also have very minimal commenting so feel free to ask if you need it.

//This will sketch will read all 8 channels of a RC reciever and input the values via serial monitor.
//Programed for the Arduino MEGA 2560!!!

// Define Variables:
const int chA=22;  //Constant variables relating to pin locations
const int chB=24;
const int chC=26;
const int chD=28;
const int chE=30;
const int chF=32;
const int chG=34;
const int chH=36;

int ch1;  //Varibles to store and display the values of each channel
int ch2;
int ch3;
int ch4;
int ch5;
int ch6;
int ch7;
int ch8;


// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(115200);
  // Set input pins
  pinMode(chA, INPUT);
  pinMode(chB,INPUT);
  pinMode(chC,INPUT);
  pinMode(chD,INPUT);
  pinMode(chE,INPUT);
  pinMode(chF,INPUT);
  pinMode(chG,INPUT);
  pinMode(chH,INPUT);
}

//Main Program
void loop() {
  // read the input channels
  ch1 = pulseIn (chA,HIGH);  //Read and store channel 1
  Serial.print ("Ch1:");  //Display text string on Serial Monitor to distinguish variables
  Serial.print (ch1);     //Print in the value of channel 1
  Serial.print ("|");
 
  ch2 = pulseIn (chB,HIGH);
  Serial.print ("Ch2:");
  Serial.print (ch2);
  Serial.print ("|");
 
  ch3 = pulseIn (chC,HIGH);
  Serial.print ("Ch3:");
  Serial.print (ch3);
  Serial.print ("|");
 
  ch4 = pulseIn (chD,HIGH);
  Serial.print ("Ch4:");
  Serial.print (ch4);
  Serial.print ("|");
 
  ch5 = pulseIn (chE,HIGH);
  Serial.print ("Ch5:");
  Serial.print (ch5);
  Serial.print ("|");
 
  ch6 = pulseIn (chF,HIGH);
  Serial.print ("Ch6:");
  Serial.print (ch6);
  Serial.print ("|");
 
  ch7 = pulseIn (chG,HIGH);
  Serial.print ("Ch7:");
  Serial.print (ch7);
  Serial.print ("|");
 
  ch8 = pulseIn (chH,HIGH);
  Serial.print ("Ch8:");
  Serial.println (ch8);
}

Step 5: Deep Tissue Signal Massaging...

Picture of Deep Tissue Signal Massaging...

Now that we can sucessfully read our receivers what about a little signal massaging? I found that even after calibrating the transmitter there was a lot of variation in the upper and lower limits of the various channels. I also wanted a slight dead band to ensure that the thumbsticks came back to "center". 

Before we continue I have a small advert. Outside of building your code in one fell swoop, I recommend that you only change/add one thing at a time when building upon the initial code. Test your changes, if successful continue and if not you should easily know where to start debugging. Commenting out sections or lines of code is a great way to easily find your mistake.

Now back to the feature presentation. Lets focus on setting a range to get clear boundaries without sacrificing too much resolution. While reading the raw values decide on the values that you want for your low and high values and set them as constant variables. Think about the conditions you need to invoke to constrain your data.  Following are my two methods to signal massaging and range variables.

Variables:
//RX signal massaging values
const int RXLo=920;
const int RXHi=1640;
const int RXDeadLo=1265;
const int RXDeadHi=1295;
const int RXMid=1280;

Method 1:
//Input Signal massaging
  for (int i=0; i<=8; i++)      //Signal Conditioning loop
  {
   if (ch[i] <= lo)             //Trim Noise from bottom end
   {
    ch[i] = lo;
   }
  
   if (ch[i] <= deadhi && ch[i] >= deadlo)     //Create Dead-Band
   {
    ch[i] = center;
   }
  
   if (ch[i] >= hi)            //Trim Noise from top end
   {
     ch[i] = hi;
   }
  }

Method 2:
//Signal Massaging
for (int i=0; i<=8; i++)      //Signal Conditioning loop
  {
    chEng[i]=constrain(chEng[i], RXLo, RXHi);  //Trim bottom and upper end
    if (chEng[i] <= RXDeadHi && chEng[i] >= RXDeadLo){     //Create Dead-Band
      chEng[i] = RXMid;}
  }

As you can see the second method is a bit less code and gives a perfect example of how the constrain function is used.

Step 6: Second Program: Servo Output

Picture of Second Program: Servo Output

I lifted the picture from Adafruit's site and couldn't say enough good things about them!

Now the fun begins!  Using the program I have outlined previously, lets add some servo output using the default servo library.  I have also decided to use an array for the input data instead of eight separate variables.  If you have an Uno, that I will be using latter, all you have to do is change the pin location variables.

//This will sketch will read all 8 channels of a RC receiver and input the values via serial monitor.
//Programed for the Arduino MEGA 2560

//Included Libraries
#include <Servo.h>

// Define Variables:
const int chA=22;  //Constant variables relating to pin locations
const int chB=27;
const int chC=30;
const int chD=35;
const int chE=38;
const int chF=43;
const int chG=46;
const int chH=51;

//Signal Conditioning limits
const int lo=920;
const int hi=1640;
const int deadlo=1270;
const int deadhi=1290;
const int center=1280;

int ch[8];  //Array to store and display the values of each channel
int ch4;  //Servo Output Variable
Servo steer;  //Steering Servo

// the setup routine runs once when you press reset:
void setup()
{
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
 
  //Input Pins:
  pinMode(chA,INPUT);
  pinMode(chB,INPUT);
  pinMode(chC,INPUT);
  pinMode(chD,INPUT);
  pinMode(chE,INPUT);
  pinMode(chF,INPUT);
  pinMode(chG,INPUT);
  pinMode(chH,INPUT);
 
  //Servo Outputs:
  steer.attach(2);      //Attach Steering Servo to PWM Pin 2
}

//Main Program
void loop()
{
  // read the input channels
  ch[0] = pulseIn (chA,HIGH);  //Read and store channel 1
  ch[1] = pulseIn (chB,HIGH);
  ch[2] = pulseIn (chC,HIGH);
  ch[3] = pulseIn (chD,HIGH);
  ch[4] = pulseIn (chE,HIGH);
  ch[5] = pulseIn (chF,HIGH);
  ch[6] = pulseIn (chG,HIGH);
  ch[7] = pulseIn (chH,HIGH);
 
  //Input Signal conditioning
  for (int i=0; i<=8; i++)      //Signal Conditioning loop
  {
   if (ch[i] <= lo)             //Trim Noise from bottom end
   {
    ch[i] = lo;
   }
  
   if (ch[i] <= deadhi && ch[i] >= deadlo)     //Create Dead-Band
   {
    ch[i] = center;
   }
  
   if (ch[i] >= hi)            //Trim Noise from top end
   {
     ch[i] = hi;
   }
  }
 
  //Steering Control Output on Channel 4
  ch4 = ch[3];
  if (ch4 >= lo && ch4 <= deadlo)
  {
    ch4 = map(ch4, lo, deadlo, 0, 90);
  }
  else if (ch4 == center)
  {
    ch4 = 90;
  }
  else if (ch4 >= deadhi && ch4 <= hi)
  {
    ch4 = map(ch4, deadhi, hi, 90, 180);
  }
  steer.write(ch4);
 
//Serial Outputs
  Serial.print ("Ch1:");  //Display text string on Serial Monitor to distinguish variables
  Serial.print (ch[0]);     //Print in the value of channel 1
  Serial.print ("|");
  Serial.print ("Ch2:");
  Serial.print (ch[1]);
  Serial.print ("|");
  Serial.print ("Ch3:");
  Serial.print (ch[2]);
  Serial.print ("|");
  Serial.print ("Ch4:");
  Serial.print (ch[3]);
  Serial.print ("|");
  Serial.print ("Ch5:");
  Serial.print (ch[4]);
  Serial.print ("|");
  Serial.print ("Ch6:");
  Serial.print (ch[5]);
  Serial.print (&q. uot;|");
  Serial.print ("Ch7:");
  Serial.print (ch[6]);
  Serial.print ("|");
  Serial.print ("Ch8:");
  Serial.println (ch[7]);
  Serial.print ("Steering Output:");
  Serial.println (ch4);
}

I'm going to assume that the servo had a nervous breakdown and developed a slight tick? The terminal window confirms that the wheels just plain fell OFF of your project, now what? Before you commit your servo or Arduino to the nut house it is something that can be worked around.  The problem is how the Arduino servo library generates the required output signal. It interrupts the main program to maintain the 50Hz pulse and will conflict with the pulsein function corrupting valuesbeing read. Both the servo library and pulsein are interrupt based but which one do we look at replacing? I chose to eliminate the servo library after a lot of google-fu.

Step 7: RCArduinoFastLib to the Rescue?

After a forum thread on the Arduino Forums I was directed to try RCArduinoFastLib from Duane at RCArduino.  I was able to get simple pass through operation but when trying to read in values via serial monitor the wheels feel off again. This is written for the Uno and is where I officially retire my Mega.  After implementing serial inputs I saw nothing on the terminal window and pass through operation ceased.  Duane seems to have done great work but I was unable to get it to work for me.  I can add more detail to this step if anyone wants to provide the info.  However, I seeked another resource and diverged from this solution.  My sketch files should be attached to this step.

Step 8: Adafruit to the Rescue!!!

Picture of Adafruit to the Rescue!!!

In the Adafruit Store I noticed this little PWM/Servo Controller Board that uses a 2 wire interface.  The only drawback is that you have to manually understand and program the signal required by the servo.  You will have to download and copy in the new library files.  Don't cringe as it isn't that hard, you have to set the boards frequency,  number of servos, board address, and your range of values to re-map values.  I used the Adafruit Tutorial and a forum question to understand the how to use the board.  The servo board is a little more tedious than the servo library because you have to manually configure the signal required by the servo.  Lastly, this board uses analog inputs 4 & 5 to communicate over a I2C (two wire) bus.  

Before we continue you should consider soldering a capacitor on the servo controller board to decouple or stabilize the power source. It is also good practice to either use separate power sources for the servos and the Arduino or decouple/stabilize the power delivered to the Arduino. Decoupling is a fancy way of saying you need to add a capacitor between the voltage source (VCC) and ground (GND). The size of the capacitor depends on your power source, how well it can keep up with surge currents and the item you are decoupling. consult the almighty Google for more information. Lastly, if you are using electrolytic caps (metal can caps) make sure to look at the PCB to get your polarity correct.

A little refresher on the servo signal:
     Frequency is 50Hz so 1 second / 50 = 20 millisecond (ms) periods
     1ms base pulse is the low signal, 0 degrees, or full reverse for a Continuous Rotation (CR) servo
     1.5ms pulse is the middle signal, 90 degrees, or center for a CR servo
     2ms pulse is the high signal, 180 degrees, or full forward for a CR servo

The servo controller board has 12 bits of resolution per period to create the servo signal. How we use these 12 bits is simple because 2^12 = 4096. Now we can apply simple ratios or stoichemetry to figure out the value associated with how long we want our signal to remain high ot duty cycle. Remember Arduino's don't like floating point calculations so try to use integer math or you will bog down your program.

Servo Signal out of 4096
Total period: 20ms = 4096
Low signal: 1ms = 4096/20 = 205
Mid signal: 1.5ms = 205*1.5 = 308
High signal: 2ms = 205*2 = 410

Software Instruction:
Those values should be stored as constants at the beggining of your program for easy editing later on. So now how are we going to use this range to output via the servo board?

First, you have to include the library file for the board.
#include <Adafruit_PWMServoDriver.h>

Second, declare the name given to the servo board and link the two with the board's address. Default address is 0x40.
Adafruit_PWMServoDriver servo=Adafruit_PWMServoDriver(0x40);

Third, within the setup loop, begin the servo board connection.
servo.begin();

Fourth, set the freqeuncy of the servo board within the setup loop.
servo.setPWMFreq(freq);

Fifth, write the value to the servo board within the main program loop.
servo.setPWM(channel, on, off);
servo.setPWM(2,0,ch3);

Hardware Instruction:
First, I soldered on the included pin headers, terminal block, and a 470uF electrolytic cap rated for 10V.  You could go with a larger cap and even one rated for 6V.

Second, I didn't change the default address of the board and is only needed if you want to daisy chain multiple boards together.

Third, I used some liquid electrical tape on the bottom of the pin headers since I will be making a crude robot for testing.

Fourth, to hook up the I2C Bus you will need to supply 5V and GND from the Arduino to the VCC and GND pins.  Next connect A5 to SCL and A4 to SDA.  See the picture I created with Fritzing!
 
That are the basics and please check out Adafruit's tutorial for more information. Next, I will show how I implemented the servo board with logic to control two continuous rotation servos.

Step 9: Tank Steering W/Continuous Rotation Servos

Picture of Tank Steering W/Continuous Rotation Servos

This final step is a culmination of all previous topics and my tank steer code. I made the tank steer code to only use the right thumb stick of the transmitter to then control two CR servos. Remember that my TX is set to mode 3 and I don't have any channels reversed. The right thumbstick is channel 3 for the horizontal axis and channel 4 for the verticle axis. Following is my sketch using an Arduino UNO instead of the MEGA. I'm using differnt servo limits that enable a micro-servo to move a full 180 degrees and them different range for tank steering to give more sensitivity.

Make sure to thank my wonderful model Sid (above picture) for being such a good sport while I've been using him to refine my program.

/* This will sketch will read all 8 channels of a RC receiver and input the values via serial
monitor. Programmed for the Arduino Uno and Adafuit Servo Driver Board(pins A5-SCL and A4-SDA).
My transmitter is also set in mode 3 so the right thumbstick is as follows:
Verticle Axis = Channel 3
Horixontal Axis = Channel 4
===========================================================================================*/
//Included Libraries
#include
#include

//Enable debug mode to input data via serial
/*0=OFF, 1=Engineering Data On, 2=Raw Data On, 3=Servo value Output,
4=Raw Eng & Servo Data Output, 5=Tank steer data output,*/
const int debug=0;

//Arrays for Channel pin locations and Channel data
const int channels=8;
const int chPin[]={2,3,4,5,6,7,8,9}; //Pin locations
int chEng[8]; //Store massaged data
int chSer[8]; //Servo value storage

//RX signal massaging values
const int RXLo=920;
const int RXHi=1640;
const int RXDeadLo=1265;
const int RXDeadHi=1295;
const int RXMid=1280;

//Servo Ranges
const int serLo=130;
const int serMid=330;
const int serHi=530;
const int tankLo=250;
const int tankHi=410;

//Servo Output
Adafruit_PWMServoDriver servo=Adafruit_PWMServoDriver(0x40);
const int freq=50;
const int serNum=8;
int ch3; //Tank steer variable
int ch4; //Tank Steer variable

//Setup pin locations, start serial, or begin I2C
void setup(){
if(debug > 0){
Serial.begin(115200); //We're going PLAID!
}

//Input Pins:
for (int i=0; i pinMode(chPin[i],INPUT);
}

servo.begin();
servo.setPWMFreq(freq);
}//End of Setup

//Main Program
void loop(){
//Move values from chy to chz, chx to chy, and read new values
for (int i=0; i chEng[i]=pulseIn(chPin[i],HIGH);

//Signal Massaging
chEng[i]=constrain(chEng[i], RXLo, RXHi); //Trim bottom and upper end
if (chEng[i] <= RXDeadHi && chEng[i] >= RXDeadLo){ //Create Dead-Band
chEng[i] = RXMid;
}

//Map Eng values to servo output
if (chEng[i]>=RXLo && chEng[i]<=RXDeadLo){ //Map lower range of values
chSer[i]=map(chEng[i], RXLo, RXDeadLo, serLo, serMid);
}
else if (chEng[i] == RXMid){ //Map middle value
chSer[i]=serMid;
}
else if (chEng[i]>=RXDeadHi && chEng[i]<=RXHi){ //Map higher range of values
chSer[i]=map(chEng[i], RXDeadHi, RXHi, serMid, serHi);
}
}//End of For Loop

//Tank Steer using right thumbstick, outputing to two continous rotation servos
/*Using cartesian quadrant system and any value given to the right motor is reversed to
ensure proper operation*/

//First quadrant.
/*Flip range on right servo with respect to vertical axis position. This will keep the
left servo set to the verticle position and reduce the right servos speed.*/
if (chSer[2]>=serMid && chSer[3]>=serMid){
ch3=chSer[2];
ch4=map(chSer[3],serMid,serHi,chSer[2],serMid);
}

//Second Quadrant
/*Flip range on left servo with respect to verticle axis position. This one isn't as
strait forward as Quadrant 1 but the following sample code I was using before should
look similar.
ch3=map(chSer[3],serLo,serMid,serMid,serHi);
servo.setPWM(2,0,map(ch3,serHi,serMid,chSer[2],serMid));
How I arrived with the following is by simplification and to save resources.*/
else if(chSer[2]>serMid && chSer[3] ch3=map(chSer[3],serLo,serMid,serMid,chSer[2]);
ch4=chSer[2];
}

//Third Quadrant
/*Flip the range on right servo with respect to verticle axis position. The left servo
will continue traveling backwards while the right servo is slowed.*/
else if(chSer[2]<=serMid && chSer[3]<=serMid){
ch3=chSer[2];
ch4=map(chSer[3],serMid,serLo,chSer[2],serMid);
}

//Fourth Quadrant
/*Flip the range on left servo with respect to vertivle axis position, The right servo
will continut traveling backwards while the left servo is slowed.*/
else if(chSer[2]serMid){
ch4=chSer[2];
ch3=map(chSer[3],serMid,serLo,chSer[2],serMid);
}

/*Enable CCW Rotation by reversing right servo and forwarding left servo with respect to
the horizontal axis.*/
if(chSer[2]==serMid && chSer[3]>=serMid){
ch3=chSer[3];
ch4=map(chSer[3],serMid,serHi,serMid,serLo);
}

/*Enable CW Rotation by forwarding right servo and reversing left servo with respect to
the horizontal axis.*/
if(chSer[2]==serMid && chSer[3]<=serMid){
ch3=chSer[3];
ch4=map(chSer[3],serMid,serLo,serMid,serHi);
}

//Remap variables to give better sensitivity and the full range isn't needed to acheive full speed.
ch3=map(ch3,serLo,serHi,tankLo,tankHi);
ch4=map(ch4,serLo,serHi,tankLo,tankHi);

//Flipping right side servo signal, channel 4 signal.
if(ch4>=serMid){
ch4=map(ch4,serMid,serHi,serMid,serLo);
}
else if (ch4 ch4=map(ch4,serMid,serLo,serMid,serHi);
}

//Output to servo driver
servo.setPWM(2,0,ch3);
servo.setPWM(3,0,ch4);

//Debug Output
if (debug==1 || debug==4) //Engineering Data
{
Serial.print ("EngData|Ch1:");
Serial.print (chEng[0]);
Serial.print ("|Ch2:");
Serial.print (chEng[1]);
Serial.print ("|Ch3:");
Serial.print (chEng[2]);
Serial.print ("|Ch4:");
Serial.print (chEng[3]);
Serial.print ("|Ch5:");
Serial.print (chEng[4]);
Serial.print ("|Ch6:");
Serial.print (chEng[5]);
Serial.print ("|Ch7:");
Serial.print (chEng[6]);
Serial.print ("|Ch8:");
Serial.print (chEng[7]);
Serial.println ("|");
}

if (debug==3 || debug==4)
{
Serial.print ("SerData|Ch1:");
Serial.print (chSer[0]);
Serial.print ("|Ch2:");
Serial.print (chSer[1]);
Serial.print ("|Ch3:");
Serial.print (chSer[2]);
Serial.print ("|Ch4:");
Serial.print (chSer[3]);
Serial.print ("|Ch5:");
Serial.print (chSer[4]);
Serial.print ("|Ch6:");
Serial.print (chSer[5]);
Serial.print ("|Ch7:");
Serial.print (chSer[6]);
Serial.print ("|Ch8:");
Serial.print (chSer[7]);
Serial.println ("|");
}

if (debug==5){
Serial.print(ch3);
Serial.print("|");
Serial.println(ch4);
}
}//End of Main Program

I know how I did the tank steering is far from optimal but it is how I figured it out.  The fourth quadrant doesn't work and I have yet to get working.  It wasn't that big of a problem as this is a proof of concept robot in working with RC control and Arduino before I move full scale.

Let me know if you have any questions and I will do my best to answer and update accordingly. Please include the step and some detail explaining your problem to help me get on the same page with you.

Step 10: Now What!?

I haven't really game to an end result here but have taught you something new or a new way for something you already knew.  My goal was to clearly explain, provide detailed documentation and explain again to pound in core concepts.  You could call Sid and end result but he is the completion of the initial "testing" or "proof of concept" phase for my RC Tank Project.  The next phase is to begin mock-up of my power-train while slowly adding required functions ending with a very rough prototype.  I have acquired a Tecumseh two-stroke engine from a snow blower and am working on setting an idle speed.  I believe the engine is set to constantly run at 4300 RPM so setting an idle speed has been trouble some but not impossible.  Below is a video of my initial attempt at remotely controlling the engine and turned out quite well until I lost power in the end of the video!  

 

Comments

Marik I. (author)2014-01-11

Deep tissue signal messaging pic... lol

charels88 (author)Marik I.2014-01-13

Glad someone enjoys a little humor. Thanks for the comment!

AbhishekN33 (author)charels882016-10-21

sir we have a transmitter and receiver.Can we use Arduino and control brushed gear motors??If so can I get the program

Professormajack (author)2016-10-03

It it possible to give an estimate to the weight of your little rc gadget?

gfunas (author)2016-08-21

I am new to this stuff, so could someone just give me code . where would be just simple receiver input and output to servo, that can move left and right

dimitrakis1992 (author)2016-08-03

Hello sir.I need your help.I have write down the readings and put it on the code and 3 wheels go forward and one backward(My car has 4 wheels).I use channels 2 and 4.I have used Arduino mega and L298N motor controller.Also i switch the + and - on motor controller but with no result.How to solve this?Can you help me please?

My readings are:

1500 tο 1550(center)

1070 to 1150(raised)

1900 to 1980(lower)

My code is this

// Motor Control Variables

int PWM1 = 44;

int ENABLE1 = 25;

int PWM2 = 45;

int ENABLE2 = 27;

int PWM3 = 46;

int ENABLE3 = 33;

int PWM4 = 3;

int ENABLE4 = 35;

int channel2 = 8; // defines the channels that are connected

int channel4 = 9;// to pins 9 and 10 of arduino respectively

int Channel2; // Used later to

int Channel4; // store values

void setup ()

{

pinMode(ENABLE1, OUTPUT);

pinMode(ENABLE2, OUTPUT);

pinMode(ENABLE3, OUTPUT);

pinMode(ENABLE4, OUTPUT);

pinMode(channel2, INPUT);// initialises the channels

pinMode(channel4, INPUT);// as inputs

//Serial.begin (9600); // Sets the baud rate to 9600 bps

}

void loop ()

{

Channel2 = (pulseIn (channel2, HIGH)); // Checks the value of channel1

Serial.println (Channel2); //Prints the channels value on the serial monitor

if (Channel2 > 1100 && Channel2 < 1300) /*If these conditions are true, do the following. These are the values that I got from my transmitter, which you may customize according to your transmitter values */

{

digitalWrite(ENABLE1, HIGH);

digitalWrite(ENABLE2, HIGH);

digitalWrite(ENABLE3, LOW);

digitalWrite(ENABLE4, HIGH);

analogWrite(PWM1, 255);

analogWrite(PWM2, 255);

analogWrite(PWM3, 255);

analogWrite(PWM4, 255);

}

if (Channel2 > 1500 && Channel2 < 1550) // Checks if Channel1 is lesser than 1300

{

digitalWrite(ENABLE1, 0);

digitalWrite(ENABLE2, 0);

digitalWrite(ENABLE3, 0);

digitalWrite(ENABLE4, 0);

analogWrite(PWM1,0);

analogWrite(PWM2,0);

analogWrite(PWM3,0);

analogWrite(PWM4,0);

}

if (Channel2 > 1900 && Channel2 < 2300) // Checks if Channel1 is greater than 1500

{

digitalWrite(ENABLE1, LOW);

digitalWrite(ENABLE2, LOW);

digitalWrite(ENABLE3, LOW);

digitalWrite(ENABLE4, LOW);

analogWrite(PWM1, 255);

analogWrite(PWM2, 255);

analogWrite(PWM3, 255);

analogWrite(PWM4, 255);

}

Channel4 = (pulseIn (channel4, HIGH)); // Checks the value of channel1

Serial.println (Channel4); //Prints the channels value value on the serial monitor

if (Channel4 > 1070 && Channel4 < 1150 ) // If these conditions are true, do the following

{

digitalWrite(ENABLE1, LOW);

digitalWrite(ENABLE2, HIGH);

digitalWrite(ENABLE3, HIGH);

digitalWrite(ENABLE4, HIGH);

analogWrite(PWM1, 255);

analogWrite(PWM2, 255);

analogWrite(PWM3, 255);

analogWrite(PWM4, 255);

}

if (Channel4 > 1500 && Channel4 < 1550) // Checks if Channel2 is lesser than 1300

{

digitalWrite(ENABLE1, 0);

digitalWrite(ENABLE2, 0);

digitalWrite(ENABLE3, 0);

digitalWrite(ENABLE4, 0);

analogWrite(PWM1, 0);

analogWrite(PWM2, 0);

analogWrite(PWM3, 0);

analogWrite(PWM4, 0);

}

if (Channel4 > 1900 && Channel4 < 1980) // Checks if Channel2 is greater than 1500

{

digitalWrite(ENABLE1, LOW);

digitalWrite(ENABLE2, LOW);

digitalWrite(ENABLE3, LOW);

digitalWrite(ENABLE4, LOW);

analogWrite(PWM1, 255);

analogWrite(PWM2, 255);

analogWrite(PWM3, 255);

analogWrite(PWM4, 255);

}

}

charels88 (author)dimitrakis19922016-08-03

I would recommend extending your use of the serial connection to transmit all of your data or just what you are having issues with. See how I manage my serial data as part of my debug at the end of the program code. Second, I would comment out your code, /* code */, to remove the sections that work and to isolate where your issue is. I cant help you directly as I don't know where your wires are going or doing for pin locations. Comment after your pinMode to highlight what that output is supposed to do and I may come up with something. Lastly, you dont need analogWrite as you are using them as digitalWrite, analogWrite(pin, 0) = digitalWrite(pin, LOW), analogWrite(pin, 255) = digitalWrite(pin, HIGH). If you use the map function to change your output with your input you will need to use analogWrite but not in your current configuration, maybe will be the next feature to add.

dimitrakis1992 (author)charels882016-08-04

Hello sir and thank you for your reply.I try to remove the analogwrite but it doesn't move at all.I found out that the problem is the HIGH function.It creates conflicts between the ENABLE and pulseln function.How to solve this?Thank you for your time

breandanmcewen9 (author)2016-05-02

I really just need a straight forward, easy to understand "R/C -> Arduino -> Servo" sketch. No matter how much I search, the results are convoluded. Even this article, although very cool and creative, goes on so many tangents I can't follow the concept, or use the basic parts. I'm new to Arduino and feeling really stupid right now. Can anyone direct me to a sketch that is as simple as: Wire the receiver, controller, and servos-upload this code-use. Please, I'm begging for help. In the meantime, I promise to keep studying my "Arduino Starter Kit" and learning the fundamentals. Thank you.

Arduino in the basic functions can't nicely operate both the pulseIn function and the servo output from the base library. The servo is expecting a 50Hz signal and will interrupt your pulse in command to output to the servo, thus causing a stutter in the servo from a interrupted input read. This is not just plug and play for a receiver and servo to play nice together. If you let me know what you are working with, transmitter, receiver, arduino model, servo, and maybe power supply. Private message me with the info and I can make something up with wiring diagrams and code in a couple of days.

Hey man, super cool that you wrote me back. I am totally new to the Arduino and Instructables environment; but I have been in r/c for 20 years (helicopters). Funny thing is, I have two UNO's and two WickedDevice 4 motor shields. I was getting some input from a moderator on that site today and when I tried to morph a small code sample he sent me (via Github);

I blew up the UNO and two (out of six) servos. Seriously they caught fire and smoked my basement up. Haha. Here's the link to that string. http://help.wickeddevice.com/t/motor-shield-servo-set-up-sketch/107

I guess you could say they certainly did not play nice. Lol. I'm calling it quits for the night but tomorrow after work I will compose a writing that explains why I even needed r/c servo override functionality in the first place and see what your thoughts are on the matter. Again, I appreciate your input and collaboration. Cheers.

setsunakaede (author)2016-03-13

Great 'ible! I am thinking of creating a RC multiswitch out of an Arduino due to the lack of a (for me) suitable version, this has given me a lot of study material. now only find the time to dive into this wonderful world...

charels88 (author)setsunakaede2016-03-14

Depending on what you are switching my other instructable may help you more. The good ole 2N2222 is cheap to find and can switch enough power to control an automotive relay.  Not to mention the knock off solid state relays, they are a great buy.  Just research flyback or snubber diodes to protect your circuit from transient voltage spikes when using relays.

https://www.instructables.com/id/24V-Motor-Controller-for-24-RC-Arduino/

cormul (author)2015-07-16

Great job! It could be even easier to read if you consider using syntax colors and better indentation while displaying code. Tools to make it automatically exists (e.g. http://qbnz.com/highlighter/ )

fansblink (author)2015-04-19

Would it be possible to use the Arduino uno ?

charels88 (author)fansblink2015-04-24

Of course! I just wanted to play with my the Mega and use an Uno for all my other projects.

fansblink (author)charels882015-06-03

Okay, thanks !! by the way, do you have a video of the results of the project? if there is then it will make my spirit to make it ..

abouraad007 (author)2015-04-12

when i write these code and run it ... its not working because apper these massege :

avrdude : stk500_getsync(): not in sync : resp=0x00

RyanP7 (author)abouraad0072015-05-09

Try checking to see if your arduino is plugged in to the correct USB port, you may need to change the COM port under tools.

charels88 (author)abouraad0072015-04-15

You may not have your arduino configured correctly with the arduino software. Check under the tools menu and maybe try just uploading the blink example to confirm everything is working.

DrKarma (author)2015-04-15

Great postings.

charels88 (author)DrKarma2015-04-15

Thanks!

abouraad007 (author)2015-04-12

rsatheesan (author)2015-03-09

excellent work

charels88 (author)rsatheesan2015-03-09

Thanks!

SidraK (author)2015-02-24

i want to read signal frm RC reciver and want to write tht signal on servo like i want to control servo using RC reciver i need help

charels88 (author)SidraK2015-02-24

All the information you request is in this instructable. As a summary you will use the pulsein function to read input from receiver but cant use the regular servo library to write signal to servo without chatter. I have covered the reason for this prior and I used I2C to talk to a seperate PWM controller from Adafruit. This program was my first attempt and I recommend to print out the program and write your own comments as you learn from my method. If you still have questions I can share a simpler program I'm using for a similar robot.

HamzaK (author)2014-11-06

this is very amazing.........very easy to follow..........good job man.

incognito0288 (author)2014-10-02

nice Instructable. .. I'm going to have to use this. . Thanks ; )

About This Instructable

222,245views

176favorites

Bio: Mechatronics Engineer
More by charels88:24V Motor Controller for $24, RC & ArduinoRC Control and Arduino: A Complete WorksFind The Center of a Circle
Add instructable to: