在嵌入式开发领域,姿态感知是实现运动追踪、平衡控制、无人机飞控等应用的基础功能。STM32F103C8T6作为经典的Cortex-M3内核微控制器,搭配MPU6050这款集成了3轴加速度计和3轴陀螺仪的6DOF传感器,构成了性价比极高的姿态检测方案。这套组合在四轴飞行器、智能手环、机器人平衡系统等场景中广泛应用。
我最近在为一个自制平衡小车项目调试MPU6050时,发现网上很多示例代码存在数据漂移严重、坐标系不统一、滤波算法效果差等问题。经过两周的实测和算法优化,最终实现了一套稳定可靠的驱动方案。本文将分享从硬件连接到卡尔曼滤波实现的完整过程,重点解决以下实际问题:
STM32F103C8T6(蓝桥杯开发板常用型号)具有以下适配优势:
MPU6050的关键参数需要注意:
具体接线方式(以I2C1为例):
code复制MPU6050 STM32F103C8T6
VCC → 3.3V
GND → GND
SCL → PB6(I2C1_SCL)
SDA → PB7(I2C1_SDA)
AD0 → GND(地址0x68)
关键提示:务必在VCC与GND之间并联0.1μF去耦电容,SCL/SDA线上拉4.7kΩ电阻。我曾在初期测试中因忽略上拉电阻导致通信失败。
使用STM32CubeMX生成基础代码后,需修改I2C参数:
c复制hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 400000; // 标准模式400kHz
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
初始化序列示例:
c复制// 唤醒设备
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, PWR_MGMT_1, 1, 0x00, 1, 100);
// 设置陀螺仪量程±500°/s
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, GYRO_CONFIG, 1, 0x08, 1, 100);
// 设置加速度计量程±4g
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, ACCEL_CONFIG, 1, 0x08, 1, 100);
// 设置DLPF带宽42Hz
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, CONFIG, 1, 0x03, 1, 100);
// 设置采样率1kHz
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, SMPLRT_DIV, 1, 0x07, 1, 100);
避坑指南:实测发现当采样率高于500Hz时,建议启用FIFO缓冲。直接读取寄存器会导致数据丢失。
通过I2C读取14字节数据(加速度+温度+陀螺仪):
c复制uint8_t mpu_data[14];
HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, ACCEL_XOUT_H, 1, mpu_data, 14, 100);
// 数据转换(大端格式)
int16_t accel_x = (mpu_data[0]<<8)|mpu_data[1];
int16_t accel_y = (mpu_data[2]<<8)|mpu_data[3];
int16_t accel_z = (mpu_data[4]<<8)|mpu_data[5];
int16_t temp = (mpu_data[6]<<8)|mpu_data[7];
int16_t gyro_x = (mpu_data[8]<<8)|mpu_data[9];
int16_t gyro_y = (mpu_data[10]<<8)|mpu_data[11];
int16_t gyro_z = (mpu_data[12]<<8)|mpu_data[13];
静态校准流程(需水平放置设备):
c复制// 陀螺仪零偏校准
gyro_offset_x = sum_gyro_x / 500;
gyro_offset_y = sum_gyro_y / 500;
gyro_offset_z = sum_gyro_z / 500;
// 加速度计校准(假设Z轴指向重力方向)
accel_scale = 1.0f / sqrt(accel_x*accel_x + accel_y*accel_y + accel_z*accel_z);
简易版互补滤波(适合资源受限场景):
c复制float alpha = 0.98; // 陀螺仪权重
float dt = 0.01; // 10ms采样周期
// 加速度计计算俯仰/横滚角
float acc_pitch = atan2(accel_y, accel_z) * 180/PI;
float acc_roll = atan2(-accel_x, sqrt(accel_y*accel_y + accel_z*accel_z)) * 180/PI;
// 融合计算
pitch = alpha*(pitch + gyro_y*dt) + (1-alpha)*acc_pitch;
roll = alpha*(roll + gyro_x*dt) + (1-alpha)*acc_roll;
状态方程简化实现:
c复制typedef struct {
float angle; // 最优估计角度
float bias; // 陀螺仪零偏
float P[2][2]; // 误差协方差矩阵
} Kalman_t;
void Kalman_Update(Kalman_t *k, float newAngle, float newRate, float dt) {
// 预测阶段
k->angle += dt * (newRate - k->bias);
k->P[0][0] += dt * (dt*k->P[1][1] - k->P[0][1] - k->P[1][0] + Q_angle);
k->P[0][1] -= dt * k->P[1][1];
k->P[1][0] -= dt * k->P[1][1];
k->P[1][1] += Q_bias * dt;
// 更新阶段
float y = newAngle - k->angle;
float S = k->P[0][0] + R_measure;
float K[2] = {k->P[0][0]/S, k->P[1][0]/S};
k->angle += K[0] * y;
k->bias += K[1] * y;
float P00_temp = k->P[0][0];
float P01_temp = k->P[0][1];
k->P[0][0] -= K[0] * P00_temp;
k->P[0][1] -= K[0] * P01_temp;
k->P[1][0] -= K[1] * P00_temp;
k->P[1][1] -= K[1] * P01_temp;
}
参数调优经验:Q_angle(过程噪声)建议0.001-0.003,R_measure(测量噪声)取0.03-0.1。可通过观察收敛速度与震荡幅度调整。
c复制// 预计算0-90度的atan2结果(间隔1度)
const float atan2_table[91] = {0.000, 0.017, ..., 1.561};
float fast_atan2(float y, float x) {
if(x == 0) return PI/2;
float ratio = fabs(y/x);
uint8_t index = (uint8_t)(ratio * 180/PI);
if(index > 90) index = 90;
float angle = atan2_table[index];
if(x<0 && y>=0) angle = PI - angle;
else if(x<0 && y<0) angle = -(PI - angle);
else if(x>0 && y<0) angle = -angle;
return angle;
}
建议采用以下任务划分:
使用RTOS的信号量同步示例:
c复制// 数据就绪信号量
osSemaphoreId_t imuDataReady;
void I2C_Read_Task(void const *arg) {
while(1) {
HAL_I2C_Mem_Read(...);
osSemaphoreRelease(imuDataReady); // 释放信号量
osDelay(1); // 1ms间隔
}
}
void Fusion_Task(void const *arg) {
while(1) {
if(osSemaphoreWait(imuDataReady, 100) == osOK) {
// 执行数据融合
}
}
}
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| I2C通信失败 | 上拉电阻缺失/地址错误 | 检查4.7kΩ上拉,确认AD0电平 |
| 数据持续漂移 | 未校准或温度影响 | 执行静态校准,添加温度补偿 |
| 角度计算震荡 | 滤波参数不合适 | 调整Q/R矩阵,降低过程噪声 |
| 采样率不稳定 | 中断优先级冲突 | 提升I2C中断优先级 |
测试数据(静止状态下角度波动范围):
| 算法类型 | 俯仰角波动(°) | 横滚角波动(°) | CPU占用率 |
|---|---|---|---|
| 无滤波 | ±8.6 | ±9.2 | 1% |
| 互补滤波 | ±1.5 | ±1.7 | 5% |
| 卡尔曼滤波 | ±0.3 | ±0.4 | 15% |
在四轴飞行器实际测试中,卡尔曼滤波可使悬停稳定性提升40%以上,但需要根据具体应用权衡性能与资源消耗。