Linefollower Robot From Arduino and Junk - Thoughts and Code

by Libahunt in Circuits > Robots

3446 Views, 9 Favorites, 0 Comments

Linefollower Robot From Arduino and Junk - Thoughts and Code

vimg_3110.jpg
This linefollower ended up as kind of failure regarding the competition, but I learned some things I'm going to share now.

If you want to see the general build, go to photoseries which I posted earlier while the building was in progress. Information is there in image notes. I just updated some of it.

Here I want to share the code and some thoughts and experiences about the problematic parts.

Here's a video about the testing stages in chronological order and also the final result.



I doubt my code would be useful to anyone as is, but if someone is facing similar problems with a linefollower there might be some ideas to peek at. I hope the comments in code are helpful, but if You have questions what does some part do and why, then please ask.

Problems - Moving

vimg_3083.jpg
6.jpg
ylekanne_vana.jpg
The robot has two differentially driven wheels with simple and cheap DC motors and rubberband transmission. Wheels are made from two CD-s each and run on inline skate bearings.

First two parts in the video (link in intro) are only riding tests, random this and that way. The very first version had especially small pulleys on wheels and had too little torque to get moving straight, the robot could only turn around its axis. Second had medium sized pulleys and was able to move, but I made them even bigger later when the sensors acted as brakes and it was also desireable to make it slower. Which was still not enough slowing down and I also coded movements into shorter impulses instead of continuous.

The final sensor attachement was also the "caster". Crucial part with all the braking was to get balance right so that the driving wheels had minimum resistance from the caster (weight in center) but also the thing did not fall over to the other side. For that moving the battery pack on the underside was perfect. If someone plans to build two wheel differentially driven robot I recommend keeping some room around the battery to adjust the balance.

Images of wheel pulleys in reverse order of versions.

Problems - Sensors

vimg_3112.jpg
vimg_3100.jpg
10.jpg
The sensors were the weakest part of this robot, reason why it did not complete the track on the competition. Three IR LEDs and transistors were from computermice, meant to detect impulses coming through holes in scrollwheel not for detecting precise intensity of the light. Additionally my desoldering job was crappy and I probably ruined them even more. Inputs varied very much even though the sensors were from same brand and similar mice. Not only varied the number it gave on the white surface but also the amplitude how much it changed when went onto black. My calibration script tries to fix all this.

It wasn't any help for the contest but I also programmed a search mode (because robot must be smart) - when the readings from sensors have been suspiciously small for some time, then the robot goes straight for a little while just to check and then starts going around in growing circles to find the line. In the video the red light on top of it goes on when it's in search mode. The program does have one problem though - the bigger the circle grows the rarer happens the sensor reading, it can go over the line without notice. Finally that script worked OK but it might work better if sensors were more close together. On the other hand then it would be more sensitive to turning. Probably four or five sensors would be better.

At first the sensors were on the front and the "caster" was on the rear, then the floor unevenness made sensors go up and down and data was quite unusable because of that. Then I made a frame for them that could move up and down itself and stay on the floor, but that braked too much. In the video there is a part where I shake the paper underneath every few seconds to help it get moving.

So I made a third sensor attachment that turned out pretty well and I'm proud of it. The final sensor attachement was also the "caster" itself and so stayed on the ground very well. Some notes about it were in previous step.

Final touch was to add a potentiometer for sensor correction. When I saw it going to one side off the line more often than to the other side, then I adjusted the potentiometer and did not have to make assumptions and upload new code etc.

The black colour of contest track was less absorbent for IR than my home test track. Just before contest I made the sensor reading resolution ten times bigger (difference from white to black when calibrating at first was 100, at the contest 1000 units, turn threshold very much lower also), but it still did not see the track most of the time. So the robot just wondered out from the first curve. I think the mouse scrollwheel sensors are just not suitable for linefollower robot. Can't entirely avoid shopping for next years contest.

Images of sensor attachement in reverse order of versions.

Sensor Schematic

