Introduction: 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.
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
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.
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
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!
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
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
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
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);
}
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...
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.
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
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.
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!!!
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.
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
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.
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!