Introduction: Line Following Arduino Robot

This robot uses a PID control algorithm to autonomously follow a black track on a white background. It uses an array of eight IR reflectance sensors to determine the position of the robot on the line, and the Arduino Nano microcontroller calculates the appropriate action to keep it moving forward on the line. This project cost ~$50, but we already had an arduino, so add about another $25 if you need to buy one.

We completed this project for our Mechatronics class at Rowan University.

Step 1: Materials and Tools

Materials:

  1. 1/8" acrylic sheet
  2. Arduino microcontroller
  3. Assortment of wire
  4. Twin Motor gearbox https://www.pololu.com/product/61
  5. Wheels https://www.pololu.com/product/62
  6. Dual motor controller https://www.pololu.com/product/2130
  7. 5 volt regulator https://www.pololu.com/product/2851
  8. 2x ball castor https://www.pololu.com/product/951
  9. 400 point breadboard https://www.pololu.com/product/351
  10. 170 point breadboard https://www.pololu.com/product/1491
  11. IR reflectance sensor array https://www.pololu.com/product/960
  12. 1 pair 18650 3.7 volt lithium batteries http://www.amazon.com/Ultrafire-18650-3000mah-Rech...
  13. Battery case http://www.amazon.com/gp/product/B009OYP3D2/ref=oh...

Tools

  1. Wire cutters/strippers
  2. Laser cutter or similar CNC cutting tool
  3. hand cutting tools can be substitued for the CNC tools if needed

Step 2: Arduino

Arduino boards are simple microcontrollers great for hobbyists and engineering students alike. It is easy to write your own code using the Arduino IDE, and there is a huge support community with forums, tutorials, and any help you could possibly need to get started. For this project, we used an Arduino Nano, but you can really use any board you want, as long as it has 8 analog inputs, 4 PWM outputs, and 1 extra digital pin. The Nano is very convenient because you can put it right in the breadboard, rather than use jumpers to connect it. The Arduino acts as the robot's brain and makes "decisions" based on information from the sensors.

Step 3: Chassis

The chassis is the physical body of the robot. We used a laser cutter to cut out the pieces of the chassis, but you could try to do it manually if you're feeling adventurous. I included an ai, a dxf, and a drw file for use with laser cutters or other CNC tools, and a dimensioned pdf for those who making them manually. The hole labels in the pdf are also referenced in the assembly instructions below.

After you cut out the top and bottom of the chassis, install the ball castors using holes A5, A6, A7, and A8.

Then install the gearbox using holes B1, B2, B3, and B4. You only need two holes, but the gear box comes with two different sets of gears that you can switch out to change the speed. If you switch them, you change the placement of the wheels, and need to change the position of the gear box. Instructions for assembling the gearbox and switching gear ratios are included with the gearbox.

Next, attach the top part of the chassis to the bottom using holes D1, D2, D3, and D4. Use 1.25" long standoffs when attaching the two halves of the chassis to ensure proper spacing.

Holes C1 and C2 are for mounting the battery holder. I used two Ultrafire 18650 3.7 volt lithium rechargeable batteries arranged in series so that the total nominal voltage output is 7.4 volts. You could substitute another power source like a 9 volt battery, but they have a capacity of about 950 mAh, while the lithium batteries have 3000 mAh.

The two breadboards are attached onto the top deck of the chassis using double sided tape and duct tape. You could use the adhesive on the bread board, but that is way stronger and almost impossible to get the board off to use on another project.

Holes A1, A2, A3, and A4 are used for mounting the sensor brackets. This will be discussed in the next step.

Step 4: Sensor Array

The sensor array acts as the eyes of the robot. There are eight sensors on the front of the robot that can differentiate black from white. Based on which sensors see black and which see white, the Arduino can find the position of the robot on the line.

If the robot tried to stay entirely on the black, it wouldn't be able to differentiate right from left 90 degree turns. Therefore it is programmed to stay on the edge of the line.

The sensor array is mounted on two laser cut acrylic brackets using #2-56 screws. The files for these parts are attached.

The pins on the sensor are labeled 1 through 8 and need to be connected to their corresponding analog pins on the Arduino, labeled A0 through A7. The Vcc pin gets connected to 5V and the GND pin is connected to ground. The full circuit diagram is explained in the next step.

Step 5: Circuit Diagram

Hooking up the sensors is a straightforward process when looking at the wiring diagram above. All of the red wires signify power, or positive voltage, with the dark red being low voltage after the power regulator and the bright red being high voltage coming from battery pack. Switch 1 turns the system on, supplying the voltage from the battery pack on the outer pin of the switch to the center pin of the switch and from there to the Vcc In (pin 4) on the power regulator. The Vcc Out (pin 1) on the regulator sends a low voltage to the board to power the rest of the circuit while the battery pack supplies the ground. Switch 2 determines whether or not the car is in manual or auto mode. This switch is supplied with power from the regulator on one side and ground on the other. The center pin on switch 2 is connected to pin D4 on the Arduino Nano to communicate the mode.

