Project Hail Mary Robot: Spec Sheet & Difficulty
Inspired by the autonomous, environment-sniffing probes in Andy Weir's sci-fi novel, the Project Hail Mary robot is a ruggedized 2WD rover designed to navigate obstacles and map volatile organic compounds (VOCs) in real-time. This build uses the ESP32's dual-core processing to handle I2C sensor polling and PWM motor control simultaneously without blocking.
Difficulty Rating: 3/5 (Intermediate - requires logic-level shifting and I2C debugging)
Estimated Build Time: 3-4 hours
Primary Function: Autonomous obstacle avoidance with environmental VOC/temperature telemetry
Parts List & Pin Mapping
To replicate this exact build, source the following specific module variants. Substituting the motor driver or sensor will require code modifications.
- Microcontroller: ESP32-WROOM-32 DevKit V1 (38-pin)
- Motor Driver: Pololu TB6612FNG Dual Motor Driver Carrier (Item #713) - chosen over the L298N for its 1.2A continuous current and low voltage drop.
- Environmental Sensor: Adafruit BME680 Breakout (Product ID 3665) - includes onboard I2C pull-ups and 3.3V regulation.
- Distance Sensor: Standard HC-SR04 Ultrasonic Module (5V logic)
- Chassis: Generic 2WD Acrylic Smart Car Chassis with TT gear motors
- Power: 2S 7.4V 18650 Li-ion battery pack with an integrated 10A BMS
Pin Mapping Table
| ESP32 GPIO | Component | Function / Signal |
|---|---|---|
| GPIO 27 | TB6612FNG | PWMA (Motor A Speed) |
| GPIO 26 | TB6612FNG | AIN1 (Motor A Direction 1) |
| GPIO 25 | TB6612FNG | AIN2 (Motor A Direction 2) |
| GPIO 33 | TB6612FNG | PWMB (Motor B Speed) |
| GPIO 32 | TB6612FNG | BIN1 (Motor B Direction 1) |
| GPIO 14 | TB6612FNG | BIN2 (Motor B Direction 2) |
| GPIO 21 | BME680 | I2C SDA |
| GPIO 22 | BME680 | I2C SCL |
| GPIO 12 | HC-SR04 | Trigger (3.3V safe) |
| GPIO 13 | HC-SR04 | Echo (Requires voltage divider to 3.3V!) |
Assembly & Critical Wiring Steps
- Power Distribution: Wire the 7.4V Li-ion pack directly to the TB6612FNG VM and VCC pins. Do not power the motors through the ESP32's VIN pin. The ESP32 should be powered via its micro-USB port for debugging, or via a separate 5V buck converter from the battery pack for field deployment.
- Logic Level Shifting (Crucial): The HC-SR04 Echo pin outputs 5V. The ESP32 GPIOs are strictly 3.3V tolerant. You must build a voltage divider using a 1kΩ and 2kΩ resistor between the HC-SR04 Echo pin and ESP32 GPIO 13. Failing to do this will permanently fry the GPIO pin.
- I2C Bus Setup: Connect the BME680 SDA/SCL to GPIO 21/22. Because we are using the Adafruit breakout, external pull-up resistors are not required. If using a generic bare-bones BME680 module, add 4.7kΩ pull-ups to 3.3V.
- Motor Driver Standby: Tie the TB6612FNG STBY pin directly to VCC (7.4V) to keep the driver active, or wire it to an ESP32 GPIO if you want software-controlled sleep modes.
Complete ESP32 Control Code
This code targets the ESP32 Arduino Core v3.x. It uses the modern ledcAttach API for PWM, avoiding deprecated channel functions. It includes a non-blocking obstacle avoidance routine and continuous VOC sampling.
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME680.h>
// --- Pin Definitions ---
#define PWMA 27
#define AIN1 26
#define AIN2 25
#define PWMB 33
#define BIN1 32
#define BIN2 14
#define TRIG_PIN 12
#define ECHO_PIN 13
#define SEALEVELPRESSURE_HPA (1013.25)
Adafruit_BME680 bme;
// Motor control helper
void setMotor(int dirPin1, int dirPin2, int pwmPin, int speed) {
if (speed > 0) {
digitalWrite(dirPin1, HIGH);
digitalWrite(dirPin2, LOW);
} else if (speed < 0) {
digitalWrite(dirPin1, LOW);
digitalWrite(dirPin2, HIGH);
} else {
digitalWrite(dirPin1, LOW);
digitalWrite(dirPin2, LOW);
}
ledcWrite(pwmPin, abs(speed));
}
float getDistance() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// 30000us timeout prevents infinite hanging
long duration = pulseIn(ECHO_PIN, HIGH, 30000);
return (duration * 0.0343) / 2.0;
}
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("Project Hail Mary Probe Initializing...");
// Configure Motor Pins (ESP32 Core v3.x API)
pinMode(AIN1, OUTPUT); pinMode(AIN2, OUTPUT);
pinMode(BIN1, OUTPUT); pinMode(BIN2, OUTPUT);
ledcAttach(PWMA, 5000, 8);
ledcAttach(PWMB, 5000, 8);
// Configure Ultrasonic Pins
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
// Initialize I2C and BME680
Wire.begin(21, 22);
if (!bme.begin(0x77)) {
Serial.println("Could not find a valid BME680 sensor, check wiring!");
while (1); // Halt execution on sensor failure
}
// Configure BME680 oversampling
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C for 150 ms
Serial.println("Probe Ready. Engaging autonomous navigation.");
}
void loop() {
float distance = getDistance();
// Obstacle Avoidance Logic
if (distance > 0 && distance < 20.0) {
// Obstacle detected within 20cm
setMotor(AIN1, AIN2, PWMA, -150); // Reverse Left
setMotor(BIN1, BIN2, PWMB, 150); // Forward Right (Pivot)
delay(600);
} else {
// Path clear, move forward
setMotor(AIN1, AIN2, PWMA, 200);
setMotor(BIN1, BIN2, PWMB, 200);
}
// Telemetry (Non-blocking sensor read)
if (bme.performReading()) {
Serial.print("VOC: "); Serial.print(bme.gas_resistance / 1000.0); Serial.print(" KOhms | ");
Serial.print("Temp: "); Serial.print(bme.temperature); Serial.println(" *C");
} else {
Serial.println("Failed to perform BME680 reading.");
}
delay(100); // Loop pacing
}
Debugging: First Three Things to Check When It Fails
When your rover fails to boot or navigates erratically, check these three specific failure modes in order.
1. I2C Sensor Initialization Failure
Exact Error String: Could not find a valid BME680 sensor, check wiring!
Ranked Causes:
- Wrong I2C Address: The Adafruit breakout defaults to
0x77(which the code uses). Generic clone boards often default to0x76. Changebme.begin(0x77)tobme.begin(0x76)in the setup block. - Missing Pull-ups: If using a bare module without onboard resistors, the I2C bus will float. Add 4.7kΩ pull-ups to the 3.3V line.
- SDA/SCL Swap: GPIO 21 is SDA, GPIO 22 is SCL on the standard ESP32 DevKit. Verify they aren't reversed on your breadboard.
2. Spontaneous ESP32 Reboots During Movement
Exact Error String: Brownout detector was triggered (Printed in the serial monitor upon reboot)
Ranked Causes:
- Shared Power Rails: The TT motors draw up to 800mA on stall. If the ESP32 is powered via the battery pack's 5V buck converter and the converter cannot handle the transient voltage sag, the ESP32's brownout detector trips. Use a dedicated 5V 3A buck converter (like the LM2596) for the ESP32 logic.
- Missing Flyback Diodes: The TB6612FNG has internal diodes, but if you are using a cheaper clone board, inductive kickback from the motors may be resetting the logic. Ensure you are using a genuine Pololu or high-quality TB6612FNG module.
3. Ultrasonic Sensor Hanging or Reading Zero
Exact Error String: Serial prints Distance: 0.00 cm continuously, and the rover crashes into walls.
Ranked Causes:
- Fried GPIO Pin: If you skipped the voltage divider on the Echo pin, the 5V signal has likely damaged GPIO 13. Move the Echo wire to a new pin (e.g., GPIO 15) and update the
#define. - Acoustic Crosstalk: If the HC-SR04 is mounted too close to the chassis or the BME680 gas tube, sound waves bounce off the chassis rather than the environment. Mount the sensor at least 2cm above the chassis plane.
Extending and Simplifying the Build
To Simplify: If the BME680 is out of stock or too expensive (~$20), swap it for a BME280 (~$5). You will lose the VOC gas resistance readings (the "Xenonite" sniffing capability), but the I2C wiring and Adafruit library syntax remain nearly identical. Just change the include to Adafruit_BME280.h and remove the setGasHeater line.
To Extend: For true off-grid telemetry, implement ESP-NOW. This allows the Project Hail Mary robot to broadcast its VOC and distance data directly to a base-station ESP32 up to 200 meters away without needing a WiFi router. You can achieve this by adding the esp_now.h library and packaging the bme.gas_resistance and distance floats into a struct sent via the esp_now_send() function inside the loop.
Frequently Asked Questions
How do I power the Project Hail Mary robot without browning out the ESP32?
The golden rule for mixed-voltage robotics is separating logic power from motive power. Run your 7.4V Li-ion pack directly to the TB6612FNG motor driver. Then, use a high-quality switching buck converter (rated for at least 3A) to step the 7.4V down to 5V, and feed that into the ESP32's 5V/VIN pin. Never power the ESP32 directly from the 3.3V output of a cheap breadboard power supply module when motors are involved; the transient noise will cause continuous brownouts.
Can I use an Arduino Uno instead of the ESP32 for this Project Hail Mary build?
You can, but it is not recommended. The Arduino Uno operates at 5V, which means you won't need the voltage divider for the HC-SR04, but you will need a logic-level shifter to safely interface with the 3.3V BME680 sensor. More importantly, the Uno lacks the processing speed and memory to handle complex non-blocking sensor fusion if you decide to add WiFi telemetry or a camera later. The ESP32's dual-core architecture allows you to dedicate Core 0 to motor control and Core 1 to I2C sensor polling.
Why is my BME680 VOC reading stuck at zero on the Hail Mary probe?
The BME680 gas sensor requires a "burn-in" period. The internal micro-hotplate needs to reach a stable 320°C to accurately measure gas resistance. When you first power the board, the initial 5 to 10 readings may return 0 or erratic values. Let the rover idle for 5 minutes before relying on the VOC telemetry. Additionally, ensure your code includes bme.setGasHeater(320, 150);—without this, the heater element never turns on, and the VOC sensor remains completely blind.






