第1章:姿态解算算法——从传感器数据到飞行姿态
各位同学,欢迎来到《惯性导航与飞控系统集成实战》的第一章。今天咱们聊聊姿态解算算法。说白了,就是怎么把陀螺仪、加速度计、磁力计这些传感器读出来的原始数据,变成飞行器当前是“抬头”还是“低头”、“左倾”还是“右倾”这些我们能理解的姿态信息。
我刚开始做飞控那会儿,觉得这步很简单——不就是积分嘛。结果第一次试飞,飞机在天上翻了几个跟头,差点炸机。嗯,从那以后我才明白,姿态解算的水深着呢。
核心观点:姿态解算是飞控系统的“感知层”,没有准确的姿态,控制就是盲人摸象。
1.1 互补滤波原理与实现
先说说互补滤波。这个名字听起来挺唬人,其实原理很简单。
陀螺仪动态响应快,但积分会漂移。加速度计静态准,但受振动干扰大。互补滤波就是把两者的优点结合起来——低频段相信加速度计,高频段相信陀螺仪。你想想看,这不就是“取长补短”嘛。
我在项目中遇到过一个问题:用纯陀螺仪积分,10秒后姿态就偏了5度。加上互补滤波后,漂移被加速度计拉回来了,误差控制在0.5度以内。
互补滤波的公式其实就一行:
// 互补滤波核心代码
angle = 0.98 * (angle + gyro * dt) + 0.02 * acc_angle;
这里的0.98和0.02就是权重系数。系数怎么选?我个人的习惯是:振动大的场景(比如四轴飞行器),加速度计权重可以再小一点,比如0.005;静态场景(比如地面机器人),可以放大到0.05。
小技巧:互补滤波的截止频率可以用公式 f = (1 - α) / (2π * dt * α) 来估算。α是加速度计权重,dt是采样周期。
1.2 Mahony滤波算法详解
Mahony滤波是互补滤波的进阶版。它不再直接融合角度,而是用PI控制器来修正陀螺仪的偏差。
我最早接触Mahony滤波是在2015年,当时做一款手持稳定器。用互补滤波效果总是不太理想,换了Mahony之后,稳得像块石头。
Mahony的核心思想是:用加速度计和磁力计的测量值,构建一个“期望的”姿态方向,然后跟当前姿态做叉积,得到误差。这个误差经过PI控制器后,去修正陀螺仪的角速度。
// Mahony滤波核心步骤(简化版)
// 1. 计算当前姿态下的重力方向
g = quat_rotate(gravity_ref, q);
// 2. 计算误差(叉积)
error = cross(acc_meas, g);
// 3. PI控制器修正陀螺仪
gyro_corrected = gyro + Kp * error + Ki * integral(error);
// 4. 用修正后的角速度更新四元数
q = quat_update(q, gyro_corrected, dt);
这里有个坑,我曾经踩过:Ki参数不能太大,否则积分项会饱和,导致姿态发散。我一般先调Kp,等动态响应满意了,再加一点点Ki消除稳态误差。
注意:Mahony滤波对磁力计比较敏感。如果磁力计受到硬铁干扰,建议先做校准,否则姿态会跟着磁场一起“跳舞”。
1.3 Madgwick滤波算法详解
Madgwick滤波是另一个经典算法。它跟Mahony的区别在于:Madgwick用梯度下降法来寻找最优姿态,而不是用PI控制器。
我记得第一次看Madgwick的论文时,被里面的数学公式吓了一跳。但实际用起来,发现代码量比Mahony还少。Madgwick算法的核心就两步:
- 陀螺仪预测:用陀螺仪角速度更新四元数,得到预测姿态
- 梯度下降修正:用加速度计/磁力计数据,沿着误差函数的负梯度方向修正姿态
// Madgwick滤波核心代码(简化版)
// 1. 陀螺仪预测
q_dot = 0.5 * quat_multiply(q, [0, gyro_x, gyro_y, gyro_z]);
q_pred = q + q_dot * dt;
// 2. 梯度下降修正
gradient = compute_gradient(q_pred, acc, mag);
q = q_pred - beta * gradient / norm(gradient);
beta参数控制修正速度。beta越大,收敛越快,但噪声也越大。我一般从0.1开始调,看实际效果再微调。
Madgwick的一个优点是:它不需要磁力计也能工作(只用加速度计和陀螺仪)。但加上磁力计后,航向角会更稳定。
对比总结:
| 算法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 互补滤波 | 简单、计算量小 | 精度一般 | 低成本MCU、简单应用 |
| Mahony滤波 | 抗干扰能力强 | 需要调PI参数 | 四轴飞行器、手持稳定器 |
| Madgwick滤波 | 收敛快、代码简洁 | beta参数敏感 | 无人机、VR/AR设备 |
1.4 四元数更新与归一化
不管用哪种滤波算法,最后都要更新四元数。四元数更新说白了就是解微分方程:
// 四元数更新(一阶龙格-库塔法)
void quaternion_update(quat_t *q, float gx, float gy, float gz, float dt) {
float q0 = q->w, q1 = q->x, q2 = q->y, q3 = q->z;
float q0_dot = 0.5 * (-q1*gx - q2*gy - q3*gz);
float q1_dot = 0.5 * ( q0*gx + q2*gz - q3*gy);
float q2_dot = 0.5 * ( q0*gy - q1*gz + q3*gx);
float q3_dot = 0.5 * ( q0*gz + q1*gy - q2*gx);
q->w += q0_dot * dt;
q->x += q1_dot * dt;
q->y += q2_dot * dt;
q->z += q3_dot * dt;
// 归一化
quaternion_normalize(q);
}
归一化这一步千万不能省!我见过有人偷懒不归一化,结果姿态越算越偏,最后直接炸机。四元数不归一化,旋转矩阵就不正交,姿态就会慢慢“变形”。
归一化公式: q = q / sqrt(q0² + q1² + q2² + q3²)
建议每次更新后都做归一化,或者至少每10次更新做一次。
1.5 姿态角解算(从四元数到欧拉角)
最后一步,把四元数转成欧拉角。欧拉角就是我们常说的俯仰角(Pitch)、横滚角(Roll)、航向角(Yaw)。
转换公式如下:
// 四元数转欧拉角
void quaternion_to_euler(quat_t q, float *roll, float *pitch, float *yaw) {
float q0 = q.w, q1 = q.x, q2 = q.y, q3 = q.z;
*roll = atan2(2*(q0*q1 + q2*q3), 1 - 2*(q1*q1 + q2*q2));
*pitch = asin(2*(q0*q2 - q3*q1));
*yaw = atan2(2*(q0*q3 + q1*q2), 1 - 2*(q2*q2 + q3*q3));
}
这里有个坑:当俯仰角接近±90度时,会出现万向锁问题。航向角和横滚角会变得不稳定。我在做固定翼飞控时遇到过这个问题,后来改用四元数做控制,绕开了欧拉角的奇异性。
警告:欧拉角在俯仰角±90度附近会“跳变”。如果做全姿态控制,建议直接用四元数,不要转欧拉角。
本章知识体系
下面这张图是我自己画的,把本章的知识结构梳理了一下。你可以把它当作一个“导航图”,方便回顾。
从这张图可以看得很清楚:传感器数据进来后,经过三种滤波算法之一处理,得到修正后的角速度,然后更新四元数并归一化,最后转成欧拉角输出。每一步都有坑,每一步也都有技巧。
好了,第一章的内容就到这里。姿态解算是飞控的“眼睛”,把这一步搞扎实了,后面的控制算法才能发挥威力。下一章我们聊聊传感器校准——别小看这一步,它决定了你整个系统的精度上限。
公众号:蓝海资料掘金营,微信deep3321