Unfortunately, none of our products qualify for the HST tax break. Go Team Canada!

Line Following Robot Car Using Arduino

Posted by Kelly M on

In this article, we will guide you on making a robot car that can follow a line. We will be using the parts that we carry in our shop. 

We will be using CAROBOT Motor Driver and Servo Shield to make connected Motors to Arduino UNO. The shield separate the Uno's PWM digital pins (3,5,6,9,10,11) and the non-PWM digital pins (2,4,7,8,12,13) for easy identification. Furthermore, it provides a 5V and GND rail for easy connections. The Motor and Shield shield is equipped with an L298N H-Bridge Motor Driver. This allows the shield to drive two DC motors. 

We will also be using the Arduino Mega R3 instead of the Uno board since we have to work with the encoders as well that uses the interrupt pins (pin 2 and 3 on Uno). 

Parts

Wiring Guide

Step 1: Assembling the Chassis and soldering the motors' wires together. We will solder both the positive (red) wires and the negative (black) wires on the same side together. 

The Chassis is assembled as so when we got it. 

Solder the same side's positive and negative wires together

Step 2: Put the Motor Shield on top of Arduino UNO 

Assembly Outcome Result 

Step 3: Wire up the Motor to Motor Shield L298N

Motor Shield A1 -- Motor 1 positive wire

Motor Shield A2 -- Motor 1 negative wire

Motor Shield B1 -- Motor 2 postive wire 

Motor Shield B2 -- Motor 2 negative wire 

Notice that the wires are hooked up in opposite ways where the red wires are right next to each other. This way, both sides of the motors will move at the same direction at the same time. 

Step 4: Write Code and check if the motors are going at the same direction.

The Motor driver uses the following pins: 

Noted that PWM refers to Pulse Width Modulation that regulates the motor speed and DIR refers to Direction input which is a logic- level signal that determines which direction the motor shaft will spin. 

PWM A --> Pin 3

PWM B --> Pin 11

DIR A -->  Pin 12

DIR B --> Pin 13

Code Explanation

We will set all the pin to HIGH, just to check if the motors are spinning in the same direction. If you want to skip the steps and jump to the Full Code

Step 1: Define the connection pins 

#define A 3
#define dirA 12
#define B 11
#define dirB 13

Step 2: Set the pins to be OUTPUT, so they will perform some actions from Arduino giving them power. 

void setup() {
  pinMode(A, OUTPUT);
  pinMode(dirA, OUTPUT);
  pinMode(B, OUTPUT);
  pinMode(dirB, OUTPUT);

}

Step 3: Set all the pins to be HIGH, this way the motors will start spinning forward. We can see if all the motors are moving at the same direction or not. 

void loop() {
      digitalWrite(A, HIGH);
      digitalWrite(dirA, HIGH);
      digitalWrite(B, HIGH);
      digitalWrite(dirB, HIGH);
}

Full Code

#define A 3
#define dirA 12
#define B 11
#define dirB 13

void setup() {
  pinMode(A, OUTPUT);
  pinMode(dirA, OUTPUT);
  pinMode(B, OUTPUT);
  pinMode(dirB, OUTPUT);

}

void loop() {
      digitalWrite(A, HIGH);
      digitalWrite(dirA, HIGH);
      digitalWrite(B, HIGH);
      digitalWrite(dirB, HIGH);
}

Output Result

The Motors should be spinning in the same direction. If they do not spin in the same direction, double check your wiring here or try switching the wires in opposite slot on the Motor Driver Shield. 

Now we can go ahead and work on how to control the directions of the motor. We will be writing codes that controls the car to move forward, backwards, turning right, and turning left, in both forward and backwards. If you want to skip the steps and jump to the Full Code

Step 1: Define the connection pins. 

#define A 3
#define dirA 12
#define B 11
#define dirB 13

Step 2: Set Up serial communication between our keyboard and Arduino. Then, set all the pins to be output, so that they will perform the actions after receiving input data from our keyboard. 

void setup() {
  Serial.begin(9600);
  pinMode(A, OUTPUT);
  pinMode(dirA, OUTPUT);
  pinMode(B, OUTPUT);
  pinMode(dirB, OUTPUT);

}

