MPU6050接ESP32教程:读加速度、角速度与姿态角

2026-11-05 07:16:46 16阅读

给桌面机械臂或者小车加"知道自己歪没歪"的能力,最省钱的起点就是一块 MPU6050。十来块钱,一颗芯片里塞了三轴加速度计和三轴陀螺仪,走 I2C 总线,ESP32 两根线就能读。这篇把它从接线、装库、读原始数据到算出姿态角的全流程理顺,代码照官方示例逐字搬运,新手照着敲就能出数。

先认识 MPU6050:一颗芯片两种传感器

MPU6050 内部是一颗三轴加速度计加一颗三轴陀螺仪,另外还附带一个温度传感器。加速度计量的是受力方向的重力分量,静止时也能读;陀螺计量的是绕三个轴的旋转角速度。两样数据各有所长,后面算姿态角时正好互补。

它是 I2C 器件,默认地址 0x68。模块上那个 AD0 引脚就是用来改 I2C 地址的,一根总线上挂两颗 MPU6050 时才用得上。常见模块的引脚一共八个:

  • VCC:供电。模块板上自带稳压,接 3.3V 或 5V 都能工作,芯片本体是 3.3V 逻辑;
  • GND:共地;
  • SCL / SDA:I2C 时钟和数据线;
  • XDA / XCL:外接 I2C 主/从口,用来级联磁力计这类外部传感器,入门阶段空着;
  • AD0:地址选择;
  • INT:中断输出,数据就绪时可以通知主控,示例里先不接。

ESP32与MPU6050模块

ESP32 与 MPU6050 同板点亮,OLED 上就是实时的姿态读数。

八个引脚对着模块丝印看一眼就能对上号:

MPU6050模块引脚图

接线:ESP32 这边只要四根线

ESP32 默认的 I2C 引脚是固定的,接错一根就总线上什么都扫不到:

MPU6050 引脚ESP32 引脚
VCC3V3
GNDGND
SCLGPIO 22
SDAGPIO 21

MPU6050接ESP32接线示意图

供电给 3V3 最稳妥。有些模块板载稳压加电平电阻,接 5V 也没问题——DroneBot Workshop 那篇教程的模块就是 5V 供电、内部再把数据电平降到 3.3V 的接法,两种模块市面都在卖,动手前看一眼自己板子上有没有稳压芯片。

装库、烧录,串口马上出数

库管理器里装三个:Adafruit MPU6050、Adafruit Unified Sensor,依赖的 Adafruit Bus IO 会自动带上。库版本对应 2.0.3 和 1.1.4 这条线就够用。

完整示例代码如下,逐字符照官方示例搬运。有一处写法说明:尖括号的头文件写法会被站点的编辑器剥掉,所以这里统一改成引号写法,在 Arduino 工具链下两种写法编译等价,不影响使用。

// Basic demo for accelerometer readings from Adafruit MPU6050

// ESP32 Guide: https://RandomNerdTutorials.com/esp32-mpu-6050-accelerometer-gyroscope-arduino/
// ESP8266 Guide: https://RandomNerdTutorials.com/esp8266-nodemcu-mpu-6050-accelerometer-gyroscope-arduino/
// Arduino Guide: https://RandomNerdTutorials.com/arduino-mpu-6050-accelerometer-gyroscope/

#include "Adafruit_MPU6050.h"
#include "Adafruit_Sensor.h"
#include "Wire.h"

Adafruit_MPU6050 mpu;

void setup(void) {
  Serial.begin(115200);
  while (!Serial)
    delay(10); // will pause Zero, Leonardo, etc until serial console opens

  Serial.println("Adafruit MPU6050 test!");

  // Try to initialize!
  if (!mpu.begin()) {
    Serial.println("Failed to find MPU6050 chip");
    while (1) {
      delay(10);
    }
  }
  Serial.println("MPU6050 Found!");

  mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
  Serial.print("Accelerometer range set to: ");
  switch (mpu.getAccelerometerRange()) {
  case MPU6050_RANGE_2_G:
    Serial.println("+-2G");
    break;
  case MPU6050_RANGE_4_G:
    Serial.println("+-4G");
    break;
  case MPU6050_RANGE_8_G:
    Serial.println("+-8G");
    break;
  case MPU6050_RANGE_16_G:
    Serial.println("+-16G");
    break;
  }
  mpu.setGyroRange(MPU6050_RANGE_500_DEG);
  Serial.print("Gyro range set to: ");
  switch (mpu.getGyroRange()) {
  case MPU6050_RANGE_250_DEG:
    Serial.println("+- 250 deg/s");
    break;
  case MPU6050_RANGE_500_DEG:
    Serial.println("+- 500 deg/s");
    break;
  case MPU6050_RANGE_1000_DEG:
    Serial.println("+- 1000 deg/s");
    break;
  case MPU6050_RANGE_2000_DEG:
    Serial.println("+- 2000 deg/s");
    break;
  }

  mpu.setFilterBandwidth(MPU6050_BAND_5_HZ);
  Serial.print("Filter bandwidth set to: ");
  switch (mpu.getFilterBandwidth()) {
  case MPU6050_BAND_260_HZ:
    Serial.println("260 Hz");
    break;
  case MPU6050_BAND_184_HZ:
    Serial.println("184 Hz");
    break;
  case MPU6050_BAND_94_HZ:
    Serial.println("94 Hz");
    break;
  case MPU6050_BAND_44_HZ:
    Serial.println("44 Hz");
    break;
  case MPU6050_BAND_21_HZ:
    Serial.println("21 Hz");
    break;
  case MPU6050_BAND_10_HZ:
    Serial.println("10 Hz");
    break;
  case MPU6050_BAND_5_HZ:
    Serial.println("5 Hz");
    break;
  }

  Serial.println("");
  delay(100);
}

