4. 姿态更新算法(一):四元数微分方程、毕卡逼近法、龙格-库塔法(RK4)实现

姿态更新,说白了就是实时算出来「载体现在朝哪个方向」。在捷联惯导里,陀螺仪输出的是角速度,我们要靠它推算姿态。这中间的核心,就是解四元数微分方程。

我个人习惯把姿态更新比作「积分」——陀螺仪给的是角速度,积分一次得到角度。但这里有个坑:四元数不是线性系统,不能简单累加。你想想看,刚体转动本身是非线性的,四元数微分方程里还带着乘法,这就逼着我们用数值方法去逼近。

4.1 四元数微分方程

先看基本形式。四元数微分方程长这样:

dq/dt = 0.5 * q ⊗ ω

其中 q 是姿态四元数,ω 是角速度向量(通常写成 [0, ωx, ωy, ωz] 的形式)。⊗ 表示四元数乘法。

展开成矩阵形式:

| dq0/dt |   | 0   -ωx  -ωy  -ωz | | q0 |
| dq1/dt | = | ωx   0    ωz  -ωy | | q1 |
| dq2/dt |   | ωy  -ωz   0    ωx | | q2 |
| dq3/dt |   | ωz   ωy  -ωx   0  | | q3 |

嗯,这里要注意:角速度是载体坐标系下的,也就是陀螺仪直接测量的值。我在项目中遇到过有人直接用导航系下的角速度去算,结果姿态飞了——这个坑踩过一次就记住了。

核心要点:四元数微分方程的解,本质上是求一个旋转矩阵的指数映射。角速度变化越快,对数值方法的要求越高。

4.2 毕卡逼近法

毕卡逼近法,说白了就是「泰勒展开截断」。把四元数微分方程的解写成指数形式:

q(t+Δt) = exp(0.5 * Ω * Δt) * q(t)

其中 Ω 是角速度的反对称矩阵。展开指数项:

exp(0.5 * Ω * Δt) = I + (0.5 * Ω * Δt) + (0.5 * Ω * Δt)^2 / 2! + ...

实际工程中,我们一般取到二阶或三阶。一阶毕卡逼近:

q(t+Δt) ≈ [I + 0.5 * Ω * Δt] * q(t)

二阶毕卡逼近:

q(t+Δt) ≈ [I + 0.5 * Ω * Δt + (0.5 * Ω * Δt)^2 / 2] * q(t)

我曾经在低端MCU上用过一阶毕卡,采样率500Hz,角速度变化不大时精度还行。但一旦遇到剧烈转动,误差就明显了。说白了,毕卡逼近法适合角速度变化平缓的场景,或者采样率足够高的时候。

我的经验:毕卡逼近法计算量小,适合资源受限的平台。但要注意归一化——每次更新后必须对四元数做归一化,否则误差会累积。

4.3 龙格-库塔法(RK4)

RK4是工程上最常用的数值积分方法之一。它通过四个中间步的加权平均,把截断误差压到 O(h^5)。对于四元数微分方程,RK4的实现如下:

// 四元数微分方程函数
void quaternion_derivative(const float q[4], const float w[3], float dq[4]) {
    dq[0] = 0.5f * (-q[1]*w[0] - q[2]*w[1] - q[3]*w[2]);
    dq[1] = 0.5f * ( q[0]*w[0] + q[2]*w[2] - q[3]*w[1]);
    dq[2] = 0.5f * ( q[0]*w[1] - q[1]*w[2] + q[3]*w[0]);
    dq[3] = 0.5f * ( q[0]*w[2] + q[1]*w[1] - q[2]*w[0]);
}

