MPU6050 GY-521 Arduino 教程:从原始数据到稳定倾角

A hand holding a small blue GY-521 MPU6050 board beside two loose header strips, a blue Arduino Uno and a blue OLED showing an abstract angle needle

把 GY-521 的 SDA、SCL 接到 Arduino Uno 的 A4、A5,先唤醒沉睡的 MPU-6050,然后把原始的 16 位读数除以 datasheet 给的 scale factor——默认量程下每 g 是 16384、每 °/s 是 131,最后用 complementary filter 把 accelerometer 和 gyro 融合成一个稳定的倾角,显示在 OLED 上。

GY-521 板上有什么?八个引脚各做什么?

GY-521 是一块蓝色小板,载着 InvenSense 的 MPU-6050:一颗黑色芯片里同时装了三轴 gyroscope 和三轴 accelerometer。gyro 量程 ±250 到 ±2000°/s,accelerometer ±2 到 ±16 g;板上的稳压器可以接 3–5V——MPU-6050 datasheet 写明芯片本身只吃 2.375–3.46V,所以 Uno 的 5V 可以放心接:它进的是板上的稳压器,芯片始终工作在低压。丝印上的箭头标出 X 轴和 Y 轴的方向。

八个金色焊盘里,有两个几乎人人都搞混。AD0 决定 I2C 地址:板上有下拉电阻把它拉低,所以悬空不接时模块应答在 0x68;把它接到 3.3V(不是 5V),地址就变成 0x69,同一条总线就能挂第二个模块。XDA 和 XCL 是 MPU-6050 芯片自己的 auxiliary I2C master,用来在芯片后面再串一个 sensor(比如磁力计)——绝对不要把 Uno 接到这两个脚上。

排针出厂是两条散装的,一条直的、一条弯角的——按你的装法选一条焊上,可以参考我们的排针焊接教程。千万不要只插不焊:靠摩擦的接触,板子一倾斜就断——而倾斜正是这个项目要做的事。

Parts list

ItemPriceQty
MB102 Breadboard 170 400 830 Holes Breadboard Donut Board Arduino Prototype Multi Color - MINI BREADBOARD 170 HOLES (WHITE)MB102 Breadboard 170 400 830 Holes Breadboard Donut Board Arduino Prototype Multi Color - MINI BREADBOARD 170 HOLES (WHITE)BRB170HRM1.00
40pcs Dupont Wire 10cm 20cm 30cm for Breadboard DIY Experiment Jumper Wire Breadboard wire - DUPONT WIRE M-M 30CM40pcs Dupont Wire 10cm 20cm 30cm for Breadboard DIY Experiment Jumper Wire Breadboard wire - DUPONT WIRE M-M 30CMDPWMM30RM4.60

把两个模块插在 breadboard 上,再用公对公 Dupont 线接到 Uno——比拎着一把散线去倾斜板子稳得多。

GY-521 和 OLED 怎么接到 Uno?

Arduino Uno 的 I2C 在两个模拟脚上:A4 是 SDA,A5 是 SCL。两个模块共用这两条线,再加上电源——总共八条跳线。

模块引脚 Uno 引脚 说明
GY-521 VCC 5V 板上稳压器会降压供给芯片
GY-521 GND GND
GY-521 SCL A5 共用的 I2C 时钟线
GY-521 SDA A4 共用的 I2C 数据线
GY-521 AD0 不接 地址保持默认的 0x68
GY-521 XDA、XCL 不接 Auxiliary I2C master——不是给 Uno 用的
GY-521 INT 不接 中断输出,本项目用不到
OLED GND GND 这块屏的第一脚——认准丝印
OLED VCC 5V 第二脚,不是第一脚
OLED SCL A5 和 GY-521 共用时钟线
OLED SDA A4 和 GY-521 共用数据线

