Gonzalo Ferrero Martín

TU/e 5EID0 · Group 10 · 2026

Venus rover

Run-on-boot firmware for a rover that hunts colored cubes, maps terrain, and shares every find with its partner.

The autopilot drawn as a plan: four states in a row, SCAN, APPROACH, EVALUATE and ROAM, joined by arrows; 72 ticks in 5 degree steps around SCAN; a dashed boundary-evade rectangle enclosing all four, checked at the top of every tick; and a three-line legend of the robustness rules. boundary evade — checked at the top of every tick SCAN APPROACH EVALUATE ROAM 5° × 72 cube confirmed: ≥2 of 6 reads within 5 cm color: average of 20 reads · registered on detection strength finds deduplicated within 12 cm · both robots stop at six cubes
Fig. 1 — Reactive autopilot: SCAN → APPROACH → EVALUATE → ROAM, a 72-position scan in 5° steps, and a boundary interrupt checked every tick that pre-empts all of them.
Role
Firmware + mission-control tooling · 5EID0 · Group 10
Stack
C · Python · MQTT · ESP32-C3
Board
PYNQ-Z2 + ESP32
Status
complete — course project · 2026

The task

For the 5EID0 “Venus Project” (Group 10, 2026) a PYNQ-Z2 rover has to hunt colored cubes, map terrain, and coordinate with a partner robot over one MQTT link — UART0 to an ESP32 to the broker. The firmware is C, runs from boot, and I also wrote the Python mission-control tool.

Two rovers start back to back at the origin of an arena bounded by black tape, with cubes of two sizes, 3 cm and 6 cm, and obstacles we called mountains. The job is to find all six cubes, report each with its position, size, color and the local temperature, and stop. No operator and no keyboard: the firmware starts with no arguments and talks only over MQTT, so the robot has to be sure of what it has seen before it acts on it.

The report’s non-functional requirements set the tempo: the distance array had to refresh the obstacle logic at ≥ 10 Hz, a cliff or boundary had to stop forward motion within 200 ms, and the map had to update within 1 s of a packet. The wireless link is an ESP32-C3-WROOM-02 connectivity board on UART, speaking MQTT to the broker; only wireless communication was allowed during the mission (Fig. 2).

Block diagram: two identical robot blocks, Robot 1 on the left and Robot 2 on the right, each holding three boxes, electronic circuits and interfaces and embedded software both pointing up to algorithms. Two-headed arrows join each robot to a central communication and networking box, which is joined by a downward arrow to computer software and user interface. Robot 1 algorithms electronic circuits and interfaces embedded software Robot 2 algorithms electronic circuits and interfaces embedded software communication and networking computer software and user interface
Fig. 2 — Two identical robots, one communication and networking subsystem, one base station — after Fig. 2.1 of the team’s final report.

Sensors and odometry

A vertical stack of three VL53L0X time-of-flight sensors sits on a shared I²C bus: the bottom beam for 3 cm cubes, the middle for 6 cm cubes, the top for obstacles — “mountains” (Fig. 3). Two TCRT5000 IR sensors watch for the black-tape boundary, a TCS3200 reads color, and an NTC thermistor reads temperature. Two stepper-driven wheels give differential drive, with dead-reckoned odometry from the commanded steps — no encoders: 1600 steps/rev, Ø75 mm wheels, 120 mm track.

Every VL53L0X ships at the same I²C address, 0x29, so the bus is sorted out at boot: the firmware holds the middle and bottom sensors in shutdown through their XSHUT pins, finds the always-on top sensor and moves it to 0x68, then wakes the other two one at a time and re-addresses them to 0x69 and 0x6A.

Elevation, drawn to scale at ten units per centimeter: the rover’s front face on the left with three time-of-flight sensors stacked on it, and three dashed sight lines running right — the bottom beam to a 3 centimeter cube, the middle beam over it to a 6 centimeter cube, the top beam over both to a hatched obstacle at the far right. VL53L0X ×3 · one I²C bus, shared bottom mid top 3 cm 6 cm obstacle (“mountain”)
Fig. 3 — Three time-of-flight sensors on one shared I²C bus, one beam per height: bottom for 3 cm cubes, middle for 6 cm cubes, top for obstacles. Cubes drawn to scale; sensor heights schematic.

