Community project

Autonomous Drift Car

ESP32
Photo of Autonomous Drift Car
Generated with AI

Marco Thome

Published October 10, 2026

This guide builds an autonomous drift car that navigates obstacles and executes controlled turns using onboard sensors and an ESP32 microcontroller. The car combines a brushless motor, metal-gear steering servo, and 7.4 V LiPo power with an MPU-6050 gyroscope and HC-SR04P ultrasonic sensor to detect obstacles and measure rotation rates, enabling autonomous obstacle avoidance and drift maneuvers.

The guide provides a complete wiring diagram, detailed parts list, and ready-to-flash firmware that implements state-based autonomous driving logic. Assembly steps cover mounting the drivetrain and steering, positioning sensors for forward detection, building the battery power distribution with proper switching and regulation, and safety checks before operation.

Wiring diagram

Wiring diagram for Autonomous Drift Car

Gather all the parts

QtyComponent
1

2S 7.4 V LiPo battery pack

7.4 V 2S, 2200 mAh minimum, 30C+

The rechargeable battery pack that supplies the car's motor and electronics.

1

30 A sensorless brushless RC ESC

30 A, 2S capable, 5 V BEC

The electronic speed controller that converts battery power into controlled power for the drive motor.

1

540-size 13.5T sensorless brushless drift motor

13.5T, 540 size, sensorless

The electric motor that turns the rear drivetrain to move the car.

1

Metal-gear 1/10 steering servo

Metal gear, 6 V, ≥10 kg·cm

The steering actuator that turns the car's front wheels through the chassis steering linkage.

1

MP1584 Buck Converter

Set output to 5.0 V before connecting ESP32

Adjustable buck (step-down) DC-DC converter, 4.5-28 V in -> 0.8-20 V out, ~3 A. Configured to 5 V to step a 9 V / 12 V supply or battery pack down to the board's 5 V rail.

1

DFRobot SEN0142 Fermion MPU-6050 6 DOF Sensor Breakout

MPU-6050, I2C

DFRobot SEN0142 MPU-6050 breakout with 3-5 V board input, I2C interface, onboard I2C pull-ups, and i2cdevlib Arduino example coverage.

1

HC-SR04P Ultrasonic Distance Sensor

3.3 V HC-SR04P / HC-SR04-33

3.3V-compatible HC-SR04P / SR04P ultrasonic distance sensor. Use VCC/GND/TRIG/ECHO directly with ESP32 3V3 logic; unlike 5V-only HC-SR04 modules, this preserves the parking sensor guide contract.

1

5 V 5 A RC UBEC regulator

5 V, 5 A minimum

The high-current 5 V regulator that powers the steering servo from the 2S battery.

1

XT60 2S LiPo arming switch, 40 A minimum

XT60, 2S rated, 40 A minimum

A high-current manual switch that disconnects the battery from the motor and electronics for safe handling.

Assemble it in 7 steps

1. Mount the drivetrain

Bolt the 13.5T brushless motor (drive_motor) into the motor mount and mesh its small gear with the chassis spur gear. Mount the 30 A ESC (esc_30a) where air can reach it, then connect its three motor leads to A, B, and C on the motor.

  • With the battery disconnected, turn the rear wheels by hand. They should turn freely with only a small amount of gear movement before the motor turns.
  • Keep fingers, loose hair, and wires away from the spinning gears; the wheels can start unexpectedly during testing.

2. Fit the steering parts

Screw the metal-gear steering servo (steering_servo) into the chassis, attach its arm with the wheels held straight, then connect the steering link. Mount the ESP32 board where it cannot touch the metal chassis or moving steering parts.

  • Leave the servo-arm screw accessible so you can center the wheels later.
  • Do not force the steering past its mechanical stops; a stalled servo can overheat or strip gears.

3. Mount the sensors facing forward

Fix the ultrasonic sensor (front_sonar) at the front bumper with its two round openings facing straight ahead. Stick the MPU-6050 board (imu_mpu6050) flat near the car’s center, with its printed top facing upward and its front pointing toward the front bumper.

  • Keep the ultrasonic sensor clear of the body shell so it can hear its own sound echo.
  • Do not mount the sensor boards where a wheel, driveshaft, or steering link can strike them.

4. Build the switched battery power leads