留意 OLED 的排针顺序:从左到右是 GND、VCC、SCL、SDA,把前两脚接反是这类屏最经典的死法。共用一条总线完全没问题,因为 I2C 给每颗芯片都编了地址:MPU-6050 应答在 0x68,这块 OLED 应答在 0x3C,Uno 点名叫谁谁答——和我们的 DHT11 OLED 温湿度监测是同一套玩法。

卡通接线图:蓝色 GY-521 传感器和蓝色 OLED 显示屏共用两条 I2C 线加电源,接到蓝色 Arduino Uno
八条线:两个模块共用 Uno 的 A4(SDA)和 A5(SCL),GY-521 的 XDA、XCL、AD0、INT 焊盘全部留空。

想看模块实际接线、原始数值在真实桌面上跳动的样子,这段实拍把硬件部分讲得很清楚:

为什么 MPU6050 会输出 16384 这种巨大的数字?

MPU-6050 送出来的不是”度”也不是”g”——它送的是 −32768 到 +32767 的有符号 16 位整数,把它们直接当实际单位来读是新手的头号错误。板子平放在桌上,Z 轴 accelerometer 读数大约是 16384:不是坏了,这正是用原始 counts 表示的 1 g。按你选的量程,除以 datasheet 里的灵敏度 scale factor:16384 ÷ 16384 = 1.00 g;gyro 读到 262,就是 262 ÷ 131 = 2°/s。这两个除数都不是随便定的:+2 g 占满 16 位量程的整个正半边——32768 个 counts——所以 1 g = 32768 ÷ 2 = 16384;而 32768 ÷ 250 正是 gyro 那个 131 的来历。

量程设置 原始值除以 结果单位
Accel ±2 g(默认) 16384 g
Accel ±4 g 8192 g
Accel ±8 g 4096 g
Accel ±16 g 2048 g
Gyro ±250°/s(默认) 131 °/s
Gyro ±500°/s 65.5 °/s
Gyro ±1000°/s 32.8 °/s
Gyro ±2000°/s 16.4 °/s

这其实是量程和分辨率之间的交换:同样 16 位摊到 ±16 g 上,每个 count 代表的加速度就大了八倍。本教程沿用上电默认值——±2 g 和 ±250°/s,测倾角正合适;以后如果把量程调宽,除数也要跟着换。

为什么 accelerometer 和 gyroscope 两个都要用?

MPU-6050 的 accelerometer 几乎是白送你一个倾角:板子一倾斜,重力那稳稳的 1 g 就按角度分配到 Y 轴和 Z 轴上,atan2(ay, az) 从这个分配比例就能还原出 roll 角——放几分钟都稳如泰山。但它有个死穴:sensor 量的是所有加速度,不只是重力。手一晃,晃动本身多出一股力,合成向量不再指向正下方,算出来的角度就乱跳——偏偏乱跳的时刻,正是你最想看角度的时刻。

gyroscope 的性格刚好相反:把它的旋转速度乘以时间步长再累加——也就是积分——得到的角度又快又顺,完全无视晃动。但每个模块静止时都带一点小小的 bias,把这个恒定误差一直积分下去,它会无限长大——哪怕只剩 0.5°/s 的残余 bias,积分一分钟就是 30° 的假倾角,而板子其实一动不动。

实用的解法是 complementary filter,一行代码各取所长:angle = 0.96 * (angle + rate * dt) + 0.04 * accelAngle。每一圈 loop,filter 保留 96% 由 gyro 更新出来的角度,再掺入 4% accelerometer 给出的答案。这 4% 小到晃动时 accelerometer 的尖峰撼动不了输出,但它每秒重复几十次,几秒之内就把 gyro 累积的 drift 全部抹平。把 0.96 往 0.98 调,就是更信 gyro 一点;往低调,accelerometer 的抖动就会浮现在输出里。不用 DMP 固件,不用 Kalman 数学,一块 Uno 跑得轻轻松松。