sensoriskeem.jpg
I used IR transistors and IR LEDs which were pulled out from computermice, so tell me if I figured the transistor type or layout wrong on the schematic. (They are actually dual transistors in common package, but I used just one half of each, this https://www.instructables.com/answers/IR-sensor-of-some-kind-How-does-it-work/ is almost all I know about them. And I also know that placed in voltage divider as on the schematic (separate pin to ground, common pin to resistor) it made output voltage decrease while it got more light.)

Dark part of the chematic is one "unit" - a LED set to reflect back from floor and transistor as sensor. And other two units are similar. Actually in my case the exact placing of LEDs is not very important, as the transistors were quite high from floor and had therefore wide viewangle.

Arduino Code - Sensor Calibration

Arduino_Uno_logo.jpg
/***
Line following robot sensors calibration script
Braian
***/

/*
HOW TO USE:
Upload this script.
Arduino has to stay connected to computer.
Sensors must have their voltage provided.
Place the robot entirely onto its white running surface
  presumably so that there is black line on its left,
  but not in the view of sensors.
Press arduinos reset button.
After 2 seconds pin 13 onboard LED should light up.
  White surface calibration measurings are done while the LED is lit - about 2 sec.
  LED will go out after that.
After a 2 seconds delay the LED will light up again.
Now you have 5 seconds to move the robot sideways over a black line.
  Line has to be on the left when facing same way as robot is going to be running.
  You have to move the robot to its left and approximately in right angle to the line.
  During this move the maximum readings are taken that occur when a single sensor is directly over the line.
  Do not hurry, but all the sensors must be over line when LED goes out after 5 sec.
After another 2 sec delay the LED will light up once again.
Now you have to move the robot back over the line, opposite to what you did in last step.
  It means during 5 sec move robot back to right over the line.
  Important: this time robot has to move right - right side sensor has to pass over line first!
  During this are taken measures that say, how low do the two side by side sensors go, when line is exactly inbetween the two.
  Measurings end when LED goes out after 5 sec.
Start serial monitor.
Stop autoscroll and copy correction values from any successive three lines to robots riding script.
*/

/*sensor input pins*/
const int sensorPins[] = {A0, A1, A2}; //right, center, left

/*onboard LED pin*/
const int indicatorPin = 13;

/*arrays for storing sensor and computed data
three sensors in each array:
0 - RIGHT, 1 - CENTER, 2 - LEFT */
float readings[3]; //sensor readings
float averageReadings[3] = {0, 0, 0}; //average readings
float whitePoints[3] = {0, 0, 0}; //reading on white surface
int blackPoints[3] = {0, 0, 0}; //maximum reading while passing over black line
float unitSteps[3]; //difference between white and black divided with 100
int corrections[3]; //white points as int
float peakDetect[3] = {0, 0, 0};
float betweenReadings[2] = {1024, 1024}; //readings when line is inbetween two sensors, RIGHT-CENTER, LEFT-CENTER
boolean betweenState[3] = {false, false, false}; //helper for knowing which minimum to store

/*loop helper variables*/
int i;
int j;


void setup() {
 
  Serial.begin(9600); //start serial monitor to output data after measurings
  pinMode(indicatorPin, OUTPUT); //set nboard led pin to indicate measuring state
 
  /*start calibration after 2 sec delay*/
  delay(2000);
 
  /*MEASURING ON WHITE SURFACE FOR ABOUT 2 SECONDS ASSUMED*/
  digitalWrite(indicatorPin, HIGH); //onboard LED lights up
  for (i=0; i<125; i++) { //125 readings from each sensor
    for (j=0; j<=2; j++) { //loop through each sensor
      readings[j] = analogRead(sensorPins[j]); //get value
      /*calculate average sensor values*/
      whitePoints[j] = (whitePoints[j] * j + readings[j]) / (j+1); //average from all measurings taken this far
      delay(2);
    }
    delay(10);
  }
  digitalWrite(indicatorPin, LOW); //onboard LED goes out
  delay(2000); //2 sec delay
 
  /*GOING SIDEWAYS OVER BLACK LINE WITHIN NEXT 5 SECONDS ASSUMED*/
  digitalWrite(indicatorPin, HIGH); //onboard LED lights up
  for (i=0; i<320; i++) { //320 readings from each sensor
    for (j=0; j<=2; j++) { //loop through each sensor
      readings[j] = analogRead(sensorPins[j]); //get value
      if (readings[j] > blackPoints[j]) { //if current value is grater than one olready saved
        blackPoints[j] = readings[j]; //store if aplicable
      }
      delay(2);
    }
    delay(10);
  }
  digitalWrite(indicatorPin, LOW); //onboard LED goes out
 
  /*calculate "unit step" - 1/100th of difference between white and black for every sensor
  and transform white points to int (corrections)*/
  for (j=0; j<=2; j++) {
    unitSteps[j] = (blackPoints[j] - whitePoints[j])/1000;
    corrections[j] = (int) (whitePoints[j] + 0.5);
  }
 
  delay(2000); //2 sec delay
 
  /*GOING SIDEWAYS (TO THE RIGHT!!!) OVER BLACK LINE WITHIN NEXT 5 SECONDS ASSUMED*/
  digitalWrite(indicatorPin, HIGH); //onboard LED lights up
  for (j=0; j<=2; j++) { //to reuse this array, set all to 0
    blackPoints[j] = 0;
  }
  for (i=0; i<320; i++) { //320 readings from each sensor
    for (j=0; j<=2; j++) { //loop through each sensor
      readings[j] = (analogRead(sensorPins[j]) - corrections[j]) / unitSteps[j]; //get value for each in final units
    }
    for (j=0; j<=2; j++) { //loop through three sensor readings
      if (readings[j] > blackPoints[j]) { //if current reading is greater than stored maxima
       blackPoints[j] = readings[j]; //store if appicable
      }
      else if (readings[j] + 30 < blackPoints[j]) { //if reading is decreasing (stored one is greater than current)
        betweenState[j] = true; //indicate in this variable that sensor has moved over a maximum reading (line)
      }
     
      if (betweenState[0] == true && betweenState[1] == false) { //if first sensor reading has passed its maxima and second has not
        if (betweenReadings[0] > readings[0] + readings[1]) { //if stored sum of two sensor readings is bigger than current sum
          betweenReadings[0] = readings[0] + readings[1]; //store if applicable
        }
      }
      else if (betweenState[1] == true && betweenState[2] == false) { //if second sensor reading has passed its maxima and third has not
        if (betweenReadings[1] > readings[1] + readings[2]) { //if stored sum of two sensor readings is bigger than current sum
          betweenReadings[1] = readings[1] + readings[2]; //store if applicable
        }
      }
      delay(2);
    }
    delay(10);
  }
  digitalWrite(indicatorPin, LOW); //onboard LED goes out
 

 
 
} // /setup

void loop() {
 
  /*print results in serial monitor*/
  Serial.print("const int corrections[3] = {");
  Serial.print(corrections[0]);
  Serial.print(",");
  Serial.print(corrections[1]);
  Serial.print(",");
  Serial.print(corrections[2]);
  Serial.println("};");
  Serial.print("const float unitSteps[3]= {");
  Serial.print(unitSteps[0]);
  Serial.print(",");
  Serial.print(unitSteps[1]);
  Serial.print(",");
  Serial.print(unitSteps[2]);
  Serial.println("};");
  Serial.print("const float betweenReadings[2] = {");
  Serial.print(betweenReadings[0]);
  Serial.print(",");
  Serial.print(betweenReadings[1]);
  Serial.println("};");
  Serial.println(" ");
 
} // /loop

Arduino Code - Robot Running

Arduino_Uno_logo.jpg
/***
Line follower robot operation script
Braian
***/

/*motors output pins*/
const int MR = 6; //Right motor forward pin
const int ML = 5;  //Left motor forward pin

/*sensor input pins*/
const int sensorPins[] = {A0, A1, A2}; //right, center, left

/*other input and output pins*/
const int ledPin = 13; //led indicates if search mode is on
const int potPin = A5; //for adjusting left-right error

/*arrays for storing sensor and computed data*/
int sensorReadings[3]; //sensor readings right, center, left
float sensorValues[3]; //sensor readings right, center, left
int potAdjust = 0; //sensor balance adjusting with potentiometer value

/*counters, helpers etc*/
int i; //general counter in for loops
int unknownCount = 0; //counter of succesive line-unknown states
int s; //pwm value for speed

/***SETTINGS***/
const int turnThreshold = 15; //on which difference between left and right sensorreadings start smooth turning (1000 units scale form white to black)
const int quickturnThreshold = 20; //on which difference between left and right sensorreadings start steep turning
const int indicateThreshold = 2; //on which reading to consider line known
const int moveLength = 55; //how long should a movement take after speed has been gained (depends on following three variables)
//and before new check of sensor state should be called (in milliseconds)
//time it takes to gain speed =[(maxSpeed - startSpeed) / speedStep] (in millis)       current: 9
const int maxSpeed = 160; //max PWM value for motors
//for better starting we increase the PWM value in time:
const int startSpeed = 80; //PWM from which to start, 1/2 of max for example
const int speedStep = 10; //how many PWM values to increase after every 1 millisecond
const int pauseLength = 140; //pause between moves for better sensor reading
int unknownMax = 25; //after how many succesive "line unknown" measurings to start search line routine

/*SENSOR CORRECTIONS for white point and "unit steps": right, center, left
COPY THESE VALUES FROM CALIBRATION PROGRAM SERIAL OUTPUT*/
const int corrections[3] = {38,38,31};
const float unitSteps[3]= {0.06,0.01,0.02};
const float betweenReadings[2] = {219.30,233.33};

/**/



void setup() {
  /*motor pins*/
  pinMode(MR, OUTPUT);
  pinMode(ML, OUTPUT);
  /*sensor pins*/
  pinMode(sensorPins[0], INPUT);
  pinMode(sensorPins[1], INPUT);
  pinMode(sensorPins[2], INPUT);
  pinMode(potPin, INPUT);
 
} // /setup


void loop() {
 
  readSensors();
  if
  (
  sensorValues[0] + sensorValues[1] < 0.1 * betweenReadings[0] ||
  sensorValues[1] + sensorValues[2] < 0.1 * betweenReadings[1] ||
  sensorValues[0] < indicateThreshold ||
  sensorValues[1] < indicateThreshold ||
  sensorValues[2] < indicateThreshold
  ) {
    unknownCount++;
  }
  else {
    unknownCount = 0;
  }
  if (unknownCount <= unknownMax) {
    rideLine();
  }
  else {
    searchLine();
  }
 
} // /loop



void readSensors() { //sets array sensorValues[] to sensor readings with calculated corrections
  for (i=0; i<=2; i++) {
    sensorReadings[i] = analogRead(sensorPins[i]);
    sensorValues[i] = (sensorReadings[i] - corrections[i]) / unitSteps[i];
  }
  potAdjust = (analogRead(potPin) - 45)*10;
} // /readSensors



void goStraight() {
  //increase speed gradually
  for (s=startSpeed; s<=maxSpeed; s=s+speedStep) {
    analogWrite(MR, s);
    analogWrite(ML, s);
    delay(1);
  }
  //full speed
  analogWrite(MR, s);
  analogWrite(ML, s);
  delay(moveLength);
  digitalWrite(MR, LOW);
  digitalWrite(ML, LOW);
  //stop
  delay(pauseLength);
}

void goLeft(int turnspeed) {//turnspeed = 0 -> smooth turning, otherwise steep turning
    digitalWrite(ML, LOW);
    //increase speed gradually
    for (s=startSpeed; s<=maxSpeed; s=s+speedStep) {
      analogWrite(MR, s);
      if(turnspeed==0) {
        analogWrite(ML, s*0.4);
      }
      delay(1);
    }
    //full speed
    analogWrite(MR, s);
    if(turnspeed==0) {
      analogWrite(ML, s*0.4);
    }
    delay(moveLength);
    digitalWrite(MR, LOW);
    digitalWrite(ML, LOW);
    //stop
    delay(pauseLength);
}

void goRight(int turnspeed) {//turnspeed = 0 -> smooth turning, otherwise steep turning
    digitalWrite(MR, LOW);
    //increase speed gradually
    for (s=startSpeed; s<=maxSpeed; s=s+speedStep) {
      analogWrite(ML, s);
      if(turnspeed==0) {
        analogWrite(MR, s*0.4);
      }
      delay(1);
    }
    //full speed
    analogWrite(ML, s);
    if(turnspeed=0) {
      analogWrite(MR, s*0.4);
    }
    delay(moveLength);
    digitalWrite(ML, LOW);
    digitalWrite(MR, LOW);
    //stop
    delay(pauseLength);
}



void rideLine() {
  //indicating difference between right and left sensors, taking into account potentiometer settings, turn accordingly
  if(sensorValues[0] - sensorValues[2] > turnThreshold + potAdjust && sensorValues[0] - sensorValues[2] < quickturnThreshold + potAdjust) {
    goRight(0);
  }
  else if(sensorValues[0] - sensorValues[2] > quickturnThreshold + potAdjust) {
    goRight(1);
  }
  else if (sensorValues[2] - sensorValues[0] > turnThreshold - potAdjust && sensorValues[2] - sensorValues[0] < quickturnThreshold - potAdjust) {
      goLeft(0);
    }
    else if (sensorValues[2] - sensorValues[0] > quickturnThreshold - potAdjust) {
      goLeft(1);
    }
  else {
      goStraight();
  }

} // /rideLine()



void searchLine() {
  digitalWrite(ledPin, HIGH); //indicate search mode with LED
  int searchVar = 1; //helper variable for increasing the search movement radius
  while
    (
    sensorValues[0] + sensorValues[1] < 0.1 * betweenReadings[0] &&
    sensorValues[1] + sensorValues[2] < 0.1 * betweenReadings[1] &&
    sensorValues[0] < indicateThreshold &&
    sensorValues[1] < indicateThreshold &&
    sensorValues[2] < indicateThreshold
    ) {
   if (searchVar <= 5) { //go straight for a while, in case actually not lost
      goStraight();
    }
    else { //rightward movement with increasing radius by inserting straight sequences
      for (i=0; i<7; i++) {
        goRight(1);
      }
      for (i=0;i<searchVar/5; i++) {
        goStraight();
      }
    }
    searchVar++; // indicate completed search routine state
    readSensors(); //get new readings
  }
  unknownCount = 0; //reset counter
  digitalWrite(ledPin, LOW); //indicate end of search mode
 
} // /searchLine()