3. 加速度计原理与数据读取:MEMS加速度计工作原理、I2C/SPI读取MPU6050数据

3.1 MEMS加速度计到底怎么工作的?

说实话,我第一次接触MEMS加速度计时,觉得这东西挺神奇的。一个米粒大小的芯片,居然能感知地球引力?

其实原理没那么玄乎。MEMS加速度计内部有一个微小的质量块,用硅材料的悬臂梁挂着。当芯片加速时,质量块会移动,改变电容值。测量这个电容变化,就能算出加速度。

我习惯把加速度计想象成一个弹簧秤。你想想看,静止时弹簧秤只受重力,读数就是1g。动起来时,惯性力会让弹簧拉伸或压缩。加速度计内部也是类似的道理。

核心要点:加速度计测量的是「比力」,不是纯加速度。静止时它测到的是重力加速度g,而不是0。这一点很多新手会搞混。

具体到MPU6050这颗芯片,它用的是电容式MEMS结构。内部有三个轴向的检测单元,分别对应X、Y、Z轴。每个轴都有一个差分电容桥,质量块位移会改变电容差值。

嗯,这里要注意:加速度计的输出是模拟信号,经过ADC转换成数字量。MPU6050内部有16位ADC,所以原始数据范围是-32768到32767。换算成g值,需要除以16384(当量程为±2g时)。

3.2 量程与灵敏度——选对参数很重要

我在项目中遇到过一个问题:无人机飞行时加速度计数据老是饱和。后来才发现,默认的±2g量程根本不够用。飞机一翻身,加速度轻松超过2g。

MPU6050支持四种量程:

量程 灵敏度 (LSB/g) 适用场景
±2g 16384 静止姿态、低动态
±4g 8192 步行、手持设备
±8g 4096 车载、机器人
±16g 2048 高动态、碰撞检测

我个人的建议是:做姿态估计时,优先用±2g。因为灵敏度最高,角度分辨率最好。如果发现数据经常到±1.9g以上,再考虑换大量程。

小技巧:读取原始数据后,先除以灵敏度得到g值。然后检查g值的模长是否接近1。如果偏差超过5%,说明加速度计可能需要校准。

3.3 I2C vs SPI——选哪个?

MPU6050同时支持I2C和SPI。我做过对比,说说我的感受。

I2C的优点:

  • 只需要两根线(SDA、SCL)
  • 可以挂多个设备
  • Arduino、STM32等平台支持完善

I2C的缺点:

  • 速度慢(标准模式100kHz,快速模式400kHz)
  • 读取多个寄存器时效率低

SPI的优点:

  • 速度快(最高可达20MHz)
  • 全双工通信
  • 读取连续寄存器时效率高

SPI的缺点:

  • 需要4根线(MOSI、MISO、SCK、CS)
  • 每个设备需要一个片选引脚

我曾经在一个项目中用I2C读取MPU6050,采样率只能到200Hz。换成SPI后,轻松跑到1kHz。如果你做的是高速姿态估计,比如四轴飞行器,我强烈建议用SPI。

3.4 实战:用I2C读取MPU6050数据

好,咱们直接上代码。下面是我常用的MPU6050初始化流程:

// MPU6050 I2C地址:AD0接GND时为0x68,接VCC时为0x69
#define MPU6050_ADDR 0x68

// 初始化函数
void MPU6050_Init(void) {
    // 1. 唤醒MPU6050(默认是睡眠模式)
    // 寄存器0x6B:电源管理
    I2C_WriteByte(MPU6050_ADDR, 0x6B, 0x00);
    
    // 2. 配置陀螺仪量程为±250°/s
    // 寄存器0x1B:陀螺仪配置
    I2C_WriteByte(MPU6050_ADDR, 0x1B, 0x00);
    
    // 3. 配置加速度计量程为±2g
    // 寄存器0x1C:加速度计配置
    I2C_WriteByte(MPU6050_ADDR, 0x1C, 0x00);
    
    // 4. 配置数字低通滤波器
    // 寄存器0x1A:配置
    // 设置带宽为44Hz(加速度计),42Hz(陀螺仪)
    I2C_WriteByte(MPU6050_ADDR, 0x1A, 0x03);
}

