☰
MPU6050姿态解算实战:STM32卡尔曼滤波源码与调参指南
2026/10/20 4:53:13 网站建设 项目流程

简介:这份资源面向嵌入式开发初学者与从事无人机、机器人、运动设备研发的工程师,提供基于STM32平台读取MPU6050六轴惯性测量单元数据并完成姿态解算的完整源码,重点解决加速度计动态噪声与陀螺仪积分漂移带来的姿态误差问题。压缩包共129个文件,约3.64MB,以c与h源码文件为核心,配合uvproj、uvopt等Keil工程配置、axf与hex可执行产物、map与lst编译映射文件,以及若干bak备份与说明文档,覆盖驱动、滤波与工程构建各环节。资源已有5129人学习下载,热度较高。源码中可看到inv_mpu、inv_mpu_dmp_motion_driver、MPU6050、IOI2C等模块,完整呈现I2C初始化、寄存器读写、原始数据预处理与卡尔曼滤波状态预测、测量更新、协方差更新的实现路径,便于读者对照理解传感器融合流程,并直接移植到自己的嵌入式项目中调试与验证。

1. 从一块 MPU6050 说起:为什么你的姿态角总在跳

如果你玩过平衡车、四轴飞控或者云台,大概率绕不开 MPU6050 这颗六轴传感器。它便宜、好买、资料多,但真正把它用起来的人都会遇到同一个问题:读出来的加速度计和陀螺仪数据,直接算角度要么抖得没法看,要么漂得离谱。加速度计静态准但动态噪声大,陀螺仪动态好但积分会累积漂移,单靠任何一个都做不出能用的姿态解算。

这份 MPU6050 姿态解算 STM32 源码,核心就是用卡尔曼滤波把两路数据融合起来,输出稳定的俯仰角、横滚角和偏航角。代码基于 STM32 标准外设库,I2C 读取原始数据,卡尔曼滤波在 MCU 上实时运行,适合做平衡车、云台、姿态记录仪这类需要实时姿态反馈的项目。如果你正在找一份能直接编译、能看懂滤波过程、能改成自己项目的姿态解算代码,这份资源值得花时间跑一遍。

2. 项目架构与目录结构:先看清代码怎么组织

2.1 整体分层设计

这份源码没有用 HAL 库,走的是 STM32 标准外设库的路子,代码量不大但分层清晰。整个工程可以分成四层:硬件驱动层、数据采集层、算法层和应用层。硬件驱动层负责 I2C 初始化和 MPU6050 寄存器读写;数据采集层把原始寄存器值转成物理量;算法层就是卡尔曼滤波的核心;应用层通过串口把姿态角输出到上位机。

这种分层的意义在于,你换传感器或者换 MCU 的时候,只需要动驱动层,算法层几乎不用改。我见过太多人把 I2C 读写和滤波计算揉在一个文件里,后面想移植到别的平台,改到怀疑人生。

2.2 目录结构说明

工程目录大致是这样的:

MPU6050_Kalman_STM32/ ├── CMSIS/ # 内核支持文件 ├── STM32F10x_StdPeriph_Driver/ # 标准外设库 ├── User/ │ ├── main.c # 主循环,初始化 + 姿态输出 │ ├── stm32f10x_it.c # 中断服务函数 │ └── system_stm32f10x.c # 系统时钟配置 ├── Hardware/ │ ├── mpu6050.c # MPU6050 驱动,I2C 读写 │ ├── mpu6050.h │ ├── i2c.c # 软件模拟 I2C │ └── i2c.h ├── Algorithm/ │ ├── kalman.c # 卡尔曼滤波实现 │ └── kalman.h └── Output/ # 编译输出

Hardware目录放的是和芯片打交道的代码,Algorithm目录只放滤波算法,User目录是业务逻辑。这种划分在中小型嵌入式项目里很常见,好处是算法可以单独拿出去做单元测试,不用连着硬件一起跑。

2.3 关键数据结构

在mpu6050.h里定义了一个结构体来存姿态数据:

