How to Build a Self-Balancing Rover with ESP32
IMU-based stabilization with live battery and tilt stats on OLED
Updated

What you'll build
This guide walks you through building a self-balancing two-wheeled rover that stays upright using an ESP32, an MPU6050 inertial measurement unit, a pair of DC gear motors with an L298N driver, and a compact OLED screen for real-time telemetry. The rover continuously reads accelerometer and gyroscope data, fuses them with a complementary filter, and feeds the resulting tilt angle into a PID control loop that adjusts motor speed dozens of times per second. The OLED displays live tilt angle, battery voltage, and PID tuning values so you can observe the control system at work without needing a serial monitor.
PID control is one of the most important concepts in robotics and industrial automation, and a self-balancing robot is the classic hands-on way to learn it. You will implement proportional, integral, and derivative terms from scratch, understand how each one contributes to stability, and develop an intuition for tuning gains by watching the rover respond in real time. The project also covers reading raw IMU registers over I2C, converting them into meaningful orientation data, and managing two independent motor channels through an H-bridge driver -- skills that underpin everything from drones to robotic arms.
When you are finished you will have a rover that balances on two wheels, recovers from gentle pushes, and displays its internal state on screen. The modular code structure makes it straightforward to add remote control over Bluetooth or Wi-Fi, introduce obstacle avoidance with an ultrasonic sensor, or swap in stepper motors for finer control. This is a satisfying intermediate project that bridges the gap between simple sensor demos and full autonomous robotics platforms. The same MPU6050 IMU used here also features in the fitness wristband, where accelerometer data drives step counting instead of balance control.
Wiring diagram
Wiring diagram
Components needed
Assembly
Connect IMU and OLED on I2C bus
Wire MPU6050 and SSD1306 to shared SDA GPIO21 and SCL GPIO22.
- Keep I2C wires short for stable readings.
Wire motor driver control lines
Connect L298N IN1-IN4 to GPIO25, GPIO26, GPIO32, and GPIO33.
- Power motors separately and share ground with ESP32.
- Do not power motors directly from ESP32 5V pin.
Pin assignments
| Pin | Connection | Type |
|---|---|---|
| GPIO 21 | mpu6050-1 SDA | I2C |
| GPIO 22 | mpu6050-1 SCL | I2C |
| GPIO 21 | oled-1 SDA | I2C |
| GPIO 22 | oled-1 SCL | I2C |
| GPIO 25 | motor-driver-1 IN1 | PWM |
| GPIO 26 | motor-driver-1 IN2 | PWM |
| GPIO 32 | motor-driver-1 IN3 | PWM |
| GPIO 33 | motor-driver-1 IN4 | PWM |
Code
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_SSD1306.h>
#define SDA_PIN 21
#define SCL_PIN 22
#define IN1 25
#define IN2 26
#define IN3 32
#define IN4 33
Adafruit_MPU6050 mpu;
Adafruit_SSD1306 display(128, 64, &Wire, -1);
float targetAngle = 0.0f;
float kp = 24.0f, kd = 0.85f;
float lastError = 0.0f;
void setup() {
Wire.begin(SDA_PIN, SCL_PIN);
mpu.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
}
void loop() {
sensors_event_t a, g, t;
mpu.getEvent(&a, &g, &t);
float angle = atan2(a.acceleration.x, a.acceleration.z) * 57.2958f;
float error = targetAngle - angle;
float control = kp * error + kd * (error - lastError);
lastError = error;
int pwm = constrain((int)abs(control), 0, 255);
bool forward = control > 0;
analogWrite(IN1, forward ? pwm : 0);
analogWrite(IN2, forward ? 0 : pwm);
analogWrite(IN3, forward ? pwm : 0);
analogWrite(IN4, forward ? 0 : pwm);
display.clearDisplay();
display.setCursor(0, 0);
display.printf("Angle: %.2f\nCTRL: %.1f", angle, control);
display.display();
delay(20);
}
// Run this and build other cool things at schematik.ioReady to build this?
Open this project in Schematik to get the full wiring diagram, pin assignments, and deployable code for the Self-Balancing Rover.
Open in Schematik →


