Loading

The Problem PID Solves

Your line follower robot zigzags. It overshoots the line, corrects too hard, overshoots the other way, and keeps oscillating. Your temperature controller brings the oven to 200°C but then keeps swinging between 180°C and 220°C. Your drone tilts, corrects, tilts the other way, and shakes violently.

The problem is binary thinking. A simple on/off controller says: "Am I off target? Apply full correction. Am I on target? Stop correcting." This causes constant overshoot and oscillation. It is like trying to park a car using only full throttle or full brake — you will overshoot the parking spot every time.

The Core Idea

PID stands for Proportional — Integral — Derivative. It is a control algorithm that calculates how much correction to apply based on: how far off you are right now (P), how long you have been off (I), and how fast you are changing (D). Together, they produce smooth, precise control.

P — React to Right Now

The Proportional term is the simplest: apply a correction that is directly proportional to the current error. Big error = big correction. Small error = small correction. Zero error = zero correction.

Think of a rubber band attached to the line. The further the robot strays, the harder the band pulls it back. Close to the line, the pull is gentle. Far away, it yanks hard.

Conceptual — Proportional term
error = setpoint - current_value  // How far are we from target?
P_output = Kp * error              // Apply correction proportional to error
// Kp is the "gain" — how aggressively to react
// Kp too small = sluggish response
// Kp too large = oscillation and overshoot

The problem with P alone: the robot settles slightly off the line, not exactly on it. This is called steady-state error. When the robot is close to the line, P gives a tiny correction — but that tiny correction is exactly cancelled by friction or other disturbances. The robot stops just off-center and stays there.

I — Remember the Past

The Integral term accumulates all past errors over time. If the robot has been slightly off the line for a long time, the integral builds up and applies a growing correction to push it all the way back.

Think of a friend who remembers every time you arrived late. The longer the pattern continues, the more they adjust their plans to account for your lateness. Similarly, I keeps a running tally of accumulated error and adds correction for it.

Conceptual — Integral term
integral = integral + error // Add current error to running total (each loop) I_output = Ki * integral // Apply correction based on accumulated error // Ki too large = system oscillates slowly and takes long to settle // Ki too small = steady-state error remains // Solution: often set a maximum integral value to prevent "windup"
Integral Windup

If the robot is stuck against a wall and cannot move, the integral keeps growing and growing. When it finally moves, it gets a massive overcorrection. This is called "integral windup." Always limit the integral to a reasonable maximum value — a technique called anti-windup.

D — Predict the Future

The Derivative term looks at how fast the error is changing. If the robot is approaching the line very quickly, D applies a braking force before it reaches the line — preventing overshoot.

Think of ABS (Anti-lock Braking System) in a car. When you brake hard, ABS pulses the brakes rapidly to prevent the wheels from locking. It is anticipating the skid before it happens. Similarly, D anticipates overshoot and applies early correction.

Conceptual — Derivative term
derivative = error - previous_error  // How fast is the error changing?
D_output = Kd * derivative           // Brake early if error is changing fast
previous_error = error               // Remember for next loop

// Kd too large = jittery, noisy response (amplifies sensor noise)
// Kd right = smooth, damped approach to target without overshoot

Tuning PID: The Practical Approach

Tuning means finding the right values for Kp, Ki, and Kd for your specific system. There is no formula that gives you perfect values upfront — it is an iterative process. But this approach gets you there efficiently.

  1. Start with Kp only — set Ki = 0, Kd = 0. Increase Kp until the system oscillates. Then back off Kp to about 60% of that value.
  2. Add Kd — increase Kd slowly until oscillations reduce and the response looks smooth.
  3. Add Ki last — increase Ki very slowly until steady-state error disappears. Keep it small.

PID for the Line Follower Robot

For a 5-sensor array line follower, the error is typically defined as the position of the line relative to the center sensor. Center = error 0. Far left = error -2. Far right = error +2.

Arduino — PID Line Follower (5-sensor array, L298N)
// PID Line Follower with 5 IR sensors
// Sensor array: S0(leftmost) S1 S2(center) S3 S4(rightmost)
// All on digital pins 2,3,4,5,6

const int SENSOR_PINS[5] = {2, 3, 4, 5, 6};
const int ENA = 9, IN1 = 7, IN2 = 8;  // Left motor
const int ENB = 10, IN3 = 11, IN4 = 12; // Right motor

// PID constants — tune these for your robot
float Kp = 25.0;
float Ki = 0.0;   // Start with 0, add small values if needed
float Kd = 15.0;

// PID state
float previousError = 0;
float integral = 0;

// Base speed
const int BASE_SPEED = 160;

void setup() {
  for (int i = 0; i < 5; i++) pinMode(SENSOR_PINS[i], INPUT);
  pinMode(ENA, OUTPUT); pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
  pinMode(ENB, OUTPUT); pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
  // Set direction: both motors forward
  digitalWrite(IN1, HIGH); digitalWrite(IN2, LOW);
  digitalWrite(IN3, HIGH); digitalWrite(IN4, LOW);
  Serial.begin(9600);
}

// Returns position: -2 (far left) to +2 (far right), 0 = center
// Returns 99 if no line detected (all sensors on white)
float readLinePosition() {
  int readings[5];
  int lineCount = 0;
  float weightedSum = 0;

  for (int i = 0; i < 5; i++) {
    readings[i] = !digitalRead(SENSOR_PINS[i]); // Invert: 1 on line, 0 off
    if (readings[i]) {
      lineCount++;
      weightedSum += (i - 2) * readings[i]; // Positions: -2,-1,0,1,2
    }
  }
  if (lineCount == 0) return 99;  // No line detected
  return weightedSum / lineCount;
}

void setMotors(int leftSpeed, int rightSpeed) {
  leftSpeed = constrain(leftSpeed, 0, 255);
  rightSpeed = constrain(rightSpeed, 0, 255);
  analogWrite(ENA, leftSpeed);
  analogWrite(ENB, rightSpeed);
}

void loop() {
  float error = readLinePosition();

  if (error == 99) {
    // No line — stop
    setMotors(0, 0);
    return;
  }

  // PID calculation
  integral = constrain(integral + error, -100, 100); // Anti-windup
  float derivative = error - previousError;

  float correction = (Kp * error) + (Ki * integral) + (Kd * derivative);
  previousError = error;

  // Apply correction: positive error = line is to the right = turn right
  // Turn right = slow down right motor, speed up left motor
  int leftSpeed  = BASE_SPEED + correction;
  int rightSpeed = BASE_SPEED - correction;

  setMotors(leftSpeed, rightSpeed);

  Serial.print("Error: "); Serial.print(error, 2);
  Serial.print("  Correction: "); Serial.println(correction, 2);
}
Tuning Challenge

Upload the code with Kp=25, Ki=0, Kd=0. Watch the robot — it will oscillate. Increase Kd to 15. Watch it smooth out. Now run it on a curve. If it is still sluggish on curves, increase Kp slightly. Document your tuning steps — this process of systematic debugging and measurement is exactly what engineers do on real systems.

← Previous
Line Follower Robot
Next →
Robot Sensors Guide