4、姿态解算与滤波:互补滤波与卡尔曼滤波(EKF)的原理对比,以及在实际飞控中的代码实现逻辑。

各位同学,欢迎来到第四章。这一章我们聊点硬核的——姿态解算与滤波。

说白了,飞控的大脑就是姿态解算。传感器给的数据,比如陀螺仪、加速度计、磁力计,它们各自都有毛病。陀螺仪飘,加速度计吵,磁力计容易受干扰。怎么把这些“半真半假”的数据揉成一个靠谱的姿态?这就是滤波干的事。

我入行那会儿,第一个项目就是调四旋翼的悬停。当时不懂滤波,直接拿原始数据算欧拉角,结果飞机像喝醉了酒一样晃。嗯,从那以后我就明白了,滤波不是锦上添花,是生死攸关。

4.1 为什么需要姿态滤波?

先看一个简单的问题:你拿手机转一下,屏幕上的姿态指示器是不是跟着动?但如果你快速晃动,它是不是会滞后或者抖动?

这就是传感器的特性决定的:

  • 陀螺仪:短时间精度高,动态响应快。但积分会漂移,时间越长越不准。
  • 加速度计:长时间稳定,能给出重力方向。但容易受振动干扰,高频噪声大。
  • 磁力计:提供航向参考。但容易受周围铁磁物质干扰,噪声也大。

所以,我们需要一个“裁判”,把这三个传感器的优点结合起来,缺点互相抵消。这个裁判就是滤波器。

核心思想:用陀螺仪的高频响应做“骨架”,用加速度计和磁力计的低频稳定做“锚点”,通过滤波算法融合出平滑、无漂移的姿态。

4.2 互补滤波:简单、高效、够用

互补滤波,名字听着玄乎,其实原理很朴素。你想想看,陀螺仪的高频信号好,加速度计的低频信号好。那把陀螺仪的高频部分和加速度计的低频部分“互补”起来,不就行了?

公式也很简单:

姿态 = α × (陀螺仪积分姿态) + (1 - α) × (加速度计/磁力计姿态)

这里的 α 是一个系数,通常在 0.98 左右。α 越大,越信任陀螺仪;α 越小,越信任加速度计。

4.2.1 互补滤波的代码实现(简化版)

我在 Pixhawk 的早期固件里见过这种实现,非常经典。下面是一个基于 Mahony 互补滤波的简化代码片段,用于四元数更新:

// 输入:陀螺仪角速度 (gx, gy, gz),加速度计 (ax, ay, az),磁力计 (mx, my, mz)
// 输出:更新后的四元数 (q0, q1, q2, q3)

void MahonyAHRSupdate(float gx, float gy, float gz,
                      float ax, float ay, float az,
                      float mx, float my, float mz) {
    float recipNorm;
    float q0q0, q0q1, q0q2, q0q3;
    float q1q1, q1q2, q1q3;
    float q2q2, q2q3, q3q3;
    float hx, hy, bx, bz;
    float halfvx, halfvy, halfvz;
    float halfwx, halfwy, halfwz;
    float ex, ey, ez;
    float qa, qb, qc;

    // 1. 归一化加速度计数据
    recipNorm = invSqrt(ax*ax + ay*ay + az*az);
    ax *= recipNorm;
    ay *= recipNorm;
    az *= recipNorm;

    // 2. 归一化磁力计数据
    recipNorm = invSqrt(mx*mx + my*my + mz*mz);
    mx *= recipNorm;
    my *= recipNorm;
    mz *= recipNorm;

    // 3. 计算参考方向(重力方向在机体坐标系下的投影)
    halfvx = q1*q3 - q0*q2;
    halfvy = q0*q1 + q2*q3;
    halfvz = q0*q0 - 0.5f + q3*q3;

    // 4. 计算误差(叉积)
    ex = (ay * halfvz - az * halfvy);
    ey = (az * halfvx - ax * halfvz);
    ez = (ax * halfvy - ay * halfvx);

    // 5. 用PI控制器修正陀螺仪偏差
    integralFBx += Ki * ex * dt;
    integralFBy += Ki * ey * dt;
    integralFBz += Ki * ez * dt;
    gx += integralFBx + Kp * ex;
    gy += integralFBy + Kp * ey;
    gz += integralFBz + Kp * ez;

    // 6. 四元数微分方程更新
    qa = q0;
    qb = q1;
    qc = q2;
    q0 += (-qb*gx - qc*gy - q3*gz) * halfT;
    q1 += ( qa*gx + qc*gz - q3*gy) * halfT;
    q2 += ( qa*gy - qb*gz + q3*gx) * halfT;
    q3 += ( qa*gz + qb*gy - qc*gx) * halfT;

    // 7. 归一化四元数
    recipNorm = invSqrt(q0*q0 + q1*q1 + q2*q2 + q3*q3);
    q0 *= recipNorm;
    q1 *= recipNorm;
    q2 *= recipNorm;
    q3 *= recipNorm;
}

个人经验:互补滤波的 Kp 和 Ki 参数很关键。Kp 太大,姿态会跟着加速度计的噪声抖;Kp 太小,又跟不上陀螺仪的漂移。我一般先调 Kp,让姿态响应不滞后,再给一点点 Ki 消除稳态误差。Ki 千万别给大,否则飞机会“硬邦邦”的,像被弹簧拉着。

4.3 卡尔曼滤波(EKF):精准、复杂、资源消耗大