卡通图:三条信号曲线对比——抖动的 accelerometer 线、漂移的 gyroscope 线和一条平滑的融合线
只用 accelerometer 会抖,只用 gyro 会越漂越远,complementary filter 融合出来的角度又平滑又稳。

正因为有这个 bias,sketch 才要在启动时校准:板子保持不动,取 500 个 gyro 读数求平均,之后每次读数都减掉这个 offset。

完整的 Arduino sketch 长什么样?

需要安装的只有 Adafruit 的显示库:在 Arduino IDE 的 Library Manager 里装 Adafruit SSD1306,弹出提示时一并接受它的 Adafruit GFXAdafruit BusIO 依赖。MPU-6050 这边完全不用库——内建的 Wire 直接读 register,所以你刚学会的每一次除法在代码里都看得见。

setup 的写法来自两个 register 事实:芯片上电时处于 sleep 模式,所以第一步要清掉 PWR_MGMT_1 (0x6B) 里的 SLEEP 位;而全部 14 个数据 byte 从 register 0x3B 开始连续排列,一次 burst read 就能全部取回。

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define MPU_ADDR 0x68        // AD0 悬空不接的 GY-521
#define OLED_ADDR 0x3C       // 这块 0.96" 模块的地址
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// MPU-6050 datasheet 里的灵敏度 scale factor
// (默认量程:+/-2 g 和 +/-250 deg/s)
const float ACCEL_LSB_PER_G   = 16384.0;
const float GYRO_LSB_PER_DPS  = 131.0;
const float ALPHA = 0.96;    // complementary filter 系数

int16_t ax, ay, az, gx, gy, gz;
float gyroBiasX = 0;         // 静止时的 gyro offset,启动时测出
float angle = 0;             // 融合后的倾角,单位是度
unsigned long lastMicros;

int16_t read16() {           // 从 MPU 读一个 big-endian 16 位值
  int16_t v = Wire.read() << 8;
  v |= Wire.read();
  return v;
}

void readMPU() {
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x3B);                      // ACCEL_XOUT_H:数据块的起点
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_ADDR, 14, true);  // accel(6) + temp(2) + gyro(6)
  ax = read16();  ay = read16();  az = read16();
  read16();                              // 跳过温度
  gx = read16();  gy = read16();  gz = read16();
}

void setup() {
  Wire.begin();

  // MPU-6050 上电时在睡觉:清掉 PWR_MGMT_1 里的 SLEEP 位
  Wire.beginTransmission(MPU_ADDR);
  Wire.write(0x6B);
  Wire.write(0x00);
  Wire.endTransmission(true);
  delay(100);

  display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println(F("Calibrating..."));
  display.println(F("Keep the board still"));
  display.display();

  // 取 500 个静止时的 gyro 读数求平均,得到 bias
  long sum = 0;
  for (int i = 0; i < 500; i++) {
    readMPU();
    sum += gx;
    delay(2);
  }
  gyroBiasX = sum / 500.0;

  lastMicros = micros();
}

void loop() {
  readMPU();

  unsigned long now = micros();
  float dt = (now - lastMicros) / 1000000.0;  // 距上一圈 loop 的秒数
  lastMicros = now;

  // 原始 counts 换算成实际单位(除以 scale factor)
  float ayG   = ay / ACCEL_LSB_PER_G;                // g
  float azG   = az / ACCEL_LSB_PER_G;                // g
  float rateX = (gx - gyroBiasX) / GYRO_LSB_PER_DPS; // deg/s,已减去 bias

  // accelerometer 倾角:长期稳定,一动就有噪声
  float accelAngle = atan2(ayG, azG) * 180.0 / PI;

  // complementary filter:gyro 负责快的变化,
  // accelerometer 慢慢把 drift 拉回来
  angle = ALPHA * (angle + rateX * dt) + (1.0 - ALPHA) * accelAngle;

  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print(F("MPU6050 tilt"));
  display.setTextSize(3);
  display.setCursor(0, 28);
  display.print(angle, 1);
  display.setTextSize(1);
  display.print(F(" deg"));
  display.display();
}

