第三章 传感器数据读取: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
3.1.2 SPI协议要点(了解即可)
SPI比I2C快,但需要四根线。如果你以后做高速数据采集(比如光学流传感器),可能会用到。对于MPU6050,I2C完全够用。
3.2 MPU6050初始化流程
初始化MPU6050,说白了就是往它的寄存器里写几个配置值。我习惯按这个顺序来:
- 复位传感器:写0x80到寄存器0x6B(电源管理寄存器)
- 等待100ms:让内部时钟稳定
- 唤醒传感器:写0x00到寄存器0x6B
- 配置陀螺仪量程:写0x00到寄存器0x1B(±250°/s)
- 配置加速度计量程:写0x00到寄存器0x1C(±2g)
- 配置数字低通滤波器:写0x06到寄存器0x1A(带宽约5Hz)
下面是具体的初始化代码。我用的是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轴
}
3.4 加速度计与陀螺仪校准
校准,说白了就是找零偏。任何传感器都有制造误差,MPU6050也不例外。我见过最夸张的一次,有个模块静止时陀螺仪Z轴读数竟然是-150,这要是不校准,飞机自己就转起来了。
3.4.1 陀螺仪零偏校准
陀螺仪测量角速度。静止时,理想输出应该是0。但实际上会有个固定偏移。校准方法很简单:
- 把飞控水平静止放置
- 连续采集N组数据(我一般取200组)
- 计算平均值,这就是零偏值
- 以后每次读取时,减去这个零偏
// 陀螺仪零偏校准
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轴朝下
- 记录每个姿态下的读数
- 计算每个轴的零偏和比例因子
不过对于初学者,我建议先做简单的水平校准:把飞控放平,记录此时的加速度计读数作为零偏。虽然不够精确,但足够让飞机先飞起来。
3.5 本章知识体系
下面这张图,是我自己梳理的本章知识脉络。你可以把它当作一个检查清单:
嗯,这一章的内容就到这里。传感器数据读出来了,也校准了,下一步就是把这些数据变成有用的姿态信息。不过那是下一章的事了。你现在要做的,就是把上面的代码烧进去,打开串口看看数据是不是在合理范围内。如果Z轴加速度在16000左右,陀螺仪三个轴都在±10以内,恭喜你,传感器部分搞定了。
- I2C通信只需要两根线,适合初学者
- MPU6050初始化顺序:复位→等待→唤醒→配置量程→配置滤波器
- 读取数据时一次性读12个字节效率更高
- 陀螺仪必须做零偏校准,加速度计至少做水平校准
- 校准前确保飞控水平放置
公众号:蓝海资料掘金营,微信deep3321