Help with PID line follower Robot code

Good afternoon everyone.

I am developing a line follower robot with a PID algorythm. I stopped working on the robot in early December, and resumed the project last week. The code I used made the robot complete a circle, however, it was quite slow. Furthermore, when I was testing it out today, I couldn't get it to do the makeshift course I made at home, and the actual course from the robtics club. It would either only turn on one wheel, or not move entirely. The code I used was adapted from a website whoose name I cannot recall, but I'm thankfull for the people over there for the help, as it is hard to do these things with my age.

The code is the following:

// Motor and sensor pins
#define engineR 11
#define inputR1 8
#define inputR2 9
#define engineL 13
#define inputL1 12
#define inputL2 10

// Sensor pins
#define sensorRR A0
#define sensorR A1
#define sensorRM A2
#define sensorLM A3
#define sensorL A4
#define sensorLL A5

QTRSensors qtr;
const int sensorCount = 6;
const int sensorPins[] = {A0, A1, A2, A3, A4, A5};
uint16_t sensorValues[sensorCount];

int P, D, I, previousError, PIDvalue, error;
int lsp, rsp, x;
int lfspeed = 400;

float Kp = 1.6;  // Adjust this value based on experimentation
float Kd = 0.1;  // Adjust this value based on experimentation
float Ki = 0.01; // Adjust this value based on experimentation

void setup() {
  // Sensor setup
  qtr.setTypeAnalog();
  qtr.setSensorPins((const uint8_t[]){sensorRR, sensorR, sensorRM, sensorLM, sensorL, sensorLL}, sensorCount);
  qtr.setEmitterPin(2);

  // Calibration
  delay(500);
  for (uint16_t i = 0; i < 400; i++) {
    qtr.calibrate();
  }

  // Motor and input pin setup
  pinMode(engineR, OUTPUT);
  pinMode(inputR1, OUTPUT);
  pinMode(inputR2, OUTPUT);

  pinMode(engineL, OUTPUT);
  pinMode(inputL1, OUTPUT);
  pinMode(inputL2, OUTPUT);

  Serial.begin(9600);
}

void loop() {
  linefollow();
  
}

void linefollow() {
  int error = analogRead(sensorL) - analogRead(sensorR);
  P = error;
  I = I + error;
  D = error - previousError;

  PIDvalue = (Kp * P) + (Ki * I) + (Kd * D);
  previousError = error;

  lsp = lfspeed - PIDvalue;
  rsp = lfspeed + PIDvalue;

  if (lsp > 255) {
    lsp = 255;
  }
  if (lsp < 0) {
    lsp = 0;
  }
  if (rsp > 255) {
    rsp = 255;
  }
  if (rsp < 0) {
    rsp = 0;
  }


  Serial.println(rsp);
  Serial.println(lsp);
  digitalWrite(inputL1, HIGH);
  digitalWrite(inputL2, LOW);
  digitalWrite(inputR1, HIGH);
  digitalWrite(inputR2, LOW);
  x = rsp/1,49;
  analogWrite(engineR, x);
  analogWrite(engineL, lsp);
 
}

I used an Arduino Mega, an L298n motor driver, a QTR8 sensor arry and two I belive 9 V batteries. The QTR Sensors library will be necessary.

I pray that the good samaritans of this forum can help help me, and I wish the best for their families.

Salutations, Robert

If you mean the PP3 block batteries, those are intended for low power devices like smoke alarms, and don't work well for robots. They cannot provide motor current for more than a few minutes. A 6xAA battery holder would be a much better choice.

Also, the L298 motor driver is old and very inefficient. It can waste up to 50% of the battery power as heat, reducing motor speed. Pololu has the best selection of modern, very efficient motor drivers.

Hi Robert,

without knowing your application very much there are two observations:

  • In the sketch you posted at least the line "#include <QTRSensors.h>" is missing ...
  • The qtr object - which seems to be crucial - is calibrated in setup() but never used in loop() e.g. linefollow(). It only would make sense If it is required to use it in loop().