上传之后,先让板子平放着等校准信息走完,然后绕它的 X 轴——丝印箭头指的那条轴——来回滚动。角度应该即时跟着你的手走,放回桌面就归零,并且稳稳停在零上。

卡通图:一只手倾斜蓝色 GY-521 传感器板,蓝色 OLED 显示屏上一根发光的指针跟着倾斜
倾斜 GY-521,融合角度就在 OLED 上跟着动——快得像 gyro,稳得像 accelerometer。

读数不对或什么都不显示,怎么排查?

GY-521 出问题的方式都很有辨识度,每种症状都指向一个具体的地方。

症状 可能原因 解决方法
I2C 总线上找不到任何设备 接线问题或排针没焊 跑一个 I2C scanner sketch;确认 SDA 在 A4、SCL 在 A5,排针是真的焊上了而不是插着
所有读数都是零 芯片还在 sleep 确认写 PWR_MGMT_1 (0x6B) 那段代码有跑到,地址是 0x68
某一轴卡在 32767 或 −32768 数值顶到满量程 猛敲一下瞬间顶满是正常的;某一轴长期卡死不动,多半是 sensor 已损坏
静止时角度持续漂移 gyro bias 没有去掉 启动校准时让板子完全静止;按 reset 重新校准
敲一下板子角度就跳 accelerometer 对冲击的反应 小幅度属正常;把 ALPHA 往 0.98 调,更信 gyro 一些
OLED 一直黑屏 电源脚接反或地址不对 检查 GND 在第一脚的排针顺序;scanner 若在 0x3D 找到屏,就改用 0x3D

真实客户最常犯的错误

我们最常遇到的,是把 Uno 接进 XDA 和 XCL——因为名字看起来像 SDA、SCL 的兄弟。那两个焊盘是 sensor 自己的 auxiliary 总线,Uno 要接的永远只有 SDA 和 SCL。

其次是跳过焊接:散装排针直接插上去,平放在桌上一切正常,板子一倾斜、针脚一动就断线。先把排针焊好,再谈其它排查。

第三是用原始输出评判模块——看到 16384 就断定 sensor 坏了,或者拿别的量程的除数来除。还有 OLED 那条 GND 在前的排针:习惯性地”红线先接第一脚”,至今仍是唯一一个硬件不会原谅的错误。

FAQ

想得到好的角度,一定要用 DMP 吗?

不用。MPU-6050 内建的 Digital Motion Processor 可以在芯片上直接算姿态,但需要上传一大段固件、用更重的 library。就测倾角而言,这里的 complementary filter 已经给出平滑稳定的结果,而且每一步算术你都真的看得懂。

MPU6050 能测位置或距离吗?

基本不能。算位置要对加速度积分两次,任何微小的 bias 都会被放大两轮,几秒之内算出来的位置就飘出几米——真正的系统要靠 GPS、轮式编码器或摄像头来补。把 MPU-6050 当姿态 sensor 用,别当定位器。

板子明明不动,为什么角度还在漂?

那是 gyro 的静止 bias 在被积分:每个模块静止时读数都不是正好为零,把这个误差一直累加,它就会无限长大。启动时让板子静止做校准,剩下的一点残余交给 filter 里的 accelerometer 项拉回来。

GY-521 能配 ESP32 用吗?

能——接到 ESP32 的 GPIO21(SDA)和 GPIO22(SCL),VCC 供 3.3V,同一套 register 代码原样能跑。我们的开发板对比指南可以帮你判断什么时候值得用上 ESP32 的速度和 WiFi。

每次上电都要重新校准吗?

要,sketch 在启动时大约两秒就做完了。gyro bias 会随温度变、隔天也会变,存起来的校准值会过期——每次开机重新平均一次,几乎没有成本。

最后更新:2026年8月。有问题?WhatsApp 联系我们

Leave a Reply

Your email address will not be published. Required fields are marked *