Step 3: If Serial Monitor is available, it will read our input. 

void loop() {
  if (Serial.available() > 0){
    int input = Serial.read();

Step 4: According to what we have input from our keyboard, Arduino will check and perform the specific action. For example, we input '1', and Arduino will check for case '1' and send the signal to the L298N and make them spin in the same direction, going forward. 

switch(input){
      case '1': 
      Serial.println("1: forward ");
      // high - high: forward 
      digitalWrite(A, HIGH);
      digitalWrite(dirA, HIGH);
      digitalWrite(B, HIGH);
      digitalWrite(dirB, HIGH);
      break;

      case '2':
      // high -- low: backwards
      Serial.println("2. backward ");
      digitalWrite(A, HIGH); 
      digitalWrite(dirA, LOW);
      digitalWrite(B, HIGH);
      digitalWrite(dirB, LOW);
      break;

      case '3':
      // turning right forward
      Serial.println("3. right forward ");
digitalWrite(A, HIGH);
digitalWrite(dirA, LOW);
digitalWrite(B, HIGH);
digitalWrite(dirB, HIGH);
break;

case '4':
// turning left forward
Serial.println("4. left forward ");
digitalWrite(A, HIGH);
digitalWrite(dirA, HIGH);
digitalWrite(B, HIGH);
digitalWrite(dirB, LOW);
break; case '5': // turning right bcakawrds digitalWrite(A, HIGH); digitalWrite(dirA, HIGH); digitalWrite(B, HIGH); digitalWrite(dirB, LOW); break; case '6': // turning left backwards digitalWrite(A, HIGH); digitalWrite(dirA, LOW); digitalWrite(B, HIGH); digitalWrite(dirB, HIGH); break;

Step 5: Add a case '0' to make everything stop moving. 

case'0':
      digitalWrite(A, LOW); 
      digitalWrite(dirA, LOW);
      digitalWrite(B, LOW);
      digitalWrite(dirB, LOW);
      break;
      
    }
  }
}

Full Code

#define A 3
#define dirA 12
#define B 11
#define dirB 13

void setup() {
  Serial.begin(9600);
  pinMode(A, OUTPUT);
  pinMode(dirA, OUTPUT);
  pinMode(B, OUTPUT);
  pinMode(dirB, OUTPUT);

}