The XSHUT bring-up was verified with an I²C bus scanner after each boot phase: one device at 0x29, then 0x68 and 0x29, then 0x69 joins, ending at a stable 0x68 / 0x69 / 0x6A. Settling delays below 50 ms caused boot-timing collisions, which is why the firmware waits 100 ms. Disconnecting AR4 or AR5 to simulate a dead sensor made the driver log a non-fatal timeout and carry on without locking the bus.

The NTC divider — 10 kΩ, 3.3 V, β = 4050 K — first read wrong because the ADC’s input impedance loaded the divider; a unity-gain op-amp buffer between divider and ADC fixed it. The TCS3200 is read as a frequency: S0/S1 at 20 % scaling, calibrated against white and black, 20 static readings per cube, classified by Euclidean distance to five color profiles. It is accurate at the 15–20 mm working distance and degrades with range. Fig. 4 shows how the sensors, the control loop and the link fit together, with the interfaces as they are actually wired.

Block diagram in three dashed groups. Sensing: four sensor boxes, TCRT5000 times two on digital GPIO, NTC 10 kilohm on ADC A5 via a unity-gain buffer, TCS3200 as a frequency output on GPIO, and VL53L0X times three on I squared C with XSHUT on AR4 and AR5, all feeding one arrow into the sensor interface. Robot control: sensor interface, then sensor processing plus navigation state machine, with a two-headed arrow to stepper motors. Communication and base station: a chain from UART with a 4-byte length plus JSON, to the ESP32-C3 connectivity board, to Wi-Fi and MQTT, to the MQTT broker, to the base station running the Python map. sensing robot control communication and base station TCRT5000 ×2 digital GPIO NTC 10 kΩ ADC A5 via unity-gain buffer TCS3200 frequency output on GPIO VL53L0X ×3 I²C, XSHUT on AR4/AR5 sensor interface (GPIO / I²C / ADC) sensor processing + navigation state machine stepper motors UART (4-byte length + JSON) ESP32-C3 connectivity board Wi-Fi / MQTT MQTT broker base station (Python map)
Fig. 4 — Sensing, control and communication, after Fig. 3.1 of the final report, with the sensor interfaces as wired.

The autopilot

Four reactive states: SCAN, turning in 5° steps, 72 per revolution, looking for the nearest unknown cube on the beams; APPROACH, driving toward a remembered cube and steering around a mountain if one is closer; EVALUATE, reading the color, registering and reporting the cube, then backing off 5 cm; and ROAM, turning a random direction and driving 40 cm when a full sweep finds nothing. A boundary-evade interrupt is checked at the top of every tick and pre-empts all four (Fig. 1).

The approach switches to an open-loop final drive inside 10 cm, because the time-of-flight sensors cannot read below about 6 cm, and gives up after 30 ticks rather than pushing forever. When the IR sensors see tape, the evade reverses, turns about 180° with ±30° of jitter, drops any approach in progress, and re-scans.

What survives a dropout

Time-of-flight reads drop out, so a cube is confirmed by a 6-read burst vote: at least two reads within 5 cm. Color is averaged over 20 reads, and a cube is registered on detection strength, so a weak color read never discards a real find. Finds are deduplicated within 12 cm. The thresholds were tuned against on-bench ToF readings, and the README says so, so the next person can re-measure rather than guess.

Both robots share one world frame — same origin, back-to-back headings — so a cube position from either robot is comparable with no transform at run time. Every new find goes out as a JSON frame over UART0 to the ESP32, which relays it to the broker; the partner drains its UART each tick and folds any find with a known color into its own registry. When the sixth cube is known, by either robot, both stop.

Mission control

The Python mission-control tool is an MQTT relay between the two robots, a JSON message logger, and a live matplotlib map of both robots’ paths, headings and every cube, drawn to scale — 3 cm against 6 cm.

Six revisions

The firmware went through six revisions. The third is the structural one: the sensor code, the ToF driver and the MQTT framing that had lived in separate files were consolidated into a single main.c of about 2,600 lines that links against only libpynq and libm, so deployment is one gcc command and a copy.