Line following is one of the oldest autonomous robot behaviours, and one of the most instructive. It demands fast sensing, smooth control, and graceful recovery from losing the line — all within a tight feedback loop. Despite its apparent simplicity, a well-tuned line follower requires careful attention to sensor calibration, centroid computation, and PID gain selection.
The TCRT5000 is an infrared emitter-detector pair in a single package. It emits IR light downward; the detector measures how much is reflected back. A dark surface (black tape or black paint on white floor) reflects little IR; a light surface reflects a lot. The analogue output voltage is roughly proportional to reflectance.
The 5-element array used on this platform has sensors spaced 25 mm apart. With the array mounted 10 mm above the floor, the detection footprint of each element is approximately 10–15 mm in diameter. The 5-element array therefore covers a 100 mm span across the line.
Digital vs. analogue readings. Many TCRT5000 breakout boards provide both an analogue output (0–3.3 V, read via ADC) and a digital output (high/low based on a potentiometer threshold). Analogue readings allow interpolation between sensors; digital readings are faster to process but lose sub-element resolution. This chapter uses analogue values.
Before computing a position, each sensor must be calibrated to map its raw ADC value to a normalised reflectance in $[0, 1]$:
def calibrate(sensors, n_samples=200):
mins = [float('inf')] * len(sensors)
maxs = [float('-inf')] * len(sensors)
for _ in range(n_samples):
values = sensors.read()
for i, v in enumerate(values):
mins[i] = min(mins[i], v)
maxs[i] = max(maxs[i], v)
return mins, maxs
Drive the robot slowly across and along the line during calibration so that each sensor sees both the line (minimum) and the floor (maximum). After calibration, readings are normalised: $r_i = (v_i - min_i) / (max_i - min_i)$
Given 5 normalised reflectance values $r_0, \ldots, r_4$, the line position as a fraction of the array width is the weighted centroid of the inverted readings (dark = high weight):
\[p = \frac{\sum_{i=0}^{4} (1 - r_i) \cdot x_i}{\sum_{i=0}^{4} (1 - r_i)}\]where $x_i$ is the physical position of sensor $i$ in millimetres (e.g., $x = {-50, -25, 0, 25, 50}$ mm for 5 sensors).
This gives a continuous position estimate in $[-50, +50]$ mm. The error fed to the PID controller is simply $p$ (when the line is centred under the array, $p = 0$).
Example: sensors read $[0.1, 0.2, 0.9, 0.3, 0.1]$ (line under sensor 2, slightly right). Inverted: $[0.9, 0.8, 0.1, 0.7, 0.9]$. Centroid: $(0.9 \times -50 + 0.8 \times -25 + 0.1 \times 0 + 0.7 \times 25 + 0.9 \times 50) / (0.9+0.8+0.1+0.7+0.9) = (-45 - 20 + 0 + 17.5 + 45) / 3.4 = -2.5/3.4 \approx -0.74$ mm — essentially centred, with a slight leftward bias.
The line-following controller uses PID on the centroid position error to generate a steering correction (angular velocity command):
pid = PIDController(Kp=0.03, Ki=0.0, Kd=0.008)
while True:
position = read_line_position() # mm, negative = line left
correction = pid.update(position, setpoint=0.0, dt=dt)
drive.move(linear_mps=0.25, angular_radps=correction)
time.sleep(dt)
Typical gains for a robot travelling at 0.25 m/s on a 20 mm wide black line: $K_p = 0.03$ rad/s per mm, $K_d = 0.008$ rad/s·s/mm. Start with $K_i = 0$; add a small value only if the robot has a persistent straight-line drift.
When all sensors read near zero (the line is absent — perhaps the robot has overshot a sharp corner), the robot must recover. A simple state machine:
The last known error direction is crucial: if the line disappeared to the left, the robot should turn left to find it again.
At a T or + intersection, multiple sensors are fully on the line. The centroid computation becomes unreliable (the denominator is very large, but the weighted sum may happen to be near zero). Detect intersections by thresholding the total activated sensor count: if four or more sensors are active, an intersection has been reached.
At an intersection, the robot can stop and apply a rule (e.g., always follow the straight-ahead path, or turn based on a programmed sequence) before resuming line following.
Line following and odometry can work together. While following a line, the odometry module continues to accumulate pose estimates. When the robot reaches a known landmark on the line (e.g., a cross-tape marker), the odometry can be reset or corrected to the known position. This hybrid approach gives the accuracy of line following in structured areas with the flexibility of odometry in unstructured ones.
Navigation: