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:
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.
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?