🌀 ESP32 + MPU6050 惯性导航:从获取数据到自平衡机器人

📅 2026-06-05 · 🏷️ ESP32-S3 · MPU6050 · 传感器 · 自平衡
🎧 听书音频(总司令和主笔请忽略):

MPU6050 是目前最流行的六轴惯性传感器,集成了三轴加速度计和三轴陀螺仪,配合 I2C 接口可以方便地与 ESP32 相连。本教程从读取原始数据开始,逐步引入卡尔曼滤波,最终实现一个能保持自平衡的小车底盘。

📦 硬件准备

元器件数量备注
ESP32-S3-DevKitC-11块主控,也可以用 ESP32 通用版
MPU6050 模块1个GY-521 板,带针座
舵机 MG996R × 22个用于自平衡车左右轮
车轮 + 底板套件1套淘宝搜索"自平衡小车底盘"
面包板 + 跳线若干杜邦线公对母
4.8V 锂电池1块给舵机供电,不要只用 USB 供电舵机

🔌 接线

MPU6050  →  ESP32-S3
VCC       →  3.3V(或 5V 也可以)
GND       →  GND
SCL       →  GPIO 1(I2C0_SCL)
SDA       →  GPIO 0(I2C0_SDA)

⚠️ ESP32-S3 默认 I2C 引脚:
  GPIO 0 = SDA
  GPIO 1 = SCL

📐 MPU6050 数据手册要点

MPU6050 内置三轴加速度计(±2/4/8/16g 可选)和三轴陀螺仪(±250/500/1000/2000°/s 可选),内部还集成了温度传感器。通过 I2C 地址 0x68(或 0x69 备选)访问内部寄存器。

① 读取原始数据

先直接读取 MPU6050 的加速度和角速度原始值,感受数据质量。

// Wire.h 由 ESP32 Arduino 核心提供,I2C 通信 #include <Wire.h>const int MPU_ADDR = 0x68; // AD0 接地 => 0x68;接 VCC => 0x69void setup() { Serial.begin(115200); Wire.begin(0, 1); // ESP32-S3: SDA=GPIO0, SCL=GPIO1 Wire.setClock(400000); // 400kHz I2C 高速模式// 唤醒 MPU6050(默认睡眠模式) Wire.beginTransmission(MPU_ADDR); Wire.write(0x6B); // PWR_MGMT_1 寄存器 Wire.write(0x00); // 唤醒,解除睡眠 Wire.endTransmission(); } void loop() { Wire.beginTransmission(MPU_ADDR); Wire.write(0x3B); // ACCEL_XOUT_H — 加速度数据起始寄存器 Wire.endTransmission(); Wire.requestFrom(MPU_ADDR, 14); // 一次读 14 字节if (Wire.available() == 14) { int ax = Wire.read() << 8 | Wire.read(); int ay = Wire.read() << 8 | Wire.read(); int az = Wire.read() << 8 | Wire.read(); int tmp = Wire.read() << 8 | Wire.read(); int gx = Wire.read() << 8 | Wire.read(); int gy = Wire.read() << 8 | Wire.read(); int gz = Wire.read() << 8 | Wire.read(); // 除以 16384 得到 g(加速度计默认 ±2g 量程)// 除以 131 得到 °/s(陀螺仪默认 ±250°/s 量程) Serial.printf("Acc: %7.2f %7.2f %7.2f | Gyr: %7.1f %7.1f %7.1f\n", ax/16384.0, ay/16384.0, az/16384.0, gx/131.0, gy/131.0, gz/131.0); } delay(20); }
💡 为什么得到的是原始 ADC 值?
MPU6050 内置 16 位 ADC,加速度原始值 0~65535 对应 ±2g,即零点(平放)在 16384 左右。用手轻轻转动传感器,观察陀螺仪数值变化。

② 卡尔曼滤波:让角度估算稳定

加速度计能直接测量静止时的俯仰角(通过 atan2),但噪声大、响应慢。陀螺仪响应快但会漂移。卡尔曼滤波将两者融合,兼顾实时性和准确性。

// 卡尔曼滤波器 — 单轴(俯仰角) class KalmanFilter { public: KalmanFilter(float q, float r) : Q(q), R(r), angle(0), bias(0), P(1) {} float update(float accAngle, float gyroRate, float dt) { // 预测步骤 angle += (gyroRate - bias) * dt; P += Q * dt; // 更新步骤float S = P + R; float K = P / S; float y = accAngle - angle; angle += K * y; bias += K * bias; P = (1 - K) * P; return angle; } float getAngle() { return angle; } private: float Q, R, angle, bias, P; };

③ 完整自平衡控制代码

用 PID 控制轮子转速,让车体保持直立。当角度倾斜时,轮子向倾斜方向运动来纠正。

