第三章 传感器数据读取:I2C/SPI通信协议、MPU6050初始化与数据读取、加速度计与陀螺仪校准

好,我们继续往下走。上一章我们把飞控的硬件框架搭好了,电源也稳了。但一个飞控如果没有传感器,就像人没有眼睛和耳朵——你根本不知道飞机现在是什么姿态。这一章,我们就来搞定飞控的“感官”:MPU6050六轴惯性测量单元。

我个人习惯把传感器调试称为“飞控的灵魂注入”。你想想看,所有后续的姿态解算、控制算法,全都依赖这一章读出来的数据。数据不准,后面全是白搭。我在项目中遇到过好几次,明明PID参数调得挺好,飞机就是乱飘,最后发现是传感器初始化顺序搞错了。嗯,这种坑,咱们今天一次性填平。

3.1 I2C与SPI:两种常见的传感器通信协议

MPU6050支持两种通信接口:I2C和SPI。说白了,这就是传感器和主控芯片之间“说话”的方式。我建议初学者先用I2C,因为接线简单,只需要两根线。

3.1.1 I2C协议要点

  • 两根线:SCL(时钟线)和SDA(数据线)
  • 主从结构:STM32是主机,MPU6050是从机
  • 设备地址:MPU6050的I2C地址是0x68(AD0引脚接地时)
  • 速率:标准模式100kHz,快速模式400kHz
我的小技巧:调试I2C时,先用逻辑分析仪抓一下波形。我曾经花了一整天查为什么读不到数据,最后发现是上拉电阻没焊。两根线必须接4.7kΩ上拉电阻到3.3V,切记。

3.1.2 SPI协议要点(了解即可)

SPI比I2C快,但需要四根线。如果你以后做高速数据采集(比如光学流传感器),可能会用到。对于MPU6050,I2C完全够用。

3.2 MPU6050初始化流程

初始化MPU6050,说白了就是往它的寄存器里写几个配置值。我习惯按这个顺序来:

  1. 复位传感器:写0x80到寄存器0x6B(电源管理寄存器)
  2. 等待100ms:让内部时钟稳定
  3. 唤醒传感器:写0x00到寄存器0x6B
  4. 配置陀螺仪量程:写0x00到寄存器0x1B(±250°/s)
  5. 配置加速度计量程:写0x00到寄存器0x1C(±2g)
  6. 配置数字低通滤波器:写0x06到寄存器0x1A(带宽约5Hz)
注意:复位后一定要等!我曾经偷懒只等了10ms,结果读出来的数据全是0xFF。MPU6050上电后内部PLL需要时间锁定,至少等50ms以上。

下面是具体的初始化代码。我用的是STM32的HAL库,I2C通信:

// MPU6050初始化函数
// 返回值:0-成功,1-失败
uint8_t MPU6050_Init(void)
{
    uint8_t data;
    
    // 1. 复位传感器
    data = 0x80;
    HAL_I2C_Master_Transmit(&hi2c1, 0xD0, &data, 1, 100);
    HAL_Delay(100);  // 等100ms,别偷懒
    
    // 2. 唤醒传感器,选择时钟源为X轴陀螺仪
    data = 0x01;  // 寄存器0x6B,bit0=1表示使用X轴陀螺仪时钟
    HAL_I2C_Master_Transmit(&hi2c1, 0xD0, &data, 1, 100);
    HAL_Delay(10);
    
    // 3. 配置陀螺仪:±250°/s
    data = 0x00;  // 寄存器0x1B,FS_SEL=0
    HAL_I2C_Master_Transmit(&hi2c1, 0xD0, &data, 1, 100);
    
    // 4. 配置加速度计:±2g
    data = 0x00;  // 寄存器0x1C,AFS_SEL=0
    HAL_I2C_Master_Transmit(&hi2c1, 0xD0, &data, 1, 100);
    
    // 5. 配置DLPF(数字低通滤波器),带宽约5Hz
    data = 0x06;  // 寄存器0x1A,DLPF_CFG=6
    HAL_I2C_Master_Transmit(&hi2c1, 0xD0, &data, 1, 100);
    
    // 6. 验证是否初始化成功:读取WHO_AM_I寄存器
    uint8_t reg = 0x75;
    HAL_I2C_Master_Transmit(&hi2c1, 0xD0, ®, 1, 100);
    HAL_I2C_Master_Receive(&hi2c1, 0xD1, &data, 1, 100);
    
    if(data != 0x68)
    {
        return 1;  // 初始化失败
    }
    
    return 0;  // 成功
}

3.3 读取加速度计与陀螺仪数据

初始化完成后,就可以读取数据了。MPU6050的加速度计和陀螺仪数据分别存储在6个寄存器中,每个轴占2个字节(高8位+低8位)。

寄存器地址 数据内容 说明
0x3B, 0x3C ACCEL_XOUT_H, ACCEL_XOUT_L X轴加速度
0x3D, 0x3E ACCEL_YOUT_H, ACCEL_YOUT_L Y轴加速度
0x3F, 0x40 ACCEL_ZOUT_H, ACCEL_ZOUT_L Z轴加速度
0x43, 0x44 GYRO_XOUT_H, GYRO_XOUT_L X轴角速度
0x45, 0x46 GYRO_YOUT_H, GYRO_YOUT_L Y轴角速度
0x47, 0x48 GYRO_ZOUT_H, GYRO_ZOUT_L Z轴角速度

