Self-Balancing Robot WebUI Dashboard
Robotics · Embedded Systems

Self-Balancing ESP32 Robot

ESP32 MPU6050 PID Control WebSockets RTOS C++
±2° Balance Tolerance
100Hz Control Loop Frequency
WS Real-Time Telemetry

Project Overview

This project evolved from my navigation bot into a dynamic self-balancing platform. The robot maintains vertical equilibrium by reading gyroscope and accelerometer data from an MPU6050 over I2C, processing it through a custom-tuned PID control loop running at 100Hz on the ESP32.

The key challenge was latency: any perceptible delay between sensor read and motor response causes the robot to fall. This demanded careful optimization of the I2C read cycle and the motor PWM write pipeline to minimize jitter.

PID Architecture

The balancing algorithm uses all three PID terms simultaneously. Each serves a specific role in achieving stable equilibrium:

Kp
Proportional
Reacts to current tilt angle
Ki
Integral
Corrects accumulated drift
Kd
Derivative
Dampens overshoot
ESP32 · PID Core Loop C++
void balancingLoop() {
    mpu.update();
    float currentPitch = mpu.getAngleY();

    error = targetPitch - currentPitch;
    P = Kp * error;
    I += Ki * error;
    D = Kd * (error - lastError);

    float motorTorque = P + I + D;
    applyMotorSpeeds(motorTorque);
    lastError = error;
}

WebSocket Telemetry Dashboard

Instead of hardcoding PID values and reflashing the firmware for every tuning iteration, I built a real-time WebUI served directly from the ESP32. Using WebSockets, the interface graphs live pitch data while providing interactive sliders to adjust Kp, Ki, and Kd on the fly — dramatically accelerating the tuning process.

Dashboard · WebSocket Handler JavaScript
const ws = new WebSocket('ws://' + location.host + '/ws');
ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    pitchGraph.update(data.pitch);
    document.getElementById('kpDisplay').innerText = data.kp;
};

function sendPID() {
    ws.send(JSON.stringify({
        kp: parseFloat(kpSlider.value),
        ki: parseFloat(kiSlider.value),
        kd: parseFloat(kdSlider.value)
    }));
}

Want similar embedded control systems for your project?

Get in Touch ← All Projects