Community project
Autonomous 6x6 AI Rover

This autonomous 6x6 rover uses an ESP32-CAM controller to navigate and sense its environment with multiple sensor modes: line-following via a 4-channel IR array, obstacle avoidance with ultrasonic ranging, and environmental monitoring through temperature, humidity, and gas sensors. The rover is powered by a protected 3S Li-ion battery pack and dual motor drivers, enabling independent control of left and right track pairs for precise maneuverability.
This guide provides a complete parts list, wiring diagram, and step-by-step assembly instructions for building the rover's power distribution, motor control, sensor integration, and camera link. The included firmware implements mode switching between manual, line-following, and autonomous obstacle-avoidance behaviors, with telemetry streaming and command handling over serial. Safe staged power-up procedures ensure all subsystems are verified before autonomous operation.
Wiring diagram
Interactive · read-only
Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.
Parts list
Bill of materials| Component | Qty | Notes |
|---|---|---|
| TB6612FNG dual motor driverTB6612FNG | 1 | TB6612FNG dual H-bridge motor driver breakout with raw AIN1/AIN2/PWMA and BIN1/BIN2/PWMB control inputs plus STBY. The motor terminals connect to the driver outputs, not directly to MCU GPIO. |
| TB6612FNG dual motor driverTB6612FNG | 1 | TB6612FNG dual H-bridge motor driver breakout with raw AIN1/AIN2/PWMA and BIN1/BIN2/PWMB control inputs plus STBY. The motor terminals connect to the driver outputs, not directly to MCU GPIO. |
| MCP23017 I/O Expander0x20 | 1 | Microchip MCP23017 16-bit GPIO expander controlled over I2C. Provides two 8-bit ports for external digital inputs or outputs. |
| HC-SR04HC-SR04 + 10k/20k divider | 1 | Ultrasonic distance measurement sensor |
| DHT22DHT22 | 1 | Digital temperature and humidity sensor |
| 4-channel TCRT5000 IR line array4-channel IR array | 1 | Four-channel line sensor array powered at 3.3 V; outputs D1 through D4 ordered from left to right. |
| MQ-2 gas sensor module with 5 V-to-3.3 V analog dividerMQ-2 + 10k/20k divider | 1 | Budget MQ-2 combustible-gas/smoke module. Its analog output is routed through a resistor divider to ESP32 ADC1; the digital threshold output is unused. It needs warm-up and is not a calibrated safety instrument. |
| 3S 18650 Li-ion battery pack3S 11.1V nominal | 1 | Three matched 18650 Li-ion cells in series, 11.1 V nominal / 12.6 V maximum, connected only through the 3S BMS. |
| 3S Li-ion BMS3S BMS | 1 | 3-series lithium battery-management board with balancing/protection. Its protected output feeds the fuse and main switch. |
| Dual-output buck regulator assembly6V motor buck + 5V/3A buck | 1 | Two buck converters fed from the protected 3S pack: one adjusted to the verified 6 V N20 motor rail, and one regulated 5 V/3 A minimum for ESP32 DevKit, ESP32-CAM, HC-SR04, and MQ-2. Use a fuse between BMS and switch. |
| ESP32-CAM AI Thinker vision controllerAI Thinker | 1 | Separate ESP32-CAM AI Thinker camera node. Power from the 5 V regulator; UART logic is 3.3 V. It is flashed independently with camera-stream firmware and accepts serial commands from the master. |
Assembly
6 stepsPrepare the 3S protected power system
Use three matched 18650 cells in the holder and connect the holder only to the B+ and B- battery terminals of the 3S BMS. Route BMS protected output through an inline fuse and the main power switch, then into the dual buck regulator VIN terminals. Set the electronics buck to exactly 5.0 V and the motor buck to the verified N20 motor voltage (assumed 6.0 V here) before connecting electronics.
- Tip: Place the fuse as close as possible to battery positive.
- Tip: Measure both buck outputs with a multimeter before connecting the ESP32 or drivers.
- Tip: Use wire/connectors rated for the expected motor-stall current.
- ⚠ Never charge 3S lithium cells with a single-cell charger.
- ⚠ Do not mix cell brands, capacities, or states of charge.
- ⚠ Do not proceed until the real motor voltage and aggregate stall current are verified.
Install motors and wire the two motor drivers
Mount the six N20 motors and wheels. Connect left front plus left middle motors in parallel to motor_driver_left channel A outputs; connect left rear motor to channel B outputs. Do the same on motor_driver_right for the right-side motors. Connect the 6 V motor rail to both VM pins, 3.3 V to both VCC pins, and all grounds to common ground.
- Tip: Temporarily label every motor lead before final harnessing.
- Tip: If a side runs backward during testing, swap that motor bank's two output wires rather than changing safety wiring.
- Tip: Add bulk capacitance close to each motor-driver VM/GND pair.
- ⚠ TB6612FNG channels must not exceed their continuous or stall-current limit; parallel motors can overload a channel.
- ⚠ Keep motor wiring physically separated from sensor and I2C wiring.
Wire the MCP23017 and motor-control signals
Connect MCP23017 VDD to 3.3 V, VSS to ground, SDA to ESP32 GPIO21, SCL to GPIO22, and RESET to 3.3 V. Connect GPA0–GPA3 to the left driver AIN1, AIN2, BIN1, BIN2; connect GPA4–GPA7 to the right driver AIN1, AIN2, BIN1, BIN2. Wire PWM and standby signals: left PWMA GPIO25, left PWMB GPIO26, left STBY GPIO27; right PWMA GPIO32, right PWMB GPIO33, right STBY GPIO5.
- Tip: Use short I2C wires and provide 3.3 V I2C pull-ups if your MCP23017 board lacks them.
- Tip: Keep all grounds joined at the regulated supply return.
- ⚠ GPIO5 is a boot-strapping pin; ensure the right-driver STBY input does not force it low during reset. If boot becomes unreliable, explicitly change this frozen allocation before hardware revision.
Install and wire navigation and environmental sensors
Power the DHT22 and four-channel line sensor array from 3.3 V. Connect DHT22 DATA to GPIO4 with a 10 kΩ pull-up to 3.3 V if the module does not include one. Connect line D1/D2/D3/D4, left to right, to GPIO13/GPIO14/GPIO18/GPIO19. Power HC-SR04 and MQ-2 from 5 V. Connect ultrasonic TRIG to GPIO23 and ECHO through a 5 V-to-3.3 V resistor divider to GPIO15. Connect MQ-2 AO through another divider to GPIO35.
- Tip: Mount the line array low and parallel to the ground; adjust each comparator trimmer on the real line surface.
- Tip: Allow the MQ-2 heater to warm up before treating readings as relative trends.
- Tip: Keep the DHT22 away from warm regulators and the MQ-2 heater.
- ⚠ Never connect HC-SR04 ECHO or MQ-2 AO directly to an ESP32 pin because these modules can output 5 V.
- ⚠ MQ-2 readings are not calibrated gas concentration or a life-safety alarm.
Install the camera node and serial link
Power the ESP32-CAM AI Thinker from the regulated 5 V rail and common ground. Cross the UART: master GPIO17 TX goes to ESP32-CAM U0R, and master GPIO16 RX goes to ESP32-CAM U0T. Mount the camera where motor vibration is minimized and keep its antenna/camera area clear.
- Tip: Use a 5 V supply capable of ESP32-CAM current peaks.
- Tip: Flash and bench-test the camera node separately before connecting the UART.
- ⚠ UART lines are 3.3 V logic only.
- ⚠ Do not power the ESP32-CAM from the DevKit 3.3 V pin.
Perform safe staged power-up and functional tests
With wheels raised off the bench, power the rover and confirm 5 V and 3.3 V rails. Deploy the master firmware with Schematik, verify the MCP23017 startup check, then test STOP and MANUAL movement commands one motor bank at a time. Confirm sensor telemetry before enabling LINE or AVOID modes. Calibrate line sensor polarity and thresholds after confirming all drive directions.
- Tip: Start at reduced PWM speed and test with the chassis lifted.
- Tip: Verify emergency STOP repeatedly before floor testing.
- Tip: Use a clear test area for obstacle avoidance.
- ⚠ Keep hands, wires, and clothing clear of wheels.
- ⚠ Disconnect the battery before changing motor, power, or driver wiring.
Pin assignments
Board wiring reference| Pin | Connection | Type |
|---|---|---|
| EXT | battery_pack B+ → 3S Li-ion BMS B+ | power |
| EXT | battery_pack B- → 3S Li-ion BMS B- | power |
| EXT | bms P+ → Dual-output buck regulator assembly VIN+ | power |
| EXT | bms P- → Dual-output buck regulator assembly VIN- | ground |
| EXT | power_regulator MOTOR_6V → TB6612FNG dual motor driver VM | power |
| VIN | power_regulator LOGIC_5V | power |
| GND | power_regulator GND | ground |
| 3V3 | motor_driver_left VCC | power |
| GND | motor_driver_left GND | ground |
| GPIO 25 | motor_driver_left PWMA | pwm |
| GPIO 26 | motor_driver_left PWMB | pwm |
| VIN | motor_driver_right VM | power |
| 3V3 | motor_driver_right VCC | power |
| GND | motor_driver_right GND | ground |
| GPIO 32 | motor_driver_right PWMA | pwm |
| GPIO 33 | motor_driver_right PWMB | pwm |
| 3V3 | gpio_expander VDD | power |
| GND | gpio_expander VSS | ground |
| GPIO 21 | gpio_expander SDA | i2c |
| GPIO 22 | gpio_expander SCL | i2c |
| 3V3 | gpio_expander RESET | power |
| VIN | ultrasonic VCC | power |
| GND | ultrasonic GND | ground |
| GPIO 23 | ultrasonic TRIG | digital |
| 3V3 | environment VCC | power |
| GND | environment GND | ground |
| GPIO 4 | environment DATA | data |
| 3V3 | line_sensor VCC | power |
| GND | line_sensor GND | ground |
| GPIO 13 | line_sensor D1 | digital |
| GPIO 14 | line_sensor D2 | digital |
| GPIO 18 | line_sensor D3 | digital |
| GPIO 19 | line_sensor D4 | digital |
| VIN | gas_sensor VCC | power |
| GND | gas_sensor GND | ground |
| GPIO 35 | gas_sensor AO | analog |
| VIN | camera_node 5V | power |
| GND | camera_node GND | ground |
| GPIO 17 | camera_node U0R | uart |
| GPIO 16 | camera_node U0T | uart |
| GPIO 27 | motor_driver_left STBY | digital |
| GPIO 5 | motor_driver_right STBY | digital |
| EXT | motor_driver_left AIN1 → MCP23017 I/O Expander GPA0-GPA7 | digital |
| EXT | motor_driver_left AIN2 → MCP23017 I/O Expander GPA0-GPA7 | digital |
| EXT | motor_driver_left BIN1 → MCP23017 I/O Expander GPA0-GPA7 | digital |
| EXT | motor_driver_left BIN2 → MCP23017 I/O Expander GPA0-GPA7 | digital |
| EXT | motor_driver_right AIN1 → MCP23017 I/O Expander GPA0-GPA7 | digital |
| EXT | motor_driver_right AIN2 → MCP23017 I/O Expander GPA0-GPA7 | digital |
| EXT | motor_driver_right BIN1 → MCP23017 I/O Expander GPA0-GPA7 | digital |
| EXT | motor_driver_right BIN2 → MCP23017 I/O Expander GPA0-GPA7 | digital |
| GPIO 15 | ultrasonic ECHO | digital |
Firmware
ESP32#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_MCP23X17.h>
#include <DHT.h>
// Hoisted type definitions
enum RoverMode { STOPPED, MANUAL, LINE_FOLLOW, AVOID };
// Forward declarations
void setDirections(bool leftForward, bool rightForward);
void setMotorPwm(uint8_t leftSpeed, uint8_t rightSpeed);
void drive(int leftSpeed, int rightSpeed);
void emergencyStop();
long readDistanceCm();
void lineFollowStep();
void obstacleAvoidStep();
void sendTelemetry();
void handleCommand(String command);
void pollCommands(Stream &port, String &buffer);
constexpr uint8_t LEFT_A_PWM = 25, LEFT_B_PWM = 26, LEFT_STBY = 27;
constexpr uint8_t RIGHT_A_PWM = 32, RIGHT_B_PWM = 33, RIGHT_STBY = 5;
constexpr uint8_t ULTRASONIC_TRIG = 23, ULTRASONIC_ECHO = 15;
constexpr uint8_t DHT_PIN = 4, GAS_ADC = 35;
constexpr uint8_t LINE_LEFT_OUTER = 13, LINE_LEFT_INNER = 14;
constexpr uint8_t LINE_RIGHT_INNER = 18, LINE_RIGHT_OUTER = 19;
constexpr uint8_t CAMERA_RX = 16, CAMERA_TX = 17;
constexpr uint8_t MOTOR_SPEED = 150;
constexpr int OBSTACLE_LIMIT_CM = 25;
Adafruit_MCP23X17 mcp;
DHT dht(DHT_PIN, DHT22);
HardwareSerial cameraSerial(2);
RoverMode mode = STOPPED;
unsigned long lastAutonomyMs = 0, lastTelemetryMs = 0;
void setDirections(bool leftForward, bool rightForward) {
mcp.digitalWrite(0, leftForward); mcp.digitalWrite(1, !leftForward);
mcp.digitalWrite(2, leftForward); mcp.digitalWrite(3, !leftForward);
mcp.digitalWrite(4, rightForward); mcp.digitalWrite(5, !rightForward);
mcp.digitalWrite(6, rightForward); mcp.digitalWrite(7, !rightForward);
}
void setMotorPwm(uint8_t leftSpeed, uint8_t rightSpeed) {
ledcWrite(LEFT_A_PWM, leftSpeed); ledcWrite(LEFT_B_PWM, leftSpeed);
ledcWrite(RIGHT_A_PWM, rightSpeed); ledcWrite(RIGHT_B_PWM, rightSpeed);
}
void drive(int leftSpeed, int rightSpeed) {
setDirections(leftSpeed >= 0, rightSpeed >= 0);
setMotorPwm(abs(leftSpeed), abs(rightSpeed));
digitalWrite(LEFT_STBY, HIGH);
digitalWrite(RIGHT_STBY, HIGH);
}
void emergencyStop() {
setMotorPwm(0, 0);
digitalWrite(LEFT_STBY, LOW);
digitalWrite(RIGHT_STBY, LOW);
mode = STOPPED;
}
long readDistanceCm() {
digitalWrite(ULTRASONIC_TRIG, LOW); delayMicroseconds(3);
digitalWrite(ULTRASONIC_TRIG, HIGH); delayMicroseconds(10);
digitalWrite(ULTRASONIC_TRIG, LOW);
unsigned long duration = pulseIn(ULTRASONIC_ECHO, HIGH, 30000);
return duration ? duration / 58 : -1;
}
void lineFollowStep() {
bool lo = digitalRead(LINE_LEFT_OUTER) == LOW;
bool li = digitalRead(LINE_LEFT_INNER) == LOW;
bool ri = digitalRead(LINE_RIGHT_INNER) == LOW;
bool ro = digitalRead(LINE_RIGHT_OUTER) == LOW;
if (li && ri) drive(MOTOR_SPEED, MOTOR_SPEED);
else if (lo || li) drive(80, MOTOR_SPEED);
else if (ro || ri) drive(MOTOR_SPEED, 80);
else emergencyStop();
}
void obstacleAvoidStep() {
long distance = readDistanceCm();
if (distance > 0 && distance < OBSTACLE_LIMIT_CM) {
drive(-130, -130); delay(250);
drive(150, -150); delay(380);
} else drive(MOTOR_SPEED, MOTOR_SPEED);
}
void sendTelemetry() {
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
long distance = readDistanceCm();
int gasRaw = analogRead(GAS_ADC);
Serial.printf("TELEMETRY mode=%d distance_cm=%ld temp_c=%.1f humidity_pct=%.1f gas_raw=%d\n", mode, distance, temperature, humidity, gasRaw);
cameraSerial.printf("STATUS mode=%d distance=%ld\n", mode, distance);
}
void handleCommand(String command) {
command.trim(); command.toUpperCase();
if (command == "STOP") emergencyStop();
else if (command == "MANUAL") { emergencyStop(); mode = MANUAL; }
else if (command == "LINE") mode = LINE_FOLLOW;
else if (command == "AVOID") mode = AVOID;
else if (command == "FWD" && mode == MANUAL) drive(MOTOR_SPEED, MOTOR_SPEED);
else if (command == "REV" && mode == MANUAL) drive(-MOTOR_SPEED, -MOTOR_SPEED);
else if (command == "LEFT" && mode == MANUAL) drive(-MOTOR_SPEED, MOTOR_SPEED);
else if (command == "RIGHT" && mode == MANUAL) drive(MOTOR_SPEED, -MOTOR_SPEED);
else if (command == "CAM_ON") cameraSerial.println("STREAM_ON");
else if (command == "CAM_OFF") cameraSerial.println("STREAM_OFF");
}
void pollCommands(Stream &port, String &buffer) {
while (port.available()) {
char c = static_cast<char>(port.read());
if (c == '\n' || c == '\r') { if (buffer.length()) handleCommand(buffer); buffer = ""; }
else if (buffer.length() < 48) buffer += c;
}
}
void setup() {
Serial.begin(115200);
cameraSerial.begin(115200, SERIAL_8N1, CAMERA_RX, CAMERA_TX);
Wire.begin(21, 22);
if (!mcp.begin_I2C(0x20, &Wire)) {
Serial.println("FATAL: MCP23017 not found; motors remain locked.");
while (true) delay(1000);
}
for (uint8_t pin = 0; pin < 8; ++pin) { mcp.pinMode(pin, OUTPUT); mcp.digitalWrite(pin, LOW); }
pinMode(LEFT_STBY, OUTPUT); pinMode(RIGHT_STBY, OUTPUT);
pinMode(ULTRASONIC_TRIG, OUTPUT); pinMode(ULTRASONIC_ECHO, INPUT);
pinMode(LINE_LEFT_OUTER, INPUT); pinMode(LINE_LEFT_INNER, INPUT);
pinMode(LINE_RIGHT_INNER, INPUT); pinMode(LINE_RIGHT_OUTER, INPUT);
ledcAttach(LEFT_A_PWM, 18000, 8); ledcAttach(LEFT_B_PWM, 18000, 8);
ledcAttach(RIGHT_A_PWM, 18000, 8); ledcAttach(RIGHT_B_PWM, 18000, 8);
dht.begin(); emergencyStop();
Serial.println("Ready: STOP, MANUAL, FWD, REV, LEFT, RIGHT, LINE, AVOID, CAM_ON, CAM_OFF");
}
void loop() {
static String usbBuffer, cameraBuffer;
pollCommands(Serial, usbBuffer); pollCommands(cameraSerial, cameraBuffer);
unsigned long now = millis();
if (now - lastAutonomyMs >= 80) {
lastAutonomyMs = now;
if (mode == LINE_FOLLOW) lineFollowStep();
if (mode == AVOID) obstacleAvoidStep();
}
if (now - lastTelemetryMs >= 3000) { lastTelemetryMs = now; sendTelemetry(); }
}“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.
Remix this project
Make it yours in one click
Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.