Follow
the Line! Code
After our last Arduino Project, the
class was asked to create a code that would allow the SciBorg to use a light
sensor to follow a continuous white line on pieces of cardboard. Brooke and I
made many adjustments to our code before our SciBorg 1) finally made it through
all the way and 2) satisfied conditions. Left behind are many iterations and
many versions. It’s been a rough ~24 of trial & error, code, and revisions.
Ideas for the “Follow the Line” Code - Setting aside proportional
steering as a method of meeting “follow the line” code objectives, what can we
do make our SciBorg follow the line from beginning to end?
- SciBorg to “runs straight” on the white line and turning all the way around when it hit the cardboard (darker area) to find the white line again
- Restricting the SciBorg to the middle of the white. This did not succeed because the SciBorg could not differentiate between left and right.
- SciBorg is caught between the white line and the darker cardboard, allowed only to run on one side of the line (didn’t think this was allowed initially, but this became the most important method to consider*)
- SciBorg runs straight on white line, hits the dark cardboard, turns (say, left?) for a certain amount of time, and if can’t find white line, turns the other direction (then, right), for a longer duration of time
- Divide into three concentric “circles” 1) the white line, allows SciBorg to run straight 2) edges of white line, allows SciBorg to turn one direction (say, left) 3) outer(most) edges of white line, allows SciBorg to turn in different direction (then, right) for longer duration. This, again, did not succeed, and asked again to create another version of the code, we would not follow this method (used up much of our time tweaking numbers with the false belief that it might work!)
- Reverse – what happens if SciBorg is asked to back up every time it hits the cardboard? By itself, restricted by only the white line, this was not efficient in making its way across the white line, nor was the SciBorg able to turn corner
- Reverse Part 2 – what happens if SciBorg is asked to back up every time it hits the darker area, but not just “back up”, instead, turns (say, left?) while backing up?
- “Nudge” functions – would the sensor be able to detect a value shift earlier if it were inching across the white line to the end goal?
- Slow – same idea as above. Is slower better? We determined that when the SciBorg went slowly, or “nudged” along the line, it was tended to stick to the line initially, but still was not able to allow the SciBorg to turn corners if the code was not efficient enough.
And many, many more. We tested out
many of these methods and ideas to check whether or not they would be more
helpful to our SciBorg’s mission. Looking back at the ideas that we have, many
all of them appeared to function correctly at the beginning…but alone, they
weren’t enough. With more time (and endurance!) we could have combined “reverse
and turn” method with the concept of restricting the sensor between the very
white, white line and the complete dark cardboard to create another successful
version.
To fit all of our iterations in this blog would not be
efficient, so I’ve picked out versions that have successfully “finished” the
line.
A) Version 1, Semi-Successful
This is
one of our initial codes that we worked and played with that was successful in
making it across the line most of the time (not all!). In the “middle”, or
“edge of the line” if statement section, the code asks the SciBorg to turn left
(delayed for a small amount of time) and to turn right (delayed for a small
amount of time) if the sensor realized that it was reading the edge of the
line, edging the SciBorg forward awkwardly. When the SciBorg read the white
line, it make a larger turn to the left, and if it read the cardboard, it would
make a larger turn to the right. The
fault here was the middle if statement. If I had split the middle section
into two parts, as we did for one of our completed versions of Bang-Bang,
Follow the Line Code, we would not be trying the guess the duration allowed for
one “right” turn before a “left” turn is allowed. Once that middle if statement
(the edge of the line is read by the sensor), the SciBorg must accomplish the
task (of both a right and left turn) inside before allowing itself to escape
and make a “large turn left” or a “large turn right” in the outer if
statements.
#include <Wire.h>
#include <Bricktronics.h>
Bricktronics brick =
Bricktronics();
Motor m = Motor(&brick,
1); //names left motor
“m”
Motor n = Motor(&brick, 2); //names right motor “n”
const int reader = A0; //analog port for reading the
sensor data
const int light = 70; //digital port for powering the
sensor LED
void setup()
{
Serial.begin(9600);
//initializes “printing”
brick.begin();
//initializes shield
brick.pinMode(light,
OUTPUT); //initializes
sensor
m.begin();
//initializes motors “m” and
“n”
n.begin();
}
void loop()
{
int value = analogRead(reader); //declares variable “value” and
sets equal to the light level that the sensor reads
brick.digitalWrite(light, HIGH); //turn LED on
Serial.println(analogRead(reader)); //print the light level reading
delay(100); //stops
code for debounce
if (value <= 555) //when on the white line, SciBorg turn left while
nudging
{
m.stop();
n.stop();
delay(200); //SciBorg stops for 200 milliseconds
m.set_speed(110);
n.set_speed(-70);
delay(200); //SciBorg turns left for 200 milliseconds
}
if (value > 555 && value <= 600) //when on the edge of the white
line, or between the white line and cardboard, SciBorg turns a little right,
then a little left
{
m.set_speed(-152);
n.set_speed(0);
delay(200); //turns right for 200 milliseconds
n.set_speed(-155);
m.set_speed(0);
delay(200);
//turns left for 200
milliseconds
}
if (value > 600) //when completely on the cardboard, SciBorg turns right
nudges between right
{
m.set_speed(120);
n.set_speed(0);
delay(50); //turns right for 50 milliseconds
m.stop();
n.stop();
delay(50); //stops for 50 milliseconds
}
}
As you may
imagine, moving the SciBorg along the line becomes extremely inefficient
because it is nudging itself all the way to the finish line.
To watch a video, follow the link: A) Version 1, Semi-Successful, Follow the Line Code
(It appears that I cannot upload videos larger than 100MB, which, unfortunately, limits the all the videos I would like to include on my post :( I uploaded the videos on YouTube instead)
1a) Bang-Bang Control, Follow the
Line! Code, Version 1
The readings for both versions:
Ignoring the arithmetic in the background...our goal was to keep the sensor (and therefore the SciBorg) as close as possible to the reading values between 540 and 580.
#include
<Wire.h>
#include <Bricktronics.h>
Bricktronics brick = Bricktronics(); //names shield “brick”
Motor m = Motor(&brick, 1); //names left motor “m”
#include <Bricktronics.h>
Bricktronics brick = Bricktronics(); //names shield “brick”
Motor m = Motor(&brick, 1); //names left motor “m”
Motor n = Motor(&brick, 2); //names right motor “n”
const
int reader = A0; //analog
port for reading the sensor data
const int light = 70; //digital port for powering the sensor LED
//declaring variables
const int light = 70; //digital port for powering the sensor LED
//declaring variables
void setup()
{
Serial.begin(9600);
brick.begin();
brick.pinMode(light, OUTPUT);
m.begin();
n.begin(); //initializes declared variables
}
void loop()
{
{
Serial.begin(9600);
brick.begin();
brick.pinMode(light, OUTPUT);
m.begin();
n.begin(); //initializes declared variables
}
void loop()
{
int value = analogRead(reader); //setting variable
"value" to the numerical value read by the light sensor
brick.digitalWrite(light, HIGH); //turn LED on
Serial.println(analogRead(reader)); //print the light level reading
brick.digitalWrite(light, HIGH); //turn LED on
Serial.println(analogRead(reader)); //print the light level reading
delay(100); // allow for debounce to occur
if ( value < 540) //if light sensor reads value of all
or almost all of white line
{
{
m.set_speed(70);
n.set_speed(-100);
delay(100);
} //turns the SciBorg left so that keeps to the right edge of the white line
if ( value > 540 && value <= 560 ) //if light sensor touches the right edge of the white line
{
m.set_speed(-100);
n.set_speed(-70);
delay(200);
m.set_speed(70);
n.set_speed(-90);
delay(100);
m.stop();
n.stop();
delay(100);
}//nudges the SciBorg forward, a little right, a
n.set_speed(-100);
delay(100);
} //turns the SciBorg left so that keeps to the right edge of the white line
if ( value > 540 && value <= 560 ) //if light sensor touches the right edge of the white line
{
m.set_speed(-100);
n.set_speed(-70);
delay(200);
m.set_speed(70);
n.set_speed(-90);
delay(100);
m.stop();
n.stop();
delay(100);
}//nudges the SciBorg forward, a little right, a
little
left at a time
if (value > 560 && value
<= 600) //if sensor reads
inner left edge of edge (more cardboard than white)
{
m.set_speed(-70);
n.set_speed(-100);
delay(200);
m.set_speed(-90);
n.set_speed(70);
delay(100);
m.stop();
n.stop();
delay(100);
}//nudges the SciBorg forward, a little left, a
{
m.set_speed(-70);
n.set_speed(-100);
delay(200);
m.set_speed(-90);
n.set_speed(70);
delay(100);
m.stop();
n.stop();
delay(100);
}//nudges the SciBorg forward, a little left, a
little right at a time
if (value > 600)//if sensor reads entirely cardboard
{
m.set_speed(-150);
n.set_speed(90);
delay(200);
m.stop();
n.stop();
delay(100);
} //nudges Sciborg to turn right until it finds edge of line }
m.set_speed(-150);
n.set_speed(90);
delay(200);
m.stop();
n.stop();
delay(100);
} //nudges Sciborg to turn right until it finds edge of line }
In this version of Bang-Bang, Follow
the Line, the SciBorg nudges forward slowly and takes a while to get to the
finish line. We revised our code to make our SciBorg move faster along the
line, taking out the nudges in the "middle" section.
Follow the Link to Watch: 1a) Bang-Bang Control, Follow the Line! Code, Version 1
1b) Bang-Bang Control, Follow the
Line! Code, Version 2
#include
<Wire.h>
#include <Bricktronics.h>
Bricktronics brick = Bricktronics(); //names shield “brick”
Motor m = Motor(&brick, 1); //names left motor “m”
#include <Bricktronics.h>
Bricktronics brick = Bricktronics(); //names shield “brick”
Motor m = Motor(&brick, 1); //names left motor “m”
Motor n = Motor(&brick, 2); //names right motor “n”
const
int reader = A0; //analog
port for reading the sensor data
const int light = 70; //digital port for powering the sensor LED
//declaring variables
const int light = 70; //digital port for powering the sensor LED
//declaring variables
void setup()
{
Serial.begin(9600);
brick.begin();
brick.pinMode(light, OUTPUT);
m.begin();
n.begin(); //initializes declared variables
}
void loop()
{
int value = analogRead(reader); //setting variable
"value" to the numerical value read by the light sensor
brick.digitalWrite(light, HIGH); //turn LED on
Serial.println(analogRead(reader)); //print the light level reading
brick.digitalWrite(light, HIGH); //turn LED on
Serial.println(analogRead(reader)); //print the light level reading
delay(100); // allow for debounce to occur
if ( value < 540) //if sensor reads only white line
{
m.set_speed(80);
n.set_speed(-120);
delay(100);
} //SciBorg turns left
if ( value > 540 && value <= 560 ) //if sensor reads mostly
{
m.set_speed(80);
n.set_speed(-120);
delay(100);
} //SciBorg turns left
if ( value > 540 && value <= 560 ) //if sensor reads mostly
white
line on right edge
{
m.set_speed(-90);
n.set_speed(-60);
} //SciBorg moves forward, slightly leaning to the right
if (value > 560 && value <= 580) //if sensor reads mostly
{
m.set_speed(-90);
n.set_speed(-60);
} //SciBorg moves forward, slightly leaning to the right
if (value > 560 && value <= 580) //if sensor reads mostly
cardboard
on right edge
{
m.set_speed(-60);
n.set_speed(-90);
} //SciBorg moves forward, slightly leaning to the left
if (value > 580)//if sensor reads entirely cardboard
{
m.set_speed(-120);
n.set_speed(80);
delay(100);
}//SciBorg turns right
}
{
m.set_speed(-60);
n.set_speed(-90);
} //SciBorg moves forward, slightly leaning to the left
if (value > 580)//if sensor reads entirely cardboard
{
m.set_speed(-120);
n.set_speed(80);
delay(100);
}//SciBorg turns right
}
Follow the Link to Watch: 1b) Bang-Bang Control, Follow the Line! Code, Version 2
After we finished the Follow the
Line Code, Bang-Bang Control, we were asked to create a version run by entirely
proportional control. We were initially confused by how we would implement
proportional control into our code, or how to visualize it. We came up with
various visualizations of how the SciBorg would work, and how to implement
proportional control, before we implemented, as requested, strict proportional
control.
Thought Process for the “Follow the Line” Code , Proportional
- Could we possibly have different gain values to adjust the speeds of turn values for our SciBorg? We initially thought that we would use proportional control on our speed, where error = real (the value that the sensor read) - ideal (around 560, where we want the sensor to always hit), and speed would be set to error*gain (a constant value that we would test for)
- If not different gain values, different error values?
- Should we substitute the speed values that we have in our bang-bang control, follow the line code at the moment and work to adjust our gain values?
- Clarifying that we should be implementing proportional steering, we were still confused by what we should be limited by...could we possibly use different base speeds and implement proportional steering in that manner? (Proportional Attempt #1)
- If we implement the speeds at the same value, could we still have three conditions, where 1) if the sensor starts to read the cardboard, it is affected by proportional control, 2) if the sensor reads range of values that represents the right edge of the white line, it moves forward, and 3) if the sensor starts to read the white line, it is again affected by proportional control? (Proportional Attempt #2)
- Clarifying again that we should be implementing strict proportional control, we could only have two conditions; from there, we tested various gain values and range values until we were able to have our SciBorg follow the line with these two conditions. (Proportional Attempt #3)
Proportional Control Attempt #1
#include
<Wire.h>
#include <Bricktronics.h>
Bricktronics brick = Bricktronics(); //names shield “brick”
Motor m = Motor(&brick, 1); //names left motor “m”
#include <Bricktronics.h>
Bricktronics brick = Bricktronics(); //names shield “brick”
Motor m = Motor(&brick, 1); //names left motor “m”
Motor n = Motor(&brick, 2); //names right motor “n”
const
int reader = A0; //analog
port for reading the sensor data
const int light = 70; //digital port for powering the sensor LED
//declaring variables
const int light = 70; //digital port for powering the sensor LED
//declaring variables
void setup()
{
Serial.begin(9600);
brick.begin();
brick.pinMode(light, OUTPUT);
m.begin();
n.begin(); //initializes declared variables
}
void loop()
{
{
Serial.begin(9600);
brick.begin();
brick.pinMode(light, OUTPUT);
m.begin();
n.begin(); //initializes declared variables
}
void loop()
{
int value = analogRead(reader); //setting variable
"value" to the numerical value read by the light sensor
brick.digitalWrite(light, HIGH); //turn LED on
Serial.println(analogRead(reader)); //print the light level reading
brick.digitalWrite(light, HIGH); //turn LED on
Serial.println(analogRead(reader)); //print the light level reading
delay(100);
// allow for debounce to
occur
int
error = value - 560; //error
= value (the numerical light level value currently read by the sensor) – ideal
(light level value, right edge of the white line, that we would like sensor to
continuously read)
int
steer = error * .2; //steer
= error * gain constant
if ( value < 540)//if sensor reads white line entirely, “steer” is implemented, adjusting the right wheel “n” to create a larger/small left turn depending on how far into the white line the sensor reads
{
m.set_speed(90);
n.set_speed(-130+steer);
}
if
( value > 540 && value <= 560 ) //if sensor reads the inner right edge of edge of the
white line, which would be part of our “ideal range”, SciBorg would continue,
slightly leaning right
{
m.set_speed(-120);
n.set_speed(-90);
}
if
(value > 560 && value <= 580) //if sensor reads the left edge of edge of the white
line, which would still be part of our “ideal range”, SciBorg would continue,
slightly leaning left
{
m.set_speed(-90);
n.set_speed(-120);
}
if (value > 580)//if sensor reads entirely cardboard, “steer” is implemented, adjusting the left wheel “m” to create a larger/small right turn depending on how far into the cardboard the sensor reads
{
m.set_speed(-130+-steer);
n.set_speed(90);
}
}
Follow Link to Watch Video: Proportional Control, Follow the Line Code Attempt #1
With more adjustments, the SciBorg runs smoother. We still aren't there yet though; we need to implement a strict proportional control!
Proportional Control Attempt #2
Attempt #2 still had three conditions: 1) starts to turn right when the SciBorg is on cardboard, 2) starts to turn left when the SciBorg is on white line, and 3) goes straight when the sensors reads the ideal range of values (the right edge). We were almost there, and were asked to take out the last condition to implement strict proportional control.
#include <Wire.h>
#include <Bricktronics.h>
Bricktronics brick = Bricktronics(); //names shield “brick”
Motor m = Motor(&brick, 1); //names left motor “m”
#include <Bricktronics.h>
Bricktronics brick = Bricktronics(); //names shield “brick”
Motor m = Motor(&brick, 1); //names left motor “m”
Motor n = Motor(&brick, 2); //names right motor “n”
const int reader = A0; //analog port for reading the sensor data
const int light = 70; //digital port for powering the sensor LED
//declaring variables
const int light = 70; //digital port for powering the sensor LED
//declaring variables
void setup()
{
Serial.begin(9600);
brick.begin();
brick.pinMode(light, OUTPUT);
m.begin();
n.begin(); //initializes declared variables
}
void loop()
{
int value = analogRead(reader); //setting variable
"value" to the numerical value read by the light sensor
brick.digitalWrite(light, HIGH); //turn LED on
Serial.println(analogRead(reader)); //print the light level reading
brick.digitalWrite(light, HIGH); //turn LED on
Serial.println(analogRead(reader)); //print the light level reading
delay(100); // allow for debounce to occur
int
error = value - 560; //error
= value (the numerical light level value currently read by the sensor) – ideal
(light level value, right edge of the white line, that we would like sensor to
continuously read)
int
steer = error * 5; //steer =
error * gain constant
if ( value < 560)//if sensor reads light level values on
the white line, start turning left
{
m.set_speed(-100-steer);
n.set_speed(-100+steer);
}
if ( value >= 545 && value <= 600) //if sensor is hitting a
{
m.set_speed(-100-steer);
n.set_speed(-100+steer);
}
if ( value >= 545 && value <= 600) //if sensor is hitting a
range of desired values (on right edge of white line), go
straight
{
m.set_speed(-100);
n.set_speed(-100);
}
if (value > 600)//if sensor reads entirely cardboard, start
{
m.set_speed(-100);
n.set_speed(-100);
}
if (value > 600)//if sensor reads entirely cardboard, start
turning right
{
m.set_speed(-100-steer);
n.set_speed(-100+steer);
}
}
{
m.set_speed(-100-steer);
n.set_speed(-100+steer);
}
}
To watch the video, follow the link: Proportional Control, Follow the Line Code Attempt #2
Proportional Control, Follow the Line Code
As noted, the last condition in attempt #2 was taken out, and we only gave the SciBorg two conditions: 1) turn right when it is on/mostly on cardboard and 2) turn left when it is on/mostly on the white line. The conditions are separated by "if" statements, but that is just so our code is easier to read; there is really only one code that the entire SciBorg runs by, and that is:
m.set_speed(-100-steer);
n.set_speed(-100+steer);
Notice that we changed the range values and, of course, the gain constant; proportional control was harder to implement because of the time consuming trial & error. As we were implementing the control, one of the difficulties we faced was the SciBorg throwing itself across the white line. When the sensor was too far out into the cardboard, as asked, it tries to maneuver back to the white line by turning right. However, when the sensor read a value that was much larger than the target value of, say, 560 which we had originally (and is closer to the white line!), this error is multiplied by our gain constant and consequently becomes our "steer" adjustment to the speed...which. then the speed increases by a large amount, throwing the SciBorg its right far too much and over the white line. The SciBorg, then, is unable to read the white line to adjust back to the right edge. In order to dissuade the SciBorg from throwing itself across the line, we adjusted the "ideal" value so that it was farther out into the cardboard (590), allowing more space so that the SciBorg wouldn't be thrown across the white line. As for the gain constant, we played around with it until we had a reasonable steer adjustment. (That was not an easy task...)
In addition tweaking the gain constant and the ideal value, we thought a lot about how to adjust the gain constant so that the speed of one wheel does not hit 0. Once, say, motor m hits 0 and motor n is at it's peak, the SciBorg finds it difficult to turn, which was key to keep in mind as we tested different gain constants.
m.set_speed(-100-steer);
n.set_speed(-100+steer);
Notice that we changed the range values and, of course, the gain constant; proportional control was harder to implement because of the time consuming trial & error. As we were implementing the control, one of the difficulties we faced was the SciBorg throwing itself across the white line. When the sensor was too far out into the cardboard, as asked, it tries to maneuver back to the white line by turning right. However, when the sensor read a value that was much larger than the target value of, say, 560 which we had originally (and is closer to the white line!), this error is multiplied by our gain constant and consequently becomes our "steer" adjustment to the speed...which. then the speed increases by a large amount, throwing the SciBorg its right far too much and over the white line. The SciBorg, then, is unable to read the white line to adjust back to the right edge. In order to dissuade the SciBorg from throwing itself across the line, we adjusted the "ideal" value so that it was farther out into the cardboard (590), allowing more space so that the SciBorg wouldn't be thrown across the white line. As for the gain constant, we played around with it until we had a reasonable steer adjustment. (That was not an easy task...)
In addition tweaking the gain constant and the ideal value, we thought a lot about how to adjust the gain constant so that the speed of one wheel does not hit 0. Once, say, motor m hits 0 and motor n is at it's peak, the SciBorg finds it difficult to turn, which was key to keep in mind as we tested different gain constants.
#include <Wire.h>
#include <Bricktronics.h>
Bricktronics brick = Bricktronics(); //names shield “brick”
Motor m = Motor(&brick, 1); //names left motor “m”
#include <Bricktronics.h>
Bricktronics brick = Bricktronics(); //names shield “brick”
Motor m = Motor(&brick, 1); //names left motor “m”
Motor n = Motor(&brick, 2); //names right motor “n”
const int reader = A0; //analog port for reading the sensor data
const int light = 70; //digital port for powering the sensor LED
//declaring variables
const int light = 70; //digital port for powering the sensor LED
//declaring variables
void setup()
{
Serial.begin(9600);
brick.begin();
brick.pinMode(light, OUTPUT);
m.begin();
n.begin(); //initializes declared variables
}
void loop()
{
{
Serial.begin(9600);
brick.begin();
brick.pinMode(light, OUTPUT);
m.begin();
n.begin(); //initializes declared variables
}
void loop()
{
int value = analogRead(reader); //setting variable
"value" to the numerical value read by the light sensor
brick.digitalWrite(light, HIGH); //turn LED on
Serial.println(analogRead(reader)); //print the light level reading
brick.digitalWrite(light, HIGH); //turn LED on
Serial.println(analogRead(reader)); //print the light level reading
delay(100); // allow for debounce to occur
int
error = value - 590; //error
= value (the numerical light level value currently read by the sensor) – ideal
(light level value, right edge of the white line, that we would like sensor to
continuously read)
int
steer = error * 2; //steer =
error * gain constant
if ( value <= 590)//if sensor reads more of the
white line
or
white line, the SciBorg begins to turn left
{
m.set_speed(-100-steer);
n.set_speed(-100+steer);
}
if (value
> 590) //if sensor reads
more of the
cardboard/cardboard,
the Sciborng begins to turn right
{
m.set_speed(-100-steer);
n.set_speed(-100+steer);
}
}
To watch the video, follow the link: Proportional Control, Follow the Line Code
If we had more endurance, we could have adjusted the gain constant and the ideal value (590) a bit more to allow the SciBorg to perform more efficiently. In reflection to this process, I think it helped to think creatively about what we can do and cannot do with the SciBorg, and to understand how to use proportional control (no matter how time-consuming...) a bit more.
