The World Robot Olympiad's Future Engineers category asks teams to build a fully autonomous self-driving car that completes laps of a track containing randomly placed obstacles. There are no humans in the loop once the run starts. The car has to detect lane boundaries, classify obstacles by color, decide which side to pass them on, and finish in under three minutes — all from on-board compute, with no off-board offload.
I led the University of Ghana's official entry. The constraints were real: a Raspberry Pi 3B+ for compute, a budget that ruled out LiDAR, and a build timeline running parallel to my final exam period. The interesting design question was how to get reliable obstacle perception out of cheap sensors without lying to ourselves about what they could see.
The car uses three perception inputs: a forward-facing camera for color classification, a ring of three VL53L0X time-of-flight sensors for distance, and an MPU6050 IMU for heading. The TOF sensors share an I²C bus, which is the part of this project that consumed the most time and is worth describing.
Three VL53L0X units cannot share an I²C bus by default — they all power up to the same address (0x29). The fix is to wire each sensor's XSHUT pin to a separate GPIO on the Pi, hold all three sensors in shutdown at boot, then bring them up one at a time and reassign each to a unique address before bringing up the next. The bring-up sequence happens in init_sensors.py and runs in roughly 80ms.
┌─────────────┐
│ Pi 3B+ │
└──────┬──────┘
│ I²C
┌────────────┼────────────┐
│ │ │
┌────▼───┐ ┌────▼───┐ ┌────▼───┐
│ VL53L0X│ │ VL53L0X│ │ VL53L0X│
│ 0x30 │ │ 0x31 │ │ 0x32 │
└────────┘ └────────┘ └────────┘
XSHUT ← G17 XSHUT ← G27 XSHUT ← G22Power is supplied by an 11.1V LiPo through an XL4015 buck converter to 5V for the Pi and a separate 6V rail for the MG996R steering servo. The drive motor is a GA12-N20 12V geared motor at 120 RPM, driven through an L298N H-bridge.
The control loop runs at 30 Hz. Each tick: read TOF distances, read IMU heading, classify the camera frame for nearest obstacle color, and decide steering. Decision logic is a small finite state machine — cruise, obstacle-left, obstacle-right, recover — rather than anything learned. Learned controllers were considered and deferred; a hand-written FSM was faster to debug under the time pressure and the rules of the FSM are auditable in a way that a network's behavior is not.
The single biggest tradeoff was vision. A real solution would use stereo cameras or depth sensing. We had one RGB camera and a Pi 3B+ with no GPU acceleration available, so the obstacle classifier is a thresholded HSV mask rather than a neural network. This works on the WRO track because the obstacles are large, primary-colored, and consistently lit — but it would not generalize to anything outside that environment, and the writeup is honest about that. Calling this “computer vision” would be overselling it.
The second tradeoff was the IMU. The MPU6050 drifts. We do not have a magnetometer to correct it. Instead, the IMU is only trusted for heading deltas within a single straight segment; at every turn, the heading estimate is reset against the known turn angle. This works because the track is regular. It would fail in any open-world setting.
The car completes practice runs reliably at the time of writing. The build is hardware-complete, the firmware is feature-complete for the qualification round, and we are now in the tuning phase — adjusting the FSM thresholds against the actual practice track in Accra rather than the simulated one I built earlier in the term.
Two open items: integrating an encoder on the drive motor for closed-loop speed control (currently the motor runs open-loop on a PWM duty cycle), and adding a second camera positioned for rear obstacle detection during reverse maneuvers. Both are deferred until after the qualification round.