typedef struct { float Accel_X; // X轴加速度,单位 g float Accel_Y; float Accel_Z; float Gyro_X; // X轴角速度,单位 °/s float Gyro_Y; float Gyro_Z; float Roll; // 横滚角,单位 ° float Pitch; // 俯仰角,单位 ° float Yaw; // 偏航角,单位 ° } MPU6050_Data_t;

这个结构体贯穿整个数据流:驱动层填充加速度和角速度,算法层读取这两组值算出角度,应用层直接拿Roll、Pitch、Yaw用。参数单位统一成 g 和 °/s,后面做三角函数运算时不用再换算,少一层出错的可能。

3. 核心模块拆解:I2C 读取与卡尔曼滤波实现

3.1 MPU6050 初始化与数据读取

MPU6050 上电后要先解除休眠、设置量程和采样率,这些寄存器配置在MPU6050_Init()里完成:

void MPU6050_Init(void) { I2C_Init(); // 先初始化 I2C 总线 MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x00); // 解除休眠 MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x07); // 采样率 1kHz/(1+7)=125Hz MPU6050_WriteReg(MPU6050_CONFIG, 0x06); // 低通滤波 5Hz MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18); // 陀螺仪 ±2000°/s MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x10); // 加速度 ±8g }

这里有几个参数值得注意。SMPLRT_DIV设成 7,采样率就是 125Hz,对于姿态解算来说够用了,太高反而增加 MCU 负担。CONFIG寄存器设成 0x06,开启 5Hz 低通滤波,能滤掉一部分电机振动带来的高频噪声。陀螺仪量程选 ±2000°/s,是因为平衡车或云台在快速调整时角速度可能很大,量程小了会饱和。

读取原始数据的函数把六个寄存器值拼成有符号 16 位数:

void MPU6050_ReadData(MPU6050_Data_t *data) { uint8_t buf[14]; MPU6050_ReadRegs(MPU6050_ACCEL_XOUT_H, buf, 14); int16_t raw_acc_x = (buf[0] << 8) | buf[1]; int16_t raw_acc_y = (buf[2] << 8) | buf[3]; int16_t raw_acc_z = (buf[4] << 8) | buf[5]; int16_t raw_gyro_x = (buf[8] << 8) | buf[9]; int16_t raw_gyro_y = (buf[10] << 8) | buf[11]; int16_t raw_gyro_z = (buf[12] << 8) | buf[13]; // ±8g 量程对应 4096 LSB/g >float Kalman_Filter(float newAngle, float newRate, float dt) { static float Q_angle = 0.001f; // 角度过程噪声 static float Q_bias = 0.003f; // 陀螺仪零偏过程噪声 static float R_measure = 0.03f; // 测量噪声 static float angle = 0.0f; // 最优角度估计 static float bias = 0.0f; // 陀螺仪零偏估计 static float P[2][2] = {{1, 0}, {0, 1}}; // 误差协方差矩阵 // 预测步:用陀螺仪角速度积分 float rate = newRate - bias; angle += dt * rate; // 更新协方差矩阵 P[0][0] += dt * (dt * P[1][1] - P[0][1] - P[1][0] + Q_angle); P[0][1] -= dt * P[1][1]; P[1][0] -= dt * P[1][1]; P[1][1] += Q_bias * dt; // 计算卡尔曼增益 float S = P[0][0] + R_measure; float K[2]; K[0] = P[0][0] / S; K[1] = P[1][0] / S; // 更新步:用加速度计角度修正 float y = newAngle - angle; angle += K[0] * y; bias += K[1] * y; // 更新协方差矩阵 float P00_temp = P[0][0]; float P01_temp = P[0][1]; P[0][0] -= K[0] * P00_temp; P[0][1] -= K[0] * P01_temp; P[1][0] -= K[1] * P00_temp; P[1][1] -= K[1] * P01_temp; return angle; }

这段代码是整个项目的灵魂,逐段拆一下。Q_angle和Q_bias是过程噪声,值越大表示越不信任陀螺仪的预测;R_measure是测量噪声,值越大表示越不信任加速度计的观测。这三个参数直接决定滤波效果,后面调参部分会细说。

预测步里angle += dt * rate是陀螺仪积分,dt是两次采样的时间间隔。协方差矩阵P的更新公式看起来复杂,本质是在传播不确定性。卡尔曼增益K是权重,K[0]决定观测值对角度估计的修正力度,K[1]用来估计陀螺仪零偏。更新步里y = newAngle - angle是观测残差,angle和bias分别按增益修正。

注意bias这一项,它能在线估计陀螺仪的零偏,这是卡尔曼滤波比互补滤波强的地方。互补滤波需要手动标定零偏,温度一变零偏就漂了,卡尔曼能自动跟。

3.3 加速度计角度计算与主循环

加速度计算角度用反三角函数:

void Accel_To_Angle(MPU6050_Data_t *data) { >int main(void) { MPU6050_Data_t mpu_data; float roll_acc, pitch_acc; float roll_kalman = 0, pitch_kalman = 0; uint32_t last_time = 0, now_time = 0; float dt; USART1_Init(115200); MPU6050_Init(); while (1) { now_time = Get_Ms_Tick(); dt = (now_time - last_time) / 1000.0f; last_time = now_time; MPU6050_ReadData(&mpu_data); Accel_To_Angle(&mpu_data); roll_acc = mpu_data.Roll; pitch_acc = mpu_data.Pitch; roll_kalman = Kalman_Filter(roll_acc, mpu_data.Gyro_X, dt); pitch_kalman = Kalman_Filter(pitch_acc, mpu_data.Gyro_Y, dt); printf("Roll:%.2f Pitch:%.2f\r\n", roll_kalman, pitch_kalman); Delay_ms(10); } }

dt用毫秒计时器算出来,每次循环更新。注意Kalman_Filter里用了static变量保存状态,所以 Roll 和 Pitch 必须分开调用,不能共用一个函数实例,否则两个角度的状态会串。这里 Roll 用Gyro_X,Pitch 用Gyro_Y,对应关系不能搞反,搞反了滤波会发散。

4. 编译、烧录与串口验证:把代码跑起来

4.1 工程编译环境

这份源码用的是 Keil MDK 工程,打开.uvprojx文件就能编译。如果你用 GCC 或者 IAR,需要自己建工程,把User、Hardware、Algorithm三个目录的源文件加进去,头文件路径也要配好。标准外设库的路径在STM32F10x_StdPeriph_Driver下,CMSIS 路径在CMSIS下。

编译前检查两个地方:一是stm32f10x_conf.h里有没有把 I2C、USART、GPIO 的宏打开,二是system_stm32f10x.c里的晶振频率和你的板子对不对。这两处不对,编译能过但跑起来没反应。

4.2 硬件连接与烧录

MPU6050 和 STM32 的连接用 I2C,代码里是软件模拟 I2C,引脚在i2c.h里定义:

MPU6050 引脚STM32 引脚说明
VCC3.3V供电,不要接 5V
GNDGND共地
SCLPB6I2C 时钟
SDAPB7I2C 数据
INT不接本代码用轮询,不用中断

烧录用 ST-Link 或者串口 ISP 都行。烧录后打开串口助手,波特率 115200,能看到 Roll 和 Pitch 的实时输出。把传感器平放,Roll 和 Pitch 应该在 0° 附近;绕 X 轴转,Pitch 变化;绕 Y 轴转,Roll 变化。如果数值乱跳或者一直是 0,先查 I2C 有没有应答。

4.3 用串口波形验证滤波效果

光看数字不够直观,建议用串口波形软件把数据画出来。把printf的格式改成 CSV,比如printf("%.2f,%.2f\r\n", roll_kalman, pitch_kalman),然后用波形工具实时显示。你会看到卡尔曼滤波后的曲线比原始加速度计角度平滑很多,快速转动时又不会像纯陀螺仪积分那样漂。

验证的时候做两个动作:一是静止放置几分钟,看角度会不会慢慢漂;二是快速来回转动,看角度能不能跟上。静止漂移大说明Q_bias太小或者R_measure太大,快速转动跟不上说明Q_angle太小。这两个动作能覆盖大部分滤波参数问题。

5. 避坑与常见问题排查

5.1 串口输出全是 0 或者不变

现象是串口有输出但数值一直是 0,或者卡在某个值不动。原因通常是 I2C 通信失败,MPU6050 没有应答。先检查接线,SCL 和 SDA 有没有接反,上拉电阻有没有。软件模拟 I2C 需要外部上拉,很多模块自带上拉,但有些裸芯片没有。再检查MPU6050_Init()里的MPU6050_WriteReg有没有返回值判断,加上应答检测能快速定位。如果 I2C 地址不对,MPU6050 的地址是 0x68(AD0 接地)或 0x69(AD0 接 VCC),代码里默认用 0x68。

5.2 角度漂移越来越快

现象是静止时角度慢慢往一个方向偏,几分钟就偏几十度。原因是陀螺仪零偏没有估计准,或者Q_bias设得太小。卡尔曼滤波的bias项需要时间收敛,如果Q_bias太小,收敛太慢,看起来就像一直在漂。把Q_bias从 0.003 调到 0.01 试试,收敛会快很多。另外检查dt是不是稳定,如果主循环里有阻塞操作导致dt忽大忽小,积分会出错。

5.3 快速转动时角度滞后或震荡

现象是快速转动传感器,角度跟不上,或者停下来后角度来回晃。滞后说明Q_angle太小,滤波器太信任陀螺仪预测,修正不够及时。震荡说明R_measure太小,太信任加速度计观测,而加速度计在动态下有噪声。这两个参数要配合调:先调Q_angle让跟随变快,再调R_measure把震荡压下去。我一般会先把Q_angle设成 0.01,R_measure设成 0.1,跑一遍看波形再微调。

5.4 Roll 和 Pitch 对调或者方向反了

现象是转动传感器,Roll 和 Pitch 的变化对不上,或者往正方向转角度反而变小。原因是加速度计和陀螺仪的轴向对应关系搞错了。MPU6050 的 X 轴和 Y 轴定义要对着数据手册看,Accel_To_Angle里 Roll 用 Y 和 Z,Pitch 用 X,这是有固定对应关系的。如果方向反了,在角度计算前加负号就行。陀螺仪那边,Roll 对应Gyro_X,Pitch 对应Gyro_Y,如果发现滤波后角度往反方向跑,把对应的陀螺仪读数取反。

5.5 编译报错找不到头文件

现象是编译时提示mpu6050.h或者kalman.h找不到。原因是头文件搜索路径没配全。在 Keil 里打开 Options for Target,C/C++ 选项卡,Include Paths 里把User、Hardware、Algorithm、STM32F10x_StdPeriph_Driver/inc、CMSIS都加进去。如果用的是标准外设库,还要确认stm32f10x.h里的宏定义和你的芯片型号匹配,比如STM32F10X_MD对应中容量产品。

6. 二次开发:把这份代码改成你自己的姿态模块

拿到这份代码之后,最直接的改造方向是换传感器。MPU6050 是六轴,如果你手上有 MPU9250 或者 ICM20602,驱动层改一下寄存器地址和量程系数,算法层几乎不用动。卡尔曼滤波的输入是角度和角速度,只要新传感器能提供这两样,融合逻辑就是通用的。

第二个方向是加磁力计做九轴融合。MPU6050 的 Yaw 会漂,加一个 HMC5883L 或者 QMC5883L,用磁力计算绝对航向,再和陀螺仪积分做融合,Yaw 就能长期稳定。代码结构上,在Algorithm目录下加一个mag.c,主循环里多一路卡尔曼或者用互补滤波融合 Yaw,改动量不大。

第三个方向是优化实时性。现在主循环是Delay_ms(10),采样率 100Hz,对大多数应用够了。如果要上 500Hz 或者 1kHz,把延时去掉,用定时器中断触发采样,dt从定时器周期直接取,比用Get_Ms_Tick()算更准。另外printf很耗时,高频采样时先把数据存到数组里,攒够一批再统一发。

调参这块,我自己的习惯是先把Q_angle、Q_bias、R_measure三个参数打印到串口,配合波形软件边看边调。每次只改一个参数,改完记录波形,不然三个参数一起动,根本分不清是谁的效果。还有一点,dt一定要实测,不要用理论值,主循环里其他代码的耗时会影响实际采样间隔,dt不准,积分和协方差更新都会偏。

最后说个移植到其他 MCU 的注意点。这份代码用的是 STM32 标准外设库,换到 GD32 或者 STM32 HAL 库,主要改 I2C 和延时函数。Kalman_Filter和Accel_To_Angle是纯 C 数学运算,不依赖任何硬件,直接拷过去就能用。我一般会先把算法层单独拿出来,在 PC 上用固定数据跑一遍,确认输出正确再往 MCU 上搬,这样能省很多在线调试的时间。从那以后我每次移植算法都先在 PC 上验证一遍,希望这个习惯也能帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询