Learning Goals 5 min
Yesterday's bang-bang follower wobbled. Today PID replaces the "straight / left / right" case statement with a smooth steering output. You'll measure where the line is using analog reflectance values, compute a continuous error, and let PID set the steering. By the end of this lesson you will be able to:
- Read analog reflectance values from sensors (QTR-8A or analog-output IR modules).
- Compute a weighted "line position" from multiple sensors — the classic line-follower trick.
- Drive both motors with a PID-derived differential so the chassis glides along the line.
Warm-Up 10 min
Hardware:
- L03-10 chassis from yesterday.
- 5 × IR reflectance sensors with analog outputs (or a Pololu QTR-8A bar — 8 analog sensors on one strip).
- If you only have digital TCRT5000 modules: turn the threshold pot to its "sensitive" mid-point so the comparator's analog output (pre-comparator) is usable. Many TCRT5000 modules expose both digital and analog out.
Why analog
Digital reading: 0 or 1. Five sensors = 32 possible patterns; each maps to one of a few actions. Coarse.
Analog reading: 0..1023 per sensor. Five sensors give 1023⁵ ≈ a quadrillion patterns. We collapse them into one continuous "where is the line" number.
New Concept · Weighted line position + PID 25 min
Weighted average
Label the sensors −2, −1, 0, +1, +2 by their position relative to the middle. The line is where the reflectance is lowest (black absorbs IR). Compute a weighted average:
linePosition = ((-2)*S[0] + (-1)*S[1] + 0*S[2] + 1*S[3] + 2*S[4])
/ (S[0] + S[1] + S[2] + S[3] + S[4])
Wait — but the line is the DARK spot, not the bright one. We want the position to be the centre of darkness. Flip: weight by (maxReading − S[i]):
int sensors[5] = {0};
const int SENSOR_PINS[] = {A0, A1, A2, A3, A4};
const int WEIGHTS[] = {-2, -1, 0, 1, 2};
int maxReading = 1023;
float computeLinePosition() {
long weightedSum = 0;
long total = 0;
for (int i = 0; i < 5; i++) {
sensors[i] = analogRead(SENSOR_PINS[i]);
int dark = maxReading - sensors[i]; // bigger when over line
weightedSum += WEIGHTS[i] * (long)dark;
total += dark;
}
if (total == 0) return 0; // no sensor sees a line
return (float)weightedSum / total; // -2..+2
}Returns approximately:
- −2 → line is far left.
- 0 → line is centred (chassis is on track).
- +2 → line is far right.
PID on linePosition
#include "PID.h"
PID linePID(40.0, 0.0, 8.0); // Kp, Ki, Kd
void loop() {
float pos = computeLinePosition(); // -2..+2, 0 = on line
float steering = linePID.update(0.0, pos); // setpoint = 0 = centred
const int BASE = 150;
int leftDuty = constrain((int)(BASE - steering), -255, 255);
int rightDuty = constrain((int)(BASE + steering), -255, 255);
motorSpeed(motorL, leftDuty);
motorSpeed(motorR, rightDuty);
}If pos drifts right (positive), error = 0 − (+) = negative, output is negative, left duty increases, right duty decreases → chassis steers left. The pivot is smooth, not bang-bang.
Tuning sequence
- Start with Kp only. Try 20. Run the chassis. Note behaviour.
- If it wobbles (oscillates across line), reduce Kp. If it's sluggish, increase.
- When Kp is in the right ballpark (smooth tracking on straights, slow on tight corners), add Kd. Try 5. Damps the oscillation as the chassis nears center.
- Increase BASE speed gradually. Bigger speed needs higher Kd (system reacts to disturbances faster).
- Ki is usually 0 for line-following (no persistent offset).
Pretty good starting values
- BASE: 120 (slow & forgiving) → up to 200 (fast & risky).
- Kp: 30–60.
- Ki: 0.
- Kd: 5–15.
Worked Example · Glide along the line 25 min
Step 1 — calibrate the sensors
Sweep the chassis manually across the line. Print the analog readings for each sensor. Note the min (over white) and max (over black) for each. Adjust the threshold in code if needed.
void loop() {
for (int i = 0; i < 5; i++) {
int v = analogRead(SENSOR_PINS[i]);
Serial.print(v); Serial.print(" ");
}
Serial.println();
delay(100);
}Step 2 — verify the weighted position
Loop printing computeLinePosition() instead of raw reads. Confirm:
- On line → ~0.
- Slight drift right → +0.5 to +1.
- Off-track right → close to +2.
Step 3 — first PID run
Start with Kp = 30, Kd = 0. Place on line. Watch. Likely wobbles slightly.
Step 4 — increase Kp
Try 50. Wobble grows? Reduce. Tracks gently? Bump speed up to 180.
Step 5 — add Kd
Kd = 5 → noticeable damping. Kd = 15 → maybe too damped. Find sweet spot.
Step 6 — time the lap
Re-run yesterday's timed lap. PID version should be significantly faster + smoother. The classic line-follower transformation.
Try It Yourself 15 min
Goal: Plot linePosition over time using the Arduino Serial Plotter. Watch it ride along the line.
Goal: Add Serial-tunable gains: type kp 50 or kd 10 in the monitor to update PID parameters live. Tune without re-uploading.
Goal: "Speed boost on straights": when the line is consistently near centre for > 500 ms, gradually increase BASE speed. When error spikes, drop back. The chassis goes fast on straights, slows on curves — competitive line-follower behaviour.
Mini-Challenge · Compete with yourself 10 min
- Same track as yesterday.
- Time your PID lap. Compare to the bang-bang lap.
- Best-of-three runs per controller.
- Optionally race a classmate's tuned PID.
Recap 5 min
Analog reflectance + weighted average → continuous line position → PID → differential motor speed. Bang-bang gone, glide arrived. Tune Kp first, Kd next, Ki rarely. Tomorrow we measure wheel rotations directly with encoders.
- Analog reflectance
- Raw ADC reading of phototransistor current. Continuous 0..1023 instead of binary 0 / 1.
- Weighted position
- The classic line-follower trick: combine N sensors into one continuous position estimate using a weighted sum.
- Differential drive
- Two wheels at different speeds = the chassis steers. PID output → speed difference.
- Line memory
- Storing recent positions to predict where the line went when temporarily lost (sharp turn, missing tape).
- Serial Plotter
- Arduino IDE's built-in tool that plots println()'d numbers in real time. Invaluable for PID tuning.
Homework 5 min
- Tune the PID line-follower. Record final Kp, Kd, lap time.
- Video a polished lap.
- Read ahead to ARD-L04-27 (Encoder Motors). Bring 2 × TT-style gearmotors with encoders + the L298N + chassis.