The motor controller is supplied with ground from the board to pins 1 and 16. Power is sent to pin 2 and power wires from Motor 1 and 2 connect to pins 3 and 5 respectively. Each motor’s ground connects to pins 4 and 6 respectively. The motor controller gets its signals from the Arduino pins D9, D6, D5, and D3 connecting to controller pins 11, 12, 13, and 14 respectively.

The Arduino gives commands to the controller based on the signals it receives from the array board. Each IR LED sends a signal back to the Arduino through its analog pins. The array LED pins 1, 2, 3, 4, 5, 6, 7, and 8 are connected to Analog pins A0, A1, A2, A3, A4, A5, A6, and A7 respectively. Power and ground are supplied to the array board from the breadboard.

Step 6: Code

The code for the robot basically just takes readings from the line sensor then interprets it has where it is on the line and tells the each of the motors how fast to run. Our robot has 8 sensor which means if we assume it is always on the same side of the line there are 9 possible positions 0-8 that are represented by how many of the sensors are on.

We want the robot to be centered over the edge of the line so we are going to set the “setpoint” to 4 which is the space between the 4th and 5th sensor. PID controllers work by taking some desired value and where you are and takes the difference of the two which is the “error”. The controller portion then converters the error in to a useable output. In this case the error is equal to the setpoint minus position. The step is to select the tuning constants call Kp and Kd in the code. These are also call the proportional and derivative constants and they will be unique to your robot based on your motors and your exact measurement though our numbers should get you close. Kp is the amount of PWM difference between the two motors per unit of line error and is multiplied by the error. For example if the sensor reads 0 the error is 4 meaning the robot is too far right you would want the left motor to stop a PWM of zero and the right motor to run full speed. If Kp is 64 then 64*error is 256 and subtracted from the normal motor speed (255) the left will come to a complete stop. As the robot starts to turn the error will be less and the motor difference will be less until the sensor sees 4 again and it is full speed ahead.

This sounds great right? Not quite because the robot is already swinging and it takes some stop it will swing right pass the 4th sensor and the 5th. To prevent this we need to slow down a lot faster as we get closer to the setpoint. This is where Kd comes in to play. Kd is multiplied by the change in error and added to the Kp part. For this example we’ll pick up where we left off from above where now the sensor is reading 1. So the error is 3 and the proportional control is telling the left motor to run at 63 and right at 255. The change in error is -1 (3-4) and our Kd is 16 for a total of -16. When add to the proportional part you get a motor speed of 47 on the left wheel and 255 on the right. All of the detail can be seen in the pid function in the code and play around with the Kp an Kd values to see how fast you can make your bot.

Lucky for you we did the hard part for you. Copy and paste the code below into the Arduino IDE and upload it to your board, and your line following robot should be ready to roll!

#define NUM_SENSORS 8
#define avgSpeed 255

int leftWheelf=3;
int leftWheelr = 5;
int rightWheelf=6;
int rightWheelr = 9;
int setpoint=4, val;
unsigned long lastTime=0, timeChange=0;
int Sampletime=20, outMax=255, outMin=-255;
float error,sumerr,lastError,output,ITerm,DTerm;
float Kp=avgSpeed/4, Ki=0, Kd=.25*Kp;
int pos;
unsigned int sensor[]={A0,A1,A2,A3,A4,A5,A6,A7};

unsigned int sensorValue[NUM_SENSORS];
int threshold = 200;

byte incomingByte;  
int bias=5;
  
void setup() {
  Serial.begin(115200);
  pinMode(4,INPUT);
  
}

void loop() {
//  Serial.println(digitalRead(4));
  if (digitalRead(4)== true) {
  unsigned int Wsum=0;
  int sum=0;
  for (int i=0;i<NUM_SENSORS;i++) {
    sensorValue[i]=analogRead(sensor[i]);
    if (sensorValue[i] < threshold) sensorValue[i]=1;
    else sensorValue[i]=0;
    //Serial.print(i); Serial.print(": "); Serial.println(sensorValue[i]);
    //delay(250);
  }
  for (int i=0;i<NUM_SENSORS;i++) {
    sum=sensorValue[i]+sum;
    pos=sum;
  } 
  
//  Serial.println(pos);
  //delay(100);
  timeChange = millis()-lastTime;
  if (timeChange >= Sampletime){
    pid(pos);
  }
 }
 
 else {
   if (Serial.available() > 0) {
      incomingByte = Serial.read();
  }
    if (incomingByte == 'w') {
      forward();
    }
    else if (incomingByte == 's') {
      reverse();
    }
    else if (incomingByte == 'd') {
      rightTurn();
    }
    else if (incomingByte == 'a') {
      leftTurn();
    }
    else {
      brake();
    }
 }

}

