4、传感器数据融合:互补滤波原理与代码实现

飞控里有个经典问题:陀螺仪短时间准,但长时间飘;加速度计短时间吵,但长时间稳。你想想看,一个怕积分,一个怕振动。那怎么办?把它们俩“互补”一下。

这就是互补滤波的核心思路。说白了,就是让陀螺仪负责高频姿态变化,加速度计负责低频修正。我最早接触这个算法是在大学做四轴飞行器的时候,当时觉得卡尔曼滤波太复杂,互补滤波刚好够用,而且调起来很直观。

4.1 为什么需要互补滤波?

先看两个传感器的“性格”:

传感器 优点 缺点
陀螺仪 动态响应快,高频噪声小 有零偏漂移,积分误差随时间累积
加速度计 长期稳定,能测重力方向 对振动敏感,高频噪声大

陀螺仪输出角速度,积分得到角度。但积分会累积误差——哪怕零点几度的零偏,几秒后就能偏出好几度。加速度计呢?它直接测重力在三个轴上的分量,能算出俯仰和横滚角,但一有振动,数据就跳得厉害。

互补滤波的思路就是:用陀螺仪做短期的姿态预测,用加速度计做长期的姿态修正。两者通过一个权重系数融合,各取所长。

核心公式(一维简化版):

angle = α × (angle + gyro × dt) + (1 - α) × acc_angle

其中 α 是互补系数,通常取 0.98 ~ 0.995。

α 越接近 1,陀螺仪权重越大,系统响应快但长期漂移大;α 越小,加速度计修正越强,系统更稳定但动态响应变慢。这个平衡点,就是调参的关键。

4.2 互补滤波的完整流程

实际工程中,我们处理的是三维姿态。下面这张图展示了完整的融合流程:

互补滤波姿态融合流程 陀螺仪 (ωx, ωy, ωz) 加速度计 (ax, ay, az) 积分得姿态角 计算俯仰/横滚角 互补融合 angle = α·gyro_angle + (1-α)·acc_angle 融合后姿态角 说明:陀螺仪提供高频姿态变化,加速度计提供低频姿态基准,两者通过互补系数 α 融合

4.3 代码实现(C语言,STM32平台)

下面是我在Pixhawk原型机上验证过的互补滤波代码。注意,这里用了四元数来避免万向锁问题。

// 互补滤波结构体
typedef struct {
    float q0, q1, q2, q3;  // 姿态四元数
    float ex, ey, ez;      // 误差积分项
    float kp, ki;          // PI 系数
    float dt;              // 采样周期
} ComplementaryFilter_t;

// 初始化
void CF_Init(ComplementaryFilter_t *cf, float kp, float ki, float dt) {
    cf->q0 = 1.0f;
    cf->q1 = 0.0f;
    cf->q2 = 0.0f;
    cf->q3 = 0.0f;
    cf->ex = cf->ey = cf->ez = 0.0f;
    cf->kp = kp;
    cf->ki = ki;
    cf->dt = dt;
}

// 互补滤波更新(核心函数)
void CF_Update(ComplementaryFilter_t *cf,
               float gx, float gy, float gz,  // 陀螺仪,单位 rad/s
               float ax, float ay, float az)  // 加速度计,单位 m/s²
{
    float norm;
    float vx, vy, vz;
    float ex, ey, ez;

    // 1. 加速度计归一化
    norm = sqrt(ax*ax + ay*ay + az*az);
    if (norm < 0.001f) return;  // 防止除零
    ax /= norm;
    ay /= norm;
    az /= norm;

    // 2. 从四元数估计重力方向
    vx = 2.0f * (cf->q1*cf->q3 - cf->q0*cf->q2);
    vy = 2.0f * (cf->q0*cf->q1 + cf->q2*cf->q3);
    vz = cf->q0*cf->q0 - cf->q1*cf->q1 - cf->q2*cf->q2 + cf->q3*cf->q3;

    // 3. 计算误差(叉积)
    ex = ay*vz - az*vy;
    ey = az*vx - ax*vz;
    ez = ax*vy - ay*vx;

    // 4. 误差积分(消除稳态误差)
    cf->ex += ex * cf->ki * cf->dt;
    cf->ey += ey * cf->ki * cf->dt;
    cf->ez += ez * cf->ki * cf->dt;

    // 5. 修正陀螺仪角速度
    gx += cf->kp * ex + cf->ex;
    gy += cf->kp * ey + cf->ey;
    gz += cf->kp * ez + cf->ez;

    // 6. 四元数更新(一阶龙格-库塔)
    float q0 = cf->q0, q1 = cf->q1, q2 = cf->q2, q3 = cf->q3;
    cf->q0 += (-q1*gx - q2*gy - q3*gz) * 0.5f * cf->dt;
    cf->q1 += ( q0*gx + q2*gz - q3*gy) * 0.5f * cf->dt;
    cf->q2 += ( q0*gy - q1*gz + q3*gx) * 0.5f * cf->dt;
    cf->q3 += ( q0*gz + q1*gy - q2*gx) * 0.5f * cf->dt;

    // 7. 归一化四元数
    norm = sqrt(cf->q0*cf->q0 + cf->q1*cf->q1 + cf->q2*cf->q2 + cf->q3*cf->q3);
    cf->q0 /= norm;
    cf->q1 /= norm;
    cf->q2 /= norm;
    cf->q3 /= norm;
}

