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的问题。

注意:MPU6050的I2C总线需要上拉电阻,典型值4.7kΩ。如果通信不稳定,先量一下SCL和SDA的波形,看上升沿是否太缓。

4.1.2 初始化流程

初始化MPU6050,说白了就是写几个关键寄存器。我一般按这个顺序来:

  1. 复位设备:写0x80到寄存器0x6B(PWR_MGMT_1)
  2. 等待复位完成:延时100ms
  3. 唤醒设备:写0x00到寄存器0x6B
  4. 配置时钟源:写0x01到寄存器0x6B(选择X轴陀螺仪作为时钟源)
  5. 设置量程:陀螺仪±2000°/s,加速度计±2g
  6. 配置数字低通滤波器:写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];
小技巧:读取温度值(寄存器0x41)可以用来做传感器自检。如果温度读数异常(比如超过85°C),说明传感器可能已经损坏。我在一次飞控返修中就用这个方法快速定位了问题。

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。片选信号低电平有效。

注意:BMP280的SPI读取操作,第一个字节是寄存器地址(最高位为1表示读),后面跟的数据字节数取决于你要读多少。写操作时,第一个字节最高位为0。

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寄存器
经验之谈:过采样倍数越高,精度越好,但测量时间也越长。×16时,一次测量大约需要20ms。如果你的飞控需要100Hz的气压更新率,建议用×8或×4。

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的驱动开发。到时候见。