飞行器控制笔记(一)——姿态解算之微分方程数值计算

飞行器控制笔记(一)—— 姿态解算之微分方程数值计算

一、引言

飞行器的姿态解算,简单地说来,就是通过测量机体角速率来计算出机体的姿态角(俯仰角 θ ,横滚角 ϕ ,偏航角 ψ )。 而角速率与姿态变化率之间有着很直观的联系,所以姿态解算最终是在求解一组包含角速率与姿态的一阶常微分方程。因此,有必要记录一下微分方程的数值解法,便于后面通过代码实现姿态解算。

二、 常用的数值计算方法

2.1 欧拉折线法

欧拉折线法是一种古老简单的求解一阶常微分方程的数值计算方法,尽管它的精度并不高,但是对于飞控这种使用MCU做计算的计算能力比较弱的场合,欧拉折线法计算量小,而且精度也能基本满足要求,所以应用得还是挺多的。不过我感觉如果使用STM32F4并且开启FPU,那么也完全可以使用精度更高的算法来提高解算精度,这样对飞行器的稳定性应该也会有一定的提升。下面还是先记录下欧拉折线法的过程。
一阶微分方程的一般形式如下:

{ d y d x = f ( x , y ( x ) ) x [ a , b ] y ( x 0 ) = y 0

如果对 d y d x 做进行处理,用差分替换,即 y ( x n ) = y n + 1 y n h ,其中 h = x n + 1 x n ,是相邻两点之间的距离,也就是步长。这样,原来的微分方程就可以写成下面的形式了:
{ y n + 1 y n h = f ( x n , y ( x n ) ) x [ a , b ] y ( x 0 ) = y 0

这样一来, y n + 1 = y n + h f ( x n , y ( x n ) ) , n = 0 , 1 , 2 , 3
这便是欧拉公式,式子很简单,也很直观,通过一系列的折线来近似代替函数,只要给定了初始值 y 0 ,就可以一步一步地迭代计算出在结点 x 1 , x 2 处的函数值 y 1 , y 2 .

2.2改进欧拉折线法

对于一阶微分方程,如果换个方式,直接对等式左右两边积分,可以得到:

y ( x ) = y 0 + a b f ( x , y ( x ) ) d x

若已知 x = x n 处的函数值 y ( x n ) ,那么上面的式子也可以写成下面的形式:
y ( x n + 1 ) = y n + x n x n + 1 f ( x , y ( x ) ) d x

对上面的等式右边的积分进行处理,用矩形公式计算积分,即 x n x n + 1 f ( x , y ( x ) ) d x = h f ( x n , y ( x n ) ) , 就可以得到下面的式子:
y n + 1 = y n + h f ( x n , y n ) , n = 0 , 1 , 2 , 3

这就是上面提到的欧拉折线法啊,挺有意思的,对同一个问题从不同的角度都得到了相同的结果。从这个式子也可以说明欧拉折线法的精度确实不高,因为它是用矩形公式近似计算积分的,很自然地,为了提高精度,可以用梯形公式计算积分,即 x n x n + 1 f ( x , y ( x ) ) d x = h 2 ( f ( x n , y ( x n ) ) + f ( x n + 1 , y ( x n + 1 ) ) 这样就能得到下面的式子:
y n + 1 = y n + h 2 ( f ( x n , y n ) + f ( x n + h , y n + h f ( x n , y n ) ) , n = 0 , 1 , 2 , 3

这就是使用梯形公式改进后的欧拉公式了,为了便于计算,又可将上式改写成下面的形式:
{ k 1 = h f ( x n , y n ) k 2 = h f ( x n + h , y n + k 1 ) y n + 1 = y n + 1 2 ( k 1 + k 2 )

这个形式更容易通过代码实现.

2.3龙格—库塔法

如果对于方程解的精度要求比较高,上诉的两种方法都不能满足要求,则可以考虑使用四阶的龙格—库塔法进行计算。我先记录二阶龙格—库塔法的推导过程,这也是一个有意思的过程,推导之后可以发现,其实与上述的欧拉折线法以及改进的欧拉折线法都是龙格—库塔法的一阶与二阶形式的特例.
假定上述微分方程存在解析解 y = y ( x ) ,对它进行泰勒级数展开可得到:

y ( x n + 1 ) = y ( x n + h ) = y ( x n ) + y ( x n ) 1 ! h + y ( x n ) 2 ! h 2 + y ( x n ) 3 ! h 3 + + y ( k ) k ! h k + O ( h k )

若令k=1,可得一阶泰勒格式为 y ( x n + 1 ) = y ( x n ) + h f ( x n , y n ) ,该式就是欧拉折线法的公式.随着k取值的增大,泰勒格式的阶数上升,也就越来越逼近真实的函数 y = y ( x ) .所以自然想到,可以直接使用函数的泰勒展开式来计算.但是求解函数的高阶导数又不是一件容易的事,因此,这种方法并不实用.但可以间接使用泰勒级数法引导推出龙格—库塔法的计算公式.
首先欧拉公式可以改写成:
{ k 1 = h f ( x n , y n ) y n + 1 = y n + k 1

改进的欧拉公式可以写成更一般的形式:
{ k 1 = h f ( x n , y n ) k 2 = h f ( x n + a h , y n + b k 1 ) y n + 1 = y n + R 1 k 1 + R 2 k 2

其中, a , b , R 1 , R 2 都是需要求的待定系数
k 2 = h f ( x n + a h , y n + b k 1 ) 做泰勒展开,即:
(1) k 2 = h f ( x n + a h , y n + b k 1 ) (2) = h [ f ( x n , y n ) + a h f x ( x n , y n ) + b k 1 f y ( x n , y n ) + ] (3) = h f ( x n , y n ) + a h 2 f x ( x n , y n ) + b h 2 f y ( x n , y n ) f ( x n , y n ) +

k 2 k 2 的表达式代入 y n + 1 = y n + R 1 k 1 + R 2 k 2 中可以得到下面的式子:
y n + 1 = y n + ( R 1 + R 2 ) f ( x n , y n ) h + [ R 2 a f x ( x n , y n ) + R 2 b f y ( x n , y n ) ] h 2 +

与二阶泰勒格式 y ( x n + 1 ) = y ( x n + h ) = y ( x n ) + y ( x n ) 1 ! h + y ( x n ) 2 ! h 2 + O ( h 2 ) 对比可以得到下面的关系:
{ R 1 + R 2 = 1 R 2 a = 1 2 R 1 b = 1 2

上面的方程组有无穷多组解,每组解对应的式子都称为二阶龙格—库塔公式.若取 R 1 = R 2 = 1 2 , a = b = 1 ,则可以得出改进的的欧拉公式.可以发现改进的欧拉公式就是二阶龙格—库塔公式的特例.因此这几种方法的本质其实是一样的,都是间接应用了泰勒级数,不过发明人看待问题的角度不同.
用同样的方法,可以推出三阶、四阶龙格—库塔公式,其中四阶龙格—库塔公式在较高精度的计算中应用得较多,它的一种常用形式如下:
{ k 1 = h f ( x n , y n ) k 2 = h f ( x n + h 2 , y n + k 1 2 ) k 3 = h f ( x n + h 2 , y n + k 2 2 ) k 4 = h f ( x n + h , y n + k 3 ) y n + 1 = y n + 1 6 ( k 1 + 2 k 2 + 2 k 3 + k 4 )

2.4 上述数值计算方法的C语言实现及精度对比

上面的计算公式都很容易转化为C语言代码,为了方便测试,我定义一个结构体来存储各个计算方法的相关参数。然后以h=0.1的步长计算下面这个微分方程在区间[0,1]的数值解

{ y = y 2 x y y ( 0 ) = 1

它的解析解为 y = 2 x + 1

#include <stdio.h>
#include <math.h>

#define f(t,y) ((y) - (2.0f*t) / (y))   
#define F(t) sqrt(2*t+1)

typedef struct {
    float a;//区间左端点
    float b;//区间右端点
    float t;//自变量
    float y;//函数值
    float h;//步长
    float k1;
    float k2;
    float k3;
    float k4;
}Calc_t;

int main()
{
    Calc_t Euler =  { 0,1,0,1,0.1,0,0,0,0 };
    Calc_t Euler2 = { 0,1,0,1,0.1,0,0,0,0 };
    Calc_t RK_4 =   { 0,1,0,1,0.1,0,0,0,0 };
    int n = (Euler.b - Euler.a) / Euler.h;
    printf("t\t欧拉折线法\t改进欧拉折线法\t四阶龙格库塔法\t真实值\n");
    printf("%.1f\t%.8f\t%.8f\t%.8f\t%.8f\n", Euler.t, Euler.y, Euler2.y, RK_4.y, F(Euler.t));
    for (int i = 0;i < n;i++) {
        /*欧拉折线法*/
        Euler.k1 = Euler.h*f(Euler.t, Euler.y);
        Euler.y += Euler.k1;
        Euler.t += Euler.h;
        /*改进欧拉折线法*/
        Euler2.k1 = Euler2.h*f(Euler2.t, Euler2.y);
        Euler2.k2 = Euler2.h*f((Euler2.t + Euler2.h), (Euler2.y + Euler2.k1));
        Euler2.y += (Euler2.k1 + Euler2.k2)/2.0f;
        Euler2.t += Euler2.h;
        /*四阶龙格库塔法*/
        RK_4.k1 = RK_4.h*f(RK_4.t, RK_4.y);
        RK_4.k2 = RK_4.h*f((RK_4.t + RK_4.h / 2.0f), (RK_4.y + RK_4.k1 / 2.0f));
        RK_4.k3 = RK_4.h*f((RK_4.t + RK_4.h / 2.0f), (RK_4.y + RK_4.k2 / 2.0f));
        RK_4.k4 = RK_4.h*f((RK_4.t + RK_4.h), (RK_4.y + RK_4.k3));
        RK_4.y += (RK_4.k1 + 2.0f*RK_4.k2 + 2.0f*RK_4.k3 + RK_4.k4) / 6.0f;
        RK_4.t += RK_4.h;
        printf("%.1f\t%.8f\t%.8f\t%.8f\t%.8f\n", Euler.t, Euler.y, Euler2.y, RK_4.y, F(Euler.t));
    }
    getchar();
}

各种方法的计算结果如下图所示,从图中可以看出欧拉折线法精度最差,四阶龙格库塔法的精度相当高了,而改进的欧拉折线法的精度也挺高的。在飞控当中,陀螺仪角速率的更新频率一般能达到100Hz左右,也就是说步长能降到0.01左右,因此,欧拉折线法的精度也是挺高的,要提高姿态解算的精度,感觉使用二阶龙格库塔公式就足够了。
这里写图片描述

参考文献

  1. 蔡锁章,杨明,雷英杰.数值计算方法.2版.北京.国防工业出版社.2016

猜你喜欢

转载自blog.csdn.net/bitaohu/article/details/80350256
今日推荐