ZYX
的顺序旋转,这样得到的一组姿态角称为欧拉角(这是最常用的,pitch
,roll
,yaw
)。不管按照什么顺序,得到的角度都可以称作广义欧拉角。实际理论分析时,旋转顺序不是很重要,这个顺序会影响四元数与欧拉角的关系,但是都可以进行解算。下面从最基本的坐标变换入手开始讲解,请看如下坐标系旋转: O A X A Y A Z A O_AX_AY_AZ_A OAXAYAZA绕OX旋转 θ \theta θ到 O B X B Y B Z B O_BX_BY_BZ_B OBXBYBZB
对于原系 O A X A Y A Z A O_AX_AY_AZ_A OAXAYAZA中的一个向量 [ r x A , r y A , r z A ] [r_{x_A},r_{y_A},r_{z_A}] [rxA,ryA,rzA],转换到新系 O B X B Y B Z B O_BX_BY_BZ_B OBXBYBZB中的向量 [ r x B , r y B , r z B ] [r_{x_B},r_{y_B},r_{z_B}] [rxB,ryB,rzB],有:
r x B = r x A r y B = c o s ( θ ) r y A + s i n ( θ ) r z A r z B = − s i n ( θ ) r y A + c o s ( θ ) r z A r_{x_B}=r_{x_A} \\ r_{y_B}=cos(\theta)r_{y_A}+sin(\theta)r_{z_A} \\ r_{z_B}=-sin(\theta)r_{y_A}+cos(\theta)r_{z_A} rxB=rxAryB=cos(θ)ryA+sin(θ)rzArzB=−sin(θ)ryA+cos(θ)rzA
可以用旋转矩阵表示
[ r x B r y B r z B ] = [ 1 0 0 0 c o s ( θ ) s i n ( θ ) 0 − s i n ( θ ) c o s ( θ ) ] [ r x A r y A r z A ] {\left[ \begin{array}{c} r_{x_B}\\ r_{y_B}\\ r_{z_B} \end{array} \right ]}= {\left[ \begin{array}{ccc} 1 & 0 & 0\\ 0 & cos(\theta) & sin(\theta)\\ 0 & -sin(\theta) & cos(\theta) \end{array} \right ]} {\left[ \begin{array}{c} r_{x_A}\\ r_{y_A}\\ r_{z_A} \end{array} \right ]} ⎣⎡rxBryBrzB⎦⎤=⎣⎡1000cos(θ)−sin(θ)0sin(θ)cos(θ)⎦⎤⎣⎡rxAryArzA⎦⎤
中间的就是 绕x轴旋转 θ \theta θ 的旋转矩阵,记作
C x ( θ ) = [ 1 0 0 0 c o s ( θ ) s i n ( θ ) 0 − s i n ( θ ) c o s ( θ ) ] C_{x_(\theta)}= {\left[ \begin{array}{ccc} 1 & 0 & 0\\ 0 & cos(\theta) & sin(\theta)\\ 0 & -sin(\theta) & cos(\theta) \end{array} \right ]} Cx(θ)=⎣⎡1000cos(θ)−sin(θ)0sin(θ)cos(θ)⎦⎤
同样的,还有 绕y轴旋转 γ \gamma γ 的旋转矩阵,记作
C y ( γ ) = [ c o s ( γ ) 0 − s i n ( γ ) 0 1 0 s i n ( γ ) 0 c o s ( γ ) ] C_{y_(\gamma)}= {\left[ \begin{array}{ccc} cos(\gamma) & 0 & -sin(\gamma)\\ 0 & 1 & 0\\ sin(\gamma) & 0 & cos(\gamma) \end{array} \right ]} Cy(γ)=⎣⎡cos(γ)0sin(γ)010−sin(γ)0cos(γ)⎦⎤
同样的,还有 绕z轴旋转 ψ \psi ψ 的旋转矩阵,记作
C z ( ψ ) = [ c o s ( ψ ) − s i n ( ψ ) 0 s i n ( ψ ) c o s ( ψ ) 0 0 0 1 ] C_{z_(\psi)}= {\left[ \begin{array}{ccc} cos(\psi) & -sin(\psi) & 0 \\ sin(\psi) & cos(\psi) & 0 \\ 0 & 0 & 1 \end{array} \right ]} Cz(ψ)=⎣⎡cos(ψ)sin(ψ)0−sin(ψ)cos(ψ)0001⎦⎤
假如现在我们按ZXY
的顺序,从地理坐标系 O n X n Y n Z n O_nX_nY_nZ_n OnXnYnZn旋转到机体坐标系 O b X b Y b Z b O_bX_bY_bZ_b ObXbYbZb,整体旋转矩阵为
C n b = C y ( γ ) C x ( θ ) C z ( ψ ) = [ c o s ( γ ) c o s ( ψ ) + s i n ( γ ) s i n ( ψ ) s i n ( θ ) − c o s ( γ ) s i n ( ψ ) + s i n ( γ ) c o s ( ψ ) c o s ( θ ) − s i n ( ψ ) c o s ( θ ) s i n ( ψ ) c o s ( θ ) c o s ( ψ ) c o s ( θ ) s i n ( θ ) s i n ( γ ) c o s ( ψ ) − c o s ( γ ) s i n ( ψ ) s i n ( θ ) − s i n ( γ ) s i n ( ψ ) − c o s ( γ ) c o s ( ψ ) s i n ( θ ) c o s ( γ ) c o s ( θ ) ] C_n^b=C_{y_(\gamma)}C_{x_(\theta)}C_{z_(\psi)}= {\left[ \begin{array}{ccc} cos(\gamma)cos(\psi)+ sin(\gamma)sin(\psi)sin(\theta) & -cos(\gamma)sin(\psi)+sin(\gamma)cos(\psi)cos(\theta) & -sin(\psi)cos(\theta) \\ sin(\psi)cos(\theta)& cos(\psi)cos(\theta) & sin(\theta) \\ sin(\gamma)cos(\psi)- cos(\gamma)sin(\psi)sin(\theta) & -sin(\gamma)s in(\psi)-cos(\gamma)cos(\psi)sin(\theta) & cos(\gamma)cos(\theta) \end{array} \right ]} Cnb=Cy(γ)Cx(θ)Cz(ψ)=⎣⎡cos(γ)cos(ψ)+sin(γ)sin(ψ)sin(θ)sin(ψ)cos(θ)sin(γ)cos(ψ)−cos(γ)sin(ψ)sin(θ)−cos(γ)sin(ψ)+sin(γ)cos(ψ)cos(θ)cos(ψ)cos(θ)−sin(γ)sin(ψ)−cos(γ)cos(ψ)sin(θ)−sin(ψ)cos(θ)sin(θ)cos(γ)cos(θ)⎦⎤
注意一点,如果从n系到b系的旋转矩阵为 C n b C_n^b Cnb,则从b系到n系的旋转矩阵为 C n b T {C_n^b}^T CnbT,显然有 C n b ∗ C n b T = E C_n^b*{C_n^b}^T=E Cnb∗CnbT=E,所以旋转矩阵是正交矩阵
这个结论不能直接使用,一方面这个直接是不可解的,另一方面大量三角函数运算会导致大量资源占用。
ZYX
的顺序从地理坐标系 O e X e Y e Z e O_eX_eY_eZ_e OeXeYeZe旋转到机体坐标系 O b X b Y b Z b O_bX_bY_bZ_b ObXbYbZb,整体旋转矩阵为一阶龙格库塔法是求解微分方程常用的工程方法,原理是把微分转化为微元增量,利用递推迭代的方法求解
设有微分方程 d y d x = f ( x , y ) \frac{dy}{dx}=f(x,y) dxdy=f(x,y)
求解y的迭代公式为 y ( λ + Δ λ ) = y ( λ ) + Δ λ ⋅ f ( x ( λ ) , y ( λ ) ) y(\lambda+\Delta\lambda)=y(\lambda)+\Delta\lambda·f(x(\lambda),y(\lambda)) y(λ+Δλ)=y(λ)+Δλ⋅f(x(λ),y(λ))
对应到四元数微分方程:
Q ( t − Δ t ) = Q ( t ) + Δ t ⋅ Φ ( t ) ⋅ Q ( t ) Q(t-\Delta t)=Q(t)+\Delta t·\Phi(t)·Q(t) Q(t−Δt)=Q(t)+Δt⋅Φ(t)⋅Q(t)
整理得
[ q 0 q 1 q 2 q 3 ] t + Δ t = [ q 0 q 1 q 2 q 3 ] t + 1 2 ⋅ Δ t ⋅ [ − ω x ⋅ q 1 − ω y ⋅ q 2 − ω z ⋅ q 3 ω x ⋅ q 0 − ω y ⋅ q 3 + ω z ⋅ q 2 ω x ⋅ q 3 + ω y ⋅ q 0 − ω z ⋅ q 1 − ω x ⋅ q 2 + ω y ⋅ q 1 + ω z ⋅ q 0 ] {\left[ \begin{array}{c} q_0\\ q_1\\ q_2\\ q_3 \end{array} \right ]}_{t+\Delta t}= {\left[ \begin{array}{c} q_0\\ q_1\\ q_2\\ q_3 \end{array} \right ]}_{t}+ \frac{1}{2}· \Delta t· {\left[ \begin{array}{c} -\omega_x·q_1 -\omega_y·q_2 -\omega_z ·q_3\\ \omega_x·q_0 -\omega_y·q_3 +\omega_z ·q_2\\ \omega_x·q_3 +\omega_y·q_0 -\omega_z ·q_1\\ -\omega_x·q_2 +\omega_y·q_1 +\omega_z ·q_0 \end{array} \right ]} ⎣⎢⎢⎡q0q1q2q3⎦⎥⎥⎤t+Δt=⎣⎢⎢⎡q0q1q2q3⎦⎥⎥⎤t+21⋅Δt⋅⎣⎢⎢⎡−ωx⋅q1−ωy⋅q2−ωz⋅q3ωx⋅q0−ωy⋅q3+ωz⋅q2ωx⋅q3+ωy⋅q0−ωz⋅q1−ωx⋅q2+ωy⋅q1+ωz⋅q0⎦⎥⎥⎤
这里 ω \omega ω就是三轴角速度,可以用陀螺仪直接测得
按Ano_Imu.c
文件顺序进行分析
Ano_Imu.c
中ZYX
顺序从地理坐标系转到机体坐标系的,这个旋转矩阵是前面提过的 (注意这三个式子中, θ \theta θ是绕Y轴转角, ϕ \phi ϕ是绕X轴转角, ψ \psi ψ是绕Z轴转角)C w a = [ c o s ( θ ) c o s ( ψ ) s i n ( ψ ) c o s ( θ ) − s i n ( θ ) s i n ( ϕ ) s i n ( θ ) c o s ( ψ ) − c o s ( ϕ ) s i n ( ψ ) s i n ( ϕ ) s i n ( θ ) s i n ( ψ ) + c o s ( ϕ ) c o s ( ψ ) s i n ( ϕ ) c o s ( θ ) c o s ( ϕ ) s i n ( θ ) c o s ( ψ ) + s i n ( ϕ ) s i n ( ψ ) c o s ( ϕ ) s i n ( θ ) s i n ( ψ ) − s i n ( ϕ ) c o s ( ψ ) c o s ( ϕ ) c o s ( θ ) ] C_w^a= {\left[ \begin{array}{ccc} cos(\theta)cos(\psi) & sin(\psi)cos(\theta) & -sin(\theta) \\ sin(\phi)sin(\theta)cos(\psi)- cos(\phi)sin(\psi) & sin(\phi)sin(\theta)sin(\psi)+cos(\phi)cos(\psi) & sin(\phi)cos(\theta) \\ cos(\phi)sin(\theta)cos(\psi)+ sin(\phi)sin(\psi) & cos(\phi)sin(\theta)sin(\psi)-sin(\phi)cos(\psi) & cos(\phi)cos(\theta) \end{array} \right ]} Cwa=⎣⎡cos(θ)cos(ψ)sin(ϕ)sin(θ)cos(ψ)−cos(ϕ)sin(ψ)cos(ϕ)sin(θ)cos(ψ)+sin(ϕ)sin(ψ)sin(ψ)cos(θ)sin(ϕ)sin(θ)sin(ψ)+cos(ϕ)cos(ψ)cos(ϕ)sin(θ)sin(ψ)−sin(ϕ)cos(ψ)−sin(θ)sin(ϕ)cos(θ)cos(ϕ)cos(θ)⎦⎤
二维数组att_matrix
表示从机体坐标系转到地理坐标系的旋转矩阵,是上面那个的转置#include "Ano_Imu.h"
#include "Ano_Math.h"
#include "Ano_Filter.h"
//#include "ANO_RC.h"
/*参考坐标,定义为ANO坐标*
俯视,机头方向为x正方向
+x
|
+y--|--
|
*/
//世界坐标平面XY转平面航向坐标XY
void w2h_2d_trans(float w[VEC_XYZ],float ref_ax[VEC_XYZ],float h[VEC_XYZ])
{
h[X] = w[X] * ref_ax[X] + w[Y] *ref_ax[Y];
h[Y] = w[X] *(-ref_ax[Y]) + w[Y] *ref_ax[X];
}
//平面航向坐标XY转世界坐标平面XY
void h2w_2d_trans(float h[VEC_XYZ],float ref_ax[VEC_XYZ],float w[VEC_XYZ])
{
w[X] = h[X] *ref_ax[X] + h[Y] *(-ref_ax[Y]);
w[Y] = h[X] *ref_ax[Y] + h[Y] * ref_ax[X];
}
//载体坐标转世界坐标(ANO约定等同与地理坐标)
float att_matrix[3][3]; //必须由姿态解算算出该矩阵
void a2w_3d_trans(float a[VEC_XYZ],float w[VEC_XYZ])
{
for(u8 i = 0;i<3;i++)
{
float temp = 0;
for(u8 j = 0;j<3;j++)
{
temp += a[j] *att_matrix[i][j];
}
w[i] = temp;
}
}
Ano_Imu.c
文件的起始部分,提供了三个坐标系转换函数,可以实现w系和h系XY方向向量互转,以及从a系到w系三轴方向向量转换[ h [ X ] h [ Y ] ] = [ r e f _ a x [ X ] r e f _ a x [ Y ] − r e f _ a x [ Y ] r e f _ a x [ X ] ] [ w [ X ] w [ Y ] ] {\left[ \begin{array}{c} h[X]\\ h[Y] \end{array} \right ]}= {\left[ \begin{array}{ccc} ref\_ax[X] & ref\_ax[Y]\\ -ref\_ax[Y] & ref\_ax[X] \end{array} \right ]} {\left[ \begin{array}{c} w[X]\\ w[Y] \end{array} \right ]} [h[X]h[Y]]=[ref_ax[X]−ref_ax[Y]ref_ax[Y]ref_ax[X]][w[X]w[Y]]
[ w [ X ] w [ Y ] ] = [ r e f _ a x [ X ] − r e f _ a x [ Y ] r e f _ a x [ Y ] r e f _ a x [ X ] ] [ h [ X ] h [ Y ] ] {\left[ \begin{array}{c} w[X]\\ w[Y] \end{array} \right ]}= {\left[ \begin{array}{ccc} ref\_ax[X] & -ref\_ax[Y]\\ ref\_ax[Y] & ref\_ax[X] \end{array} \right ]} {\left[ \begin{array}{c} h[X]\\ h[Y] \end{array} \right ]} [w[X]w[Y]]=[ref_ax[X]ref_ax[Y]−ref_ax[Y]ref_ax[X]][h[X]h[Y]]
[ w [ X ] w [ Y ] w [ Z ] ] = [ a t t _ m a t r i x [ 0 ] [ 0 ] a t t _ m a t r i x [ 0 ] [ 1 ] a t t _ m a t r i x [ 0 ] [ 2 ] a t t _ m a t r i x [ 1 ] [ 0 ] a t t _ m a t r i x [ 1 ] [ 1 ] a t t _ m a t r i x [ 1 ] [ 2 ] a t t _ m a t r i x [ 2 ] [ 0 ] a t t _ m a t r i x [ 2 ] [ 1 ] a t t _ m a t r i x [ 2 ] [ 2 ] ] ⋅ [ a [ X ] a [ Y ] a [ Z ] ] {\left[ \begin{array}{c} w[X]\\ w[Y]\\ w[Z] \end{array} \right ]}= {\left[ \begin{array}{cccc} att\_matrix[0][0] & att\_matrix[0][1] & att\_matrix[0][2]\\ att\_matrix[1][0] & att\_matrix[1][1] & att\_matrix[1][2]\\ att\_matrix[2][0] & att\_matrix[2][1] & att\_matrix[2][2] \end{array} \right ]}· {\left[ \begin{array}{c} a[X]\\ a[Y]\\ a[Z] \end{array} \right ]} ⎣⎡w[X]w[Y]w[Z]⎦⎤=⎣⎡att_matrix[0][0]att_matrix[1][0]att_matrix[2][0]att_matrix[0][1]att_matrix[1][1]att_matrix[2][1]att_matrix[0][2]att_matrix[1][2]att_matrix[2][2]⎦⎤⋅⎣⎡a[X]a[Y]a[Z]⎦⎤
形参ref_ax
的实参是imu_data.hx_vec
imu_data.hx_vec
是什么,找到 //水平面方向向量
//my_sqrt_reciprocal求开方的倒数;my_pow求平方
float hx_vec_reci = my_sqrt_reciprocal(my_pow(att_matrix[0][0]) + my_pow(att_matrix[1][0]));
imu->hx_vec[X] = att_matrix[0][0] *hx_vec_reci;
imu->hx_vec[Y] = att_matrix[1][0] *hx_vec_reci;
还记得嘛
a t t _ m a t r i x = [ c o s ( θ ) c o s ( ψ ) s i n ( ϕ ) s i n ( θ ) c o s ( ψ ) − c o s ( ϕ ) s i n ( ψ ) c o s ( ϕ ) s i n ( θ ) c o s ( ψ ) + s i n ( ϕ ) s i n ( ψ ) s i n ( ψ ) c o s ( θ ) s i n ( ϕ ) s i n ( θ ) s i n ( ψ ) + c o s ( ϕ ) c o s ( ψ ) c o s ( ϕ ) s i n ( θ ) s i n ( ψ ) − s i n ( ϕ ) c o s ( ψ ) − s i n ( θ ) s i n ( ϕ ) c o s ( θ ) c o s ( ϕ ) c o s ( θ ) ] att\_matrix= {\left[ \begin{array}{ccc} cos(\theta)cos(\psi) & sin(\phi)sin(\theta)cos(\psi)- cos(\phi)sin(\psi) &cos(\phi)sin(\theta)cos(\psi)+ sin(\phi)sin(\psi) \\ sin(\psi)cos(\theta) &sin(\phi)sin(\theta)sin(\psi)+cos(\phi)cos(\psi) & cos(\phi)sin(\theta)sin(\psi)-sin(\phi)cos(\psi) \\ -sin(\theta) &sin(\phi)cos(\theta) & cos(\phi)cos(\theta) \end{array} \right ]} att_matrix=⎣⎡cos(θ)cos(ψ)sin(ψ)cos(θ)−sin(θ)sin(ϕ)sin(θ)cos(ψ)−cos(ϕ)sin(ψ)sin(ϕ)sin(θ)sin(ψ)+cos(ϕ)cos(ψ)sin(ϕ)cos(θ)cos(ϕ)sin(θ)cos(ψ)+sin(ϕ)sin(ψ)cos(ϕ)sin(θ)sin(ψ)−sin(ϕ)cos(ψ)cos(ϕ)cos(θ)⎦⎤
所以
h x _ v e c _ r e c i = 1 c o s 2 ( θ ) c o s 2 ( ψ ) + c o s 2 ( θ ) s i n 2 ( ψ ) = 1 c o s 2 ( θ ) = 1 c o s ( θ ) hx\_vec\_reci = \frac{1}{\sqrt{cos^2(\theta)cos^2(\psi)+cos^2(\theta)sin^2(\psi)}}=\frac{1}{\sqrt{cos^2(\theta)}}=\frac{1}{cos(\theta)} hx_vec_reci=cos2(θ)cos2(ψ)+cos2(θ)sin2(ψ)1=cos2(θ)1=cos(θ)1 i m u − > h x _ v e c [ X ] = c o s ( ψ ) imu->hx\_vec[X]=cos(\psi) imu−>hx_vec[X]=cos(ψ) i m u − > h x _ v e c [ Y ] = s i n ( ψ ) imu->hx\_vec[Y]=sin(\psi) imu−>hx_vec[Y]=sin(ψ)
物理意义上,hx_vec[X]
和hx_vec[Y]
来自机体系转地面系的第一列,表示机体系中的X单位向量(机头方向) [ 1 , 0 , 0 ] T [1 ,0 ,0]^T [1,0,0]T,在 θ = ϕ = 0 \theta=\phi=0 θ=ϕ=0时,仅绕Z轴旋转 ψ \psi ψ后,得到的向量中的XY项。如下图所示,不难看出这指示了航向坐标的机头方向,所以匿名在注释中把它们称为水平面方向向量
回代,有以下关系,可见航向坐标系h就是地理系w只绕Z轴旋转(yaw),忽略pitch和roll旋转的结果
[ h [ X ] h [ Y ] ] = [ c o s ( ψ ) s i n ( ψ ) − s i n ( ψ ) c o s ( ψ ) ] [ w [ X ] w [ Y ] ] {\left[ \begin{array}{c} h[X]\\ h[Y] \end{array} \right ]}= {\left[ \begin{array}{ccc} cos(\psi) & sin(\psi)\\ -sin(\psi) & cos(\psi) \end{array} \right ]} {\left[ \begin{array}{c} w[X]\\ w[Y] \end{array} \right ]} [h[X]h[Y]]=[cos(ψ)−sin(ψ)sin(ψ)cos(ψ)][w[X]w[Y]]
[ w [ X ] w [ Y ] ] = [ c o s ( ψ ) − s i n ( ψ ) s i n ( ψ ) c o s ( ψ ) ] [ h [ X ] h [ Y ] ] {\left[ \begin{array}{c} w[X]\\ w[Y] \end{array} \right ]}= {\left[ \begin{array}{ccc} cos(\psi) & -sin(\psi)\\ sin(\psi) &cos(\psi) \end{array} \right ]} {\left[ \begin{array}{c} h[X]\\ h[Y] \end{array} \right ]} [w[X]w[Y]]=[cos(ψ)sin(ψ)−sin(ψ)cos(ψ)][h[X]h[Y]]
在三个坐标系变换函数后,下一个就是IMU_update函数了,下面进行分析
此函数原型void IMU_update(float dT,_imu_state_st *state,float gyr[VEC_XYZ], s32 acc[VEC_XYZ],s16 mag_val[VEC_XYZ],_imu_st *imu)
,参数包括计算周期dT
,Imu标志*state
,滤波转换后的三轴陀螺仪数据gyr
,滤波转换后的三轴加速度计数据acc
,滤波转换后的三轴磁力计数据gyr
,Imu数据结构体*Imu
任务:
*Imu
。*state
中的标记,对互补滤波系数进行修正,进一步提高解算准确度。姿态角可以用两个传感器计算
注意这两个传感器测到的数据都是在传感器坐标系中的,因为传感器和机体固连,所以也可以看做测得的数据在机体坐标系下
两个传感器的数据漂移有不同的特点:
针对这种特点,如果我们可以找到一个方法融合两个传感器的数据,对误差进行补偿,就可以大大提高获取姿态角的精度 ,这种方法就是互补滤波
要补偿的对象:还记得吗,四元数更新方法如下
[ q 0 q 1 q 2 q 3 ] t + Δ t = [ q 0 q 1 q 2 q 3 ] t + 1 2 ⋅ Δ t ⋅ [ − ω x ⋅ q 1 − ω y ⋅ q 2 − ω z ⋅ q 3 ω x ⋅ q 0 − ω y ⋅ q 3 + ω z ⋅ q 2 ω x ⋅ q 3 + ω y ⋅ q 0 − ω z ⋅ q 1 − ω x ⋅ q 2 + ω y ⋅ q 1 + ω z ⋅ q 0 ] {\left[ \begin{array}{c} q_0\\ q_1\\ q_2\\ q_3 \end{array} \right ]}_{t+\Delta t}= {\left[ \begin{array}{c} q_0\\ q_1\\ q_2\\ q_3 \end{array} \right ]}_{t}+ \frac{1}{2}· \Delta t· {\left[ \begin{array}{c} -\omega_x·q_1 -\omega_y·q_2 -\omega_z ·q_3\\ \omega_x·q_0 -\omega_y·q_3 +\omega_z ·q_2\\ \omega_x·q_3 +\omega_y·q_0 -\omega_z ·q_1\\ -\omega_x·q_2 +\omega_y·q_1 +\omega_z ·q_0 \end{array} \right ]} ⎣⎢⎢⎡q0q1q2q3⎦⎥⎥⎤t+Δt=⎣⎢⎢⎡q0q1q2q3⎦⎥⎥⎤t+21⋅Δt⋅⎣⎢⎢⎡−ωx⋅q1−ωy⋅q2−ωz⋅q3ωx⋅q0−ωy⋅q3+ωz⋅q2ωx⋅q3+ωy⋅q0−ωz⋅q1−ωx⋅q2+ωy⋅q1+ωz⋅q0⎦⎥⎥⎤
最终的姿态角是用旋转矩阵,也就是用四元数解算出来的,而四元数依靠的唯一参数就是来自陀螺仪的三轴角速度数据,因此我们最终要补偿的是陀螺仪数据
补偿数据来自:补偿数据来自两个传感器测算同一数据的误差,不妨称它为标准值
,当补偿为0时,输出为陀螺仪积分数据,也就是完全置信于陀螺仪;当补偿为全部误差时,输出为加速度计数据,也就是完全置信于加速度计。补偿的比例即为置信于两个传感器的比例
“标准值”是什么数据:通常我们用机体系下的地理重力加速度方向向量作为上面所说的标准值
旋转矩阵中的一列
相关内容,直接拿出旋转矩阵的第三列,就是利用陀螺仪测算的机体系下地理重力加速度方向向量
(可以看做是积分计算得到的理论重力加速度分量)机体系下地理重力加速度方向向量
(可以看做是实际重力加速度分量)误差的计算:机体系下地理重力加速度方向向量
实质是一个三维向量,所谓误差,实质就是两个三维方向向量的夹角,求这个夹角的方法相信大家高中都用过,就是计算向量叉积
有 向 量 A , B , 夹 角 为 θ , 则 有向量A,B,夹角为\theta,则 有向量A,B,夹角为θ,则 A × B = ∣ A ∣ ⋅ ∣ B ∣ ⋅ s i n θ A\times B=|A|·|B|·sin\theta A×B=∣A∣⋅∣B∣⋅sinθ
补偿的方法:构造一个PI (比例-积分)控制器进行补偿,
G y r o _ c o m p e n s t a e = K p ⋅ e r r o r + K i ⋅ ∫ e r r o r Gyro\_compenstae=K_p·error+K_i·\int {error} Gyro_compenstae=Kp⋅error+Ki⋅∫error
虽然形式上完全一致,但最好不要把这个理解成自动控制的那种控制器。请这样理解:积分项用来消除静态误差,比例项用来控制传感器的可信度
小结一下加速度计和陀螺仪的互补融合:
匿名对互补融合滤波做了优化,进一步融合了磁力计的数据,下面类比加速度计的融合方法进行分析。
以下内容是IMU_update
完整函数,前半部分是互补滤波相关的,后半部分是一些校准标志处理什么的(这里不分析了)。带括号的是我补充的注释,没有的是匿名官方注释
//输入:acc是载体系a下的三轴加速度测量值(经过滤波和变换什么的),是加速度计直接反馈的(包含重力加速度成分,匀速时是重力加速度在机体下的投影)
// gyr: 陀螺仪数据(经过滤波和变换什么的),表示(载体系)相对地面系的三轴角速度
// mag_val:磁力计数据(没滤过波),表示地理系正比方向在机体系的三轴投影
// dT: 计算周期
// *state: imu的状态结构体
// *imu: imu相关数据,要向这里赋值
void IMU_update(float dT,_imu_state_st *state,float gyr[VEC_XYZ], s32 acc[VEC_XYZ],s16 mag_val[VEC_XYZ],_imu_st *imu)
{
// const float kp = 0.2f,ki = 0.001f;
// const float kmp = 0.1f;
//互补滤波系数
static float kp_use = 0; //(加速度计误差比例系数,越大越倾向加速度计)
static float ki_use = 0; //(加速度计误差积分积分系数)
static float mkp_use = 0; //(磁力计修正系数)
float acc_norm_l; //(加速度向量的模)
float acc_norm_l_recip; //(加速度向量的模的倒数)
float q_norm_l; //(四元数的模)
float acc_norm[VEC_XYZ]; //(单位化的加速度向量)
float d_angle[VEC_XYZ]; //(微元转角,即滤波修正后的角速度值)
//(旋转矩阵需要的一些四元数,先算好调用快一点)
// q0q0 = imu->w * imu->w;
q0q1 = imu->w * imu->x;
q0q2 = imu->w * imu->y;
q1q1 = imu->x * imu->x;
q1q3 = imu->x * imu->z;
q2q2 = imu->y * imu->y;
q2q3 = imu->y * imu->z;
q3q3 = imu->z * imu->z;
q1q2 = imu->x * imu->y;
q0q3 = imu->w * imu->z;
//(这里实际执行不到)
if(state->obs_en)
{
//计算机体坐标下的运动加速度观测量。坐标系为北西天
for(u8 i = 0;i<3;i++)
{
s32 temp = 0;
for(u8 j = 0;j<3;j++)
{
temp += imu->obs_acc_w[j] *att_matrix[j][i];//t[i][j] 转置为 t[j][i]
}
imu->obs_acc_a[i] = temp;
imu->gra_acc[i] = acc[i] - imu->obs_acc_a[i];
}
}
//(只执行这句)
else
{
for(u8 i = 0;i<3;i++)
{
imu->gra_acc[i] = acc[i]; //(把加速度计测量值传给结构体保存)
}
}
//(加速度向量模的倒数)
acc_norm_l_recip = my_sqrt_reciprocal(my_pow(imu->gra_acc[X]) + my_pow(imu->gra_acc[Y]) + my_pow(imu->gra_acc[Z]));
//(加速度向量模)
acc_norm_l = safe_div(1,acc_norm_l_recip,0);
// 加速度计的读数,单位化。
for(u8 i = 0;i<3;i++)
{
acc_norm[i] = imu->gra_acc[i] *acc_norm_l_recip;
}
//(att_matrix是从机体系a到地理系w的转换矩阵)
// 载体坐标下的(地理)x方向向量,单位化。 (地面系北方x [1 0 0]T转到机体系中,w到a系旋转矩阵第一列,可以看作地理x方向在机体系的三个分量)
att_matrix[0][0] = imu->x_vec[X] = 1 - (2*q2q2 + 2*q3q3);
att_matrix[0][1] = imu->x_vec[Y] = 2*q1q2 - 2*q0q3;
att_matrix[0][2] = imu->x_vec[Z] = 2*q1q3 + 2*q0q2;
// 载体坐标下的(地理)y方向向量,单位化。 (地面系西方y [0 1 0]T转到机体系中,w到a系旋转矩阵第二列,可以看作地理y方向在机体系的三个分量)
att_matrix[1][0] = imu->y_vec[X] = 2*q1q2 + 2*q0q3;
att_matrix[1][1] = imu->y_vec[Y] = 1 - (2*q1q1 + 2*q3q3);
att_matrix[1][2] = imu->y_vec[Z] = 2*q2q3 - 2*q0q1;
// 载体坐标下的(地理)z方向向量(等效重力向量、重力加速度向量),单位化。 (地面系天空z [0 0 1]T转到机体系中,w到a系旋转矩阵第三列,可以看作地理z方向在机体系的三个分量)
att_matrix[2][0] = imu->z_vec[X] = 2*q1q3 - 2*q0q2;
att_matrix[2][1] = imu->z_vec[Y] = 2*q2q3 + 2*q0q1;
att_matrix[2][2] = imu->z_vec[Z] = 1 - (2*q1q1 + 2*q2q2);
//水平面方向向量 (a到w系第一列XY,机体系x [1 0 0]T 转到地面系后的XY向量,也就是航向坐标系的机头指向)
float hx_vec_reci = my_sqrt_reciprocal(my_pow(att_matrix[0][0]) + my_pow(att_matrix[1][0]));
imu->hx_vec[X] = att_matrix[0][0] *hx_vec_reci;
imu->hx_vec[Y] = att_matrix[1][0] *hx_vec_reci;
//计算载体坐标下的运动加速度 (加速度传感器测量值去掉重力加速度成分,得到机体系中的a_acc,与姿态解算无关)
for(u8 i = 0;i<3;i++)
{
imu->a_acc[i] = (s32)(acc[i] - 981 *imu->z_vec[i]);
}
//计算世界坐标下的运动加速度。坐标系为北西天 (利用旋转矩阵变换a_acc,得到地理系中的加速度值w_acc)
for(u8 i = 0;i<3;i++)
{
s32 temp = 0;
for(u8 j = 0;j<3;j++)
{
temp += imu->a_acc[j] *att_matrix[i][j];
}
imu->w_acc[i] = temp;
}
//(再从地理系转换到航向系,得到航向系中加速度值h_acc)
w2h_2d_trans(imu->w_acc,imu_data.hx_vec,imu->h_acc);
// 测量值与等效重力向量的叉积(计算向量误差)。
/*(这里是把加速度计测量的三轴向量,和载体坐标下的(地理)z方向向量imu->z_vec做叉乘,得到和二者垂直的新向量,
acc_norm表示的重力方向来自加速度计,imu->z_vec表示的重力方向来自陀螺仪,这里计算向量外积实质上是在算二者夹角,准备进行融合滤波) */
vec_err[X] = (acc_norm[Y] * imu->z_vec[Z] - imu->z_vec[Y] * acc_norm[Z]);
vec_err[Y] = -(acc_norm[X] * imu->z_vec[Z] - imu->z_vec[X] * acc_norm[Z]);
vec_err[Z] = -(acc_norm[Y] * imu->z_vec[X] - imu->z_vec[Y] * acc_norm[X]);
//(进行磁力计互补融合滤波)
#ifdef USE_MAG
//电子罗盘赋值为float矢量
for(u8 i = 0;i<3;i++)
{
mag_val_f[i] = (float)mag_val[i];
}
if(!(mag_val[X] ==0 && mag_val[Y] == 0 && mag_val[Z] == 0))
{
//把载体坐标下的罗盘数据转换到地理坐标下
a2w_3d_trans(mag_val_f,imu->w_mag);
//计算方向向量归一化系数(模的倒数)
float l_re_tmp = my_sqrt_reciprocal(my_pow(imu->w_mag[0]) + my_pow(imu->w_mag[1]));
//计算南北朝向向量
mag_2d_w_vec[1][0] = imu->w_mag[0] *l_re_tmp;
mag_2d_w_vec[1][1] = imu->w_mag[1] *l_re_tmp;
//计算南北朝向误差(叉乘),地理坐标中,水平面磁场方向向量应恒为南北 (1,0)
mag_yaw_err = vec_2_cross_product(mag_2d_w_vec[1],mag_2d_w_vec[0]);
//计算南北朝向向量点乘,判断同向或反向
mag_err_dot_prudoct = vec_2_dot_product(mag_2d_w_vec[1],mag_2d_w_vec[0]);
//若反向,直接给最大误差
if(mag_err_dot_prudoct<0)
{
mag_yaw_err = my_sign(mag_yaw_err) *1.0f;
}
//
}
#endif
for(u8 i = 0;i<3;i++)
{
//(这个没用)
#ifdef USE_EST_DEADZONE
if(state->G_reset == 0 && state->obs_en == 0)
{
vec_err[i] = my_deadzone(vec_err[i],0,imu->gacc_deadzone[i]);
}
#endif
//(用了这个,这里是利用加速度模长判断,如果水平方向加速度太大,就不能直接把加速度数据看作重力加速度数据,这一轮计算的偏差清零不补偿了)
#ifdef USE_LENGTH_LIM
if(acc_norm_l>1060 || acc_norm_l<900)
{
vec_err[X] = vec_err[Y] = vec_err[Z] = 0;
}
#endif
//误差积分 (用于互补滤波的pi控制器)
vec_err_i[i] += LIMIT(vec_err[i],-0.1f,0.1f) *dT *ki_use;
// 构造增量旋转(含融合纠正)。
// d_angle[X] = (gyr[X] + (vec_err[X] + vec_err_i[X]) * kp_use - mag_yaw_err *imu->z_vec[X] *kmp_use *RAD_PER_DEG) * dT / 2 ;
// d_angle[Y] = (gyr[Y] + (vec_err[Y] + vec_err_i[Y]) * kp_use - mag_yaw_err *imu->z_vec[Y] *kmp_use *RAD_PER_DEG) * dT / 2 ;
// d_angle[Z] = (gyr[Z] + (vec_err[Z] + vec_err_i[Z]) * kp_use - mag_yaw_err *imu->z_vec[Z] *kmp_use *RAD_PER_DEG) * dT / 2 ;
#ifdef USE_MAG
//(用的这个,在普通互补融合滤波基础上增加了磁力计补偿)
d_angle[i] = (gyr[i] + (vec_err[i] + vec_err_i[i]) * kp_use + mag_yaw_err *imu->z_vec[i] *mkp_use) * dT / 2 ;
#else
//(这个没用,普通的互补融合滤波,用加速度计pi控制器输出补偿陀螺仪)
d_angle[i] = (gyr[i] + (vec_err[i] + vec_err_i[i]) * kp_use ) * dT / 2 ;
#endif
}
// 计算姿态(一节龙格库塔法,wxyz初值为1000)
imu->w = imu->w - imu->x*d_angle[X] - imu->y*d_angle[Y] - imu->z*d_angle[Z];
imu->x = imu->w*d_angle[X] + imu->x + imu->y*d_angle[Z] - imu->z*d_angle[Y];
imu->y = imu->w*d_angle[Y] - imu->x*d_angle[Z] + imu->y + imu->z*d_angle[X];
imu->z = imu->w*d_angle[Z] + imu->x*d_angle[Y] - imu->y*d_angle[X] + imu->z;
q_norm_l = my_sqrt_reciprocal(imu->w*imu->w + imu->x*imu->x + imu->y*imu->y + imu->z*imu->z);
imu->w *= q_norm_l;
imu->x *= q_norm_l;
imu->y *= q_norm_l;
imu->z *= q_norm_l;
/修正开关(以下内容不是姿态解算不分析了)
#ifdef USE_MAG
if(state->M_fix_en==0)//磁力
{
mkp_use = 0;//不修正
state->M_reset = 0;//罗盘修正不复位,清除复位标记
}
else
{
if(state->M_reset)//
{
//通过增量进行对准
mkp_use = 10.0f;
if(mag_yaw_err != 0 && ABS(mag_yaw_err)<0.02f)
{
state->M_reset = 0;//误差小于2的时候,清除复位标记
}
}
else
{
mkp_use = state->mkp; //正常修正
}
}
#endif
if(state->G_fix_en==0)//重力方向修正
{
kp_use = 0;//不修正
}
else
{
if(state->G_reset == 0)//正常修正
{
kp_use = state->gkp;
ki_use = state->gki;
}
else//快速修正,通过增量进行对准
{
kp_use = 10.0f;
ki_use = 0.0f;
// imu->est_speed_w[X] = imu->est_speed_w[Y] = 0;
// imu->est_acc_w[X] = imu->est_acc_w[Y] = 0;
// imu->est_acc_h[X] = imu->est_acc_h[Y] =0;
//计算静态误差是否缩小
// imu_reset_val += (ABS(vec_err[X]) + ABS(vec_err[Y])) *1000 *dT;
// imu_reset_val -= 0.01f;
imu_reset_val = (ABS(vec_err[X]) + ABS(vec_err[Y]));
imu_reset_val = LIMIT(imu_reset_val,0,1.0f);
if((imu_reset_val < 0.05f) && (state->M_reset == 0))
{
//计时
reset_cnt += 2;
if(reset_cnt>400)
{
reset_cnt = 0;
state->G_reset = 0;//已经对准,清除复位标记
}
}
else
{
reset_cnt = 0;
}
}
}
}
终于到最后了,这是imu.c
中最后一个函数,利用旋转矩阵反解欧拉角,不多解释了,注意避免一下机头垂直向上时的特殊情况就行了(好像是处理陀螺仪死锁问题)
static float t_temp;
void calculate_RPY()
{
///输出姿态角///
t_temp = LIMIT(1 - my_pow(att_matrix[2][0]),0,1);
//imu_data.pit = asin(2*q1q3 - 2*q0q2)*57.30f;
if(ABS(imu_data.z_vec[Z])>0.05f)//避免奇点的运算
{
imu_data.pit = fast_atan2(att_matrix[2][0],my_sqrt(t_temp))*57.30f;
imu_data.rol = fast_atan2(att_matrix[2][1], att_matrix[2][2])*57.30f;
imu_data.yaw = -fast_atan2(att_matrix[1][0], att_matrix[0][0])*57.30f;
}
}
mag_2d_w_vec[1][0]
和 mag_2d_w_vec[1][1]
的含义static float mag_2d_w_vec[2][2] = { {1,0},{1,0}};
,mag_2d_w_vec[0]
就是地理系里的实际正北方向。叉乘那句,就是磁力计测出的南北方向和正北方向的叉乘,来算误差的。这里是利用加速度模长判断,如果水平方向加速度太大,就不能直接把加速度数据看作重力加速度数据
中, 代码是通过加速度的模长超出[900,1060]这个区间才舍掉加速度数据的, 并不是根据’水平方向的加速度太大’而舍弃掉加速度数据的. 不知道我的理解是否正确?acc_norm
的3个分量中包含了飞机本身的加速度和重力加速度分量, 所以应该是’要避免飞机本身在载体系3个方向的加速度太大’ 吧? 因为飞机毕竟会有横滚和俯仰的飞行姿态并同时加速啊.a_acc
, w_acc
, h_acc
并没有在后续代码中进一步用到. 这三个量在没被你截取的代码中被用到了么?//位置速度环代码节选
LPF_1_(5.0f,dT_ms*1e-3f,imu_data.h_acc[X],vel_fb_d_lpf[X]);
LPF_1_(5.0f,dT_ms*1e-3f,imu_data.h_acc[Y],vel_fb_d_lpf[Y]);
//位置_速度环反馈=上次检测的光流速度+20ms*当前加速度=当前理论速度(20ms为Loc_1level_Ctrl的执行周期)
loc_ctrl_1.fb[X] = OF_DX2 + 0.02f *vel_fb_d_lpf[X];
loc_ctrl_1.fb[Y] = OF_DY2 + 0.02f *vel_fb_d_lpf[Y];
calculate_RPY()
中的第8,12,14行, 等式右侧的符号是不是搞反了? 是否应该为: imu_data.pit = -asin; imu_data.pit =- fast_atan2; imu_data.yaw =+fast_atan2
fast_atan()
函数原型REAL fast_atan2(REAL y, REAL x)
,参数可以看成一个点的坐标(写形参(sin(a),cos(a))可以解出a的弧度值)。3.(1)公式中绕三个轴的转角,正方向是按右手螺旋定则确定的,这和匿名规定的欧拉角正方向不同,匿名规定正方向为:pit-仰角;roll-右倾;yaw-右转
。以俯仰角为例,此这是绕Y轴的转角θ,对应的欧拉角为pit,pit=-θ,带入参数atan(-sin(θ),cos(θ))
,解出的是-θ;其他两个轴同理