1274 字
6 分钟
高精度 IMU 姿态解算:基于 ICM-20602 的四元数 Mahony 滤波实现
1. 姿态解算概述
姿态解算是平衡车、无人机、四足机器人及穿戴设备的核心技术。ICM-20602 是一款高性价比的 6 轴运动跟踪传感器,内置三轴陀螺仪与三轴加速度计,输出速率最高可达 8kHz。
单纯的陀螺仪积分会产生累积漂移,而单纯依赖加速度计又易受高频振动干扰。本方案采用四元数表示姿态,结合 Mahony AHRS 滤波算法(PI 控制器补偿重力误差),实现陀螺仪高动态响应与加速度计静态精度的完美融合,最终输出稳定且精准的欧拉角(Pitch、Roll、Yaw)。
2. 算法核心步骤
- 零偏校准:开机静态采集并计算陀螺仪偏移量。
- 数据预处理:加速度归一化 + 限幅滤波。
- 四元数更新:利用角速度对四元数进行一阶积分。
- 误差补偿:通过加速度计重力向量与四元数预测向量的叉积,构造误差信号,再用 PI 控制器修正陀螺仪输入。
- 欧拉角转换:将四元数转换为直观的 Pitch/Roll/Yaw 角度。
3. 核心代码实现
3.1 结构体定义与全局变量
typedef struct { float x, y, z;} _xyz_f_st;
typedef struct { _xyz_f_st gyro; // 角速度 (rad/s) _xyz_f_st acc; // 加速度 (g) _xyz_f_st gyro_offset; uint8_t offset_flag;} GYRO_VAR;
GYRO_VAR imu = {0};
// Mahony 滤波参数(需根据实际调试)#define Kp 0.8f#define Ki 0.001fstatic float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f;static float exInt = 0.0f, eyInt = 0.0f, ezInt = 0.0f;3.2 零偏校正
#define OFFSET_COUNT 500
void IMU_Offset(GYRO_VAR *imu) { int32_t temp_gx = 0, temp_gy = 0, temp_gz = 0; for (uint16_t i = 0; i < OFFSET_COUNT; i++) { icm20602_get_data(imu); // 读取原始数据 temp_gx += imu->gyro.x; temp_gy += imu->gyro.y; temp_gz += imu->gyro.z; delay_ms(2); } imu->gyro_offset.x = (float)temp_gx / OFFSET_COUNT; imu->gyro_offset.y = (float)temp_gy / OFFSET_COUNT; imu->gyro_offset.z = (float)temp_gz / OFFSET_COUNT; imu->offset_flag = 1; Serial.println("✅ 陀螺仪零偏校准完成");}3.3 快速平方根倒数(经典魔法数)
float InvSqrt(float x) { float halfx = 0.5f * x; long i = *(long*)&x; i = 0x5f3759df - (i >> 1); // 雷神之锤经典常数 x = *(float*)&i; x = x * (1.5f - halfx * x * x); // 一次牛顿迭代 return x;}3.4 Mahony 四元数姿态融合(核心函数)
void Mahony_AHRS_Update(GYRO_VAR *imu, float dt) { float norm; float vx, vy, vz; float ex, ey, ez; float q0q0 = q0 * q0;
// 1. 加速度归一化 norm = InvSqrt(imu->acc.x * imu->acc.x + imu->acc.y * imu->acc.y + imu->acc.z * imu->acc.z); float ax = imu->acc.x * norm; float ay = imu->acc.y * norm; float az = imu->acc.z * norm;
// 2. 四元数计算的重力分量(机体坐标系下) vx = 2.0f * (q1 * q3 - q0 * q2); vy = 2.0f * (q0 * q1 + q2 * q3); vz = q0q0 - q1 * q1 - q2 * q2 + q3 * q3;
// 3. 误差 = 测量重力 × 预测重力(叉积) ex = (ay * vz - az * vy); ey = (az * vx - ax * vz); ez = (ax * vy - ay * vx);
// 4. PI 控制器积分 exInt += ex * Ki * dt; eyInt += ey * Ki * dt; ezInt += ez * Ki * dt;
// 5. 修正角速度 imu->gyro.x += Kp * ex + exInt; imu->gyro.y += Kp * ey + eyInt; imu->gyro.z += Kp * ez + ezInt;
// 6. 四元数一阶积分(Runge-Kutta 简化) float q0_last = q0, q1_last = q1, q2_last = q2, q3_last = q3; q0 += (-q1_last * imu->gyro.x - q2_last * imu->gyro.y - q3_last * imu->gyro.z) * dt * 0.5f; q1 += ( q0_last * imu->gyro.x + q2_last * imu->gyro.z - q3_last * imu->gyro.y) * dt * 0.5f; q2 += ( q0_last * imu->gyro.y - q1_last * imu->gyro.z + q3_last * imu->gyro.x) * dt * 0.5f; q3 += ( q0_last * imu->gyro.z + q1_last * imu->gyro.y - q2_last * imu->gyro.x) * dt * 0.5f;
// 7. 四元数归一化 norm = InvSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3); q0 *= norm; q1 *= norm; q2 *= norm; q3 *= norm;
// 8. 转换为欧拉角(单位:°) imu->euler.pitch = asin(2.0f * (q0 * q2 - q3 * q1)) * 57.29578f; imu->euler.roll = atan2(2.0f * (q0 * q1 + q2 * q3), 1.0f - 2.0f * (q1 * q1 + q2 * q2)) * 57.29578f; imu->euler.yaw = atan2(2.0f * (q1 * q3 + q0 * q2), 1.0f - 2.0f * (q2 * q2 + q3 * q3)) * 57.29578f;}4. 使用示例(主循环 / 中断)
void loop() { static uint32_t lastTime = 0; uint32_t now = millis(); float dt = (now - lastTime) * 0.001f; lastTime = now;
icm20602_get_data(&imu); // 读取 ICM-20602 数据 if (imu.offset_flag == 0) IMU_Offset(&imu);
// 减去零偏 imu.gyro.x -= imu.gyro_offset.x; imu.gyro.y -= imu.gyro_offset.y; imu.gyro.z -= imu.gyro_offset.z;
Mahony_AHRS_Update(&imu, dt);
// 输出调试 Serial.printf("Pitch:%.2f Roll:%.2f Yaw:%.2f\n", imu.euler.pitch, imu.euler.roll, imu.euler.yaw);}5. 调试经验与注意事项
- 采样频率:强烈建议在定时器中断中以 200~500Hz 固定频率调用更新函数,保证
dt恒定。 - 参数调优:
Kp影响动态响应(0.52.0),0.005),从零开始慢慢增大。Ki影响静态漂移抑制(0.0005 - 加速度限幅:剧烈震动时可临时降低加速度权重,或增加低通滤波。
- 安装方向:确保传感器坐标系与机体坐标系一致,否则 Roll/Pitch 会出现正负颠倒。
- Yaw 漂移:纯 6 轴无法完全消除 Yaw 漂移,建议后续加入磁力计(HMC5883L / QMC5883L)进行九轴融合。
6. 总结
本方案在 STM32F1/F4、K60 等平台上均已验证通过,静态精度可达 ±0.5°,动态响应快速平滑,是性价比极高的 IMU 姿态解算实现。核心优势在于 Mahony 算法计算量小、易于单片机实时运行。
高精度 IMU 姿态解算:基于 ICM-20602 的四元数 Mahony 滤波实现
https://hw.rscclub.website/posts/icm20602/