// RK4 姿态更新
void rk4_update(float q[4], const float w[3], float dt) {
    float k1[4], k2[4], k3[4], k4[4];
    float q_temp[4];
    float w_mid[3];
    
    // k1
    quaternion_derivative(q, w, k1);
    
    // k2: 使用中点角速度
    w_mid[0] = w[0];  // 实际应用中角速度可能变化
    w_mid[1] = w[1];  // 这里假设采样间隔内角速度恒定
    w_mid[2] = w[2];
    
    q_temp[0] = q[0] + 0.5f * dt * k1[0];
    q_temp[1] = q[1] + 0.5f * dt * k1[1];
    q_temp[2] = q[2] + 0.5f * dt * k1[2];
    q_temp[3] = q[3] + 0.5f * dt * k1[3];
    quaternion_derivative(q_temp, w_mid, k2);
    
    // k3
    q_temp[0] = q[0] + 0.5f * dt * k2[0];
    q_temp[1] = q[1] + 0.5f * dt * k2[1];
    q_temp[2] = q[2] + 0.5f * dt * k2[2];
    q_temp[3] = q[3] + 0.5f * dt * k2[3];
    quaternion_derivative(q_temp, w_mid, k3);
    
    // k4
    q_temp[0] = q[0] + dt * k3[0];
    q_temp[1] = q[1] + dt * k3[1];
    q_temp[2] = q[2] + dt * k3[2];
    q_temp[3] = q[3] + dt * k3[3];
    quaternion_derivative(q_temp, w, k4);
    
    // 加权平均
    q[0] += (dt / 6.0f) * (k1[0] + 2.0f*k2[0] + 2.0f*k3[0] + k4[0]);
    q[1] += (dt / 6.0f) * (k1[1] + 2.0f*k2[1] + 2.0f*k3[1] + k4[1]);
    q[2] += (dt / 6.0f) * (k1[2] + 2.0f*k2[2] + 2.0f*k3[2] + k4[2]);
    q[3] += (dt / 6.0f) * (k1[3] + 2.0f*k2[3] + 2.0f*k3[3] + k4[3]);
    
    // 归一化
    float norm = sqrtf(q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]);
    q[0] /= norm;
    q[1] /= norm;
    q[2] /= norm;
    q[3] /= norm;
}

注意:RK4的计算量是毕卡逼近法的4倍左右。在嵌入式平台上,如果采样率是1000Hz,每个姿态更新周期只有1ms,RK4可能吃紧。我建议先评估CPU负载,再决定用哪种方法。

4.4 三种方法对比

方法 精度 计算量 适用场景
一阶毕卡 O(Δt^2) 高采样率、低动态
二阶毕卡 O(Δt^3) 中等动态、资源受限
RK4 O(Δt^5) 高动态、精度优先

我个人的选择原则:如果MCU主频在100MHz以上,采样率500Hz以内,我直接上RK4。如果资源紧张,比如用STM32F1跑1000Hz,我会用二阶毕卡,配合高精度定时器。

4.5 知识体系结构图

姿态更新算法知识体系 四元数微分方程 毕卡逼近法 龙格-库塔法(RK4) 其他数值方法 一阶逼近 二阶逼近 四步加权平均 欧拉法/梯形法 关键考量 精度 vs 计算量 | 采样率匹配 | 归一化处理 | 角速度变化率

这张图把姿态更新的核心脉络理清楚了。从四元数微分方程出发,三个分支对应不同的数值解法。毕卡逼近法适合资源受限场景,RK4适合高精度需求。底部那四个关键考量,是我在实际项目中反复踩坑后总结出来的。

避坑指南:我曾经在无人机项目里用RK4,采样率400Hz,一切正常。后来换到200Hz,姿态开始漂移。查了半天发现是角速度变化太快,RK4的中间步假设角速度恒定已经不成立了。解决办法是提高采样率,或者在陀螺仪数据之间做插值。

好了,这一章就到这里。姿态更新是惯导算法的基石,四元数微分方程的各种解法各有优劣。毕卡逼近法简单高效,RK4精度更高。实际选型时,一定要结合你的硬件平台和动态场景来权衡。


专注资料整理