void loop() {
  if (Serial.available() > 0){
    int input = Serial.read();
    switch(input){
      case '1': 
      Serial.println("1: forward ");
      // high - high: forward 
      digitalWrite(A, HIGH);
      digitalWrite(dirA, HIGH);
      digitalWrite(B, HIGH);
      digitalWrite(dirB, HIGH);
      break;

      case '2':
      // high -- low: backwards
      Serial.println("2. backward ");
      digitalWrite(A, HIGH); 
      digitalWrite(dirA, LOW);
      digitalWrite(B, HIGH);
      digitalWrite(dirB, LOW);
      break;

      case '3':
      // turning right forward
      Serial.println("3. right forward ");
digitalWrite(A, HIGH);
digitalWrite(dirA, LOW);
digitalWrite(B, HIGH);
digitalWrite(dirB, HIGH);
break;

case '4':
// turning left forward
Serial.println("4. left forward ");
digitalWrite(A, HIGH);
digitalWrite(dirA, HIGH);
digitalWrite(B, HIGH);
digitalWrite(dirB, LOW);
break; case '5': // turning right bcakawrds digitalWrite(A, HIGH); digitalWrite(dirA, HIGH); digitalWrite(B, HIGH); digitalWrite(dirB, LOW); break; case '6': // turning left backwards digitalWrite(A, HIGH); digitalWrite(dirA, LOW); digitalWrite(B, HIGH); digitalWrite(dirB, HIGH); break; case'0': digitalWrite(A, LOW); digitalWrite(dirA, LOW); digitalWrite(B, LOW); digitalWrite(dirB, LOW); break; } } }

Output

Going Forward

Serial Monitor Output 

 

Going Backward

Serial Monitor Output 

 

Going Right Forward

Serial Monitor Output 

Going Left Forward

Serial Monitor Output 

Going Right Backward

Serial Monitor Output 

Going Left Backward

Serial Monitor Output 

Stop 

Serial Monitor Output 

 

Combining with the Encoder Module

Now that we have coded the directions from Arduino and L298N Motor Driver. We shall start exploring the encoder and install it into the slotted disc of the motor. The encoder is used to sense the motor's position and velocity. It operates with 5V and produces a digital output within the range of 0V to 5V.

It will provide 5V output when the beam is blocked, and a 0V output when the beam is unblocked. Arduino can read the pulse train to determine how far the motor has travelled at what velocity. If you would like to know more about it, check this blog that walk you through the set up of the Encoder Module. 

Wiring Guide

Connect the wires with male to male jumper wires. 

Noted that each of the encoder comes with 3 female to female jumper wires to connect the pins. Please check this blog post to set up the slotted disc. 

Step 1: Check this blog post on set up slotted disc. 

Step 2: Place the encoders underneath the plastic piece. Make sure that the slotted disc is in between the encoder gap. (Same thing as the other side) 

 

Step 3: Connect the pins to Arduino with 3 female to male jumper wires. Noted that the pins on Arduino are different now since we are using a Motor Shield. Please follow this guide, and change your pinouts from this blog.

We will connect the Encoder Modules to pin 18 and pin 19, GND pin to the GND rail where the green pointer is, and VCC pin to 5V rail where the orange pointer is.  

 

Programming 

Now, we have the encoder modules installed to the slotted disc on the gear motor. We can calculate the rpm of the motor while it is spinning. If you want to know how to code the basic of the encoder, please check this blog for the code. Now, we know how to code the basics for the encoders, we shall implement the basics with the L298N code. If you want to skip all the steps, and jump to the Full Code

Step 1: We will install a library called TimerOne. Go to this link and download the latest library. Skip the steps on how to install the library on Arduino and jump to the code

Click on the zip file to download the latest version. 

 

Add to Arduino Library. Go to Sketch > Include Library > Add .ZIP Library.

A pop-up window will come out, choose TimerOne-r11 (or the latest version you have downloaded it) and click Choose at the bottom right corner. 

Now if we go to Sketch > Include Library > Contributed Libraries, we will be able to see TimerOne-r11 (or the latest version you downloaded) is there. 

Now, we will add the library on to the code. 

This library is used for the timer, so we can calculate the rpm every second. 

#include "TimerOne.h" 

Step 2: Define the connections. We connected Motor A and Encoder A to:

PWM A --> Pin 3

DIR A -->  Pin 12

Encoder A --> Pin 18

And Motor B and Encoder B to:

PWM B --> Pin 11

DIR B --> Pin 13

Encoder B --> Pin 19

#define A 3
#define dirA 12
#define B 11
#define dirB 13

#define encoderA 18
#define encoderB 19

Step 3: Create 2 variables for the counter, this is for both the motors. This variable is to store how many turns the wheel has spun in a certain time range. 

unsigned int countA = 0;
unsigned int countB = 0;

Step 4: Create a method for the counter. This increment method is used to increase +1 to the value every time when it is passed. 

void counterA(){ // couts from the speed sensor 
  countA++; // increase 1 onto the value 
}

void counterB(){
  countB++;
}

Step 5: Create a timer method. This timer method is used to enable and disable the timer, and calculate for the rpm of the motor. Repeat the calculation for Motor B. 

void timer(){
  Timer1.detachInterrupt(); // stop timer 
  Serial.print("MotorA Speed: ");
  int rotationA = (countA /20); 
  // divide by the number of slots in the slotted disc
  Serial.print(rotationA, DEC);
  Serial.print(" Rotation per seconds");
  countA = 0;
  
  Serial.print("\t MotorB Speed: ");
  int rotationB = (countB /20); 
  // divide by the number of slots in the slotted disc
  Serial.print(rotationB, DEC);
  Serial.println(" Rotation per seconds");
  countB = 0; // reset value to 0
  
  Timer1.attachInterrupt(timer); // enable the timer
}

Step 6: Set up serial communication between Arduino and the encoder, so the encoder can send the signals to Arduino. And from there, we will be able to read how fast the motor is going. Also, set the motor as OUTPUT, so Arduino will give current to it and start spinning. 

void setup(){
  Serial.begin(9600);
  pinMode(A, OUTPUT);
  pinMode(dirA, OUTPUT);
  pinMode(B, OUTPUT);
  pinMode(dirB, OUTPUT);

Step 7: Set the timer to 1 second, and interrupt the encoder where it will go to counter method every time when it is HIGH.

  Timer1.initialize(1000000); // set timer for 1 second
  attachInterrupt(digitalPinToInterrupt(encoderA), counter, RISING);  
attachInterrupt
(digitalPinToInterrupt(encoderB), counterB, RISING);

Step 8: Enable the timer and get ready to receive the next data from the encoder ! 

  // increase count when the sensor pin goes HIGH
  Timer1.attachInterrupt(timer); // enable the timer
}

Step 9: Lastly, make the motor to start spinning to calculate the rpm of it. We did analogWrite(A, 255) to give the motor maximum power and let it spin. 

void loop (){
  analogWrite(A, 255);
  digitalWrite(A, HIGH);

  analogWrite(B, 255);
  digitalWrite(B, HIGH);
}

Full Code

#include "TimerOne.h" 

#define A 3
#define dirA 12
#define B 11
#define dirB 13

#define encoderA 18
#define encoderB 19

unsigned int countA = 0;
unsigned int countB = 0;

void counterA(){ // couts from the speed sensor 
  countA++; // increase 1 onto the value 
}

void counterB(){
  countB++;
}

void timer(){
  Timer1.detachInterrupt(); // stop timer 
  Serial.print("MotorA Speed: ");
  int rotationA = (countA /20); 
  // divide by the number of slots in the slotted disc
  Serial.print(rotationA, DEC);
  Serial.print(" Rotation per seconds");
  countA = 0;
  
  Serial.print("\t MotorB Speed: ");
  int rotationB = (countB /20); 
  // divide by the number of slots in the slotted disc
  Serial.print(rotationB, DEC);
  Serial.println(" Rotation per seconds");
  countB = 0; // reset value to 0
  
  Timer1.attachInterrupt(timer); // enable the timer
}

void setup(){
  Serial.begin(9600);
  pinMode(A, OUTPUT);
  pinMode(dirA, OUTPUT);
  pinMode(B, OUTPUT);
  pinMode(dirB, OUTPUT);

  Timer1.initialize(1000000); // set timer for 1 second
  attachInterrupt(digitalPinToInterrupt(encoderA), counterA, RISING);
  attachInterrupt(digitalPinToInterrupt(encoderB), counterB, RISING);
  // increase count when the sensor pin goes HIGH
  Timer1.attachInterrupt(timer); // enable the timer
}

void loop (){
  analogWrite(A, 255);
  digitalWrite(A, HIGH);

  analogWrite(B, 255);
  digitalWrite(B, HIGH);
}

Serial Monitor Output

Adding with Photodiode Modules 

Now, we have explored the encoders and the motors working together. We shall go ahead and work on the light sensors, so we can create a robot car that follows a line. 

We will be using the TCRT5000 Infrared sensor module that is commonly used for robot applications like a line following robot car. The infrared module operates at 3V to 5V DC and produces a digital output. There are two different types of infrared modules in the market where one can produce analog output and the other cannot. We will be using the E2A3 infrared module (the one with no analog output), the one we carry in our shop. If you would like to know more about this module and learn the basics, check this blog that will walk you through the set up of the infrared module. 

Wiring

The module consists of 3 output pins that connect the TCRT5000 module to a microcontroller, like Arduino. 

  • GND: Ground pin to connect the tracking sensor to the ground with the microcontroller
  • VCC: Power pin for 3.3V to 5V operation voltage with the microcontroller 
  • D0: Digital output pin based on a predefined threshold through the potentiometer and the operation voltage of the microcontroller. 

Wiring Guide 

Step 1: Connect the pins with female to female jumper wires. 

Step 2: Connect the module to the motor shield that is connected to the Arduino. 

Noted that the colours of the jumper wires can be different, it is up to you. 

Yellow wire (S) -- Motor Shield A0 pin 

RED wire (VCC) -- Motor Shield 5V pin 

GREEN wire (G) -- Motor Shield GND pin

Step 3: Use screw to install the module onto the chassis. 

Try to place it in the centre of the chassis. 

 

Step 4: Repeat the steps for the other module, and screw it onto the chassis. 

The two modules should be around 4cm wide (around an electrical tape width)

Programming 

Now, we have the infrared sensor module installed to the chassis. We can make the robot car to follow a line. If you want to know how to code the basic of the module, please check this blog for the code. Now, we know how to code the basics for the infrared module, we shall implement the basics with the encoders and the L298N code. If you want to skip all the skips, and jump to the Full Code

How it works

It basically follows the Z pattern turning towards the line and turning away from the line. 

1. When the infrared sensor sees the black line, one side of the wheels rotate and turn away from the line. 

2. When the infrared sensor sees the white background the other side of the wheels rotate and turns slightly towards the line. 

Noted that we are just adding more codes into the previous ones. 

Step 1: Define all the connection pins. We added the sensor pins into the previous code.

#define sensor A0

Step 2: Create a variable for the sensor to store its state. 

int state = 0;

Step 3: Set the sensor as an input, so it will receive the data and then send to Arduino. 

 pinMode(sensor, INPUT);

Step 4: Read the input data from the sensor

state = digitalRead(sensor);

Step 5: If the sensor is triggered (HIGH), it means it is on the line and we will want the wheels to turn left. 

if (state == HIGH){
    digitalWrite(A, HIGH);
    digitalWrite(dirA, LOW);
    digitalWrite(B, LOW);
    digitalWrite(dirB, LOW);
  }

Step 6: If the sensor is not triggered (LOW), it means it is not on the line and we want the wheels to turn right, and go back to the line. 

else{
    digitalWrite(A, LOW);
    digitalWrite(dirA, LOW);
    digitalWrite(B, HIGH);
    digitalWrite(dirB, LOW);
  }

Full Code

#include "TimerOne.h"

#define A 3
#define dirA 12
#define B 11
#define dirB 13

#define encoderA 18
#define encoderB 19

#define sensor A0
int state = 0;

unsigned int countA = 0;
unsigned int countB = 0;


void counterA(){ // couts from the speed sensor 
  countA++; // increase 1 onto the value 
}

void counterB(){
  countB++;
}
void timer(){
  Timer1.detachInterrupt(); // stop timer 
  Serial.print("MotorA Speed: ");
  int rotationA = (countA /20); 
  // divide by the number of slots in the slotted disc
  Serial.print(rotationA, DEC);
  Serial.print(" Rotation per seconds");
  countA = 0;
  
  Serial.print("\t MotorB Speed: ");
  int rotationB = (countB /20); 
  // divide by the number of slots in the slotted disc
  Serial.print(rotationB, DEC);
  Serial.println(" Rotation per seconds");
  countB = 0; // reset value to 0
  
  Timer1.attachInterrupt(timer); // enable the timer
}

void setup(){
  Serial.begin(9600);
  pinMode(A, OUTPUT);
  pinMode(dirA, OUTPUT);
  pinMode(B, OUTPUT);
  pinMode(dirB, OUTPUT);
  pinMode(sensor, INPUT);

  Timer1.initialize(1000000); // set timer for 1 second
  attachInterrupt(digitalPinToInterrupt(encoderA), counterA, RISING);
  attachInterrupt(digitalPinToInterrupt(encoderB), counterB, RISING);
  // increase count when the sensor pin goes HIGH
  Timer1.attachInterrupt(timer); // enable the timer
}

void loop (){
  state = digitalRead(sensor);
  if (state == HIGH){
    digitalWrite(A, HIGH);
    digitalWrite(dirA, LOW);
    digitalWrite(B, LOW);
    digitalWrite(dirB, LOW);
  }
  else{
    digitalWrite(A, LOW);
    digitalWrite(dirA, LOW);
    digitalWrite(B, HIGH);
    digitalWrite(dirB, LOW);
  }
}

Output

The robot car will move by following the line.  

 


Share this post



← Older Post Newer Post →


Leave a comment

Please note, comments must be approved before they are published.