Instead of that the sketch reads the analog values of pin sensorL and pin sensorR directly.

If the original sketch was working with QTRSensors it has been changed significantly ...

An introduction to the correct use of QTR sensors you will find here
https://www.pololu.com/docs/0J19/3

and here
https://www.instructables.com/Arduino-based-line-follower-using-Pololu-QTR-8RC-l/

as well as here
https://www.instructables.com/PID-Based-Line-Following-Robot-With-POLALU-QTR-8RC/

Maybe this is the closest to your application:
https://robotresearchlab.com/2019/02/12/how-to-program-a-line-following-robot/
Scroll down to " PID Line Following Algorithm" ...

Hello Edison,

First and foremost I would like to thank you for the timely response.
Here is the completed code

#include <QTRSensors.h>

// Motor and sensor pins
#define engineR 11
#define inputR1 8
#define inputR2 9
#define engineL 13
#define inputL1 12
#define inputL2 10

// Sensor pins
#define sensorRR A0
#define sensorR A1
#define sensorRM A2
#define sensorLM A3
#define sensorL A4
#define sensorLL A5

QTRSensors qtr;
const int sensorCount = 6;
const int sensorPins[] = {A0, A1, A2, A3, A4, A5};
uint16_t sensorValues[sensorCount];

int P, D, I, previousError, PIDvalue, error;
int lsp, rsp, x;
int lfspeed = 234;

float Kp = 0.02;  // Adjust this value based on experimentation
float Kd = 0.001;  // Adjust this value based on experimentation
float Ki = 0.00001; // Adjust this value based on experimentation

void setup() {
  // Sensor setup
  qtr.setTypeAnalog();
  qtr.setSensorPins((const uint8_t[]){sensorRR, sensorR, sensorRM, sensorLM, sensorL, sensorLL}, sensorCount);
  qtr.setEmitterPin(2);

  // Calibration
  delay(500);
  for (uint16_t i = 0; i < 400; i++) {
    qtr.calibrate();
  }

  // Motor and input pin setup
  pinMode(engineR, OUTPUT);
  pinMode(inputR1, OUTPUT);
  pinMode(inputR2, OUTPUT);

  pinMode(engineL, OUTPUT);
  pinMode(inputL1, OUTPUT);
  pinMode(inputL2, OUTPUT);

  Serial.begin(9600);
}

void loop() {
  linefollow();
  
}

void linefollow() {
  int error = analogRead(sensorL) - analogRead(sensorR);
  P = error;
  I = I + error;
  D = error - previousError;

  PIDvalue = (Kp * P) + (Ki * I) + (Kd * D);
  previousError = error;

  lsp = lfspeed - PIDvalue;
  rsp = lfspeed + PIDvalue;

  if (lsp > 255) {
    lsp = 255;
  }
  if (lsp < 0) {
    lsp = 0;
  }
  if (rsp > 255) {
    rsp = 255;
  }
  if (rsp < 0) {
    rsp = 0;
  }


  Serial.println(rsp);
  Serial.println(lsp);
  digitalWrite(inputL1, HIGH);
  digitalWrite(inputL2, LOW);
  digitalWrite(inputR1, HIGH);
  digitalWrite(inputR2, LOW);
  x = rsp/1.49;
  analogWrite(engineR, x);
  analogWrite(engineL, lsp);
 
}

