姿态解算基础:从欧拉角到EKF

姿态解算,说白了就是回答一个问题:飞行器现在朝哪边?

我刚开始做飞控那会儿,觉得这问题挺简单——不就是测个角度嘛。后来真上了硬件才发现,传感器数据全是噪声,加速度计抖得像筛子,陀螺仪漂得没边。嗯,这里面的门道,比想象中深得多。

这一章,咱们把姿态解算的底裤扒干净。从数学工具到工程实现,一条线捋下来。

3.1 坐标系定义——先定规矩

没有坐标系,谈姿态就是耍流氓。飞控里最常用的两套坐标系,你得刻在脑子里。

3.1.1 机体坐标系(Body Frame)

原点在飞行器重心,跟着飞机转。我习惯这么记:

  • X轴:指向机头(前进方向)
  • Y轴:指向右翼
  • Z轴:指向机腹(向下)

注意,Z轴向下是航空界的惯例。你想想看,如果Z轴向上,那滚转正方向就得反过来,容易搞混。

3.1.2 地理坐标系(Navigation Frame / NED)

也叫导航坐标系,原点在地面某点,不跟着飞机转。

  • X轴:指向北
  • Y轴:指向东
  • Z轴:指向地心(向下)

这就是常说的NED坐标系(North-East-Down)。

核心概念:姿态解算的本质,就是求机体坐标系到地理坐标系的旋转关系。这个关系,可以用欧拉角、旋转矩阵、四元数三种方式表达。

3.2 三种姿态表达方式

同一个旋转,三种数学工具。各有各的脾气。

3.2.1 欧拉角——最直观,但最危险

欧拉角就是三个角度:横滚(Roll φ)、俯仰(Pitch θ)、偏航(Yaw ψ)。

旋转顺序我建议固定为 Z-Y-X(偏航→俯仰→横滚),这是航空界标准。顺序错了,姿态就全乱了。

⚠ 万向锁(Gimbal Lock):当俯仰角接近 ±90° 时,横滚和偏航的旋转轴重合,丢失一个自由度。我在做固定翼飞控时遇到过这个问题——飞机垂直爬升时姿态解算直接崩了。所以,全姿态飞行(比如四旋翼特技)千万别只用欧拉角。

3.2.2 旋转矩阵——数学上完美,计算上沉重

3×3 的正交矩阵,没有奇点。但每次更新要算 9 个元素,嵌入式 MCU 扛不住。

从欧拉角到旋转矩阵的转换公式(Z-Y-X 顺序):

R = Rz(ψ) * Ry(θ) * Rx(φ)

其中:
Rz(ψ) = [cosψ  -sinψ  0;  sinψ  cosψ  0;  0  0  1]
Ry(θ) = [cosθ   0     sinθ;  0    1    0;  -sinθ  0  cosθ]
Rx(φ) = [1     0      0;  0   cosφ  -sinφ;  0  sinφ  cosφ]

我个人只在需要做坐标变换时用旋转矩阵,比如把机体加速度转换到地理坐标系。平时解算,不用它。

3.2.3 四元数——工程首选

四元数用四个数表达旋转:q = [q0, q1, q2, q3],其中 q0 是标量部分。

为什么选它?

  • 无奇点(万向锁不存在)
  • 计算量小(只更新4个参数)
  • 插值平滑(做姿态平滑时特别好用)

我几乎所有的飞控项目,姿态解算核心都用四元数。只在最后输出给上层控制时,才转成欧拉角。

四元数转欧拉角(Z-Y-X 顺序):

φ = atan2(2*(q0*q1 + q2*q3), 1 - 2*(q1² + q2²))
θ = asin(2*(q0*q2 - q3*q1))
ψ = atan2(2*(q0*q3 + q1*q2), 1 - 2*(q2² + q3²))

💡 我的习惯:在代码里用四元数做所有内部运算,只在日志记录和地面站显示时转成欧拉角。这样既避免了万向锁,又方便人看。

3.3 姿态解算算法

传感器各有各的毛病:

  • 陀螺仪:动态响应快,但积分会漂移
  • 加速度计:长期稳定,但受振动干扰大
  • 磁力计:提供偏航参考,但容易受磁场干扰

姿态解算的核心,就是融合这些传感器的优点,抑制它们的缺点。

3.3.1 互补滤波——简单粗暴,够用就好

思路很直白:低频信加速度计,高频信陀螺仪。

公式就一行:

角度 = α * (角度 + 陀螺仪积分) + (1-α) * 加速度计角度

其中 α 是滤波系数,一般取 0.98 左右。

优点:代码量少,计算量小,适合资源受限的 MCU。

