PCA9685 Servo Controller
This page records the decision to drive the Ackermann steering servo through a PCA9685 Servo Controller over I2C instead of generating the servo pulse directly from a Raspberry Pi 5 GPIO pin.
Decision
The steering servo is wired to a PCA9685 Servo Controller at I2C address 0x40, channel 0,
running at 50 Hz, and is driven through the Adafruit ServoKit abstraction. The
Raspberry Pi 5 never toggles the servo signal line itself; it writes an angle to the
PCA9685 Servo Controller, which owns the pulse-width generation in dedicated hardware. The exact
configuration lives in code/controller/current/rc_car_app/config.py:
PCA9685_I2C_ADDRESS = 0x40,PCA9685_SERVO_CHANNEL = 0,PCA9685_FREQUENCY_HZ = 50- Pulse range
STEERING_SERVO_MIN_PULSE_US = 1000toSTEERING_SERVO_MAX_PULSE_US = 2000 STEERING_SERVO_ACTUATION_RANGE_DEG = 180
The wrapper class PCA9685SteeringServo in
code/controller/current/rc_car_app/hardware.py wraps the board and exposes a
simple value setter in the logical 0..180 degree space (0 = left,
90 = center, 180 = right). Any real-hardware center/trim compensation is
applied inside that layer by apply_steering_center_trim_degrees(...), so the
rest of the runtime and the training labels only ever deal with clean logical
degrees.
Alternatives Considered
| Option | Pros | Cons |
|---|---|---|
| Direct Raspberry Pi 5 GPIO PWM (software or hardware PWM pin) | One fewer board; no I2C dependency | The Raspberry Pi 5's software PWM jitters under CPU load, causing servo twitch, and competes with the other GPIO/PWM users on the Raspberry Pi 5 (four motor pins and the Hall-effect wheel-speed sensor) |
| PCA9685 Servo Controller over I2C (chosen) | dedicated 50 Hz pulse generation reduces dependence on Raspberry Pi 5 loop timing; frees Raspberry Pi 5 GPIO; reuses the Adafruit ServoKit library |
one extra board + I2C, power, calibration, and servo dependencies |
Reason
The Raspberry Pi 5 is already running a 60 Hz software control loop that consumes the latest LiDAR, GPS, camera, and hall-sensor state while updating motor PWM. A software-timed servo pulse on that same CPU jitters whenever the loop is busy, and steering jitter is directly visible and bad for both driving and clean training data. Offloading the pulse to the PCA9685's own timer removes that class of problem entirely, and it follows the project rule to lean on existing, proven libraries rather than reinvent servo timing.
How to Know It Worked (Test Gate)
code/test_files/steering/pca9685_servo_test.pyandcode/test_files/steering/calibrate_servo.pydrive the servo through its range on the bench with no runtime attached.- On the car, a centered logical command (
90) should hold a steady physical center with no visible twitch even while the rest of the control loop is busy. - If the board is missing or I2C fails,
Hardware.__init__catches the error and falls back to aDummyServoso the controller still boots (simulation mode) instead of crashing.