Build a Self-Balancing Robot Using Your Phone's Gyroscope

Self-balancing robots are a rite of passage for robotics hobbyists, but the classic build depends on an MPU6050 IMU that needs careful mounting, calibration, and a complementary filter to fuse its noisy readings. SensorCast lets you prototype the control loop first using the pre-calibrated gyroscope and accelerometer already in your Android phone.
Why start with your phone
The hardest part of a balancing robot isn't the motors — it's getting a clean, low-latency tilt angle. Phone sensors are factory calibrated and fused by the operating system, so you get a stable orientation estimate out of the box. That means you can focus on tuning your PID loop instead of debugging sensor drift.
The control loop
Stream the gyroscope and accelerometer at 100Hz over Bluetooth to your Arduino or ESP32. Use the fused orientation to calculate how far the robot is leaning from vertical, feed that error into a PID controller, and drive the motors to correct it. Sub-10ms latency keeps the loop responsive enough to stay upright.
Tuning your PID
Start with only the proportional term and increase it until the robot oscillates around vertical. Add the derivative term to dampen those oscillations, then a small integral term to eliminate steady-state lean. Because your sensor data is consistent, tuning becomes a repeatable process rather than guesswork.
Moving to onboard sensors
Once your control loop is dialed in, you can transplant the same logic to an onboard IMU if you want an untethered robot — but many makers find the phone-streaming setup reliable enough to keep. Either way, you've removed the biggest source of frustration from the project.
The PID control loop in code
Below is the core of the balancing sketch for an ESP32. It reads the fused orientation streamed from SensorCast (the app can send a pitch angle derived from the accelerometer and gyroscope), runs a PID controller, and outputs a motor command. Motor driver specifics depend on your hardware, so driveMotors() is left as a stub.
// PID gains — tune these for your robot
float Kp = 25.0;
float Ki = 0.8;
float Kd = 1.2;
float setpoint = 0.0; // upright = 0 degrees of pitch
float integral = 0.0;
float lastError = 0.0;
unsigned long lastTime = 0;
// Call this every time a new orientation packet arrives
void updateBalance(float pitchDegrees) {
unsigned long now = millis();
float dt = (now - lastTime) / 1000.0;
if (dt <= 0) dt = 0.001;
lastTime = now;
float error = setpoint - pitchDegrees;
integral += error * dt;
integral = constrain(integral, -50, 50); // anti-windup
float derivative = (error - lastError) / dt;
lastError = error;
float output = Kp * error + Ki * integral + Kd * derivative;
output = constrain(output, -255, 255);
driveMotors((int)output); // >0 drives forward, <0 drives backward
}
// Replace with your motor driver logic (e.g. L298N, TB6612)
void driveMotors(int speed) {
int magnitude = abs(speed);
bool forward = speed >= 0;
// set direction pins based on `forward`, then:
// analogWrite(PWM_LEFT, magnitude);
// analogWrite(PWM_RIGHT, magnitude);
}
Start with Ki and Kd at zero, raise Kp until the robot oscillates, then add Kd to damp it and a little Ki to remove steady lean. Because the phone's orientation data is stable and pre-calibrated, these numbers stay repeatable between runs.