With the battery unplugged, connect battery BAT+ to arming_switch BAT+ IN and battery BAT- to arming_switch BAT- IN. Connect the switch LOAD+ OUT to ESC VCC, MP1584 VIN, and UBEC VIN+. Connect the switch LOAD- OUT, ESC GND, MP1584 GND, and UBEC VIN- together as the shared ground return.

  • Use suitably thick RC power wire and insulated connectors for the battery, ESC, switch, and both regulator input leads.
  • A 2S LiPo can deliver enough current to melt wires or start a fire if positive and negative touch. Build and inspect these leads with the battery disconnected.

5. Set the ESP32 and steering power

Before connecting the ESP32, use a meter to adjust MP1584 VOUT to exactly 5.0 V. Connect MP1584 VOUT to ESP32 VIN/5V and its GND to ESP32 GND. Connect UBEC VOUT+ to steering servo VCC and UBEC VOUT- to steering servo GND. Join the servo ground to the ESP32 GND so the steering signal has the same reference.

  • The steering servo usually has red for VCC, brown or black for GND, and orange, yellow, or white for SIG.
  • Do not connect the raw 7.4 V battery to the ESP32 or steering servo; it can damage them. Do not connect the ESC’s 5 V receiver lead to this circuit because the separate UBEC already powers the servo.

6. Wire the control and sensor leads

Connect ESC PWM to ESP32 GPIO25 (throttle signal). Connect steering servo SIG to GPIO33 (steering signal). Connect MPU-6050 VIN to 3V3, GND to GND, SDA to GPIO21, and SCL to GPIO22. Connect ultrasonic VCC to 3V3, GND to GND, TRIG to GPIO27, and ECHO to GPIO26.

  • Use short signal wires and secure them to the chassis so vibration cannot pull them loose.
  • Make sure the ultrasonic module is the 3.3 V HC-SR04P or HC-SR04-33 version; an ordinary 5 V HC-SR04 can send a damaging 5 V signal into the ESP32.

7. Check it with the wheels off the floor

Place the chassis on a stand so all four wheels are clear. Turn on the arming switch, plug the ESP32 into USB, and use Deploy. Keep the car pointed into an empty area when you first test the distance response.

  • If the car moves when it should be still, turn off the arming switch immediately and check the ESC’s neutral setting before trying again.
  • Never hold the car by hand during a powered test; a spinning wheel can injure you or pull the car from your grip.

Review all connections

1. Connections between "lipo_2s" and "ESP32"

Functionlipo_2sESP32
powerBAT+ → XT60 2S LiPo arming switch, 40 A minimum BAT+ INEXT
groundBAT- → XT60 2S LiPo arming switch, 40 A minimum BAT- INEXT

2. Connections between "arming_switch" and "ESP32"

Functionarming_switchESP32
powerLOAD+ OUT → 30 A sensorless brushless RC ESC VCCEXT
groundLOAD- OUTGND
powerLOAD+ OUT → MP1584 Buck Converter VINEXT
powerLOAD+ OUT → 5 V 5 A RC UBEC regulator VIN+EXT

3. Connections between "esc_30a" and "ESP32"

Functionesc_30aESP32
groundGNDGND
pwmPWMGPIO 25
powerMOTOR A → 540-size 13.5T sensorless brushless drift motor AEXT
powerMOTOR B → 540-size 13.5T sensorless brushless drift motor BEXT
powerMOTOR C → 540-size 13.5T sensorless brushless drift motor CEXT

4. Connections between "mp1584_5v" and "ESP32"

Functionmp1584_5vESP32
groundGNDGND
powerVOUTVIN

5. Connections between "servo_ubec" and "ESP32"

Functionservo_ubecESP32
groundVIN-GND
powerVOUT+ → Metal-gear 1/10 steering servo VCCEXT
groundVOUT- → Metal-gear 1/10 steering servo GNDEXT

6. Connections between "steering_servo" and "ESP32"

Functionsteering_servoESP32
pwmSIGGPIO 33

7. Connections between "imu_mpu6050" and "ESP32"

Functionimu_mpu6050ESP32
powerVIN3V3
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22

8. Connections between "front_sonar" and "ESP32"

Functionfront_sonarESP32
powerVCC3V3
groundGNDGND
digitalTRIGGPIO 27
digitalECHOGPIO 26

Deploy the firmware

#include <Arduino.h>
#include <Wire.h>
#include <ESP32Servo.h>
#include <MPU6050.h>


// Hoisted type definitions
enum DriveState { CRUISE, BRAKE, TURN };