void pid (int val) {
  error=setpoint-val;
  ITerm+=(Ki*error);
  DTerm=Kd*(error-lastError)/(Sampletime/1000.0);
  lastError=error;
  if(ITerm > outMax) ITerm=outMax;
  else if (ITerm < outMin) ITerm=outMin;
  output=Kp*error+ITerm+DTerm;
  if (output>outMax) output=outMax;
  else if (output<outMin) output=outMin;
  lastTime=millis();
  Serial.println(val);
  if (output>0) {
    analogWrite(leftWheelf,avgSpeed);
    analogWrite(rightWheelf,avgSpeed);
    analogWrite(leftWheelr,abs(output));
    analogWrite(rightWheelr,0);
  }
  else if (output<0) {
    analogWrite(leftWheelf,avgSpeed);
    analogWrite(rightWheelf,avgSpeed);
    analogWrite(leftWheelr,0);
    analogWrite(rightWheelr,abs(output));
  }
  else {
    analogWrite(leftWheelf,avgSpeed);
    analogWrite(rightWheelf,avgSpeed);
    analogWrite(leftWheelr,0);
    analogWrite(rightWheelr,0);
  }
}
  
void forward() {
  analogWrite(leftWheelf, avgSpeed - bias);
  analogWrite(leftWheelr, 0);
  analogWrite(rightWheelr, 0);
  analogWrite(rightWheelf, avgSpeed);
}

void leftTurn() {
  analogWrite(leftWheelf,0);
  analogWrite(leftWheelr, 0);
  analogWrite(rightWheelr,0);
  analogWrite(rightWheelf, avgSpeed);
}

void rightTurn() {
  analogWrite(leftWheelf,avgSpeed);
  analogWrite(leftWheelr, 0);
  analogWrite(rightWheelr,0);
  analogWrite(rightWheelf, 0);
}

void reverse() {
  analogWrite(leftWheelf, 0);
  analogWrite(leftWheelr, avgSpeed-bias);
  analogWrite(rightWheelr, avgSpeed);
  analogWrite(rightWheelf, 0);
}

void brake() {
  analogWrite(leftWheelf, 0);
  analogWrite(leftWheelr, 0);
  analogWrite(rightWheelr, 0);
  analogWrite(rightWheelf, 0);
}

Step 7: BONUS! Remote Control

If you want the option to manually control you robot, all you need is this bluetooth adapter from Adafruit!

http://www.adafruit.com/products/1588

If you connect the GND to ground, the Vin to +5 volts, and TX to pin 0 on the arduino and the RX to pin 1 on the arduino, you can pair your computer to your arduino. It acts just like a serial connection, and you can use it to upload scripts or send data.

We wrote a script using Processing (https://processing.org/download/) that transmits keystrokes to the arduino and allows you to move your robot around using the WASD keys.

// Click on the image to give it focus,
// and then press any key.

import processing.serial.*;

int value = 0;
String word = "       ";
String mode = "      ";
boolean auto = false;

Serial myPort;


void setup() {
  size(300, 90);  // size always goes first!
  String portName = Serial.list() [0];
  myPort = new Serial(this, portName, 115200);
}

void draw() {

  background(0);
  
  myPort.write(key);

  if (auto) {
    mode = "  AUTO ";
    word = "       "; 
  }
  else {
    mode = "MANUAL";
  }
  
  fill(255);
  textSize(40);
  textAlign(CENTER);
  text(mode, width/2, 40); 
  textAlign(CENTER);
  text(word, width/2, 80); 
}

void keyPressed() {
  if (key == ' ') {
      auto = !auto;
    } 
  
  if (!auto) {
    if (key == 'w') {
      word = "FORWARD";
    }
    else if (key == 'd') {
      word = " RIGHT ";
    }
    else if (key == 'a') {
      word = "  LEFT ";
    }
    else if (key == 's') {
      word = "REVERSE";
    }
    else {key = 'p';}
  }
}

void keyReleased() {
  key = 'p';
}

Comments

author
KyleP85 (author)2016-12-07

you seem to have left off the dimmensions and files for the sensor array mounting brackets.

author
boyles35 (author)KyleP852016-12-07

I uploaded the missing files, and for good measure, I uploaded a zip file containing all of the solidworks parts for the assembly. If you build one, let me know how it goes!

About This Instructable

7,452views

44favorites

License:

More by boyles35:Raspberry pi Controller Automatic Drink Dispensing Robotic BartenderLine Following Arduino RobotHow to tie a bowline
Add instructable to: