top of page
Search

How a Line-Following Robot Works: PID Control Explained (with Arduino Code)

Oct 4
4 min read

A line-following robot is one of the first "real" robotics projects most students build, and it's also one of the best ways to learn the idea behind almost every competition robot: feedback control. In this post we'll explain how a line follower works, why the simple version wobbles, and how a PD controller (the "P" and "D" of PID) makes it smooth. The full Arduino code is at the end.

This is the same idea our FIRST LEGO League and FIRST Tech Challenge students use to drive straight, turn precisely and follow lines on the competition field.

The setup

  • An Arduino Uno

  • Two analog reflectance sensors (for example, TCRT5000 modules) mounted under the front of the robot, one on each side of the line

  • A two-wheel-drive chassis with an L298N motor driver

  • A black line (electrical tape works) on a light floor

Each sensor shines infrared light down and measures how much bounces back. A light floor reflects a lot, a black line reflects very little, so the reading changes depending on whether the sensor is over the line.

Version 1: bang-bang control

The simplest approach: if the left sensor sees the line, turn left. If the right sensor sees the line, turn right. Otherwise go straight.

It works, but the robot zig-zags. It only knows "left" or "right", never "a little bit left". So it always over-corrects, swings past the line, and over-corrects again. On a tight curve it often loses the line completely.

Version 2: proportional control (P)

Instead of yes/no, use how far off the line we are. Subtract the two sensor readings:

error = left reading − right reading

If the robot is centered, both sensors see a bit of the line and the error is close to 0. If it drifts, one sensor sees more of the line and the error grows. Now we steer by an amount proportional to the error:

correction = Kp × error

Then slow one wheel and speed up the other by that correction. Small drift means a gentle correction, big drift means a sharp one. Kp is the "gain", a number you tune.

  • Kp too small: the robot reacts slowly and drifts off on curves.

  • Kp too big: it overshoots and oscillates, just like bang-bang.

Version 3: add the derivative (D)

P control still overshoots a little, because by the time the error is back to zero the robot is already turning hard. The derivative term fixes that by looking at how fast the error is changing:

derivative = error − last error

correction = Kp × error + Kd × derivative

If the robot is swinging back toward the line quickly, the derivative is large and works against the turn, like easing off the steering wheel before you're fully straight. The result is a robot that settles onto the line instead of wobbling across it.

What about the "I" in PID?

The integral term adds up error over time. It's useful when there's a steady bias, like one motor that is slightly weaker than the other. For a line follower, PD is usually enough, and adding I without care can make the robot sluggish or unstable. In FTC, students often use full PID for things like holding an arm at an exact angle, where gravity creates a constant pull the I term can cancel.

The code

// Two-sensor line follower with PD control
// Arduino Uno + L298N motor driver + 2 analog reflectance sensors

const int LEFT_SENSOR = A0;
const int RIGHT_SENSOR = A1;

const int ENA = 5, IN1 = 7, IN2 = 8;   // left motor (ENA must be a PWM pin)
const int ENB = 6, IN3 = 9, IN4 = 10;  // right motor (ENB must be a PWM pin)

const int BASE_SPEED = 150;   // 0-255, start slow
float Kp = 0.25;              // tune this first
float Kd = 1.5;               // then tune this
int lastError = 0;

// speed from -255 (full reverse) to 255 (full forward)
void setMotor(int en, int inA, int inB, int speed) {
  speed = constrain(speed, -255, 255);
  digitalWrite(inA, speed >= 0 ? HIGH : LOW);
  digitalWrite(inB, speed >= 0 ? LOW : HIGH);
  analogWrite(en, abs(speed));
}

void setup() {
  int pins[] = {ENA, IN1, IN2, ENB, IN3, IN4};
  for (int p : pins) pinMode(p, OUTPUT);
}

void loop() {
  int left = analogRead(LEFT_SENSOR);    // 0-1023
  int right = analogRead(RIGHT_SENSOR);

  int error = left - right;
  int derivative = error - lastError;
  lastError = error;

  int correction = Kp * error + Kd * derivative;

  setMotor(ENA, IN1, IN2, BASE_SPEED - correction);
  setMotor(ENB, IN3, IN4, BASE_SPEED + correction);

  delay(5);  // keep the loop timing steady
}

This assumes your sensors read higher over the black line. Many modules do the opposite. If your robot steers away from the line, swap the two sensor wires or change the error to right − left.

How to tune it

  1. Set Kd to 0. Start with a low Kp and a low BASE_SPEED.

  2. Raise Kp until the robot follows gentle curves but starts to wobble. Then back off a little.

  3. Raise Kd until the wobble calms down.

  4. Only then increase BASE_SPEED, and re-tune. Faster robots need more D.

  5. Change one number at a time and write down what happened. That habit is what separates good competition teams from everyone else.

Same idea, different robots

You can build exactly this logic in LEGO SPIKE Prime (with one or two color sensors), on mBot2 with its quad RGB sensor, or in Java on an FTC robot. The hardware changes, but the controller doesn't: measure the error, react in proportion, and dampen the swing.

Want to go deeper? Our Arduino Adventures eBook has 10 hands-on robotics projects for ages 10–15, and our online courses cover Arduino, SPIKE Prime and mBot2. In San Diego, students can build projects like this with a coach at our weekend group sessions in Scripps Ranch. The first session is free: text (619) 786-5731.

 
 
 

Comments


bottom of page