// Forward declarations
void setOutputs(int escPulse, int steeringPulse);
void stopCar();
uint16_t readDistanceCm();
int16_t yawRateRaw();

constexpr int ESC_SIGNAL_PIN = 25;
constexpr int STEERING_SIGNAL_PIN = 33;
constexpr int SONAR_TRIG_PIN = 27;
constexpr int SONAR_ECHO_PIN = 26;
constexpr int I2C_SDA_PIN = 21;
constexpr int I2C_SCL_PIN = 22;

// Conservative receiver-style commands. Confirm ESC neutral and servo travel with
// the chassis raised before placing it on the ground.
constexpr int ESC_NEUTRAL_US = 1500;
constexpr int ESC_CRUISE_US = 1565;
constexpr int ESC_BRAKE_US = 1450;
constexpr int STEER_CENTER_US = 1500;
constexpr int STEER_LEFT_US = 1270;
constexpr int STEER_RIGHT_US = 1730;
constexpr uint16_t OBSTACLE_CM = 80;
constexpr uint32_t SENSOR_INTERVAL_MS = 70;
constexpr uint32_t BRAKE_TIME_MS = 250;
constexpr uint32_t TURN_TIME_MS = 750;
constexpr int16_t MAX_TURN_RATE_RAW = 18000;



Servo esc;
Servo steering;
MPU6050 imu;
DriveState driveState = CRUISE;
uint32_t stateStartedAt = 0;
uint32_t lastSensorAt = 0;
bool turnLeft = true;

void setOutputs(int escPulse, int steeringPulse) {
  esc.writeMicroseconds(escPulse);
  steering.writeMicroseconds(steeringPulse);
}

void stopCar() {
  setOutputs(ESC_NEUTRAL_US, STEER_CENTER_US);
}

uint16_t readDistanceCm() {
  digitalWrite(SONAR_TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(SONAR_TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(SONAR_TRIG_PIN, LOW);

  const unsigned long echoUs = pulseIn(SONAR_ECHO_PIN, HIGH, 24000);
  return echoUs == 0 ? 400 : static_cast<uint16_t>(echoUs / 58UL);
}

int16_t yawRateRaw() {
  int16_t gx, gy, gz;
  imu.getRotation(&gx, &gy, &gz);
  return gz;
}

void setup() {
  Serial.begin(115200);
  pinMode(SONAR_TRIG_PIN, OUTPUT);
  pinMode(SONAR_ECHO_PIN, INPUT);
  digitalWrite(SONAR_TRIG_PIN, LOW);

  esc.setPeriodHertz(50);
  steering.setPeriodHertz(50);
  esc.attach(ESC_SIGNAL_PIN, 1000, 2000);
  steering.attach(STEERING_SIGNAL_PIN, 1000, 2000);
  stopCar();
  delay(3000); // Gives the ESC a neutral throttle command while it starts.

  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  imu.initialize();
  if (!imu.testConnection()) {
    Serial.println("MPU-6050 not found; drive is disabled.");
    while (true) {
      stopCar();
      delay(100);
    }
  }

  stateStartedAt = millis();
  Serial.println("Autonomous drift prototype ready.");
}

void loop() {
  const uint32_t now = millis();

  if (driveState == CRUISE) {
    setOutputs(ESC_CRUISE_US, STEER_CENTER_US);
    if (now - lastSensorAt >= SENSOR_INTERVAL_MS) {
      lastSensorAt = now;
      const uint16_t distanceCm = readDistanceCm();
      Serial.printf("Distance: %u cm\n", distanceCm);
      if (distanceCm < OBSTACLE_CM) {
        driveState = BRAKE;
        stateStartedAt = now;
      }
    }
    return;
  }

  if (driveState == BRAKE) {
    setOutputs(ESC_BRAKE_US, STEER_CENTER_US);
    if (now - stateStartedAt >= BRAKE_TIME_MS) {
      turnLeft = !turnLeft;
      driveState = TURN;
      stateStartedAt = now;
    }
    return;
  }

  const int steeringPulse = turnLeft ? STEER_LEFT_US : STEER_RIGHT_US;
  const int throttlePulse = abs(yawRateRaw()) > MAX_TURN_RATE_RAW
                              ? ESC_NEUTRAL_US
                              : ESC_CRUISE_US;
  setOutputs(throttlePulse, steeringPulse);

  if (now - stateStartedAt >= TURN_TIME_MS) {
    driveState = CRUISE;
    stateStartedAt = now;
  }
}

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