跳转到主要内容
杨月昌的硬件博客
返回

高精度 IMU 姿态解算:基于 ICM-20602 的四元数 Mahony 滤波实现

1. 姿态解算概述

姿态解算是平衡车、无人机、四足机器人及穿戴设备的核心技术。ICM-20602 是一款高性价比的 6 轴运动跟踪传感器,内置三轴陀螺仪与三轴加速度计,输出速率最高可达 8kHz。

单纯的陀螺仪积分会产生累积漂移,而单纯依赖加速度计又易受高频振动干扰。本方案采用四元数表示姿态,结合 Mahony AHRS 滤波算法(PI 控制器补偿重力误差),实现陀螺仪高动态响应与加速度计静态精度的完美融合,最终输出稳定且精准的欧拉角(Pitch、Roll、Yaw)。


2. 算法核心步骤

  1. 零偏校准:开机静态采集并计算陀螺仪偏移量。
  2. 数据预处理:加速度归一化 + 限幅滤波。
  3. 四元数更新:利用角速度对四元数进行一阶积分。
  4. 误差补偿:通过加速度计重力向量与四元数预测向量的叉积,构造误差信号,再用 PI 控制器修正陀螺仪输入。
  5. 欧拉角转换:将四元数转换为直观的 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.001f
static 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. 调试经验与注意事项

  1. 采样频率:强烈建议在定时器中断中以 200~500Hz 固定频率调用更新函数,保证 dt 恒定。
  2. 参数调优Kp 影响动态响应(0.52.0),Ki 影响静态漂移抑制(0.00050.005),从零开始慢慢增大。
  3. 加速度限幅:剧烈震动时可临时降低加速度权重,或增加低通滤波。
  4. 安装方向:确保传感器坐标系与机体坐标系一致,否则 Roll/Pitch 会出现正负颠倒。
  5. Yaw 漂移:纯 6 轴无法完全消除 Yaw 漂移,建议后续加入磁力计(HMC5883L / QMC5883L)进行九轴融合。

6. 总结

本方案在 STM32F1/F4、K60 等平台上均已验证通过,静态精度可达 ±0.5°,动态响应快速平滑,是性价比极高的 IMU 姿态解算实现。核心优势在于 Mahony 算法计算量小、易于单片机实时运行。



上一篇
EMC设计必读:32个工业与消费电子标准防护电路图集(附选型与布局指南)
下一篇
混合信号PCB接地设计实战全攻略:星形单点、多点机箱与分区布局