缺点:精度一般,对振动敏感。我曾经在一个四旋翼上试过,悬停还行,一做大机动就飘。

3.3.2 Mahony 滤波——互补滤波的升级版

Mahony 滤波在互补滤波基础上,加入了PI 反馈修正。它用加速度计和磁力计的测量值,计算出一个误差,然后用 PI 控制器去修正陀螺仪的偏差。

核心步骤:

  1. 用当前四元数估计重力方向
  2. 用加速度计测量实际重力方向
  3. 两者叉积得到误差
  4. 误差经过 PI 控制器,修正陀螺仪角速度
  5. 用修正后的角速度更新四元数
// Mahony 滤波核心伪代码
void MahonyUpdate(float gx, float gy, float gz, float ax, float ay, float az) {
    // 1. 估计重力方向
    float halfvx = q1*q3 - q0*q2;
    float halfvy = q0*q1 + q2*q3;
    float halfvz = q0*q0 - 0.5f + q3*q3;
    
    // 2. 计算误差(叉积)
    float ex = ay * halfvz - az * halfvy;
    float ey = az * halfvx - ax * halfvz;
    float ez = ax * halfvy - ay * halfvx;
    
    // 3. PI 修正
    integralFBx += Ki * ex * dt;
    integralFBy += Ki * ey * dt;
    integralFBz += Ki * ez * dt;
    
    gx += ex * Kp + integralFBx;
    gy += ey * Kp + integralFBy;
    gz += ez * Kp + integralFBz;
    
    // 4. 更新四元数
    // ... 四元数微分方程
}

💡 参数调优经验:Kp 决定收敛速度,Ki 决定稳态精度。我一般先调 Kp 让姿态能跟上,再加一点点 Ki 消除静差。Ki 太大反而会引入低频振荡。

3.3.3 EKF(扩展卡尔曼滤波)——精度天花板

EKF 是姿态解算的终极方案。它用状态空间模型,把陀螺仪偏差也作为状态量一起估计。

状态向量(7维):

x = [q0, q1, q2, q3, bgx, bgy, bgz]

前4个是四元数,后3个是陀螺仪零偏。

观测向量:加速度计(3轴)+ 磁力计(3轴)

EKF 的流程:

  1. 预测步:用陀螺仪角速度更新状态
  2. 更新步:用加速度计和磁力计修正状态

我做过对比测试:在同样硬件上,EKF 的姿态精度比 Mahony 高约 30%,但计算量大了 5 倍。所以选型时要权衡——STM32F4 以上跑 EKF 没问题,F103 就老老实实用 Mahony。

⚠ 我曾经踩过的坑:EKF 的协方差矩阵初始化很关键。如果初始协方差设得太大,滤波器收敛慢;设得太小,又跟不上动态。我后来总结了一个经验:先跑一段静态数据,用 Allan 方差分析出传感器噪声特性,再用这些参数初始化协方差矩阵。效果立竿见影。

3.4 三种算法对比

算法 精度 计算量 适用场景 我推荐的使用条件
互补滤波 低 极低 玩具、低成本飞控 MCU 主频 < 72MHz
Mahony 中 低 大多数消费级飞控 STM32F1/F3 级别
EKF 高 高 工业级、自动驾驶 STM32F4/H7 或更高

3.5 姿态解算知识体系

下面这张图,把这一章的核心逻辑串起来了:

姿态解算知识体系 传感器输入 陀螺仪 加速度计 磁力计(可选) 数学表达工具 欧拉角 旋转矩阵 四元数 ★ 解算算法 互补滤波 Mahony 滤波 EKF 输出:姿态四元数 → 欧拉角 核心逻辑 1. 陀螺仪提供高速率角速度 → 积分得到姿态(短期准,长期漂) 2. 加速度计/磁力计提供绝对参考 → 修正漂移(长期稳,短期噪) 3. 算法融合两者 → 输出最优姿态估计 4. 内部用四元数运算,避免万向锁 5. 最终输出给控制层时转成欧拉角

3.6 工程选型建议

说了这么多,到底用哪个?我个人的决策逻辑是这样的:

  • 玩具/教育级:互补滤波,10行代码搞定
  • 消费级无人机:Mahony 滤波,性价比最高
  • 工业级/自动驾驶:EKF,精度就是生命

还有一个容易被忽略的点:传感器质量。再好的算法也救不了烂传感器。我曾经在一个项目里花了两周调 EKF 参数,最后发现是加速度计噪声太大。换了传感器,问题迎刃而解。

嗯,姿态解算这块,数学是基础,工程是艺术。多动手调,多上机试,慢慢就有感觉了。