读取加速度数据的函数:

// 读取加速度计原始数据
void MPU6050_ReadAccel(int16_t *ax, int16_t *ay, int16_t *az) {
    uint8_t buf[6];
    
    // 从寄存器0x3B开始连续读取6个字节
    // 0x3B: ACCEL_XOUT_H
    // 0x3C: ACCEL_XOUT_L
    // 0x3D: ACCEL_YOUT_H
    // 0x3E: ACCEL_YOUT_L
    // 0x3F: ACCEL_ZOUT_H
    // 0x40: ACCEL_ZOUT_L
    I2C_ReadBytes(MPU6050_ADDR, 0x3B, buf, 6);
    
    // 合并高字节和低字节(大端模式)
    *ax = (int16_t)((buf[0] << 8) | buf[1]);
    *ay = (int16_t)((buf[2] << 8) | buf[3]);
    *az = (int16_t)((buf[4] << 8) | buf[5]);
}

注意:MPU6050的数据寄存器是大端模式(Big Endian)。高字节在前,低字节在后。如果你用的小端模式的MCU,合并时要注意字节顺序。

把原始数据转换成g值:

// 加速度计灵敏度:±2g时为16384 LSB/g
#define ACCEL_SENSITIVITY 16384.0f

// 转换为g值
float accel_x_g = (float)(*ax) / ACCEL_SENSITIVITY;
float accel_y_g = (float)(*ay) / ACCEL_SENSITIVITY;
float accel_z_g = (float)(*az) / ACCEL_SENSITIVITY;

3.5 避坑指南——我踩过的那些坑

坑1:忘记唤醒芯片

我曾经调试了一整天,读出来的数据全是0。后来才发现,MPU6050上电默认是睡眠模式。必须写寄存器0x6B为0x00才能唤醒。这个坑我至少踩过两次。

坑2:I2C速率太高

MPU6050的I2C接口最高支持400kHz。我试过用1MHz去读,结果数据偶尔会跳变。后来老老实实降到400kHz,问题就解决了。

坑3:数据读取时序不对

读取加速度数据时,必须从高字节寄存器开始读。如果你只读低字节,或者顺序搞反了,数据会完全错乱。我建议用连续读取方式,一次读6个字节,省心。

坑4:忽略数字低通滤波器

MPU6050内部有一个可配置的数字低通滤波器。默认设置下带宽很高,噪声很大。我习惯把带宽设在44Hz左右,既能滤除高频噪声,又不会影响姿态更新率。

3.6 知识体系总览

下面这张图总结了本章的核心内容,我画成了流程图,方便你理解整个数据链路:

加速度计数据读取流程 MEMS物理结构 质量块 + 差分电容 模拟前端 C/V转换 + 放大 16位ADC 量程选择 ±2/4/8/16g 数字处理 数字低通滤波 + 数据寄存器 通信接口 I2C (400kHz) / SPI (20MHz) 主控读取 连续读取6字节 → 合并 → 转换g值 应用层 姿态解算 → 互补滤波/卡尔曼滤波 → 输出欧拉角 关键参数 灵敏度: 16384 带宽: 44Hz

这张图把整个数据链路串起来了。从物理层的MEMS结构,到模拟前端的电容电压转换,再到ADC量化、数字滤波,最后通过I2C或SPI传给主控。每一步都有讲究,任何一个环节出问题,最终数据都会不准。

我的经验:调试加速度计时,先检查原始数据是否在合理范围内。静止时,X和Y轴应该接近0,Z轴应该接近16384(±2g量程下)。如果偏差太大,先检查硬件连接,再检查配置寄存器。

好了,关于加速度计的原理和读取,就聊这么多。记住一点:加速度计是姿态估计的基础,数据质量直接决定最终效果。花时间把这一块吃透,后面会省很多事。


公众号:蓝海资料掘金营,微信deep3321