Want i asked for is if there were any flaws or something wrong with the code. I am quite old (born in '65), so this code research isn't quite for me. I would rather talk with a person about it insted of the whole QTR sensors think.

Sorry for bothering, and thank you again for the help.

Coordialy, Robert.

Robert! You are not old. My father is old.

You need the right-sized power supply for any project, and as @jremington noted, you have the wrong-sized power supply. Get a 6xAA holder and buy stock in Duracell, because the L298N will consume half the available power just to exist.

If this is your first Arduino project, a self-balancing line following robot is like learning to skydive on the way down. Why not start with parts of your robot, like the line sensor, and make it report that it has detected a line or a cross or a box?

I noticed something in your sketch...

The sketch uses six sensor pins, while having eight sensors. Are the two missing sensors using digital pins? Did they get lost in the original-to-current move? The sketch uses Pin 2 for the emitter LED... I wonder if the hardware changed and the software did not.

Hello there,
I would like to thank you for the compliment first.
I will look into the matter for the pins. You are the second fellow to recommend me that. As for the pins, the only using six sensors was more of a circumstance than anything to do with problems with the machine. Me and the person who helped me code where making smaller stuff, and this was birthed from a project with six sensors(and a shortness of cables also did not help). The thing I came here to ask for, is if the code has any mistakes. The coding part eludes me a bit, as I am still reading the pages that the previous person sent me. So it would be nice If some of you code whizes could help me with that.

I once again thank you for your help, and the people of this website for how quickly they responded to my calls for aid.

Send my regards to your father,
Cordially and gratefuly,
Robert.

Your code has zero compile errors. Whether it drives off a cliff or executes perfectly, I do not know.

On a Nano...

Sketch uses 7252 bytes (23%) of program storage space. Maximum is 30720 bytes.
Global variables use 238 bytes (11%) of dynamic memory, leaving 1810 bytes for local variables. Maximum is 2048 bytes.

Your code sets up a PID-controlled line follower robot using an Arduino board. It initializes constants for motor and sensor pins, configures sensor setup with calibration for accurate readings, and sets motor and input pins as outputs. The PID constants (Kp, Ki, Kd) are defined for controlling the robot's movement based on sensor feedback. In the loop function, the linefollow function is called repeatedly to calculate error, proportional, integral, and derivative terms, and adjust motor speeds accordingly. However, tuning the PID parameters is essential for optimal performance. Additionally, consider handling edge cases such as intersections or curves for a more robust line-following behavior. Overall, your code provides a solid foundation for a PID line follower robot, with room for customization and fine-tuning to achieve desired performance.

Thanks, chatGPT! Very insightful.

Hi @roboticsastutius ,

I still recommend to read

https://robotresearchlab.com/2019/02/12/how-to-program-a-line-following-robot/

as this page describes in detail how to use a two sensor application and a PID application easy to understand including appropriate sketches.

Good luck!
ec2021

P.S.: That your sketch compiles without error is no guarantee that it works as wanted ...

I have ported the relevant parts of your sketch to Wowki so that we can check the results of different analog input combinations of the left and right sensor.

See here https://wokwi.com/projects/390702904999743489

Here is the modified Sketch
/*
  Forum: https://forum.arduino.cc/t/help-with-pid-line-follower-robot-code/1228016/8
  Wokwi: https://wokwi.com/projects/390702904999743489

  This sketch calculates and prints input and results of the PID algorithm
  as realized in the function linefollow()

  It shows that whatever data are coming from the sensors 
  the robot should always go left if the right motor speed is calculated 
  as x = rsp/1.49

*/

int P, D, I, previousError, PIDvalue, error;
int lsp, rsp, x;
int lfspeed = 234;

float Kp = 0.02;  // Adjust this value based on experimentation
float Kd = 0.001;  // Adjust this value based on experimentation
float Ki = 0.00001; // Adjust this value based on experimentation

void setup() {
  Serial.begin(115200);
  Serial.println("Using the divider 1.49 to calculate right motor speed");
  Serial.println("SensorL\tSensorR\trsp\tlsp\tx");
  for (int i = 0; i <= 1000;i += 100){
     linefollow(i,1000-i, true);
  }
  Serial.println();
  Serial.println("Using rsp as right motor speed");
  Serial.println("SensorL\tSensorR\trsp\tlsp\tx");
  for (int i = 0; i <= 1000;i += 100){
     linefollow(i,1000-i, false);
  }
}

void loop() {
}

void linefollow(int analogValueSensorLeft, int analogValueSensorRight, boolean useDivider) {
  int error = analogValueSensorLeft - analogValueSensorRight;
  P = error;
  I = I + error;
  D = error - previousError;

  PIDvalue = (Kp * P) + (Ki * I) + (Kd * D);
  previousError = error;

  lsp = lfspeed - PIDvalue;
  rsp = lfspeed + PIDvalue;

  if (lsp > 255) {
    lsp = 255;
  }
  if (lsp < 0) {
    lsp = 0;
  }
  if (rsp > 255) {
    rsp = 255;
  }
  if (rsp < 0) {
    rsp = 0;
  }
  Serial.print(analogValueSensorLeft);
  Serial.print('\t');
  Serial.print(analogValueSensorRight);
  Serial.print('\t');
  Serial.print(rsp);
  Serial.print('\t');
  Serial.print(lsp);
  Serial.print('\t');
  if (useDivider) {
     x = rsp/1.49;
  } else {
    x = rsp;
  }
  Serial.print(x);
  Serial.print('\t');
  // x and lsp used to control the speed of x -> right motor, lsp = left motor
  // analogWrite(engineR, x);
  // analogWrite(engineL, lsp);
  byte mode = 0;
  if (lsp > x) { mode = 1;}  // left motor quicker than right one 
  if (lsp < x) { mode = 2;}  // left motor slower than right one
  switch(mode){
    case 0:
        Serial.println("Same speed - Robot should move straight forward");
      break;
    case 1:
        Serial.println("Left quicker - Robot turns to the right");
      break;
    case 2:
        Serial.println("Right quicker - Robot turns to the left");
      break;
  }
}

It does not control the motors but only prints the relevant data, assuming that we could get almost a maximum range of analog inputs from both sensors.

The printed results look like this

So - if(!!!!) you get a valid input from the two sensors - the line

    x = rsp/1.49;

in your code would make your robot turn to the right under all circumstances, unless the right motor requires a speed that is about 2/3rd of the left one's for straight forward movement.

If both motors including wheels and gearing are identical the speed for both motors should also be identical.

Could you please check that the type of QTR8 sensor you use is a QTR-8A and not a QTR-8RC? They use quite different methods to signalize the measured reflectivity. Your sketch implies it must be 8A, but just to be sure ...

Hello there,

I've checked the sensors and I'm pretty sure it's an qtr8a. That being said, as i'm waiting for the new parts the good samaritans of this website suggested,i'm trying to fine tune the code.

The code I'm using is this one

#include <QTRSensors.h>

// Motor and sensor pins
#define engineR 11
#define inputR1 8
#define inputR2 9
#define engineL 13
#define inputL1 12
#define inputL2 10

// Sensor pins
#define sensorRR A0
#define sensorR A1
#define sensorRM A2
#define sensorLM A3
#define sensorL A4
#define sensorLL A5

QTRSensors qtr;
const int sensorCount = 6;
const int sensorPins[] = {A0, A1, A2, A3, A4, A5};
uint16_t sensorValues[sensorCount];

int P, D, I, previousError, PIDvalue, error;
int lsp, rsp, x;
int lfspeed =150;

float Kp = 4.5;  // Adjust this value based on experimentation
float Kd = 0.1;  // Adjust this value based on experimentation
float Ki = 0.00001; // Adjust this value based on experimentation

void setup() {
  // Sensor setup
  qtr.setTypeAnalog();
  qtr.setSensorPins((const uint8_t[]){sensorRR, sensorR, sensorRM, sensorLM, sensorL, sensorLL}, sensorCount);
  qtr.setEmitterPin(2);

  // Calibration
  delay(500);
  for (uint16_t i = 0; i < 400; i++) {
    qtr.calibrate();
  }

  // Motor and input pin setup
  pinMode(engineR, OUTPUT);
  pinMode(inputR1, OUTPUT);
  pinMode(inputR2, OUTPUT);

  pinMode(engineL, OUTPUT);
  pinMode(inputL1, OUTPUT);
  pinMode(inputL2, OUTPUT);

  Serial.begin(9600);
}

void loop() {
  linefollow();
  
}

void linefollow() {
  int error = analogRead(sensorL) - analogRead(sensorR);
  P = error;
  I = I + error;
  D = error - previousError;

  PIDvalue = (Kp * P) + (Ki * I) + (Kd * D);
  previousError = error;

  lsp = lfspeed - PIDvalue;
  rsp = lfspeed + PIDvalue;

  if (lsp > 255) {
    lsp = 255;
  }
  if (lsp < 0) {
    lsp = 0;
  }
  if (rsp > 255) {
    rsp = 255;
  }
  if (rsp < 0) {
    rsp = 0;
  }


  Serial.println(rsp);
  Serial.println(lsp);
  digitalWrite(inputL1, HIGH);
  digitalWrite(inputL2, LOW);
  digitalWrite(inputR1, HIGH);
  digitalWrite(inputR2, LOW);
  x = rsp;
  analogWrite(engineR, x);
  analogWrite(engineL, lsp);
 
}

The car, when I put it on the track, only turns on one wheel backwards, sometimes just for a few seconds, other times indefinitely. I ask for help with the Kp, Ki and speed values, as i've been told that my problems arise from those. Could anyone please help me figure out what I should do? I've read the PID tunning page and tried to aply it, but it did not translate very well to my machine.

Dearest regards,
Robert

Hi Robert,

I understand that you think someone might just tell you some data for the PID values...

However as you are using the sensors not as they have been designed and nobody knows what data are returned by analogRead() of separate sensors it would be just complete guesswork.

The QTR sensors use up to 8 sensors, thoroughly calibrated , to identify under which of the sensors the line is placed.

Your approach is based on only scanning left and right of the line.

My suggestion if you want to stick with this approach is to change the sketch to print the analogRead() output of the both sensors while you are moving the sensors from left to right over the line.

Copy the data and post them here in code tags.

Show data separately

  • while the robot is placed on the line
  • then move to the right leaving the line
  • move back to the line
  • move to the left leaving the line

That will in minimum allow to validate the sensor data and to see if it is worth to work on the PID parameters.

P.S.:

For your convenience here is a sketch so that prints about 4 values per second in two columns. The QTRSensor part is in there so that the sensors should be in exactly the state as in your sketch (without the calibration which is not used anyway as you want to read two sensors directly).

The first for the left sensor data, the second for the right sensor data:

#include <QTRSensors.h>

// Sensor pins
#define sensorRR A0
#define sensorR A1
#define sensorRM A2
#define sensorLM A3
#define sensorL A4
#define sensorLL A5

QTRSensors qtr;
const int sensorCount = 6;
const int sensorPins[] = {A0, A1, A2, A3, A4, A5};

void setup() {
  // Sensor setup
  qtr.setTypeAnalog();
  qtr.setSensorPins((const uint8_t[]) {
    sensorRR, sensorR, sensorRM, sensorLM, sensorL, sensorLL
  }, sensorCount);
  qtr.setEmitterPin(2);
  Serial.begin(115200);
  Serial.println("Left, Right");
}

void loop() {
  sensorData();
}

void sensorData() {
  static unsigned long lastMeasurement = 0;
  if (millis() - lastMeasurement >= 250) {
    lastMeasurement = millis();
    int sensorLeft = analogRead(sensorL);
    int sensorRight = analogRead(sensorR);
    Serial.print(sensorLeft);
    Serial.print(", ");
    Serial.println(sensorRight);
  }
}

You can apply the movements as described above slowly to the sensors/robot and copy the results with a description of what you did (e.g. moving over the line from left to right).

Good luck!

Hello again,

I've included the 6 batteries and tested the sensors. Can anyone help me figure out what should I do next? I've tried the recommendations and still can't do it!

I'm eternaly thankfull for your patientence and help.

Robert.

The basic information required to start with are the raw data read from your sensor when you move it slowly from right to left (and back again) over the line which shall be followed.

The PID constants are meaningless if this data is garbage.

Did you try the sketch from post #12? If yes, what are the results?

There is very little chance that anyone can help you without your support ...

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.