Introduction
Build a two-wheeled self-balancing robot using MPU6050 IMU, Kalman filter, and PID control — a classic control theory project. This comprehensive guide covers everything from design through implementation, testing, and deployment.
Build a two-wheeled self-balancing robot using MPU6050 IMU, Kalman filter, and PID control — a classic control theory project.
Build a two-wheeled self-balancing robot using MPU6050 IMU, Kalman filter, and PID control — a classic control theory project. This comprehensive guide covers everything from design through implementation, testing, and deployment.
A self-balancing robot is an inverted pendulum — inherently unstable. Without control, it falls. The control system continuously measures the tilt angle from vertical, predicts the falling direction, and accelerates the wheels in that direction to bring the center of mass back over the wheel base. This is the same principle used in Segway personal transporters and modern hoverboards.
10 components required for this project.
| # | Component | Purpose | Qty |
|---|---|---|---|
| 1 | Arduino Nano/Uno | Real-time PID control loop | x1 |
| 2 | MPU6050 6-DOF IMU (Gyro+Accel) | Tilt angle measurement | x1 |
| 3 | L298N Motor Driver | Driving two DC gear motors | x1 |
| 4 | DC Gear Motors with Encoders (12V, 300RPM) | Drive wheels with speed feedback | x2 |
| 5 | 65mm Rubber Wheels | Traction wheels | x2 |
| 6 | 11.1V 3S LiPo (1300mAh) | Compact power source | x1 |
| 7 | Custom 3D-Printed or Laser-Cut Chassis | Tall, narrow body structure | x1 |
| 8 | Voltage Regulator (LM2596 buck) | 5V for Arduino from LiPo | x1 |
| 9 | Bluetooth HC-05 | Remote speed setpoint and tuning | x1 |
| 10 | On/Off Power Switch (rated > 10A) | Safe power cutoff | x1 |
Follow these 7 steps carefully.
A self-balancing robot is an inverted pendulum — inherently unstable. Without control, it falls. The control system continuously measures the tilt angle from vertical, predicts the falling direction, and accelerates the wheels in that direction to bring the center of mass back over the wheel base. This is the same principle used in Segway personal transporters and modern hoverboards.
MPU6050 provides raw accelerometer and gyroscope data. Accelerometer measures absolute angle (arctan(Ax/Az)) but is noisy from motor vibration. Gyroscope measures angular rate (degrees/second) — integrate over time for angle, but drifts. Complementary filter combines both: angle = 0.98 × (angle + gyro_rate × dt) + 0.02 × accel_angle. The 0.98/0.02 ratio means 98% trust in gyro for fast changes, 2% correction from accelerometer to prevent drift.
Kalman filter is the optimal linear state estimator. State vector: [angle, gyro_bias]. Prediction step uses gyroscope measurements. Update step corrects with accelerometer. The filter tracks its own uncertainty (covariance matrix) and weights measurements accordingly. Kalman filter outperforms complementary filter in noisy environments. Use the Kalman library for Arduino: Kalman kalmanX; angle = kalmanX.getAngle(accel_angle, gyro_rate, dt).
setpoint = 0° (vertical). error = setpoint - measured_angle. PID output drives motor speed: forward to catch falling backward, reverse to catch falling forward. Critical: control loop must run fast (> 100 Hz) because the pendulum falls quickly. Use a 10ms control loop (100Hz). Start with only P control (Kp=15–25), then add D (Kd=0.5–2) to reduce oscillation, add small I (Ki=50–150) to correct for mechanical imperfections.
Critical mechanical factors: low center of mass (CoM) position improves controllability — too high makes it too sensitive, too low makes balancing impossible (CoM below wheel center = stable, not self-balancing). Wheel radius determines speed-to-motor-PWM ratio. Heavy motors low on chassis, battery centered. Symmetric weight distribution — calibrate IMU mounting angle to match true vertical when balanced.
Connect HC-05 Bluetooth. Phone app (MIT App Inventor or Serial Bluetooth Terminal): send 'F', 'B', 'L', 'R', 'S' commands. Implement: setpoint += 0.3 for forward tilt (drives robot forward as it catches up), setpoint -= 0.3 for backward. For turning: add differential speed offset to one motor. This setpoint manipulation is more stable than trying to overcome PID with direct motor commands.
Tuning order: Kd first (prevents oscillation during tuning of P), then Kp (increase until robot stands for 3+ seconds without going out of control), then Ki (small value to handle constant disturbances). Test on flat and slightly inclined surfaces. Test with 100g weight added high (harder) and low (easier) on chassis. Good tune: robot should absorb a gentle push and return to balance within 1–2 oscillations.
Core code for balancing_robot.ino:
#include <MPU6050_tockn.h>
#include <Wire.h>
MPU6050 mpu(Wire);
#define ENA 9 #define IN1 7 #define IN2 8
#define ENB 6 #define IN3 4 #define IN4 5
float Kp=25, Ki=120, Kd=1.2;
float error=0, prev_error=0, integral=0;
float setpoint = 0.5; // Tune this for balance point
void setMotors(int L, int R) {
L = constrain(L,-255,255); R = constrain(R,-255,255);
analogWrite(ENA, abs(L)); analogWrite(ENB, abs(R));
digitalWrite(IN1, L>0); digitalWrite(IN2, L<0);
digitalWrite(IN3, R>0); digitalWrite(IN4, R<0);
}
void setup() {
Wire.begin(); mpu.begin(); mpu.calcGyroOffsets(true);
pinMode(IN1,OUTPUT); pinMode(IN2,OUTPUT); // etc.
}
void loop() {
static unsigned long lastTime = 0;
mpu.update();
float angle = mpu.getAngleX(); // Tilt angle in degrees
float dt = (millis() - lastTime) / 1000.0;
lastTime = millis();
error = setpoint - angle;
integral = constrain(integral + error * dt, -40, 40);
float output = Kp*error + Ki*integral + Kd*(error-prev_error)/dt;
prev_error = error;
if(abs(angle) > 45) { setMotors(0,0); integral=0; return; } // Fallen
setMotors(-output, -output); // Negative because falling forward needs backward motors
delay(10);
}
Test Self-Balancing Robot (Inverted Pendulum) by verifying each subsystem individually before full integration.
Verify power voltages, check ground connections, use serial monitor for debug.
An interactive simulator will be available here — simulate circuits and run code in-browser without hardware.