#include <Wire.h> #include <servo.h>#define SERVO_L_PIN 2 #define SERVO_R_PIN 3 #define MPU_ADDR 0x68 #define SERVO_MIN 40 #define SERVO_MAX 115 #define BALANCE_ANG 0 Servo servoL, servoR; KalmanFilter kf(0.001, 0.1); // PID 参数(需根据实际重心机械调参) float Kp = 50, Ki = 0.1, Kd = 10; float integral = 0, prevAngle = 0; void readMPU6050(float& ax, float& ay, float& az, float& gx, float& gy, float& gz) { Wire.beginTransmission(MPU_ADDR); Wire.write(0x3B); Wire.endTransmission(); Wire.requestFrom(MPU_ADDR, 14); if (Wire.available() == 14) { ax = (Wire.read() << 8 | Wire.read()) / 16384.0; ay = (Wire.read() << 8 | Wire.read()) / 16384.0; az = (Wire.read() << 8 | Wire.read()) / 16384.0; Wire.read(); Wire.read(); gx = (Wire.read() << 8 | Wire.read()) / 131.0; gy = (Wire.read() << 8 | Wire.read()) / 131.0; gz = (Wire.read() << 8 | Wire.read()) / 131.0; } } float calcAngle(float ax, float az) { return atan2(ax, az) * 180.0 / 3.1415926; } void setup() { Serial.begin(115200); Wire.begin(0, 1); Wire.setClock(400000); Wire.beginTransmission(MPU_ADDR); Wire.write(0x6B); Wire.write(0x00); Wire.endTransmission(); servoL.attach(SERVO_L_PIN, SERVO_MIN, SERVO_MAX); servoR.attach(SERVO_R_PIN, SERVO_MIN, SERVO_MAX); } void loop() { static unsigned long lastT = 0; float ax, ay, az, gx, gy, gz; readMPU6050(ax, ay, az, gx, gy, gz); float accAngle = calcAngle(ax, az); float dt = (millis() - lastT) / 1000.0; lastT = millis(); float angle = kf.update(accAngle, gy, dt); float error = angle - BALANCE_ANG; integral += error * dt; integral = constrain(integral, -50, 50); float output = Kp * error + Ki * integral + Kd * (error - prevAngle) / dt; prevAngle = error; int pwm = map(constrain(output, -400, 400), -400, 400, SERVO_MIN, SERVO_MAX); servoL.writeMicroseconds(pwm); servoR.writeMicroseconds(pwm); Serial.printf("%.2f,%.2f,%.1f\n", angle, accAngle, output); delay(5); }
⚠️ 调参建议:
如果车体发抖:降低 P
如果车体响应迟钝:提高 P
如果持续漂移:提高 I
如果震荡衰减慢:提高 D
建议从 Kp=30 开始,每次乘以 1.5 倍慢慢找最优值。机械重心越低越容易平衡。

🎬 视频演示

📺 ESP32 + MPU6050 自平衡车完整过程(点击观看)

📊 数据校准技巧

MPU6050 出厂有零偏,买回来第一件事是校准:在平面上静止放置,记录 200 次数据的平均值,在代码里把这个偏移量减去。

void calibrateMPU6050(float& gyroXBias, float& gyroYBias, float& gyroZBias, float& accXBias, float& accYBias, float& accZBias) { const int N = 200; float sumGX = 0, sumGY = 0, sumGZ = 0; float sumAX = 0, sumAY = 0, sumAZ = 0; Serial.println("Calibrating MPU6050 — keep sensor still..."); for (int i = 0; i < N; i++) { float ax, ay, az, gx, gy, gz; readMPU6050(ax, ay, az, gx, gy, gz); sumGX += gx; sumGY += gy; sumGZ += gz; sumAX += ax; sumAY += ay; sumAZ += az; delay(10); } gyroXBias = sumGX / N; gyroYBias = sumGY / N; gyroZBias = sumGZ / N; accXBias = sumAX / N; accYBias = sumAY / N; accZBias = (sumAZ / N) - 1.0; Serial.printf("Calibration done: gyro(%.2f, %.2f, %.2f) acc(%.4f, %.4f, %.4f)\n", gyroXBias, gyroYBias, gyroZBias, accXBias, accYBias, accZBias); }

📋 寄存器速查

寄存器地址功能
PWR_MGMT_10x6B睡眠/唤醒/时钟源
ACCEL_XOUT_H0x3B加速度 X 高字节
TEMP_OUT_H0x41温度高字节
GYRO_XOUT_H0x43角速度 X 高字节
CONFIG0x1A数字低通滤波器配置
ACCEL_CONFIG0x1C加速度量程设置
GYRO_CONFIG0x1B陀螺仪量程设置

🔗 扩展方向

💡 快速验证 MPU6050 是否正常:用 Arduino 的 I2C Scanner(搜索"Wire I2C Scanner"),确认能扫描到地址 0x68。如果扫不到:检查接线、SCL/SDA 是否接反、供电是否正常(部分模块需要 5V)。

粤ICP备2026055292号-1