An Arduino-based autonomous line following robot featuring PID control, ultrasonic obstacle avoidance, adaptive speed ramping, and intelligent lost-line recovery.
Wiring schematic showing connections between the Arduino, L298N motor driver, IR sensor array, and HC-SR04 ultrasonic sensor.
Fully assembled robot showing sensor placement, motor mounting, and chassis layout.
- Hardware Requirements
- Pin Mapping
- System Architecture
- Algorithms Explained
- State Machine
- Configuration & Tuning
- Serial Monitor Output
| Component | Description |
|---|---|
| Arduino Uno / Nano | Microcontroller |
| L298N Motor Driver | Dual H-bridge for 2 DC motors |
| 8x IR Reflectance Sensors | Analog line sensors (A0–A7) |
| HC-SR04 Ultrasonic Sensor | Front-facing obstacle detection |
| 2x DC Gear Motors | Left and right drive wheels |
| Power Supply | 7–12V for motors; 5V for logic |
Motor Driver (L298N)
ENA → Pin 10 (Left motor PWM speed)
IN1 → Pin 9 (Left motor direction A)
IN2 → Pin 8 (Left motor direction B)
IN3 → Pin 7 (Right motor direction A)
IN4 → Pin 6 (Right motor direction B)
ENB → Pin 5 (Right motor PWM speed)
Ultrasonic Sensor (HC-SR04)
TRIG → Pin 3
ECHO → Pin 4
IR Sensor Array
S0–S7 → A0–A7 (8 analog inputs)
Status LED
LED → Pin 13 (built-in, active during calibration)
┌─────────────────────────────────────────────────────┐
│ MAIN LOOP │
│ │
│ ┌─────────────┐ ┌──────────────────────────┐ │
│ │ Ultrasonic │────▶│ Obstacle Check (60ms) │ │
│ │ Sensor │ └──────────┬───────────────┘ │
│ └─────────────┘ │ clear │
│ ▼ │
│ ┌─────────────┐ ┌──────────────────────────┐ │
│ │ IR Sensor │────▶│ Read Line Position │ │
│ │ Array │ │ (Weighted Average) │ │
│ └─────────────┘ └──────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ PID Controller │ │
│ │ error = 3500 - pos │ │
│ │ correction = Kp·P │ │
│ │ + Ki·I │ │
│ │ + Kd·D │ │
│ └──────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────┐ │
│ │ Motor Drive │ │
│ │ Left = base + correct │ │
│ │ Right = base - correct │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────────────────┘
Before the robot starts driving, it runs a 5-second auto-calibration routine. During this window, you manually sweep the sensor array over both the line and the background surface. The firmware records the minimum and maximum raw ADC readings for each of the 8 sensors independently.
At runtime, each raw sensor value is normalised to a 0–1000 scale:
mappedValue = map(rawValue, sensorMin[i], sensorMax[i], 0, 1000)
A noise gate then zeros out any reading below 60, preventing ambient light interference from being misread as a line signal.
Why this matters: Different surfaces, lighting conditions, and sensor aging all affect raw readings. Calibration makes the robot adapt to its environment rather than relying on hardcoded thresholds.
The 8 sensors are assigned position weights of 0, 1000, 2000, ..., 7000. A weighted average across active sensors gives a continuous position value from 0 (far left) to 7000 (far right), with 3500 representing the centre.
position = Σ(sensorValue[i] × i×1000) / Σ(sensorValue[i])
To reduce noise, the weighted average is computed only over a 3-sensor window centred on the sensor with the highest reading, rather than all 8 sensors simultaneously.
Special cases handled:
| Condition | Detection | Action |
|---|---|---|
| > 5 sensors active simultaneously | Intersection | Return centre (3500), continue straight |
| No sensor above threshold 150 | Line lost | Return extreme value, trigger recovery |
| 1–5 sensors active | Normal line | Return weighted average position |
The heart of the steering system is a Proportional–Integral–Derivative (PID) controller.
error = 3500 - position // How far off-centre the line is
motorCorrection = Kp×P + Ki×I + Kd×D
P = error
Applies a steering correction proportional to how far off-centre the line currently is. A large error produces a strong turn; a small error produces a gentle correction.
I += error (clamped to ±5000)
Accumulates persistent error over time. This corrects for systematic bias — such as one motor being slightly faster than the other — that the P term alone cannot fix. An anti-windup clamp prevents the integral from growing unboundedly during recovery manoeuvres.
rawDerivative = error - lastError
smoothedDerivative = (smoothedDerivative × 0.7) + (rawDerivative × 0.3)
D = smoothedDerivative
Predicts where the error is heading and applies a damping force to prevent overshoot and oscillation. The exponential moving average (EMA) smoothing (70/30 split) prevents noisy sensor spikes from causing sudden jerky steering corrections.
leftMotorSpeed = baseSpeed + correction // REVERSE_STEERING = true
rightMotorSpeed = baseSpeed - correction
The REVERSE_STEERING flag accommodates robots where the motor/sensor orientation is flipped.
| Gain | Value | Role |
|---|---|---|
Kp |
0.08 | Proportional — main steering authority |
Ki |
0.0001 | Integral — drift correction |
Kd |
1.2 | Derivative — overshoot damping |
Instead of jumping to full speed immediately (which causes wheel slip and sensor blur), the robot uses linear velocity ramping:
Every 50ms: currentBaseSpeed += 2
(until targetBaseSpeed = 130 is reached)
This produces smooth acceleration from the initial speed of 80 PWM to the cruise speed of 130 PWM over approximately 1.25 seconds.
A stall prevention function also ensures that any PWM value between 1 and 109 is bumped up to the minimum effective motor threshold (minMotorSpeed = 110), preventing the motors from receiving a voltage too low to overcome static friction.
An HC-SR04 ultrasonic sensor is polled every 60ms in a non-blocking fashion (the main loop is never halted by the sonar check). The sensor fires a 10µs trigger pulse and measures the echo return time with a 8ms timeout to avoid lockups in open-air situations.
distance (cm) = duration × 0.034 / 2
If the measured distance drops below stopDistance = 10 cm, the robot immediately stops all motors and enters the OBSTACLE_DETECTED state. It resumes automatically once the path is clear.
When all sensor readings fall below the line threshold, the robot enters smart recovery mode. Rather than spinning blindly, it uses a path history buffer to make an informed decision:
- A circular buffer stores the last 10 error values (updated every 50ms).
- The average of the history is computed to determine which side the line was most recently on.
- The robot spins toward the side with the higher historical error magnitude.
- Each successive recovery attempt increases the search speed by 5 PWM (exponential search), up to
maxSpeed.
averageError < 0 → line was to the right → spin right
averageError > 0 → line was to the left → spin left
If the line is not reacquired within 3 seconds (maxRecoveryTime), the robot halts and enters the STOPPED state to prevent runaway behaviour.
The robot operates as a five-state finite state machine:
power on
│
▼
┌─────────────┐
│ CALIBRATING │ (5 second sensor calibration)
└──────┬──────┘
│ done
▼
┌─────────────┐◀──────────────────────────────┐
│ RUNNING │ │
└──────┬──────┘ │
┌───────┴───────┐ │
line │ │ obstacle │
lost │ │ detected │
▼ ▼ │
┌────────────┐ ┌──────────────────┐ │
│ LOST_LINE │ │ OBSTACLE_DETECTED│──── clear ───────┘
└──────┬─────┘ └──────────────────┘
│
timeout (3s)
│
▼
┌─────────┐
│ STOPPED │
└─────────┘
All key parameters are defined as constants at the top of the sketch for easy adjustment:
// PID Gains — tune these first
float Kp = 0.08;
float Ki = 0.0001;
float Kd = 1.2;
// Speed Settings
const int targetBaseSpeed = 130; // Cruise speed (0–255 PWM)
const int maxSpeed = 220; // Maximum speed during recovery turns
const int minMotorSpeed = 110; // Stall prevention threshold
// Obstacle Avoidance
const int stopDistance = 10; // Stop if obstacle within 10 cm
// Recovery
const unsigned long maxRecoveryTime = 3000; // Give up after 3 seconds
// Motor Orientation (flip if robot drives backwards or steers wrong way)
const bool REVERSE_STEERING = true;
const bool INVERT_LEFT_MOTOR = true;
const bool INVERT_RIGHT_MOTOR = true;| Symptom | Likely Cause | Adjustment |
|---|---|---|
| Oscillates/wobbles on straight line | Kp too high | Reduce Kp |
| Slow to react to curves | Kp too low | Increase Kp |
| Drifts to one side over time | Insufficient Ki | Increase Ki slightly |
| Overshoots and corrects repeatedly | Kd too low | Increase Kd |
| Jittery, erratic movement | Kd too high | Reduce Kd |
Set the Serial Monitor baud rate to 9600. During operation you will see:
=== LINE FOLLOWER ROBOT ===
Calibrating... Move sensors over line and background!
......................
Calibration complete!
Sensor ranges (Min/Max):
S0: 120/890
S1: 115/905
...
Starting in 3 seconds...
GO!
Pos:3487 Err:13 L:131 R:129
Pos:3210 Err:290 L:148 R:112
OBSTACLE at 8cm
Path clear, resuming
Line lost! Recovering...
Line reacquired!
MIT License — free to use, modify, and distribute with attribution.