读取时,我习惯一次性读取所有6个轴的数据(共12个字节),这样效率更高:

// 读取MPU6050所有数据
void MPU6050_ReadAll(int16_t* accel, int16_t* gyro)
{
    uint8_t buf[14];
    uint8_t reg = 0x3B;  // 从ACCEL_XOUT_H开始读
    
    HAL_I2C_Master_Transmit(&hi2c1, 0xD0, ®, 1, 100);
    HAL_I2C_Master_Receive(&hi2c1, 0xD1, buf, 14, 100);
    
    // 加速度计数据(高8位左移8位 + 低8位)
    accel[0] = (int16_t)((buf[0] << 8) | buf[1]);   // X轴
    accel[1] = (int16_t)((buf[2] << 8) | buf[3]);   // Y轴
    accel[2] = (int16_t)((buf[4] << 8) | buf[5]);   // Z轴
    
    // 跳过温度寄存器(buf[6]和buf[7])
    
    // 陀螺仪数据
    gyro[0] = (int16_t)((buf[8] << 8) | buf[9]);    // X轴
    gyro[1] = (int16_t)((buf[10] << 8) | buf[11]);  // Y轴
    gyro[2] = (int16_t)((buf[12] << 8) | buf[13]);  // Z轴
}
关键点:读出来的原始值是16位有符号整数。要转换成物理单位,需要乘以灵敏度系数。比如±2g量程下,加速度计灵敏度是16384 LSB/g。所以实际加速度 = 原始值 / 16384(单位:g)。

3.4 加速度计与陀螺仪校准

校准,说白了就是找零偏。任何传感器都有制造误差,MPU6050也不例外。我见过最夸张的一次,有个模块静止时陀螺仪Z轴读数竟然是-150,这要是不校准,飞机自己就转起来了。

3.4.1 陀螺仪零偏校准

陀螺仪测量角速度。静止时,理想输出应该是0。但实际上会有个固定偏移。校准方法很简单:

  1. 把飞控水平静止放置
  2. 连续采集N组数据(我一般取200组)
  3. 计算平均值,这就是零偏值
  4. 以后每次读取时,减去这个零偏
// 陀螺仪零偏校准
int16_t gyro_offset[3] = {0, 0, 0};

void CalibrateGyro(void)
{
    int32_t sum[3] = {0, 0, 0};
    int16_t accel[3], gyro[3];
    
    // 采集200组数据
    for(int i = 0; i < 200; i++)
    {
        MPU6050_ReadAll(accel, gyro);
        sum[0] += gyro[0];
        sum[1] += gyro[1];
        sum[2] += gyro[2];
        HAL_Delay(2);  // 每2ms采一组
    }
    
    // 计算平均值
    gyro_offset[0] = (int16_t)(sum[0] / 200);
    gyro_offset[1] = (int16_t)(sum[1] / 200);
    gyro_offset[2] = (int16_t)(sum[2] / 200);
}

3.4.2 加速度计校准

加速度计校准稍微复杂一点。静止时,理论上三个轴的平方和应该等于1g(即16384²)。但实际上会有偏差。我常用的方法是六面校准:

  • 分别让X轴朝上、X轴朝下、Y轴朝上、Y轴朝下、Z轴朝上、Z轴朝下
  • 记录每个姿态下的读数
  • 计算每个轴的零偏和比例因子

不过对于初学者,我建议先做简单的水平校准:把飞控放平,记录此时的加速度计读数作为零偏。虽然不够精确,但足够让飞机先飞起来。

避坑指南:我曾经在校准时忘了把飞控放水平,结果零偏全是歪的。飞机一解锁就往一边猛冲。所以校准前,一定用水平仪确认一下桌面是平的。或者,你可以在代码里加一个判断:如果加速度计Z轴读数不在15000~17000之间,就提示“未水平放置”。

3.5 本章知识体系

下面这张图,是我自己梳理的本章知识脉络。你可以把它当作一个检查清单:

第三章:传感器数据读取知识体系 通信协议 I2C(推荐) SPI(备用) MPU6050初始化 复位→唤醒 配置量程 数据读取 加速度+陀螺仪 传感器校准 陀螺仪零偏校准 加速度计六面校准 水平校准(入门) 核心流程:通信协议 → 初始化配置 → 数据读取 → 校准补偿 校准是飞控稳定的基石,千万别跳过

嗯,这一章的内容就到这里。传感器数据读出来了,也校准了,下一步就是把这些数据变成有用的姿态信息。不过那是下一章的事了。你现在要做的,就是把上面的代码烧进去,打开串口看看数据是不是在合理范围内。如果Z轴加速度在16000左右,陀螺仪三个轴都在±10以内,恭喜你,传感器部分搞定了。

本章核心要点
  • I2C通信只需要两根线,适合初学者
  • MPU6050初始化顺序:复位→等待→唤醒→配置量程→配置滤波器
  • 读取数据时一次性读12个字节效率更高
  • 陀螺仪必须做零偏校准,加速度计至少做水平校准
  • 校准前确保飞控水平放置

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