Hardware Integration
The Hardware class in code/controller/current/rc_car_app/hardware.py is the single hardware interface for the Raspberry Pi 5 controller. It owns the steering servo through the PCA9685 Servo Controller, the four PWM control channels for the JGB37-520 DC motors (12 V, 550 RPM) through the Yahboom AT8236 Motor Controller, and the Hall-effect wheel-speed sensor. The main loop in runtime.py never talks to GPIO or I2C directly; it reads and writes attributes on one Hardware instance, which keeps model changes, safety logic, and dashboard code from silently changing pin behavior.
How It Works
Hardware.__init__(pulse_callback) builds every device once at startup. It first assigns dummy stand-ins (DummyServo, DummyPWM, DummyDigitalInput) to every attribute, then attempts to bring up the real devices in order:
- Steering servo: if
USE_PCA9685_SERVOis true, aPCA9685SteeringServowrapper is created for the PCA9685 Servo Controller at I2C address0x40, channel 0. - JGB37-520 drive motors: four
PWMOutputDevicecontrol channels are opened at 1000 Hz on the AT8236 Motor Controller pins: right forward/backward on GPIO 19/20 and left forward/backward on GPIO 25/13. - Hall-effect wheel-speed sensor: if enabled, a pull-up
DigitalInputDeviceon GPIO 24 sends both signal edges to the runtime pulse callback.
The exposed interface is deliberately attribute-based. The runtime writes hardware.steering_servo.value, hardware.motor_left_fwd.value, and so on, and reads hall pulses through the callback. It also exposes hardware.gpio_initialized (set true only after every device came up) and hardware.cleanup().
Device creation is retry-guarded. _init_device(label, pin, factory) calls the factory up to four times, sleeping 0.25 s between attempts, but only retries on transient errno.EAGAIN / "Resource temporarily unavailable" errors — a real wiring/config fault fails fast with a labeled RuntimeError naming the device and pin.
Why This Choice
- One abstraction means the whole hardware surface can be swapped for dummies. If any device fails to initialize, the
exceptblock in__init__printsError initializing GPIO: ...; Running in simulation mode., runscleanup(), and re-assigns all-dummy devices. The rest of the runtime keeps running (useful for bench work off the car, and so a single bad sensor does not crash the whole controller). - The dummy classes share the same
.value/.close()shape as the real devices, so runtime code never needs to branch on whether hardware is present. - Keeping every write behind this class enforces the project rule that hardware compensation (steering center trim, per-side motor PWM scaling) lives in the mapping/hardware layer, not in model labels or the control loop.
Constants and Interface
| Field | Value |
|---|---|
| Owning file | code/controller/current/rc_car_app/hardware.py |
| Public devices | steering_servo, motor_left_fwd, motor_left_bwd, motor_right_fwd, motor_right_bwd, hall_sensor |
| Init flag | gpio_initialized (true only after all devices came up) |
| Retry policy | _init_device: 4 attempts, 0.25 s apart, only on EAGAIN / "Resource temporarily unavailable" |
| Fallback | On any init error: print, cleanup(), then all-dummy devices ("simulation mode") |
| Config gates | USE_PCA9685_SERVO, ENABLE_HALL_SENSOR in config.py |
Failure Symptom
If wiring or I2C is unavailable at boot, the console prints Error initializing GPIO: <reason>. Running in simulation mode. The car then reads and writes dummy devices: steering does not move, motors stay at 0, and the Hall-effect wheel-speed sensor produces no pulses, so the dashboard shows 0 speed even while throttle is applied. The reason string identifies the device or pin that failed.
Motor and Cleanup Rules
Positive normalized PWM drives the forward pin pair; negative PWM selects reverse. Braking and gear logic are resolved before these writes. Left and right scale factors belong here rather than in model labels, and both are neutral unless a controlled balance test supports a change.
cleanup() sets owned outputs to zero and closes GPIO/PCA9685 devices. Initialization failure also invokes cleanup before installing dummy devices. Dummy mode is useful for software work, but it must be treated as "no physical control," not a healthy-car state.