Controlling An Arduino Via SerialThis is a in depth tutorial on how control your arduino micro-controller via serial. Unlike other tutorials I will not use the 'char' variable but 'unassigned long's' to store numbers much larger than 255 "If this dose not make sense I will explain later".
This tutorial will focus on the serial control of the digital/analog ports as well as servos but the concepts will be applicable to anything you want to control. Also I will go over how to read data from the digital and analog ports sending it back to the computer.
For this tutorial I will just use the arduino Serial Monitor to send and receive data but any language that can send and receive serial data will be interface with the arduino with no modifications to the code. Examples would be python, C, C++, Processing, an so on...
Before starting I assume you have an arduino board and have install the standard programing environment. If not the boards and the programing environment can be found here. I also assume you are familiar with the arduino, and the basics of the language so I will skip over the setup.
Also this is my first tutorial so keep that in mind while reading and please post a comment if you notice anything off.
Serial Protocol
Before we jump into code we need to think about serial Protocol. How do we tell the arduino what we want it to do?
A lot of peoples first thoughts for controlling the arduino is to send a command in the form of words. For example "digital pin 4 on". This works OK, but because the arduino isn't human it dose not understand English; so a command like that takes a lot more code to translate into an action. Also that is quite a bit of data to send over the serial port!
The best way I have found to talk to the arduino is not with words but with numbers. Each number we send will correspond to an action. In this tutorial we will group the actions into three groups a '1' will change the state of a pin "analog/digital", a '2' will read data from a pin "analog/digital", and a '3' will control or read from a servo.
By using numbers we can make it easy for the arduino to understand what we want, but what about the extra variables the arduino needs to carry out one of the above tasks? How do we tell the arduino to read an analog pin not a digital one? Actually its simple just have another group inside of the first; a '1' will read digital, a '2' will read analog.
Finally we need to get the variables the arduino needs; which in this case is pin number. All you need to do for that is read a final number from the serial port!
So for example to "read digital pin 4" we would send '1 1 4'
To "read analog pin 4" we send '1 2 4'
The full serial protocol for all the tasks I mentioned would look this:
1 = Wright:
1 = digital:
2 = analog:
2 = Read:
1 = digital:
2 = analog:
3 = Servo:
1 = read:
2 = wright:
You may be wondering why make a group within a group when you could make separate groups for analog read and analog wright? Really its my personal preference once you understand the concepts you can do it any way you want.
It may look complex but I found it to be the best way to do it. Also I don't have any good flow charts for it yet
.
If you ever want to add a function like motor control or what ever else is not covered by these categories just add a fourth position and so on...
Like this.1 = Wright:
1 = digital:
2 = analog:
2 = Read:
1 = digital:
2 = analog:
3 = Servo:
1 = read:
2 = wright:
4 = Your Function:
1 = Your Sub Function:
2 = Your Sub Function:
OK now that you have a general idea of the protocol I will be using lets jump into the code!
Code
Disclaimer: I will not go into great depth about the assigning of arrays or really any code that dose not pertain to serial "this includes the servo library". So of something like this looks foreign myservos[pinNumber] = Servo s1; you may need to read up on the arduino documentation found here before preceding. Also some of this code deals with variable scope so make sure you know the programing basics. However even if you have limited experience many of the code examples as far as getting the serial data may be extremely useful. Also at the end there is an attached file with the complete and working code and the final example will show the complete program.
Step 1:
The first step is to figure out how to receive serial data from the computer. The easiest way to do this is with the Serial.read() statement. Sending data is accomplished a similar way with the Serial.print() or Serial.println() statements. The first statement simply prints the data you want, and the other dose the same thing but with a /r/n newline characters on the end.
So lets try a simple program.
Code:
int serialData = 0;
void setup()
{
Serial.begin(9600);
}
void loop()
{
if (Serial.available() > 0)
{
serialData = Serial.read();
Serial.println(serialData);
}
}
I'll skip over the basics like the assigning of the variable, but whats happening here is the serial is being started at a "9600" data rate
Serial.begin(9600);
Next we check if there is new data available
if (Serial.available() > 0)
Finally we read the data
serialData = Serial.read(); and print it back over the same serial port
Serial.println(serialData);
If you run this program and open the serial monitor you should have what ever you type echoed back but in ASCII . There for 1 = 49, 2 = 50 and so on... An ASCII chart is available
here .
Step 2:
Now time to make things more complicated. As you may have noticed the arduino only receives 1 piece of data at a time. This works OK if we are only needed to send numbers between 0-9 but we want to be able to tell a servo to go to position 180 right? This is where we need to take multiple numbers one at a time and combine them into one big number.
This is where some very basic math comes in here is our formula.
x = number we want
n = our current number
i = number we want to add to the end
x = n * 10 + i
As you can see this simple attaches the number received to the end of the number we are making. Now we can append each new number to the end of what will be come our final number.
Lets translate this into code. The variables used through this tutorial will be:
unassigned long serialdata; Used to store the chain of numbers
int inbyte; The number we just received from the serial port
So the formula looks like this to the arduino:
Code:
serialdata = serialdata * 10 + inbyte;
This gives us a number we can use to determine which function the user wants to run 1, 2, or 3. Or we can plug it directly into a varable like pinNumber or servoPose .
So lets modify the old program to combine the numbers like this.
Code:
unsigned long serialdata;
int inbyte;
void setup()
{
Serial.begin(9600);
}
void loop()
{
inbyte = Serial.read();
if (inbyte > 0)
{
serialdata = serialdata * 10 + inbyte;
Serial.println(serialdata);
}
}
This should output the combined value of numbers you input via serial. If strange numbers come up things may be being printed in ASCII. I'm not 100% sure why the arduino sometimes nor am I sure how to fix it but the variable its self is OK and everything but the Serial.println() statement will see it correctly.
You have probably noticed by now that the program just jumbles all the values you enter into one giant variable? But if it takes three numbers to read a analog pin '1' '2' '7' wouldn't the function combine that all in to '127' ? If it did that our program would fail. We need a way to tell the program when one number begins and the next ends. I like to do that with the '/' character. Because our program only receives numbers the '/' character will never be sent so we can use it as a special character. In this case it will signify the end of a number.
Code to implement this '/' number terminator is below:
Code:
unsigned long serialdata;
int inbyte;
void setup()
{
Serial.begin(9600);
}
void loop()
{
getSerial();
}
long getSerial()
{
serialdata = 0;
while (inbyte != '/')
{
inbyte = Serial.read();
if (inbyte > 0 && inbyte != '/')
{
serialdata = serialdata * 10 + inbyte - '0';
Serial.println(serialdata);
}
}
return serialdata;
inbyte = 0;
}
As you can see I migrated the all the code to a separate function. This will come in handy later when we are writing the final program. This will print the current number every time you add a number then clear the data when you send a '/' . Also it will return the variable serialdataso code in the main loop can use it.
Congrats! The serial data gathering part is over! For beginners and experts alike the above function can be very useful for getting numbers from the serial port. If you want to see how the protocol I went over way back at the beginning works and create the program for controlling the arduino keep reading.
Step 3:
Now we need to build the program that requests and handles the serial data. Remember our end goal is to control the analog/digital ports, servos, and read the analog/digital ports. So lets begin.
Now we see the serial protocol way back at the beginning come into full play. I will be using "switch/case" statements but "if" statements work just as well. Info on those can be found here and here . The reason I chose to use a switch/case statement is because it is easier to expand on.
So lets start by adding data to control analog/digital pins. I'll use the program I just made as a base to build the code on.
Code:
unsigned long serialdata;
int inbyte;
int digitalState;
int pinNumber;
int analogRate;
void setup()
{
Serial.begin(9600);
}
void loop()
{
getSerial();
switch(serialdata)
{
case 1:
{
//analog digital write
getSerial();
switch (serialdata)
{
case 1:
{
//analog write
getSerial();
pinNumber = serialdata;
getSerial();
analogRate = serialdata;
pinMode(pinNumber, OUTPUT);
analogWrite(pinNumber, analogRate);
pinNumber = 0;
break;
}
case 2:
{
//digital write
getSerial();
pinNumber = serialdata;
getSerial();
digitalState = serialdata;
pinMode(pinNumber, OUTPUT);
if (digitalState == 0)
{
digitalWrite(pinNumber, LOW);
}
if (digitalState == 1)
{
digitalWrite(pinNumber, HIGH);
}
pinNumber = 0;
break;
}
}
break;
}
}
}
long getSerial()
{
serialdata = 0;
while (inbyte != '/')
{
inbyte = Serial.read();
if (inbyte > 0 && inbyte != '/')
{
serialdata = serialdata * 10 + inbyte - '0';
}
}
inbyte = 0;
return serialdata;
}
Here is the new code minus new variables:
Code:
getSerial();
switch(serialdata)
{
case 1:
{
//analog digital write
getSerial();
switch (serialdata)
{
case 1:
{
//analog write
getSerial();
pinNumber = serialdata;
getSerial();
analogRate = serialdata;
pinMode(pinNumber, OUTPUT);
analogWrite(pinNumber, analogRate);
pinNumber = 0;
break;
}
case 2:
{
//digital write
getSerial();
pinNumber = serialdata;
getSerial();
digitalState = serialdata;
pinMode(pinNumber, OUTPUT);
if (digitalState == 0)
{
digitalWrite(pinNumber, LOW);
}
if (digitalState == 1)
{
digitalWrite(pinNumber, HIGH);
}
pinNumber = 0;
break;
}
}
break;
}
}
This may look very complex but its function is simple turn analog/digital ports on and off. As you can see every time the getSerial();command is called the value stored in serialdata is updated. That allows us to check if it is a known command or assign it to a variable depending on what part of the loop we are at.
With this code added sending a command like '1/2/7/1/' will turn pin 7 on HIGH
Sending '1/2/7/0/' would turn it off
Sending '1/1/7/255/' would turn pin 7 on at a analog rate of 255 or full power
And so on...
Each case statement has a getSerial(); statement before it so that when it determines which case the user wants to run the data us up to date.
The first two numbers are fed into switch statements each switch branches of to the correct commands for the numbers received.
The third number received is stored in pinNumber and that is used to initialize the pin and write to the pin. The fourth number received is the state between 1-2 for digital and 0-255 for analog.
That covers the important points of that function now lets move on to reading values. As I said I will not really go over the code line by line but instead brush over the important parts concerning serial.
Now lets add our analog/digital read code.
Code:
unsigned long serialdata;
int inbyte;
int digitalState;
int pinNumber;
int analogRate;
int sensorVal;
void setup()
{
Serial.begin(9600);
}
void loop()
{
getSerial();
switch(serialdata)
{
case 1:
{
//analog digital write
getSerial();
switch (serialdata)
{
case 1:
{
//analog write
getSerial();
pinNumber = serialdata;
getSerial();
analogRate = serialdata;
pinMode(pinNumber, OUTPUT);
analogWrite(pinNumber, analogRate);
pinNumber = 0;
break;
}
case 2:
{
//digital write
getSerial();
pinNumber = serialdata;
getSerial();
digitalState = serialdata;
pinMode(pinNumber, OUTPUT);
if (digitalState == 1)
{
digitalWrite(pinNumber, LOW);
}
if (digitalState == 2)
{
digitalWrite(pinNumber, HIGH);
}
pinNumber = 0;
break;
}
}
break;
}
case 2:
{
getSerial();
switch (serialdata)
{
case 1:
{
//analog read
getSerial();
pinNumber = serialdata;
pinMode(pinNumber, INPUT);
sensorVal = analogRead(pinNumber);
Serial.println(sensorVal);
sensorVal = 0;
pinNumber = 0;
break;
}
case 2:
{
//digital read
getSerial();
pinNumber = serialdata;
pinMode(pinNumber, INPUT);
sensorVal = digitalRead(pinNumber);
Serial.println(sensorVal);
sensorVal = 0;
pinNumber = 0;
break;
}
}
break;
}
}
}
long getSerial()
{
serialdata = 0;
while (inbyte != '/')
{
inbyte = Serial.read();
if (inbyte > 0 && inbyte != '/')
{
serialdata = serialdata * 10 + inbyte - '0';
}
}
inbyte = 0;
return serialdata;
}
And here is what was added minus new variables:
Code:
case 2:
{
getSerial();
switch (serialdata)
{
case 1:
{
//analog read
getSerial();
pinNumber = serialdata;
pinMode(pinNumber, INPUT);
sensorVal = analogRead(pinNumber);
Serial.println(sensorVal);
sensorVal = 0;
pinNumber = 0;
break;
}
case 2:
{
//digital read
getSerial();
pinNumber = serialdata;
pinMode(pinNumber, INPUT);
sensorVal = digitalRead(pinNumber);
Serial.println(sensorVal);
sensorVal = 0;
pinNumber = 0;
break;
}
}
break;
}
As you can see the same principles used in the first one apply to this.
First two numbers determine which case we want.
Third number is stored in
pinNumber and used to determine which pin to read.
However a statement I introduced back at the beginning is added
Serial.println(); this is used to print the sensors value back to the computer via serial. If you are using serial monitor when running a command like '2/1/7/' you should get a value.
That sums up the analog/digital read one more step to go!
The servo code is the most complex in my opinion and I may have done things poorly/strangely so feel free to leave a comment asking WTF and recommending a better way to do things.
Code:
#include <Servo.h>
unsigned long serialdata;
int inbyte;
int servoPose;
int servoPoses[80] = {};
int attachedServos[80] = {};
int servoPin;
int pinNumber;
int sensorVal;
int analogRate;
int digitalState;
Servo myservo[] = {};
void setup()
{
Serial.begin(9600);
}
void loop()
{
getSerial();
switch(serialdata)
{
case 1:
{
//analog digital write
getSerial();
switch (serialdata)
{
case 1:
{
//analog write
getSerial();
pinNumber = serialdata;
getSerial();
analogRate = serialdata;
pinMode(pinNumber, OUTPUT);
analogWrite(pinNumber, analogRate);
pinNumber = 0;
break;
}
case 2:
{
//digital write
getSerial();
pinNumber = serialdata;
getSerial();
digitalState = serialdata;
pinMode(pinNumber, OUTPUT);
if (digitalState == 1)
{
digitalWrite(pinNumber, LOW);
}
if (digitalState == 2)
{
digitalWrite(pinNumber, HIGH);
}
pinNumber = 0;
break;
}
}
break;
}
case 2:
{
getSerial();
switch (serialdata)
{
case 1:
{
//analog read
getSerial();
pinNumber = serialdata;
pinMode(pinNumber, INPUT);
sensorVal = analogRead(pinNumber);
Serial.println(sensorVal);
sensorVal = 0;
pinNumber = 0;
break;
}
case 2:
{
//digital read
getSerial();
pinNumber = serialdata;
pinMode(pinNumber, INPUT);
sensorVal = digitalRead(pinNumber);
Serial.println(sensorVal);
sensorVal = 0;
pinNumber = 0;
break;
}
}
break;
}
case 3:
{
getSerial();
switch (serialdata)
{
case 1:
{
//servo read
getSerial();
servoPin = serialdata;
Serial.println(servoPoses[servoPin]);
break;
}
case 2:
{
//servo write
getSerial();
servoPin = serialdata;
getSerial();
servoPose = serialdata;
if (attachedServos[servoPin] == 1)
{
myservo[servoPin].write(servoPose);
}
if (attachedServos[servoPin] == 0)
{
Servo s1;
myservo[servoPin] = s1;
myservo[servoPin].attach(servoPin);
myservo[servoPin].write(servoPose);
attachedServos[servoPin] = 1;
}
servoPoses[servoPin] = servoPose;
break;
}
case 3:
{
//detach
getSerial();
servoPin = serialdata;
if (attachedServos[servoPin] == 1)
{
myservo[servoPin].detach();
attachedServos[servoPin] = 0;
}
}
}
break;
}
}
}
long getSerial()
{
serialdata = 0;
while (inbyte != '/')
{
inbyte = Serial.read();
if (inbyte > 0 && inbyte != '/')
{
serialdata = serialdata * 10 + inbyte - '0';
}
}
inbyte = 0;
return serialdata;
}
Here is the added code minus the variables:
Code:
case 3:
{
getSerial();
switch (serialdata)
{
case 1:
{
//servo read
getSerial();
servoPin = serialdata;
Serial.println(servoPoses[servoPin]);
break;
}
case 2:
{
//servo write
getSerial();
servoPin = serialdata;
getSerial();
servoPose = serialdata;
if (attachedServos[servoPin] == 1)
{
myservo[servoPin].write(servoPose);
}
if (attachedServos[servoPin] == 0)
{
Servo s1;
myservo[servoPin] = s1;
myservo[servoPin].attach(servoPin);
myservo[servoPin].write(servoPose);
attachedServos[servoPin] = 1;
}
servoPoses[servoPin] = servoPose;
break;
}
case 3:
{
//detach
getSerial();
servoPin = serialdata;
if (attachedServos[servoPin] == 1)
{
myservo[servoPin].detach();
attachedServos[servoPin] = 0;
}
}
}
break;
}
There it is! Full serial control of analog/digital, servos, and analog/digital read. You will see in the servo code there is three cases, that is because there is an attach/write function, detach function, and a read function that returns the current position of a servo or a '0' if its not set.
I will go over the variables for the final piece of servo code just because there are so many.
int servoPoses[80] = {};
Stores the current position of all the servos
int attachedServos[80] = {};
If a servo is attached the value corresponding to the pin number is set to one; this is important so you don't attach the same servo twice.
Servo myservo[] = {};
Stores all the servo object in an array so they are easy to access
int servoPin;
Pin of the servo the user wants
int servoPose;
Position the user wants the specified servo to go to
That sums up the coding part of this tutorial.
Tips & Tricks
Finally I would like to go over some key concepts/extra information you may need.
Most importantly is the serial bus length. The arduino has a serial bus length of I believe 128bits but for the life of me I could not find it on google. If you send to many numbers at once and the arduino dose not read them fast enough it will overflow and any data you pile on it will be lost! A good method to prevent this is called handshaking where the arduino asks the computer for data then the computer sends sends it, and the process repeats... I may go over that in a new tutorial but this one is getting to long.
You may have noticed that I never really went over the computer side of the equation besides mentioning the serial monitor on the arduino IDE. The main reason is there are tons of program languages and they are all different. Making a example for each one would be very difficult.
However I will include two programs in the attached file with the arduino code made in python. On lights up lights sequentially on pins 1-13 in the "knight rider" style. The other cycles two servos between 0-180 degrees. To run the programs you will need pyserial installed. Also make sure you edit s = serial.Serial(2, 9600, timeout = 2) to reflect your serial port -1 .
Here is a video of the "knight rider" style led display:[youtube]http://www.youtube.com/watch?v=gZtYLvO5PwU[/youtube]
Thanks for reading!
There is an error with the youtube video
For now the youtube link is:
http://www.youtube.com/watch?v=gZtYLvO5PwU
Sorry!
Comments
Post a Comment