Pages

Wednesday, April 29, 2015

Rewind: Back to Arduino, Follow the Line!


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”
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
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
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
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
 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   }

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. 


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”
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

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
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
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
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
}

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)
Similar to our process for Bang-Bang Control, Follow the Line Code, we created different versions that allowed our SciBorg to follow the line before our misconceptions were cleared. Below, I showed a few iterations of our code, as well as our final Proportional Control, Follow the Line Code.

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”
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
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
 

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);
     }
}

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”
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

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
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
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
turning right
     {
          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. 
 
#include <Wire.h>
#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
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
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. 

1 comment:

  1. Wow! your blog post is incredibly thorough in its explanation of your process leading up to the final outcome. My partners and I have had trouble working through the proportional control line following code, but reading this gives me some clues on how to address the issues in our own code. Good work!

    ReplyDelete