TL;DR:
A balancing robot is an upside-down pendulum. It stays up by driving its wheels toward the side it is falling.
An IMU gives two imperfect tilt signals. A complementary filter blends them: trust the gyro in the short term and the accelerometer in the long term.
A PID controller turns tilt angle into motor power. Tune P first, then D, and keep the robot tethered while you do.
Balance a broomstick on your palm and you already know how this robot works. When the stick leans, you move your hand under it. The robot does the same with its wheels, many times per second, with no one steering.
What you will build
You will build a tall two-wheel robot with a motion sensor on its body. A loop measures the tilt angle, computes a motor command from it using PID, and drives both wheels. If the robot leans forward, the wheels roll forward under it. If it leans back, they roll back.
Parts and tools
A tall, narrow chassis with two DC gear motors and big wheels.
A microcontroller board and a dual H-bridge motor driver.
An IMU module (a gyroscope and accelerometer in one chip).
A battery pack suited to the motors, with a switch.
Heavy parts, such as the battery, mounted high. A high center of mass makes balancing easier.
Thread or light string for a safety tether.
Mounting the IMU
Fix the IMU rigidly, with its axes lined up with the robot: one axis pointing up, one forward. Motors vibrate, so use a firm mount and a little foam under the board. A loose sensor adds noise and the robot will shake itself over.
Keep the wiring neat, with the IMU wires short. Place the sensor near the wheel axle line if you can, since sensors far from the axle also feel the robot's acceleration.
Two sensors, one angle
The accelerometer measures gravity's direction, so it can compute tilt with angle = atan2(ax, az). It is stable over time but noisy, and it gets fooled when the robot itself accelerates. The gyroscope measures turning rate. Add the rate up over time and you have the angle, which is smooth, but small errors accumulate and the angle slowly drifts.
A complementary filter keeps the best of both:
angle = 0.98 × (angle + gyro × dt) + 0.02 × accel_angleWorked example, with dt = 0.01 s (a 100 Hz loop):
Previous angle
2.0°, gyro reads10 °/s:2.0 + 10 × 0.01 = 2.1°.Accelerometer reads
ax = 0.052 g,az = 0.9986 g:atan2(0.052, 0.9986) ≈ 2.98°, about3.0°.Blend:
0.98 × 2.1 + 0.02 × 3.0 = 2.058 + 0.06 = 2.118°.
The gyro does most of the work each step, and the accelerometer gently pulls the answer back so drift cannot build up.
The PID loop
The target angle is 0°, upright. The error is the tilt. The motor command is:
output = Kp × angle + Ki × (sum of angle × dt) + Kd × angle_rateWith the numbers above and Kp = 20, Kd = 0.5, Ki = 0: the proportional part is 20 × 2.118 = 42.36, and the derivative part, using the gyro's 10 °/s, is 0.5 × 10 = 5. The output is 47.36 motor units, on the 0 to 255 scale, which you send to both wheels in the direction of the lean.
float angle = 0;
const float KP = 20, KD = 0.5;
const float DT = 0.01;
void loop() {
float gyro = readGyroDegPerSec();
float accAngle = readAccelAngleDeg();
angle = 0.98 * (angle + gyro * DT) + 0.02 * accAngle;
if (fabs(angle) > 30) { setMotors(0, 0); return; } // fallen: cut power
float out = KP * angle + KD * gyro;
out = constrain(out, -255, 255);
setMotors(out, out);
delay(10);
}The 30° cut-off matters. A robot that has fallen should stop its wheels instead of spinning them against the floor.
Why the wheels must out-run the fall
The robot only balances if the wheels can accelerate faster than the body falls. A tall body tips slowly, so modest motors can keep up. A short, heavy body tips quickly and needs strong motors and good traction. If your robot reacts correctly on the bench but cannot recover in practice, the limit may be the motors and not your code.
Loop speed matters too. At 100 updates a second the controller looks at the tilt every 10 ms. If your code has a slow serial print inside the loop, the real rate may drop to 20 or 30 updates a second, and the filter, which assumes dt = 0.01, will quietly give wrong angles. Time the loop with the board clock and use the measured dt, or remove slow prints while balancing.
Finally, check the signs. Lean the robot forward by hand with the wheels in the air: the wheels should spin forward. If they spin backward, the correction pushes the fall along, and the robot will crash within a fraction of a second. Flip the sign of the output or swap the motor wires.
Tuning safely
Start with P only: Set
KdandKito zero and raiseKpslowly until the robot reacts to a nudge. Too low and it falls without a fight. Too high and it shakes harder each swing.Add D to calm the shaking: Raise
Kdin small steps until the wobble dies out quickly. The derivative term acts like a brake against fast movement.Fix drift last: If it slowly rolls in one direction, adjust the zero-angle offset to match its true balance point first. Add a very small
Kionly if that is not enough.
Stay safe
Falls and spinning wheels: Test over a clear, soft floor and keep fingers away from the wheels. Hold the safety tether during early tests. Use only the battery pack made for your robot. If you use a LiPo pack, charge it with its own charger, never leave it charging unattended, and stop if it puffs, gets hot or is dented.
Make it better
Add wheel encoders so the robot can hold its position instead of drifting.
Add a remote command that shifts the target angle by one or two degrees to make it drive.
Log angle and output to a laptop and plot them. A plot makes tuning problems visible.
Frequently asked questions
Why does it fall over right after I switch it on?
Usually the filter starts at the wrong angle or the zero-angle offset is off. Hold it upright at the balance point as you power on.
Why do taller robots balance more easily?
A taller body falls more slowly, which gives the loop more time to react before the lean grows.
