Advanced 14: Drive Straight
Prerequisites: Advanced 13 (Encoder Fundamentals) Time: ~60 min
Overview
In the Motor Control lab, your robot couldn't drive a straight line — two motors at "the same" PWM always drift apart. Now that you have calibrated encoders, you can measure the drift and correct it in real time.
This module builds a differential speed controller: independent P-controllers for left and right wheels that keep their tick rates matched.
You'll learn:
- How to quantify straight-line drift with encoder data
- Why matched PWM ≠ matched speed (motor manufacturing tolerance, friction, battery sag)
- How to build independent left/right speed controllers
- How to verify improvement with a repeatable measurement
Part 1: Quantify the Problem (~15 min)
Task 1 — Measure Open-Loop Drift
Drive the robot at equal PWM for both wheels, measure how many ticks each wheel produces:
from picobot import Robot
import time
robot = Robot(encoders=True)
robot.encoders.reset()
PWM = 80
DURATION = 3 # seconds
print(f"Open-loop test: both motors at PWM {PWM}")
input("Place robot on a flat surface. Press Enter...")
robot.set_motors(PWM, PWM)
time.sleep(DURATION)
robot.stop()
left_ticks = robot.encoders.left.ticks
right_ticks = robot.encoders.right.ticks
diff = left_ticks - right_ticks
diff_pct = (diff / max(abs(left_ticks), abs(right_ticks), 1)) * 100
print(f"Left: {left_ticks} ticks")
print(f"Right: {right_ticks} ticks")
print(f"Difference: {diff} ticks ({diff_pct:+.1f}%)")
print()
if abs(diff_pct) > 5:
print("Significant drift — closed-loop control will help!")
else:
print("Your motors are unusually well-matched. Lucky!")
Run 3 times and record:
| Run | Left ticks | Right ticks | Difference | Drift direction |
|---|---|---|---|---|
| 1 | left / right | |||
| 2 | ||||
| 3 |
Note
Even 5% difference between wheels causes visible drift over 1 meter. This isn't a defective robot — it's normal. Motor manufacturing tolerance, bearing friction, and even floor surface cause asymmetry.
Task 2 — Measure Physical Drift
Mark a starting line on the floor. Drive the robot at PWM 80 for 3 seconds and measure:
- Distance traveled: ______ cm
- Lateral drift from straight line: ______ cm
This is your open-loop baseline. You'll compare against it at the end.
Part 2: Close the Loop (~20 min)
The Idea
Instead of commanding equal PWM (and hoping), command a target speed and let each wheel's controller adjust its own PWM to match:
Target speed ──►(+)──► P-Controller LEFT ──► PWM_L ──► Motor L ──┐
│-│ │
▲ │
└──────────────── Encoder L ◄──────────────────────┘
Target speed ──►(+)──► P-Controller RIGHT ──► PWM_R ──► Motor R ──┐
│-│ │
▲ │
└──────────────── Encoder R ◄──────────────────────┘
Two independent controllers, one per wheel. Each corrects its own motor.
Task 3 — Dual-Wheel Speed Controller
from picobot import Robot
import time
robot = Robot(encoders=True)
TARGET_SPEED = 40 # ticks/sec — adjust for your robot
KP = 5 # Start small
pwm_left = 0.0
pwm_right = 0.0
print(f"Closed-loop: target {TARGET_SPEED} tps, Kp = {KP}")
input("Place robot on starting line. Press Enter...")
robot.encoders.reset()
try:
for i in range(20): # ~5 seconds at 50 ms intervals
robot.encoders.update()
# Left controller
error_l = TARGET_SPEED - robot.encoders.left.speed_tps
pwm_left = max(0, min(255, pwm_left + KP * error_l))
# Right controller
error_r = TARGET_SPEED - robot.encoders.right.speed_tps
pwm_right = max(0, min(255, pwm_right + KP * error_r))
robot.set_motors(int(pwm_left), int(pwm_right))
if i % 20 == 0:
print(f"L: {robot.encoders.left.speed_tps:6.0f} tps "
f"(PWM {pwm_left:5.1f}) "
f"R: {robot.encoders.right.speed_tps:6.0f} tps "
f"(PWM {pwm_right:5.1f})")
time.sleep(0.05)
finally:
robot.stop()
# Final tick comparison
left_ticks = robot.encoders.left.ticks
right_ticks = robot.encoders.right.ticks
diff = left_ticks - right_ticks
print(f"\nLeft: {left_ticks} Right: {right_ticks} Diff: {diff}")
Task 4 — Tune Kp
Run the controller with different Kp values and observe:
| Kp | Left-right tick difference | Behavior |
|---|---|---|
| 0.01 | ||
| 0.05 | ||
| 0.1 | ||
| 0.3 | ||
| 0.5 |
What to Look For
- Too low Kp: Speed converges slowly, wheels may still drift noticeably
- Good Kp: Both wheels reach target quickly, small tick difference, smooth motion
- Too high Kp: Jerky motion, PWM values oscillate, robot may vibrate
Part 3: Verify the Improvement (~15 min)
Task 5 — Side-by-Side Comparison
Repeat the physical measurement from Task 2, now with closed-loop control:
| Test | Distance (cm) | Lateral drift (cm) | Left-right tick diff |
|---|---|---|---|
| Open-loop (PWM 80, 80) | |||
| Closed-loop (encoder feedback) |
Task 6 — The 1-Meter Challenge
Mark a 1-meter track on the floor. Drive the robot along it and measure lateral deviation at the end:
from picobot import Robot
import time
robot = Robot(encoders=True)
# === YOUR CALIBRATION ===
MM_PER_TICK = ___ # From Module 13
TARGET_DISTANCE_MM = 1000
# ========================
TARGET_SPEED = 400
KP = ___ # Your tuned value from Task 4
pwm_left = 0.0
pwm_right = 0.0
print("1-meter straight line challenge")
input("Align robot at start. Press Enter...")
robot.encoders.reset()
try:
while True:
robot.encoders.update()
# Check if we've traveled far enough (average of both wheels)
avg_ticks = (robot.encoders.left.ticks + robot.encoders.right.ticks) / 2
distance_mm = avg_ticks * MM_PER_TICK
if distance_mm >= TARGET_DISTANCE_MM:
break
# Speed controllers
error_l = TARGET_SPEED - robot.encoders.left.speed_tps
pwm_left = max(0, min(255, pwm_left + KP * error_l))
error_r = TARGET_SPEED - robot.encoders.right.speed_tps
pwm_right = max(0, min(255, pwm_right + KP * error_r))
robot.set_motors(int(pwm_left), int(pwm_right))
time.sleep(0.05)
finally:
robot.stop()
print(f"Encoder distance: {distance_mm:.0f} mm")
print(f"Left ticks: {robot.encoders.left.ticks}")
print(f"Right ticks: {robot.encoders.right.ticks}")
print(f"Measure lateral drift with a ruler: ______ cm")
Goal: less than 2 cm lateral drift over 1 meter.
Checkpoint — Straight Line Achieved
Compare your open-loop drift from Task 2 to your closed-loop result. The improvement should be dramatic. The robot still won't be perfect — there's no heading feedback yet (that comes in Module 17 with encoder-based turns). But matched wheel speeds eliminate the largest source of drift.
What You Discovered
| Concept | What You Learned |
|---|---|
| Motor asymmetry | Equal PWM ≠ equal speed — this is normal, not a defect |
| Independent controllers | One P-controller per wheel, each correcting its own motor |
| Measurable improvement | Quantify before/after with tick differences and ruler measurements |
| Distance-based stopping | Use encoder odometry to stop at a target distance, not a time |
What's Next?
You've driven a straight line with matched speeds. Next: Drive to Distance — use encoder odometry to command exact distances with smooth velocity profiles.