调参经验:

  • kp(比例系数):控制加速度计修正的强度。我一般从 0.5 开始试,如果姿态响应太慢就加大,如果高频抖动就减小。
  • ki(积分系数):消除陀螺仪零偏漂移。通常设 kp 的 1/10 ~ 1/100,太大容易引起低频振荡。
  • dt:采样周期必须准确。我踩过坑——用定时器中断时忘了考虑中断执行时间,结果 dt 偏大,姿态发散。

4.4 避坑指南

我在实际项目中遇到过不少问题,挑几个典型的说说:

我曾经踩过的坑:

  • 加速度计未归一化直接参与叉积运算——导致修正量随飞行器倾斜角度变化,姿态发散。一定要先归一化。
  • 四元数更新后忘记归一化——四元数会逐渐偏离单位长度,导致姿态畸变。每次更新后必须归一化。
  • 陀螺仪零偏未校准——静止时陀螺仪输出不为零,积分后姿态一直漂。上电后先采集 100 组数据取平均,减去零偏。
  • dt 不准确——用延时函数或阻塞式循环会导致 dt 抖动,姿态忽快忽慢。建议用硬件定时器或 RTOS 的精确延时。

4.5 互补系数 α 的选取

前面提到 α 是关键参数。怎么选?我一般用这个公式:

α = τ / (τ + dt)

其中 τ 是时间常数,单位秒。τ 越大,陀螺仪信任时间越长,系统越“相信”陀螺仪;τ 越小,加速度计修正越强。

应用场景 τ 推荐值 α(dt=0.004s 时) 特点
竞速穿越机 0.5 ~ 1.0 0.992 ~ 0.996 响应快,适合剧烈机动
航拍无人机 1.0 ~ 2.0 0.996 ~ 0.998 平滑稳定,抗振动
自稳模式 0.3 ~ 0.5 0.987 ~ 0.992 加速度计修正强,悬停稳

嗯,这里要注意:α 不是越大越好。我见过有人设 α=0.999,结果飞行器悬停时姿态缓慢漂移,因为加速度计几乎不起作用了。反过来,α 太小,飞控会跟着振动乱跳。

4.6 验证方法

代码写完了,怎么验证对不对?我习惯做两个测试:

  1. 静态测试:把飞控放在水平桌面上,观察姿态角输出。理想情况是俯仰和横滚在 ±0.5° 以内波动。如果漂移超过 1°,检查陀螺仪零偏或 ki 是否太小。
  2. 动态测试:用手快速翻转飞控 90°,看姿态角能否快速跟上。如果滞后明显,加大 kp 或减小 τ。如果超调振荡,减小 kp。

我的个人习惯:先用串口把原始陀螺仪和加速度计数据打印出来,确认传感器本身没问题,再开融合算法。这样能快速定位问题是出在传感器还是算法上。

互补滤波虽然简单,但调好了非常可靠。我做过对比测试,在同样的硬件上,调好的互补滤波和卡尔曼滤波在姿态精度上差距不到 0.5°,但互补滤波的计算量只有卡尔曼的 1/5。对于大多数飞控应用,互补滤波完全够用。