4、无人机传感器驱动开发(上):MPU6050六轴姿态传感器(I2C驱动)、SPI接口BMP280气压计驱动
各位同学,欢迎来到传感器驱动开发的第一讲。
说实话,飞控系统里最核心的硬件,除了MCU就是传感器。没有传感器,无人机就是个睁眼瞎。今天咱们要啃的两块硬骨头——MPU6050六轴姿态传感器和BMP280气压计,几乎是所有入门级飞控的标配。
我当年做第一个飞控项目时,就在MPU6050的I2C时序上栽过跟头。嗯,今天我把这些坑都给你们标出来。
4.1 MPU6050:六轴姿态传感器的I2C驱动
MPU6050内部集成了3轴陀螺仪和3轴加速度计。它通过I2C接口与主控通信,最高支持400kHz的快速模式。
我个人习惯,在写任何I2C驱动前,先确认两件事:设备地址和寄存器映射。
4.1.1 设备地址与I2C时序
MPU6050的7位I2C地址是0x68(AD0引脚接地时)。如果AD0接VCC,地址变成0x69。
为什么强调这个?我在项目中遇到过,有块飞控板为了省一个GPIO,直接把AD0悬空了,结果地址飘忽不定。排查了一整天,最后发现是PCB layout的问题。
4.1.2 初始化流程
初始化MPU6050,说白了就是写几个关键寄存器。我一般按这个顺序来:
- 复位设备:写0x80到寄存器0x6B(PWR_MGMT_1)
- 等待复位完成:延时100ms
- 唤醒设备:写0x00到寄存器0x6B
- 配置时钟源:写0x01到寄存器0x6B(选择X轴陀螺仪作为时钟源)
- 设置量程:陀螺仪±2000°/s,加速度计±2g
- 配置数字低通滤波器:写0x06到寄存器0x1A(DLPF_CFG=6,带宽5Hz)
你想想看,为什么要把DLPF设成5Hz?因为飞控的角速度更新率通常在250Hz左右,5Hz的截止频率可以有效滤除高频振动噪声。我刚开始做时设成了256Hz,结果姿态解算出来的数据抖得像筛子。
4.1.3 读取原始数据
读取数据时,我习惯一次性读取所有6轴数据。MPU6050的加速度计数据从寄存器0x3B开始,陀螺仪数据从0x43开始,每个轴占2个字节,共12个字节。
// 读取MPU6050原始数据
uint8_t buf[14];
MPU6050_ReadRegs(0x3B, buf, 14); // 从ACCEL_XOUT_H开始读14字节
// 解析加速度计数据
int16_t accel_x = (buf[0] << 8) | buf[1];
int16_t accel_y = (buf[2] << 8) | buf[3];
int16_t accel_z = (buf[4] << 8) | buf[5];
// 解析陀螺仪数据
int16_t gyro_x = (buf[8] << 8) | buf[9];
int16_t gyro_y = (buf[10] << 8) | buf[11];
int16_t gyro_z = (buf[12] << 8) | buf[13];
4.1.4 数据转换与校准
原始数据需要转换成物理量。加速度计的量程是±2g时,灵敏度为16384 LSB/g。陀螺仪±2000°/s时,灵敏度为16.4 LSB/°/s。
// 转换为物理量
float accel_x_g = accel_x / 16384.0f;
float gyro_x_dps = gyro_x / 16.4f;
校准这块,我建议做零偏校准。把无人机静止放在水平面上,采集1000组陀螺仪数据取平均,这个平均值就是零偏值。后续每次读取时减去它。
核心要点:MPU6050的I2C驱动,本质上就是「写寄存器配置→读寄存器数据→转换物理量」三步。但每一步都有坑——地址别搞错、时序别太快、校准别偷懒。
4.2 BMP280:SPI接口气压计驱动
BMP280是博世的气压传感器,精度高、功耗低。它支持I2C和SPI两种接口,但在飞控上,我强烈建议用SPI。为什么?因为SPI速度更快,而且不占用I2C总线。
我记得有一次,I2C总线上挂了MPU6050、BMP280和磁力计三个设备,结果通信冲突导致气压数据更新率掉到10Hz。换成SPI后,直接飙到200Hz。
4.2.1 SPI通信配置
BMP280的SPI模式是模式0(CPOL=0, CPHA=0),时钟频率最高10MHz。片选信号低电平有效。
4.2.2 初始化与校准系数
BMP280出厂时,每个芯片都有独特的校准系数,存储在寄存器0x88到0xA1。这些系数用于补偿温度和气压的测量误差。
// 读取校准系数
uint8_t calib_data[24];
BMP280_ReadRegs(0x88, calib_data, 24);
// 解析校准系数(部分示例)
uint16_t dig_T1 = (calib_data[1] << 8) | calib_data[0];
int16_t dig_T2 = (calib_data[3] << 8) | calib_data[2];
int16_t dig_T3 = (calib_data[5] << 8) | calib_data[4];
校准系数一旦读取,后续可以一直复用,不需要每次上电都读。我习惯在初始化时把它们存到全局结构体里。
4.2.3 配置测量模式
BMP280支持多种工作模式。飞控场景下,我推荐使用强制模式(Forced Mode)。每次触发一次测量,完成后自动休眠,省电又可控。
// 配置为强制模式,过采样×16
uint8_t config = 0x00; // 0x00: 无滤波, 0ms standby
uint8_t ctrl_meas = 0x57; // 0x57: 温度过采样×16, 气压过采样×16, 强制模式
BMP280_WriteReg(0xF4, ctrl_meas); // ctrl_meas寄存器
BMP280_WriteReg(0xF5, config); // config寄存器
4.2.4 读取并计算气压值
测量完成后,从寄存器0xF7读取20位的气压原始值,从0xFA读取20位的温度原始值。然后利用校准系数进行补偿计算。
// 读取原始数据
uint8_t data[6];
BMP280_ReadRegs(0xF7, data, 6);
uint32_t raw_press = ((uint32_t)data[0] << 12) | ((uint32_t)data[1] << 4) | ((uint32_t)data[2] >> 4);
uint32_t raw_temp = ((uint32_t)data[3] << 12) | ((uint32_t)data[4] << 4) | ((uint32_t)data[5] >> 4);
// 温度补偿计算(简化版)
int32_t t_fine;
int32_t adc_T = raw_temp;
int32_t var1 = ((((adc_T >> 3) - ((int32_t)dig_T1 << 1))) * ((int32_t)dig_T2)) >> 11;
int32_t var2 = (((((adc_T >> 4) - ((int32_t)dig_T1)) * ((adc_T >> 4) - ((int32_t)dig_T1))) >> 12) * ((int32_t)dig_T3)) >> 14;
t_fine = var1 + var2;
float temperature = (t_fine * 5 + 128) >> 8; // 单位:0.01°C
气压计算更复杂一些,但博世官方提供了完整的补偿算法。我建议直接复制粘贴官方代码,别自己手写——我曾经手写过一次,结果算出来的海拔高度差了50米,排查了三天才发现是某个中间变量溢出了。
4.2.5 海拔高度换算
得到气压值后,可以用国际气压公式换算海拔高度:
float pressure_Pa = (float)raw_press; // 单位:Pa
float altitude_m = 44330.0f * (1.0f - pow(pressure_Pa / 101325.0f, 1.0f / 5.255f));
注意,这个公式假设海平面气压是101325Pa。实际飞行中,海平面气压会随天气变化,所以飞控通常会在起飞时记录一次基准气压,然后计算相对高度。
总结一下:BMP280的SPI驱动,核心在于校准系数的正确读取和补偿算法的实现。SPI通信本身不复杂,但寄存器地址的读写位设置容易搞错。我建议在调试时,先用逻辑分析仪抓一下SPI波形,确认数据对不对。
好了,今天的内容就到这里。MPU6050和BMP280这两个传感器,是飞控驱动开发的入门必修课。下一讲,我们会继续深入,聊聊磁力计和GPS的驱动开发。到时候见。