void loop() {
  /* Get new sensor events with the readings */
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);

  /* Print out the values */
  Serial.print("Acceleration X: ");
  Serial.print(a.acceleration.x);
  Serial.print(", Y: ");
  Serial.print(a.acceleration.y);
  Serial.print(", Z: ");
  Serial.print(a.acceleration.z);
  Serial.println(" m/s^2");

  Serial.print("Rotation X: ");
  Serial.print(g.gyro.x);
  Serial.print(", Y: ");
  Serial.print(g.gyro.y);
  Serial.print(", Z: ");
  Serial.print(g.gyro.z);
  Serial.println(" rad/s");

  Serial.print("Temperature: ");
  Serial.print(temp.temperature);
  Serial.println(" degC");

  Serial.println("");
  delay(500);
}

setup 里三行配置是关键:加速度计量程设 8G 挡,陀螺设 500 度每秒挡,低通滤波带宽 5Hz。烧进去以后打开串口监视器,波特率 115200,就能看到这样的输出:

MPU6050串口读数输出

加速度单位是米每二次方秒,角速度是弧度每秒,温度是摄氏度。模块静止平放时 Z 轴加速度读数在 9 到 10 之间是正常的——那就是 1 个 g 的重力,不是传感器坏了。

从原始读数到姿态角:互补滤波一句话讲透

上面读到的是原始数据,机器人要的是横滚 roll、俯仰 pitch、偏航 yaw 三个姿态角——绕着前进方向滚叫横滚,前后点头叫俯仰,水平转圈叫偏航。把三个轴的原始数据变成这三个角,业内常用两条路:

第一条自己算。加速度计用 X、Z 分量做反正切就能算出静止时的倾角;陀螺仪把角速度乘以采样时间累加成角度,动态响应好但会漂。互补滤波就是把两者按比例融合——HowToMechatronics 的经典写法是取 96% 陀螺角加 4% 加速度角,DroneBot Workshop 的电平仪项目用的则是 90% 加 10%,系数不是唯一的,共同思路都是让陀螺管动态、加速度计兜底防漂。裸寄存器路线的话,数据从 0x3B 寄存器开始连读,±2g 量程下原始值按数据手册要除以 16384 才是 g。

滤波带宽的档位也别忽视。示例里 setFilterBandwidth 设的是 5Hz,库里有从 260Hz 到 5Hz 一共七挡可选。带宽越高数据越跟手,抖动噪声也越多;带宽越低输出越顺滑,但响应越迟钝。装在会震动的车身或者臂身上,低带宽挡能替你省掉不少软件滤波的功夫。

第二条直接用片上 DMP。MPU6050 内置的运动处理器能在芯片里完成融合,Jeff Rowberg 库里的 MPU6050_DMP6 示例可以直接输出四元数和欧拉角,四元数表示法还不受欧拉角万向锁的困扰。想省 CPU 就走 DMP,想理解原理就手写互补滤波,两条路都通。

新手最容易翻车的四个点

  1. 扫不到 0x68:先跑 I2C Scanner,九成是 SDA、SCL 接反或者没共地;确认地址还是 0x68,AD0 拉高过的模块地址会变;
  2. mpu.begin() 返回失败:报 "Failed to find MPU6050 chip" 时先查供电,再确认三个库都装齐,尤其是容易漏掉的 Adafruit Bus IO;
  3. 陀螺读数漂移:零偏是每个个体都有的,开机后静止几秒取平均做校准,DroneBot Workshop 的做法是上电连读一千次取基准,这一步不做,角度积分几秒钟就飘没了;
  4. 量程怎么选:桌面机械臂、云台这类慢动作,陀螺 500 度每秒挡足够;快速甩动或者装在腿上,才需要往 2000 度每秒那挡去。量程越大分辨率越粗,够用就好。

还有一点经验值得提前知道:DMP 输出的 yaw 角同样会缓慢漂移,这是消费级 IMU 没有磁力计的先天限制,短时间的俯仰横滚很稳,长时间绝对航向就得靠磁力计或者上更高一级的九轴模块来补。选型时把这条预期定对,就不会觉得模块"不准"。

整体下来,MPU6050 是那种"下午就能出结果"的器件:四根线、三个库、一段现成示例,串口就有数。等串口读数稳定了,再往 DMP 或者互补滤波走一步,机械臂的关节角度反馈、自平衡小车的倾角测量就都有了着落。踩到别的坑,评论区聊。


作者:固件烧录仔
链接:https://www.aiddithome.com/p/5e5242ed9906a.html
本作版权归作者所有,未经允许请勿转载。若存在违规行为,您可以点击 “举报”。


登录 后发表评论
4条评论
调参到凌晨
1楼 · 2天前

串口一出数那个瞬间还是很爽的,Z轴1g那次还以为坏了查了半天,后来才知道是重力哈哈

试过DMP6示例的路过,四元数输出的yaw确实会慢慢飘,文章最后那条预期说得很到位,别指望九轴以下的模块给你绝对航向。

触底反弹的Tony
3楼 · 2天前

问一下接线上面的坑:我手上有块老模块,板上没有稳压,之前图省事直接怼了5V,读数是有了但芯片烫手,后来换3V3才踏实。你们要是买模块先看一眼板上有没有那颗稳压,文章里这个提醒真的实用。

深圳打工人Alan
4楼 · 2天前

互补滤波那段的系数说法挺良心的,0.96/0.04和0.9/0.1都见过,一直以为有个标准答案,原来思路对就行。