Community project

Autonomous 6x6 AI Rover

Tarunkumar

Published July 29, 2026

ESP3211 components6 assembly steps
Remix this project
Photo of 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
Wiring diagram for Autonomous 6x6 AI Rover

Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.

Parts list

Bill of materials
ComponentQtyNotes
TB6612FNG dual motor driverTB6612FNG1TB6612FNG 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 driverTB6612FNG1TB6612FNG 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 Expander0x201Microchip MCP23017 16-bit GPIO expander controlled over I2C. Provides two 8-bit ports for external digital inputs or outputs.
HC-SR04HC-SR04 + 10k/20k divider1Ultrasonic distance measurement sensor
DHT22DHT221Digital temperature and humidity sensor
4-channel TCRT5000 IR line array4-channel IR array1Four-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 divider1Budget 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 nominal1Three 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 BMS13-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 buck1Two 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 Thinker1Separate 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 steps
  1. Prepare 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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
PinConnectionType
EXTbattery_pack B+3S Li-ion BMS B+power
EXTbattery_pack B-3S Li-ion BMS B-power
EXTbms P+Dual-output buck regulator assembly VIN+power
EXTbms P-Dual-output buck regulator assembly VIN-ground
EXTpower_regulator MOTOR_6VTB6612FNG dual motor driver VMpower
VINpower_regulator LOGIC_5Vpower
GNDpower_regulator GNDground
3V3motor_driver_left VCCpower
GNDmotor_driver_left GNDground
GPIO 25motor_driver_left PWMApwm
GPIO 26motor_driver_left PWMBpwm
VINmotor_driver_right VMpower
3V3motor_driver_right VCCpower
GNDmotor_driver_right GNDground
GPIO 32motor_driver_right PWMApwm
GPIO 33motor_driver_right PWMBpwm
3V3gpio_expander VDDpower
GNDgpio_expander VSSground
GPIO 21gpio_expander SDAi2c
GPIO 22gpio_expander SCLi2c
3V3gpio_expander RESETpower
VINultrasonic VCCpower
GNDultrasonic GNDground
GPIO 23ultrasonic TRIGdigital
3V3environment VCCpower
GNDenvironment GNDground
GPIO 4environment DATAdata
3V3line_sensor VCCpower
GNDline_sensor GNDground
GPIO 13line_sensor D1digital
GPIO 14line_sensor D2digital
GPIO 18line_sensor D3digital
GPIO 19line_sensor D4digital
VINgas_sensor VCCpower
GNDgas_sensor GNDground
GPIO 35gas_sensor AOanalog
VINcamera_node 5Vpower
GNDcamera_node GNDground
GPIO 17camera_node U0Ruart
GPIO 16camera_node U0Tuart
GPIO 27motor_driver_left STBYdigital
GPIO 5motor_driver_right STBYdigital
EXTmotor_driver_left AIN1MCP23017 I/O Expander GPA0-GPA7digital
EXTmotor_driver_left AIN2MCP23017 I/O Expander GPA0-GPA7digital
EXTmotor_driver_left BIN1MCP23017 I/O Expander GPA0-GPA7digital
EXTmotor_driver_left BIN2MCP23017 I/O Expander GPA0-GPA7digital
EXTmotor_driver_right AIN1MCP23017 I/O Expander GPA0-GPA7digital
EXTmotor_driver_right AIN2MCP23017 I/O Expander GPA0-GPA7digital
EXTmotor_driver_right BIN1MCP23017 I/O Expander GPA0-GPA7digital
EXTmotor_driver_right BIN2MCP23017 I/O Expander GPA0-GPA7digital
GPIO 15ultrasonic ECHOdigital

Firmware

ESP32
main.cppDeploy to device
#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.

Open in Schematik