A four-wheel rover that reads its environment, changes how it drives in response, shows everything live on a dashboard, and logs it all to an SD card — a mobile sensing platform, not just a moving robot.
Build a rover that does more than move — one that senses, reacts, and reports.
The goal was a mobile sensing platform: detect changes in its surroundings, change its behaviour in response, and communicate what it's sensing in real time. Light sets its speed, a water sensor and an ultrasonic sensor act as safety stops, temperature and distance stream to an on-board dashboard, and every reading is logged to an SD card for later.
The ESP32 reads every sensor on a non-blocking loop, turns light into a target speed, watches for hazards, and drives the display, the motors, and the SD log together.
A photoresistor sets the driving speed on a smoothed curve — brighter surroundings drive faster, darker ones slower — across a tunable light window shown live as a percentage.
A water sensor halts the rover the moment it detects water; an ultrasonic sensor stops it at 15 cm from an obstacle and releases at 20 cm, so it doesn't stutter on the threshold.
A boxed "Rover Status" dashboard shows raw and interpreted values — light, temperature, distance, water, speed mode — with segmented bars for light level and motor speed.
Every sample is written to a CSV on a microSD card — raw and interpreted values, motor state, and mode — sharing the SPI bus with the display.
An RGB LED signals the rover's state at a glance — running, warning, or stopped — without needing to read the screen.
Built on a millis() loop with sensor filtering, smooth speed ramping, motors held off until init completes, and safety overrides that always win.
Multiple sensors and outputs sharing one ESP32 and a single SPI bus — part of what made integration the real challenge.
Each sensor and output proved on its own, then integrated one at a time — the only sane way to bring this many subsystems together.
A. Motor & light
Mapped light levels to motor speeds so the rover changed pace with its surroundings.
B. Temperature
Read the LM35 and showed changing temperature live on the TFT.
C. Water detection
Verified the rover stops on water and updates the display to match.
D. Ultrasonic
Tested obstacle detection and distance measurement.
E. Dashboard & logging
Confirmed the TFT dashboard and SD logging ran together while driving.
Three problems shaped the final rover: a sensor that lied, motors that wouldn't start, and a board that ran out of pins.
The motors would cut out and the dashboard flipped to a water warning — with no water anywhere near the sensor.
The water sensor read HIGH when wet and LOW when dry — the opposite of the usual module default. My code assumed the default, so a dry sensor looked "wet," firing the safety override and killing the motors.
Set the pin to INPUT_PULLDOWN and defined the wet state as HIGH to match how this sensor actually behaves.
Don't trust a datasheet's "default" — measure what the specific part in your hand actually does before wiring logic around it.
From a standstill the motors buzzed but didn't move — they needed a physical finger-push to get going, even at full power.
A power-delivery limit, not software. The battery pack's internal resistance plus the driver's drop — with two motors paralleled per channel — made the supply sag hard under the four-motor stall inrush, so the motors saw far less than the measured idle voltage at the moment of breakaway.
Added a battery, then replaced a single kick with a pulsed, alternating fwd/rev "rock" kickstart to break stiction; recommended a large capacitor across the motor supply and a low-internal-resistance pack as the real hardware cure.
Software can only paper over a power problem so far. The marginal supply is the honest ceiling on reliable self-starting — and the reason a better battery is next.
The ESP32 board didn't break out enough usable GPIOs to add the ultrasonic sensor on top of everything else.
The header exposed only one input-only pin and none of the other spare inputs, so the ultrasonic's echo line had nowhere clean to go.
Moved the echo line to a free GPIO and dropped the sound sensor to reclaim its pin, adding a resistor divider to step the echo's 5 V down to the ESP32's 3.3 V.
Gave up ambient-sound sensing to gain obstacle detection — the more useful safety feature won the pin.
Where the constrained board and the power budget forced a choice.
Limited usable GPIOs meant one had to go; obstacle detection was worth more than ambient sound.
The battery pack sags under stall inrush, so reliable self-start needs a better pack and a buffer capacitor — a hardware fix, not a code one.
The display and SD card share one bus, which keeps wiring simple but means they take turns rather than run truly in parallel.
Added charging for the logic side while the motors keep their own pack, so motor noise and sag don't disturb the ESP32 — at the cost of two supplies to manage.