I still remember the first line following robot I built 16 years ago when I participated in a line following robot competition in my campus. That was an era where no facebook, no smartphone, no Arduino, and no open-source hardware. That was an era where me and my friends had to build everything from scratch. Without Arduino and the ready-to-use sensor modules, we need to spend a few weeks to put all components together into a working circuit (by hand solder) before we can work on the programming part.
Thanks to the open-source hardware modules that allowed us to build a robot so much cheaper and easier now. However, there is still some critical knowledge about an autonomous mobile robot that every beginner needs to know before you can start building one using the off-the-shelf modules.
One of the main reasons of writing this post is because we have seen many articles written on the similar topic but most of them only provide sample code without guiding the beginners to understand the line follow algorithm in phases. I went through the sample code line by line but I still do not understand why it is written in that way and hence I am unable to create my own code from scratch. This problem is significant especially for educators who want to teach line following lessons in the classroom.
Parts SelectionSince this post is aiming to teach beginners to build an easy to build and low cost robot, the parts selected will be based on these guidelines. We will be using these main parts in this project. If you want to learn more about how to select the right parts, I would strongly suggest you to read this article.
Prepare the motors. It's recommended to buy motors which come with wires pre-soldered to eliminate these steps.
Solder wires to the motors. If you are total beginner, I encourage you to match closely the wire colors and motor terminals as shown in the last picture below (press > to view pictures). Otherwise, it's totally okay to solder any color you wish. We'll look into the polarity when we wire them up to Maker Drive motor driver module later.
Tighten 4x 5mm copper spacer to Maker Drive module.
Mount Maker Drive module to robot chassis. We'll only use two screws as indicated in the pictures below.
Mount motors to robot chassis. Please note that the side with a small stud or protrusion on the motor should touch the chassis.
Now connect the wires from motors to Maker Drive module.
Install the wheels. Take note on the shape of the motor shaft and the hole shape on the wheel.
Connect 4x AA batteries holder to Maker Drive board. Mind the polarities - Red wire to VB+ and black wire to VB-.
Insert the AA batteries to the holder.
Turn on the switch on the battery holder and press A buttons (M1A & M2A) on Maker Drive. Make sure that the motors are moving forward as indicated by the arrows in the pictures below. If any of the motor is moving in reverse direction, you may swap the corresponding wires and test again (see next step). The purpose of setting this rule is so that it's easier for us to work on the same coding later. You may omit this if you prefer to swap at Arduino I/O pins or swap in your code.
Eg: When M1A button is pressed, the motor connected to it is moving backward. You may swap the motor wires connected to M1A and M1B connectors. This should change its direction forward.
Here I use Velcro tapes to stick the AA batteries holder to the chassis as it's easier to remove the holder for changing batteries in the future.
Now let's install Maker Line, line follow sensor module. First, fasten the shorter copper spacer at the chassis
Then follow by the longer copper spacers.
Take out 7 pcs of Male to Female Jumper Wires and connect to Maker Line as shown below.
Take out another 5 pcs of Male to Female Jumper Wires. Connect them to Maker Drive module as shown below.
After that we can proceed to mount the brain of the robot - Maker UNO. First, fix 3x15mm copper spacer to robot chassis. Please take note of the location of the holes.
Hold Maker UNO in place (right above Maker Drive) with 3x screws.
Stick the mini breadboard at the front of the robot.
This mini breadboard has altogether 170 points/holes for connection. Referring to the picture below, all 5 points within a row have electrical connections between them. However, each of the row is NOT electrically connected to other rows nearby and rows in section A are NOT electrically connected to rows in section B too.
Now let's start wiring up the robot!
Connect the two wires at the middle from Maker Drive module (5VO & GND) to the breadboard. Then connect Maker Line's VCC next to Maker Drive's 5VO point and Maker Line's GND to Maker Drive's GND:
- 5VO (Maker Drive) --- Breadboard --- VCC (Maker Line)
- GND (Maker Drive)---- Breadboard --- GND (Maker Line)
Connect the wires from Maker Drive to Maker UNO:
- M1A (Maker Drive) --- pin 3 (Maker UNO)
- M1B (Maker Drive) --- pin 9 (Maker UNO)
- M2A (Maker Drive) --- pin 10 (Maker UNO)
- M2B (Maker Drive) --- pin 11 (Maker UNO)
Connect the wires from Maker Line to Maker UNO:
- D1 (Maker Line) --- pin 4 (Maker UNO)
- D2 (Maker Line) --- pin 5 (Maker UNO)
- D3(Maker Line) --- pin 6 (Maker UNO)
- D4 (Maker Line) --- pin 7 (Maker UNO)
- D5 (Maker Line) --- pin 12 (Maker UNO)
Then use 2x Male to Make Jumper Wire to connect 5V and GND pins on Maker UNO to 5V and GND on the breadboard.
And that's it! The hardware setup and wiring are done!
Now that we have set up the hardware and electronics for the robot, we will proceed to learn coding using Arduino IDE.
Coding1. Installing Arduino IDE & USB Driver
You need to follow this post if this is your first time using Arduino UNO compatible board (using CH340 USB-Serial chip). In short, you need to install Arduino IDE and USB driver on your PC.
2. Let It Move
In this part, we will be learning on how to code the robot to move forward, reverse, turn left and turn right.
First of all, launch the Arduino IDE that you have installed just now.
Copy & paste these code into your Arduino IDE.
#include "CytronMotorDriver.h" //Include "Cytron Motor Library"
// Configure the motor driver.
CytronMD motorL(PWM_PWM, 3, 9); // Left motor, M1A = Pin 3, M1B = Pin 9.
CytronMD motorR(PWM_PWM, 10, 11); // Right motor, M2A = Pin 10, M2B = Pin 11.
void setup() {
}
void loop() {
motorL.setSpeed(128); // Left motor moves forward at 50% speed.
motorR.setSpeed(128); // Right motor moves forward at 50% speed.
delay(3000); // delay for 3 seconds.
motorL.setSpeed(0); // Left motor stops.
motorR.setSpeed(0); // Right motor stops.
delay(1000); // delay for 1 second.
}
To include the Cytron motor driver's library, go to Tools > Manage Libraries...
Search for "Cytron Motor Drivers Library", then click 'Install".
Upload the program to your Arduino board. Click the "upload" button located at the top bar of the Arduino IDE.
Make sure your have connected the USB cable on the PC and your board. You will see "Done uploading" appear at the bottom when it is done.
Unplug the USB cable from the board. Turn on the battery and your robot should be moving forward for 3 seconds before it stop for 1 seconds then it will repeat again.
Note: If your robot is not moving forward but moving backward or turning instead, you can either swap the corresponding wires between Maker UNO and Maker Drive or change the pins initialization in your code.
Eg: If the left motor is turning backward, swap these wires to:
- M1A --- pin 9
- M1B --- pin 3
OR edit the code to:
// Configure the motor driver.
CytronMD motorL(PWM_PWM, 9, 3); // motor 1 = Left motor, M1A = Pin 9, M1B = Pin 3.
CytronMD motorR(PWM_PWM, 10, 11); // motor 2= right motor, M2A = Pin 10, M2B = Pin 11.
Note: Before we proceed to our second program, it is good to know the Arduino filling system. Each Arduino program that you save, it will be saved under a new folder. No two programs will be allowed in 1 folder. So it is a good practice if we can create a parents folder for our project so that all programs (folder) of the same project can be saved under the same parents folder.
Once the motors direction is correct, we will try to code the robot to move backward. Load this program into your robot.
#include "CytronMotorDriver.h" //Include "Cytron Motor Library"
// Configure the motor driver.
CytronMD motorL(PWM_PWM, 3, 9); // Left motor, M1A = Pin 3, M1B = Pin 9.
CytronMD motorR(PWM_PWM, 10, 11); // Right motor, M2A = Pin 10, M2B = Pin 11.
void setup() {
}
void loop() {
motorL.setSpeed(-128); // Left motor moves backward at 50% speed.
motorR.setSpeed(-128); // Right motor moves backward at 50% speed.
delay(3000); // delay for 3 seconds.
motorL.setSpeed(0); // Left motor stops.
motorR.setSpeed(0); // Right motor stops.
delay(1000); // delay for 1 second.
}
You will find your robot moving backward once the program is loaded to the board with the battery power turned on.
Next, we will try with turning left and right.
#include "CytronMotorDriver.h" //Include "Cytron Motor Library"
// Configure the motor driver.
CytronMD motorL(PWM_PWM, 3, 9); // Left motor, M1A = Pin 3, M1B = Pin 9.
CytronMD motorR(PWM_PWM, 10, 11); // Right motor, M2A = Pin 10, M2B = Pin 11.
void setup() {
}
void loop() {
// Turn Right
motorL.setSpeed(128); // Left motor moves forward at 50% speed.
motorR.setSpeed(-128); // Right motor moves backward at 50% speed.
delay(1000); // delay for 1 seconds.
// Stop
motorL.setSpeed(0); // Left motor stops.
motorR.setSpeed(0); // Right motor stops.
delay(500); // delay for 0.5 second.
// Turn Left
motorL.setSpeed(-128); // Left motor moves backward at 50% speed.
motorR.setSpeed(128); // Right motor moves forward at 50% speed.
delay(1000); // delay for 1 seconds.
// Stop
motorL.setSpeed(0); // Left motor stops.
motorR.setSpeed(0); // Right motor stops.
delay(500); // delay for 0.5 second.
}
The robot should turn to the right first, stop for half a second, then turn to the left, stop for half a second and continues.
Next, we are going to learn how to change the robot speed. It is very easy actually because we are using the PWM function. 255 is the maximum speed (fastest) and 0 is the minimum speed (it will completely stop). That's why we put motorL.setSpeed(0) in the previous program to command the robot to stop. So, if we just want to slow down the robot, we can put any smaller number but not zero. You can try to load this code to see how the speed varies while the robot is moving.
#include "CytronMotorDriver.h" //Include "Cytron Motor Library"
// Configure the motor driver.
CytronMD motor1(PWM_PWM, 3, 9); // motor 1 = Left motor, M1A = Pin 3, M1B = Pin 9.
CytronMD motor2(PWM_PWM, 10, 11); // motor 2= right motor, M2A = Pin 10, M2B = Pin 11.
void setup() {
}
void loop() {
motor1.setSpeed(128); // Motor 1 runs forward at 50% speed.
motor2.setSpeed(128); // Motor 2 runs backward at 50% speed.
delay(1000); // For 1s
motor1.setSpeed(255); // Motor 1 runs backward at full speed.
motor2.setSpeed(255); // Motor 2 runs backward at full speed.
delay(1000); // For 1s
motor1.setSpeed(80); // Motor 1 runs backward at full speed.
motor2.setSpeed(80); // Motor 2 runs backward at full speed.
delay(2000); // For 2s
motor1.setSpeed(0); // Motor 1 stops.
motor2.setSpeed(0); // Motor 2 stops.
delay(2000); //For 2s
}
You will observe the robot moves at a speed, becomes faster, changes to slower speed, then stops and repeat.
For a differential drive's mobile robot, it is important to know that we have various turning patterns. When both wheels spin in the same direction at the same speed, the robot will move almost in a straight line; either forward or backward.
If both wheels spin in the opposite direction at the same speed, the robot will rotate left or right about the central point of the wheels axis.
When one of the wheels stops while the other side spins, the robot will steer towards the side of the wheel that stops, with the center of rotation on the stopping wheel.
If both of the wheels spin in the same direction at different speeds, the robot veer towards the side of the wheel that is spinning at a slower speed, with the center of rotation outside of the robot base. The nearer the center of rotation to the robot, the sharper the robot turns.
Load this program to try it out.
Note: You need to press the on-board push button to run the program.
#include "CytronMotorDriver.h" //Include "Cytron Motor Library"
// Configure the motor driver.
CytronMD motor1(PWM_PWM, 3, 9); // motor 1 = Left motor, M1A = Pin 3, M1B = Pin 9.
CytronMD motor2(PWM_PWM, 10, 11); // motor 2= right motor, M2A = Pin 10, M2B = Pin 11.
void setup() {
pinMode(2,INPUT_PULLUP); //Define Pin 2 (connected to the on-board pushbutton) as input
}
void loop() {
while(digitalRead(2)==HIGH);
while(digitalRead(2)==LOW); //Wait for user to press the on-board pushbutton
//robot's turning point at the center
motor1.setSpeed(128); // Motor 1 runs forward at 50% speed.
motor2.setSpeed(-128); // Motor 2 runs backward at 50% speed.
delay(1000);
motor1.setSpeed(0); // Motor 1 stops
motor2.setSpeed(0); // Motor 2 stops
while(digitalRead(2)==HIGH);
while(digitalRead(2)==LOW); //Wait for user to press the on-board pushbutton
//robot's turning point at the wheel
motor1.setSpeed(255); // Motor 1 runs forward at full speed.
motor2.setSpeed(0); // Motor 2 stops
delay(1000);
motor1.setSpeed(0); // Motor 1 stops
motor2.setSpeed(0); // Motor 2 stops
while(digitalRead(2)==HIGH);
while(digitalRead(2)==LOW); //Wait for user to press the on-board pushbutton
//robot move forward
motor1.setSpeed(128); // Motor 1 runs forward at 50% speed.
motor2.setSpeed(128); // Motor 2 runs forward at 50% speed.
delay(1000);
//robot move slightly to the right
motor1.setSpeed(178); // Motor 1 runs forward at 70% speed
motor2.setSpeed(128); // Motor 2 runs forward at 50% speed
delay(1000);
//robot move slightly to the left
motor1.setSpeed(128); // Motor 1 runs forward at 50% speed
motor2.setSpeed(178); // Motor 2 runs forward at 70% speed
delay(1000);
motor1.setSpeed(0); // Motor 1 stops
motor2.setSpeed(0); // Motor 2 stops
}
(SC: video)
3. The Concept of Line Following
The concept is very simple. You have an array of infrared (IR) sensors that able to differentiate bright color (white or grey) surface and dark color (black or brown) surface. We will use black line and bright color surface in this tutorial.
Let's start with a simple example. Look at the figures below. If you have 2 IR sensors on your robot. If the Ieft IR detects the black line, it tells us that our robot is now on the right side of the line. If right IR detects the black line, it is clearly shows that our robot is now sit at the left side of the line.
Easy right? But what happen if both IRs detect no line? It could have two possibilities. Either the robot is in the middle of the line or it is totally off the track.
Let's add an IR in between the left IR and the right IR. If left IR detects the line, your robot is at the right position; If the right IR detects the line, robot is on the left; If the center IR detects the line, it tells us the robot is at the center of the line. It makes more sense now.
What happen if the line is thiner and it sits in between left IR and the mid IR between the mid IR and the right IR? We probably need to add 2 more IR sensors between them.
Even if the line thickness remains same, we also can consider adding more sensors to increase the accuracy of the robot position on the line. In short, the minimum requirement for a robot to follow line is 2 IR sensors but you can add more sensors to make your robot 'smarter' so that it can run faster. We have seen a 32x sensors array on one robot before. It can move lightning fast especially on a track with many sharp curves.
In this project, we will be using the 5x sensor array which I think sufficient to serve our objective.
4. The Line Following Sensor Setup and Calibration
Next, I need to draw your attention into the line following sensor that we are going to use- Maker Line. The advantage of this sensor is the easiness in calibration and the selection of dark on-light on feature.
Before calibration, we need to get our line track ready. There is a few ways of doing this. But what we usually did was getting some vinyl electrical tape from the hardware shop then tape in on a bright color floor at your home or on a big white board. You can tape the track to look like the F1 track.
Note: To follow the sample codes that we are going to use later, it is advised that the width of the black line is 1.3cm-1.8cm.
Next, we need to ensure our sensor is mounted firmly on the robot and the distance from the ground to the sensor is fixed. Press the calibration button for more than 2 seconds. The LEDs will keep blinking when it is in the calibration mode. Swing your robot left and right, front and back on top of the track you prepared earlier. Press the same button again and you are done.
There is a slide switch on the sensor that allows you to choose whether you are using the black line or the white line. For my case, I'm using the black line, so I slide the switch to the 'dark' side.
Line Following Robot
It is the time to combine what we have learned earlier to command the robot to follow the line.
Figure below gives us a clear picture of how the sensor's signals indicate the robot position and what should the robot does. There are 5 conditions.
From there we can utilise "if-else" function statement to develop a simple line following robot program.
#include "CytronMotorDriver.h" //Include "Cytron Motor Library"
// Maker Line Sensor Pin Connection
#define LINE_D1 4
#define LINE_D2 5
#define LINE_D3 6
#define LINE_D4 7
#define LINE_D5 12
// Configure the motor driver.
CytronMD motorL(PWM_PWM, 3, 9); // motor 1 = Left motor, M1A = Pin 3, M1B = Pin 9.
CytronMD motorR(PWM_PWM, 10, 11); // motor 2= right motor, M2A = Pin 10, M2B = Pin 11.
void setup() {
pinMode(LINE_D1, INPUT);
pinMode(LINE_D2, INPUT);
pinMode(LINE_D3, INPUT);
pinMode(LINE_D4, INPUT);
pinMode(LINE_D5, INPUT);
pinMode(2,INPUT_PULLUP); //Define Pin 2 (connected to the on-board pushbutton) as input
while(digitalRead(2)==HIGH);
while(digitalRead(2)==LOW); //wait for user to press the on-board pushbutton
}
void loop() {
// Perform line following
int D1 = digitalRead(LINE_D1);
int D2 = digitalRead(LINE_D2);
int D3 = digitalRead(LINE_D3);
int D4 = digitalRead(LINE_D4);
int D5 = digitalRead(LINE_D5);
if (D1==0 && D2==0 && D3==1 && D4==0 && D5==0) {
motorL.setSpeed(100);
motorR.setSpeed(100); //robot move forward
}
else if (D1==0 && D2==1 && D3==0 && D4==0 && D5==0) {
motorL.setSpeed(50);
motorR.setSpeed(100); //move to left
}
else if (D1==1 && D2==0 && D3==0 && D4==0 && D5==0) {
motorL.setSpeed(0);
motorR.setSpeed(100); //robot slowing down, move more to left
}
else if (D1==0 && D2==0 && D3==0 && D4==1 && D5==0) {
motorL.setSpeed(100);
motorR.setSpeed(50); //move to right
}
else if (D1==0 && D2==0 && D3==0 && D4==0 && D5==1) {
motorL.setSpeed(100);
motorR.setSpeed(0); //robot slowing down, move more to right
}
else {
}
}
So this is the most basic way to program a mobile robot to follow line using the Maker Line. But if we want a seamless line following robot, we can also try to add more conditions from the line sensor's signals.
#include "CytronMotorDriver.h" //Include "Cytron Motor Library"
// Maker Line Sensor Pin Connection
#define LINE_D1 4
#define LINE_D2 5
#define LINE_D3 6
#define LINE_D4 7
#define LINE_D5 12
// Configure the motor driver.
CytronMD motorL(PWM_PWM, 3, 9); // motor 1 = Left motor, M1A = Pin 3, M1B = Pin 9.
CytronMD motorR(PWM_PWM, 10, 11); // motor 2= right motor, M2A = Pin 10, M2B = Pin 11.
void setup() {
pinMode(LINE_D1, INPUT);
pinMode(LINE_D2, INPUT);
pinMode(LINE_D3, INPUT);
pinMode(LINE_D4, INPUT);
pinMode(LINE_D5, INPUT);
pinMode(2,INPUT_PULLUP); //Define Pin 2 (connected to the on-board pushbutton) as input
while(digitalRead(2)==HIGH);
while(digitalRead(2)==LOW); //wait for user to press the on-board pushbutton
}
void loop() {
// Perform line following
int D1 = digitalRead(LINE_D1);
int D2 = digitalRead(LINE_D2);
int D3 = digitalRead(LINE_D3);
int D4 = digitalRead(LINE_D4);
int D5 = digitalRead(LINE_D5);
if (D1==0 && D2==0 && D3==1 && D4==0 && D5==0) {
motorL.setSpeed(100);
motorR.setSpeed(100); //robot move forward
}
else if (D1==0 && D2==1 && D3==1 && D4==0 && D5==0) {
motorL.setSpeed(80);
motorR.setSpeed(100); //robot move slightly left
}
else if (D1==0 && D2==1 && D3==0 && D4==0 && D5==0) {
motorL.setSpeed(60);
motorR.setSpeed(100); //robot move slightly left
}
else if (D1==1 && D2==1 && D3==0 && D4==0 && D5==0) {
motorL.setSpeed(30);
motorR.setSpeed(100); //robot slowing down, move more to left
}
else if (D1==1 && D2==0 && D3==0 && D4==0 && D5==0) {
motorL.setSpeed(0);
motorR.setSpeed(100); //robot slowing down, move more to left
}
else if (D1==0 && D2==0 && D3==1 && D4==1 && D5==0) {
motorL.setSpeed(100);
motorR.setSpeed(80); //robot move slightly right
}
else if (D1==0 && D2==0 && D3==0 && D4==1 && D5==0) {
motorL.setSpeed(100);
motorR.setSpeed(60); //robot move slightly right
}
else if (D1==0 && D2==0 && D3==0 && D4==1 && D5==1) {
motorL.setSpeed(100);
motorR.setSpeed(30); //robot slowing down, move more to right
}
else if (D1==0 && D2==0 && D3==0 && D4==0 && D5==1) {
motorL.setSpeed(100);
motorR.setSpeed(0); //robot slowing down, move more to right
}
else {
}
}
You can tune the speed for the motors to make it run faster and smoother. You also can challenge yourself to try on the grid maze while waiting for our solution that we will be sharing on part II.
If you are using Maker UNO or Maker UNO Plus as the robot main controller, you can try load to this program where we have added a line of code to command the robot to stop and buzz when it detects a cross junction.
#include "CytronMotorDriver.h" //Include "Cytron Motor Library"
// Maker Line Sensor Pin Connection
#define LINE_D1 4
#define LINE_D2 5
#define LINE_D3 6
#define LINE_D4 7
#define LINE_D5 12
// Configure the motor driver.
CytronMD motorL(PWM_PWM, 3, 9); // motor 1 = Left motor, M1A = Pin 3, M1B = Pin 9.
CytronMD motorR(PWM_PWM, 10, 11); // motor 2= right motor, M2A = Pin 10, M2B = Pin 11.
void setup() {
pinMode(8, OUTPUT); //Define Pin 2 (on-board piezo buzzer) as output
pinMode(LINE_D1, INPUT);
pinMode(LINE_D2, INPUT);
pinMode(LINE_D3, INPUT);
pinMode(LINE_D4, INPUT);
pinMode(LINE_D5, INPUT);
pinMode(2,INPUT_PULLUP); //Define Pin 2 (connected to the on-board pushbutton) as input
while(digitalRead(2)==HIGH);
while(digitalRead(2)==LOW); //wait for user to press the on-board pushbutton
}
void loop() {
// Perform line following
int D1 = digitalRead(LINE_D1);
int D2 = digitalRead(LINE_D2);
int D3 = digitalRead(LINE_D3);
int D4 = digitalRead(LINE_D4);
int D5 = digitalRead(LINE_D5);
if (D1==0 && D2==0 && D3==1 && D4==0 && D5==0) {
motorL.setSpeed(100);
motorR.setSpeed(100); //robot move forward
}
else if (D1==0 && D2==1 && D3==1 && D4==0 && D5==0) {
motorL.setSpeed(80);
motorR.setSpeed(100); //robot move slightly left
}
else if (D1==0 && D2==1 && D3==0 && D4==0 && D5==0) {
motorL.setSpeed(60);
motorR.setSpeed(100); //robot move slightly left
}
else if (D1==1 && D2==1 && D3==0 && D4==0 && D5==0) {
motorL.setSpeed(30);
motorR.setSpeed(100); //robot slowing down, move more to left
}
else if (D1==1 && D2==0 && D3==0 && D4==0 && D5==0) {
motorL.setSpeed(0);
motorR.setSpeed(100); //robot slowing down, move more to left
}
else if (D1==0 && D2==0 && D3==1 && D4==1 && D5==0) {
motorL.setSpeed(100);
motorR.setSpeed(80); //robot move slightly right
}
else if (D1==0 && D2==0 && D3==0 && D4==1 && D5==0) {
motorL.setSpeed(100);
motorR.setSpeed(60); //robot move slightly right
}
else if (D1==0 && D2==0 && D3==0 && D4==1 && D5==1) {
motorL.setSpeed(100);
motorR.setSpeed(30); //robot slowing down, move more to right
}
else if (D1==0 && D2==0 && D3==0 && D4==0 && D5==1) {
motorL.setSpeed(100);
motorR.setSpeed(0); //robot slowing down, move more to right
}
else if (D1==1 && D2==1 && D3==1 && D4==1 && D5==1) {
motorL.setSpeed(0);
motorR.setSpeed(0); //robot completely stop
tone (8,392,600);
delay (800); //buzz
}
else {
}
}
Comments