互补滤波够用,但不够“聪明”。它不会根据传感器的噪声水平动态调整信任度。卡尔曼滤波就不一样了,它有一套完整的数学框架,能实时估计状态的最优值,还能告诉你这个估计有多准。

扩展卡尔曼滤波(EKF)是卡尔曼滤波在非线性系统上的推广。飞控系统本质上是非线性的,所以用 EKF 更合适。

4.3.1 EKF 的核心思想

EKF 分两步走:预测和更新。

  1. 预测:根据上一时刻的状态和当前的控制输入(比如陀螺仪角速度),预测当前时刻的状态和协方差。
  2. 更新:用当前的观测值(比如加速度计、磁力计、GPS)去修正预测值,得到最优估计。

说白了,就是“先猜,再看,再修正”。

4.3.2 EKF 的代码实现逻辑(伪代码)

下面是一个简化的 EKF 姿态估计伪代码,展示了核心的预测和更新步骤:

// 状态向量 x = [q0, q1, q2, q3, bgx, bgy, bgz]  (四元数 + 陀螺仪零偏)
// 观测向量 z = [ax, ay, az, mx, my, mz]  (加速度计 + 磁力计)

// 1. 预测步骤
x_pred = f(x_prev, u)  // 用陀螺仪数据更新四元数,零偏保持不变
P_pred = F * P_prev * F^T + Q  // 更新协方差矩阵,Q是过程噪声

// 2. 更新步骤
y = z - h(x_pred)  // 计算残差(观测值与预测观测值的差)
S = H * P_pred * H^T + R  // 计算残差协方差,R是观测噪声
K = P_pred * H^T * inv(S)  // 计算卡尔曼增益
x_updated = x_pred + K * y  // 更新状态
P_updated = (I - K * H) * P_pred  // 更新协方差

避坑指南:我曾经在一个项目中,EKF 的协方差矩阵 P 初始化得太小,导致滤波器“过于自信”,新观测值几乎不起作用。飞机起飞后姿态慢慢漂移,最后炸机了。教训就是:P 矩阵的初始值不能太小,要给滤波器留出学习空间。

4.4 互补滤波 vs EKF:对比表格

为了让你看得更清楚,我整理了一个对比表:

特性 互补滤波 扩展卡尔曼滤波(EKF)
计算复杂度 低(几十行代码,无矩阵运算) 高(需要矩阵乘法、求逆,计算量大)
内存占用 低(几个全局变量) 高(需要存储协方差矩阵等)
精度 中等(依赖固定系数) 高(能动态调整,融合多传感器)
鲁棒性 较好(参数调好后很稳定) 依赖模型和噪声参数(调参困难)
适用场景 低成本飞控、玩具级无人机 高端飞控、工业级、需要高精度定位
调参难度 低(2-3个参数) 高(Q、R矩阵,初始P,模型参数)

4.5 如何选择?我的建议

很多新手会问:到底用哪个好?我的回答是:看你的硬件和需求。

  • 如果你用的是 STM32F103 这种低端 MCU,或者做的是玩具级飞控,互补滤波完全够用。我早期做的小四轴,用的就是 Mahony 互补滤波,飞得也挺稳。
  • 如果你用的是 STM32F4/F7 或者更高性能的芯片,而且需要融合 GPS、光流、气压计等多传感器做精准定位,那 EKF 是更好的选择。PX4 和 ArduPilot 的默认姿态估计都是 EKF。

我的经验:不要盲目追求“高级”。EKF 虽然精度高,但调参是个大坑。Q 矩阵和 R 矩阵的取值,直接影响滤波效果。我见过有人调了两个月 EKF 还没调好,最后换回互补滤波,飞机反而稳了。所以,先跑通互补滤波,再考虑升级到 EKF。

4.6 本章知识体系图

下面这张 SVG 图,帮你理清本章的核心逻辑:

姿态解算与滤波知识体系 姿态解算与滤波核心逻辑 陀螺仪 (角速度) 加速度计 (加速度) 磁力计 (磁场) 数据融合与滤波 消除噪声、补偿漂移 互补滤波 简单、高效、低资源 扩展卡尔曼滤波 (EKF) 精准、复杂、高资源 输出:稳定、准确的姿态 选择依据:硬件性能 & 精度需求

这张图很直观:传感器数据进来,经过融合滤波,输出稳定姿态。两种滤波各有优劣,选哪个,看你的具体场景。

4.7 实际飞控中的代码实现逻辑

最后,我们聊聊在实际飞控代码里,滤波是怎么嵌入到主循环中的。

以 PX4 为例,它的姿态估计模块(EKF2)在主循环中是这样工作的:

  1. 传感器数据采集:从陀螺仪、加速度计、磁力计、气压计、GPS 等读取原始数据。
  2. 数据预处理:校准、去偏、坐标系转换。
  3. 时间更新(预测):用陀螺仪数据更新状态(四元数)和协方差。
  4. 观测更新(修正):依次用加速度计、磁力计、气压计、GPS 等观测数据修正状态。
  5. 输出:将更新后的四元数转换为欧拉角或旋转矩阵,供控制模块使用。

小技巧:在实际代码中,EKF 的观测更新顺序很重要。我一般先更新加速度计(修正俯仰和横滚),再更新磁力计(修正航向),最后更新 GPS(修正位置和速度)。这样能避免不同观测之间的相互干扰。

好了,这一章的内容就到这里。滤波是飞控的基石,理解透了,后面的控制算法才能站得稳。希望你能动手写一写互补滤波的代码,感受一下数据融合的魅力。