How to Program a Line Following Robot
In yesterday’s post I described the physical construction of a line following robot with an Arduino brain.
Today I’m going to cover the details of how to program that brain.
Read on for a full explanation.
Get the line following robot parts
Want all the pieces to build your own? Get the kit here.
Got the tools to make the parts? Here are the open hardware laser cutting plans.
Your first Arduino program
This is an empty Arduino program.
[code]void setup() {}
void loop() {}[/code]
setup() and loop() are methods – they describe to the robot how to do something (literally, the method). the Arduino looks for and runs through the steps in setup() once when it turns on. After setup() it looks for loop() and calls it over and over again.
[code]void setup() {
Serial.begin(57600);
Serial.println("Hello,");
}
void loop() {
Serial.println("World!");
}[/code]
Plug your Arduino into your computer with the USB cable and upload the code. Open the Arduino serial monitor and make sure the baud rate is set to 57600. You should see the word “Hello,” once and then “World!” repeating non-stop.
setup()
Eyes are very sensitive to light. Some animals see better than others. This robot is no different. When the robot wakes up it needs a moment to adjust to the light in the room.
[code]void setup() {
study_white();
study_black();
learn_difference();
setup_wheels();
}[/code]
Do you see how methods work? Each one covers one idea and then they can stack like LEGO blocks to build bigger ideas.
study_white()
[code]
long avg_white[NUM_EYES];
void study_white() {
int i;
memset(avg_white,0,sizeof(int)*NUM_EYES);
int samples=0;
long start=millis();
while(millis()-start < 2000) {
look();
samples++;
for(i=0;i<NUM_EYES;++i) {
avg_white[i]+=inputs[i];
}
delay(5);
}
// average = sum / number of samples
for(i=0;i<NUM_EYES;++i) {
avg_white[i] = (float)avg_white[i] / (float)samples;
}
}[/code]
I’m telling Arduino to watch the eyes for two seconds and then get an average of what it sees. study_black() is the same idea. If the robot studies one color for both white and black, it would be very confused!
millis() gets the time in milliseconds from the Arduino clock. delay(5) does nothing for 5/1000th of a second.
This is an example of top-down programming: I wrote this method first, then I wrote the look() method on which it depends.
look()
look() reads the amount of light hitting each eyeball.
[code]int inputs[NUM_EYES];
void look() {
int i;
for(i=0;i<NUM_EYES;++i) {
inputs[i] = analogRead(A1+i);
}
}[/code]
analogRead() returns a number between 0 and 1023. inputs[i] stores each eye value for use by some other method later on.
learn_difference
[code]void learn_difference() {
int i;
for(i=0;i<NUM_EYES;++i) {
cutoff[i] = ( avg_white[i] + avg_black[i] ) / 2.0f;
}
}[/code]
The cutoff value is halfway between the average white color and the average black color. I’m storing a separate cutoff for each eyeball because there’s a chance each sensor has slightly different numbers, just like some people have one eye better than the other.
Putting it together
[code]char eye_sees_white(int i) {
return inputs[i] > cutoff[i];
}[/code]
if the light hitting the sensor is greater than the cutoff value – in other words, closer to white than to black – return 1.
if the light is less than the cutoff value, return 0.
With this I can say
[code]if(eye_sees_white(0)) {
this will only happen if eyeball 0 sees white.
}
[/code]
I have similar methods for eye_sees_black, all_eyes_see_white, and all_eyes_see_black.
[code]char all_eyes_see_black() {
int i;
for(i=0;i<NUM_EYES;++i) {
if(eye_sees_white(i)) return 0; // eye doesn’t see black, quit
}
return 1;
}[/code]
Notice how it builds on top of eyes_sees_white. If none of the eyes see white, they must all see black.
setup_wheels()
[code]#include <Servo.h>
Servo left, right;
setup_wheels() {
left.attach(LEFT_MOTOR_PIN);
right.attach(RIGHT_MOTOR_PIN);
// make sure we aren’t moving.
steer(0,0);
}[/code]
You may remember Servo from yesterday’s post. It’s a special piece of code that lets us drive our wheels. All we have to do is tell it one time which Servo is on which Arduino pin.
The last thing we do in setup is make sure we’re not driving yet.
steer()
[code]void steer(float forward,float turn) {
int a = LEFT_NO_MOVE – forward + turn;
int b = RIGHT_NO_MOVE + forward + turn;
if(a<10) a=10; if(a>170) a=170;
if(b<10) b=10; if(b>170) b=170;
left.write(a);
right.write(b);
}[/code]
I guess the best way to explain this is to start in the bottom. We saw write() yesterday – it takes a number from 0 to 180 and adjusts the speed of the wheel. 0 is full backwards, 180 is full forwards. To play it safe i’ve added some if()s in the middle that prevent A and B from being too big or too small. I need to play it safe because the first two lines in the method are pretty deep magic.
Remember how each servo is facing a different direction on big plate of the line following robot’s skeleton? That means if both servos were going forward at full speed the differential steering would make the robot turn in circles. That’s why there’s a minus sign in the line for A.
Hrm. Maybe a table would help? Let’s assume for a moment that LEFT_NO_MOVE = RIGHT_NO_MOVE = 0 so that we can focus on the difference forward and turn make.
[code] int a = – forward + turn;
int b = + forward + turn;
[/code]
forward | turn | a | b | effect |
---|---|---|---|---|
0 | 0 | 0 | 0 | no move |
0 | 1 | 1 | 1 | turn left |
0 | -1 | -1 | -1 | turn right |
2 | 0 | -2 | 2 | forward |
2 | 1 | -1 | 3 | forward and left |
2 | -1 | -3 | 1 | forward and right |
-2 | 0 | -2 | 2 | reverse |
-2 | 1 | 3 | -1 | reverse and left |
-2 | -1 | 1 | -3 | reverse and right |
by using bigger numbers for forward and turn we can get faster movement.
follow()
So now the robot can see, it knows the difference between white and black in this light, and we can tell it how to steer. The last part is to bring those pieces together. loop() repeats forever, and loops calls follow().
[code]void follow() {
float forward, turn;
look();
// follow line
forward = FORWARD_SPEED;
// …and steer
turn = 0;
if(!eye_sees_white(EYE_LEFT) || !eye_sees_white(EYE_FAR_LEFT)) {
// left eye sees black. turn left.
turn+=TURN_SPEED;
}
if(!eye_sees_white(EYE_RIGHT) || !eye_sees_white(EYE_FAR_RIGHT)) {
// right eye sees black. turn right.
turn-=TURN_SPEED;
}
// if all eyes see black, no turn happens.
steer(forward,turn);
}[/code]
Results
Here’s a video of the robot in action.
Follow up
When your robot is a success and you’d like a new challenge, teach it to turn right when it gets to a T or + intersection, teach it to turn around when it reaches a dead end, then teach it to solve a maze!
I’d love to see your tweaks to the design. Share it in the forums, I’d love to see what you’ve done.