Mechatronics Control Systems 2024

PID Line-Following
Robot

A line-following robot tuned with a PID controller for smooth, accurate path tracking. IR sensors feed data into the control loop, managed by an Arduino Mega, with live data logged to a Python dashboard.

PID Control Arduino Python IR Sensors
// The Problem & Solution

Mechanical Design

A line follower sounds simple until you try to make it fast and smooth. At high speed, a naive bang-bang controller oscillates wildly across the line. The goal was to build a robot that could maintain consistent tracking even through tight curves, using a proper closed-loop control approach.

The chassis was designed around an array of five TCRT5000 IR sensors mounted at the front, spaced 10 mm apart to give good positional resolution over a 20 mm black line. The two rear drive motors are differential-drive, each powered by a separate channel of an L298N H-bridge, allowing independent speed control.

The frame was 3D-printed in PLA with low-profile motor mounts to keep the sensor array close to the ground — reducing ambient light interference and improving line contrast readings.

// The Code & Electronics

PID Controller

The Arduino Mega reads the five-sensor array every 10 ms and computes a weighted average to get a signed error value (negative = line left, positive = line right). This error drives a PID loop whose output is added to / subtracted from the base motor speed on each side.

Tuning Kp, Ki, and Kd was done empirically on a test track. The gains were transmitted live over UART to a Python dashboard built with pyserial and matplotlib, which plotted error and motor deltas in real time — making tuning dramatically faster than reflashing firmware each iteration.

C++ · Arduino pid_follower.ino
// PID loop — runs every 10 ms
float Kp = 0.45, Ki = 0.002, Kd = 1.8;
float integral = 0, lastError = 0;

void pidLoop() {
  float error  = readLineError(); // −2..+2
  integral    += error;
  float deriv  = error - lastError;
  float output = Kp*error + Ki*integral
                           + Kd*deriv;
  lastError   = error;

  setMotors(BASE_SPEED + output,
            BASE_SPEED - output);
  Serial.println(error); // → Python
}

What I Learned

📊

PID Tuning Methodology

Starting with only Kp and increasing until oscillation, then adding Kd to dampen it, and finally a tiny Ki to eliminate steady-state drift — following the Ziegler–Nichols approach gave me a solid starting point and taught me why each term matters physically, not just mathematically.

💡

Live Data Logging

Streaming sensor data over UART to a Python dashboard was a game changer. Seeing the error signal plotted live made it immediately obvious when the derivative term was too aggressive (spiky response) versus too low (sluggish recovery). I'll use real-time telemetry on every future robot project.

Integral Windup

When the robot left the line entirely, the integral term accumulated an enormous value and caused violent overcorrection on re-acquisition. Clamping the integral to ±50 counts eliminated this and is now a rule I apply to every PID implementation from the start.

// Explore Further

See It in Action

The full Arduino sketch, Python dashboard, and tuning notes are available on GitHub. A track-run demo is on YouTube.