第2章 传感器基础:加速度计与陀螺仪(MPU6050)原理、I2C通信协议、数据读取与校准

好,咱们开始聊传感器。飞控系统里,传感器就是飞机的「眼睛」和「耳朵」。没有它们,飞控就是瞎子。

这一章,我重点讲 MPU6050。这颗芯片,我用了不下十年。从最早的四轴玩具,到后来的工业级飞控,都有它的身影。说白了,它是你入门飞控绕不开的「老朋友」。

2.1 MPU6050 是什么?

MPU6050 是 InvenSense 公司出的一款 6 轴运动处理芯片。它把三轴加速度计和三轴陀螺仪封装在一起,还带了一个数字运动处理器(DMP)。

嗯,这里要注意:它内部其实有两个传感器。

  • 加速度计:测量物体的加速度。静态时,它感知的是重力。所以你能用它算出俯仰角和横滚角。
  • 陀螺仪:测量角速度。说白了,就是物体转得有多快。积分后可以得到角度变化。

我个人习惯,把加速度计比作「水平仪」,陀螺仪比作「旋转感应器」。两者互补,才能得到稳定的姿态数据。

核心参数速览

参数加速度计陀螺仪
量程±2g / ±4g / ±8g / ±16g±250 / ±500 / ±1000 / ±2000 °/s
分辨率16位 ADC16位 ADC
输出速率最高 1kHz最高 8kHz
通信接口I2C(最高 400kHz)

2.2 加速度计原理:别被「加速度」三个字骗了

很多人以为加速度计只测运动加速度。其实它测的是「比力」。什么意思?

你想想看,把加速度计平放在桌面上,它读出来的是 1g(约 9.8 m/s²),方向指向地心。为什么?因为桌面给它一个向上的支撑力,抵消了重力。传感器感受到的是这个支撑力,而不是重力本身。

我在项目中遇到过一个问题:有同学把加速度计竖直安装,结果发现 Z 轴读数一直是 0。他以为传感器坏了。其实不是,因为竖直时重力作用在 Y 轴上,Z 轴自然没读数。

所以,用加速度计算角度,本质上是利用重力在三个轴上的分量。

// 从加速度计原始值计算俯仰角(Pitch)和横滚角(Roll)
// 假设 acc_x, acc_y, acc_z 是经过校准后的值
float pitch = atan2(-acc_x, sqrt(acc_y * acc_y + acc_z * acc_z)) * 180.0 / M_PI;
float roll  = atan2(acc_y, acc_z) * 180.0 / M_PI;

小技巧:atan2 比 atan 好用。它能自动处理象限问题,不会出现角度跳变。

2.3 陀螺仪原理:积分是个坑

陀螺仪测的是角速度。要得到角度,必须做积分。

但积分有个大问题:误差会累积。哪怕零点几度的偏差,积分几秒钟后,角度就漂到天边去了。

我记得有一次调试,飞机悬停时姿态角一直在缓慢增加。查了半天,发现是陀螺仪零偏没校准好。嗯,从那以后,我每次上电都会做一次静态校准。

// 陀螺仪积分得到角度(简化版)
// 注意:实际应用中需要配合加速度计做互补滤波或卡尔曼滤波
float gyro_angle = previous_angle + gyro_rate * dt;

警告:千万不要只用陀螺仪积分来算角度!时间一长,角度会漂移到离谱。必须用加速度计或磁力计来修正。

2.4 I2C 通信协议:两根线搞定一切

MPU6050 通过 I2C 与 STM32 通信。I2C 只需要两根线:SCL(时钟)和 SDA(数据)。

说白了,I2C 就是主从通信。STM32 是主机,MPU6050 是从机。主机发起通信,从机响应。

MPU6050 的 I2C 地址是 0x68(AD0 引脚接地时)。如果 AD0 接高电平,地址变成 0x69。

我在项目中习惯把 AD0 接地,省得地址冲突。

I2C 读写流程

  1. 主机发送起始信号
  2. 主机发送从机地址 + 写位(0)
  3. 从机应答
  4. 主机发送寄存器地址
  5. 从机应答
  6. 主机发送数据(写操作)或重新发送起始信号 + 读位(读操作)
  7. 从机应答并发送/接收数据
  8. 主机发送停止信号
// STM32 HAL 库读取 MPU6050 加速度计数据示例
uint8_t buf[6];
HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, MPU6050_ACCEL_XOUT_H, I2C_MEMADD_SIZE_8BIT, buf, 6, 100);

int16_t acc_x = (buf[0] << 8) | buf[1];
int16_t acc_y = (buf[2] << 8) | buf[3];
int16_t acc_z = (buf[4] << 8) | buf[5];

经验之谈:I2C 速率不要设太高。我一般用 100kHz 或 400kHz。速率太高,线长一点就容易出问题。

2.5 数据读取:从原始值到物理量

MPU6050 的 ADC 是 16 位的。原始值范围是 -32768 到 32767。

要转换成物理量,需要除以灵敏度。灵敏度取决于量程设置。

量程加速度计灵敏度陀螺仪灵敏度
±2g16384 LSB/g
±4g8192 LSB/g
±8g4096 LSB/g
±16g2048 LSB/g
±250 °/s131 LSB/(°/s)
±500 °/s65.5 LSB/(°/s)
±1000 °/s32.8 LSB/(°/s)
±2000 °/s16.4 LSB/(°/s)
// 原始值转物理量
float acc_x_g = (float)acc_x / 16384.0f;  // 假设量程为 ±2g
float gyro_x_dps = (float)gyro_x / 131.0f; // 假设量程为 ±250 °/s

2.6 校准:不做校准,飞控就是废物

这是我最想强调的部分。不校准的传感器,数据根本不能用。

校准主要做两件事:

  1. 零偏校准:静止时,陀螺仪读数应该为 0,加速度计 Z 轴应该为 1g。但实际上有偏差。
  2. 比例校准:传感器的输出比例可能与标称值有微小差异。

我的校准流程

我曾经因为校准没做好,导致飞机起飞就翻。嗯,从那以后,我总结了一套流程:

  • 静态采集:上电后,保持飞机水平静止,采集 100-200 组数据。
  • 计算均值:陀螺仪三个轴的均值就是零偏值。加速度计 X、Y 轴均值应该是 0,Z 轴均值应该是 1g。
  • 保存参数:把校准值写入 Flash 或 EEPROM,每次上电自动加载。
// 陀螺仪零偏校准示例
// 采集 100 次数据,求平均
float gyro_offset_x = 0;
for (int i = 0; i < 100; i++) {
    gyro_offset_x += read_gyro_x();
    HAL_Delay(10);
}
gyro_offset_x /= 100.0f;

// 使用时减去零偏
float gyro_x_calibrated = read_gyro_x() - gyro_offset_x;

注意:校准必须在传感器完全静止的状态下进行。哪怕有一点点震动,校准值都会不准。

2.7 避坑指南

最后,分享几个我踩过的坑:

  • I2C 无应答:检查接线,检查地址。我遇到过 AD0 没接导致的地址错误。
  • 数据跳变:检查电源纹波。MPU6050 对电源质量敏感,最好加个 100nF 去耦电容。
  • 温度漂移:陀螺仪零偏会随温度变化。如果要求高,可以做温度补偿。
  • DMP 别乱用:MPU6050 内置 DMP 可以输出四元数,但精度一般。我建议自己写滤波算法。

好了,这一章就到这里。下一章我们讲磁力计和气压计,到时候你会看到更多有意思的东西。