vSLAMNet(五)-姿态估计-Madgwick算法

1. 概述

  • Madgwick S. An efficient orientation filter for inertial and inertial/magnetic sensor arrays[J]. Report x-io and University of Bristol (UK), 2010, 25: 113-118.
  • Madgwick S O H, Harrison A J L, Vaidyanathan R. Estimation of IMU and MARG orientation using a gradient descent algorithm[C]//2011 IEEE international conference on rehabilitation robotics. IEEE, 2011: 1-7.
  • 位姿估计
  • 传感器
    • 陀螺仪 + 加速度计
    • 陀螺仪 + 加速度计 + 磁力计
  • 优点
    • 计算量低,适用于轻量级设备
    • 适用于低采样频率
    • 仅有2个需要调的参数
  • 核心思想
    • 首先,通过陀螺仪的角速度初步计算传感器方向偏移$^{E}_{S}q_{w,t}$;然后,通过对重力加速度和地磁这两个恒定量在传感器坐标系下的投影计算传感器方向偏移$^{E}_{S}q_{\nabla,t}$;最后通过互补滤波器融合$^{E}_{S}q_{w,t}$和$^{E}_{S}q_{\nabla,t}$来得到一个更加可信的结果。除此之外还需要校正地磁计数据畸变和陀螺仪零点漂移。

2. 算法详解

2.1 陀螺仪姿态解算

  • 主要思想

    • 建立四元数和陀螺仪读数之间的微分方程
    • 欧拉中值积分
  • 坐标系定义

    • 世界坐标系E
    • 传感器坐标系S
    • $^{S}_{E}q$表示坐标系E相对于坐标系S的单位旋转四元数
  • 陀螺仪数据输出
    • $^{S}w = [0 w_x w_y w_z]$
  • 地面参考系(Earth)相对于传感器参考系(Sensor)的旋转可以由其四元数的导数对时间的积分获得,$^{S}_{E}q_{w,t} = ^{S}_{E}\hat{q}_{est.t-1} + ^{S}_{E}q_{w,t}\Delta t$,其中$^{S}_{E}\dot{q} = \frac{1}{2}^{S}_{E}\hat{q} \otimes ^{S}w$,$^{S}_{E}\dot{q}$表示姿态四元数变化的速度。该公式中的角速度可以直接从传感器读出,上一时刻的位姿也是已知,时间间隔也可以从传感器芯片中的获得。可以轻松的计算出结果,下面推导下该公式

已知t时刻的四元数$^{S}_{E}q_{t-1}$和角速度$^{S}w_{t-1}$、t时刻的加速度$^{S}w_t$,系统采样间隔为$\Delta t$,求t时刻的四元数$^{S}_{E}q_{t}$。

基于常微分方程,套用中值积分公式,得到$^{S}_{E}q_t = ^{S}_{E}q_{t-1} + \frac{\Delta t}{2}(K_1 + K_2)$,其中,$K_1 = \frac{1}{2}^{S}_{E}q_{t-1}\otimes ^{S}w_{t-1}$和$K_2 = \frac{1}{2}(^{S}_{E}q_{t-1} + \Delta tK_1)\otimes^{S}w_t$。

此处,近似估计物体四元数在t-1到t时刻之间的平均变化速度$^{S}_{E}\dot{q}_{w,t-1,t}$。在实际过程中,往往并不能得到t-1时刻的准确的四元数,二是一个最优的估计值$^{S}_{E}\hat{q}_{est,t-1}$。

我们得到$^{S}_{E}\dot{q}_{w,t-1,t} = \frac{1}{2}(K_1 + K_2) = \frac{1}{2}(\frac{1}{2}^{S}_{E}\hat{q}_{est,t-1}\otimes ^{S}w_{t-1} + \frac{1}{2}(^{S}_{E}\hat{q}_{est,t-1} + \Delta t(\frac{1}{2}^{S}_{E}\hat{q}_{est,t-1}\otimes ^{S}w_{t-1})) \otimes ^{S}w_{t})$

$^{S}_{E}q_{w,t} = ^{S}_{E}\hat{q}_{est,t-1} + ^{S}_{E}\dot{q}_{w,t-1,t}\Delta t$

从而得到,

$^{S}_{E}q_{w,t} = ^{S}_{E}\hat{q}_{est,t-1} + \frac{\Delta}{2}(\frac{1}{2}^{S}_{E}\hat{q}_{est,t-1}\otimes ^{S}w_{t-1} + \frac{1}{2}(^{S}_{E}\hat{q}_{est,t-1} + \Delta t(\frac{1}{2}^{S}_{E}\hat{q}_{est,t-1}\otimes ^{S}w_{t-1})) \otimes ^{S}w_{t})$

以上迭代公式是计算精度要求较高时采用的。而在实际工程中,不要求如此的高精度而追求计算速度时,可采用以下的近似迭代公式。亦是参考Madgwick算法详细解读

在t-1时刻到t时刻之间的变化速度$^S_E\dot{q}_{w,t-1,t}$可近似为,

$^S_E\dot{q}_{w,t-1,t} = \frac{1}{2}^S_E\hat{q}_{est,t-1}\otimes ^Sw_t$,

所以,$^S_Eq_{w,t} = ^S_E\hat{q}_{est,t-1} + ^S_E\dot{q}_{w,t-1,t}\Delta t = ^S_E\hat{q}_{est,t-1} + (\frac{1}{2}^S_E\hat{q}_{est,t-1}\otimes ^Sw_t)\Delta t$.

2.2 加速度计和磁力计姿态解算

  • 传感器介绍
    • 加速度计
      • 测量重力和外界施加给物体的力产生的加速度的和
    • 磁力计
      • 测量地球磁场和外界地磁干扰在传感器坐标系下的磁通量
  • 假设
    • 该算法假设加速度计和磁力计分别仅仅观测重力和地球磁场
  • 目标
    • 利用加速度计和磁力计求解姿态$^{S}_{E}q_{\bigtriangledown, t}$

以上的推导参考Madgwick算法详细解读,推导思路同论文一致,只是有些符号定义不同。此外,论文中首先分别对加速度计读数和磁力计读数进行推导,然后组合在一起。以下的推导直接将加速度计读数和磁力计读数组合在一起。

当物体静止朝北时,加速度计理论输出为$^{E}\hat{g} = [0\ 0\ 0\ 1]$,磁力计理论输出为$^E\hat{b} = [0\ b_x\ 0\ b_z]$,表示为实部为0的四元数。进一步地,我们得到向量$^E\hat{y} = [0\ 0\ 1\ b_x\ 0\ b_z]$。

当物体运动后,在t时刻,我们得到新的加速度计和磁力计的向量分别为$^S\hat{a}_t = [0\ a_x\ a_y\ a_z]$和$^S\hat{m}_t = [0\ m_x\ m_y\ m_z]$。进一步地,我们得到新的向量$^S\hat{y}_t = [a_x\ a_y\ a_z\ m_x\ m_y\ m_z]$。

设t时刻物体的姿态为$^{S}_{E}q_t$,那么根据四元数旋转可得到,

$^S\hat{a}_t = ^{S}_{E}q_t \otimes ^E\hat{g} \otimes ^{S}_{E}q_t^*$,

$^S\hat{m}_t = ^{S}_{E}q_t \otimes ^E\hat{b} \otimes ^{S}_{E}q_t^*$。

利用旋转矩阵表示为,

$^S\hat{a}_t^T = R_t ^E\hat{g}^T$,

$^S\hat{m}_t^T = R_t ^E\hat{b}^T$,

将$R_t$组合为新的矩阵$M_t$,

$M_t = \left[
\begin{matrix}
R_t & 0 \\
0 & R_t
\end{matrix}
\right]$,

那么,我们得到,

$^S\hat{y}_t^T = M_t^E\hat{y}^T$.

上式中,$^S\hat{y}_t$和$^E\hat{y}$是已知的,可以求出$^{S}_{E}q_t$,即估计值$^{S}_{E}\hat{q}_t = [q_0\ q_1\ q_2\ q_3]^T$,由此转换成$\hat{M}_t$,使得误差平方和$F(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)$最小,得到如下优化方程,

$\min_{^{S}_{E}\hat{q}_t\in R^4}F(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)$,

其中,$F(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t) = ||f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)||^2 = (\hat{M}_t\ ^E\hat{y}^T - ^S\hat{y}_t^T)^T(\hat{M}_t\ ^E\hat{y}^T - ^S\hat{y}_t^T)$。

函数$f(\cdot)$回一个多元向量函数,采用数值解法,例如梯度下降法、牛顿法等。论文中采用梯度下降法。

设$^{S}_{E}\hat{q}_t$的初值为$^{S}_{E}\hat{q}_{t,k}$,为4行1列矩阵,那么误差平方和为$F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t$。假设初值四元数需要修正的量为$\bigtriangledown f(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)$,那么修正后的误差平方和为$F(^{S}_{E}\hat{q}_{t,k} + \bigtriangledown f, ^E\hat{y}, ^S\hat{y}_t)$。

由泰勒公式展开得到,

$F(^{S}_{E}\hat{q}_{t,k} + \bigtriangledown f, ^E\hat{y}, ^S\hat{y}_t) = F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t) + \frac{\partial F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q}\bigtriangledown f + O(||\bigtriangledown f||^2) \approx F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t) + \frac{\partial F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q}\bigtriangledown f$,

其中,$\bigtriangledown f$为4行1列矩阵,$\frac{\partial F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q}$为1行4列矩阵,得到,

$\frac{\partial F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q} = [\frac{\partial F}{\partial q_0}\ \frac{\partial F}{\partial q_1}\ \frac{\partial F}{\partial q_2}\ \frac{\partial F}{\partial q_3}]$,

那么,梯度为,

$\lim_{\bigtriangledown f\rightarrow 0}\frac{F(^{S}_{E}\hat{q}_{t,k} + \bigtriangledown f, ^E\hat{y}, ^S\hat{y}_t) - F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{||\bigtriangledown f||} = \frac{\partial F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q}\frac{\bigtriangledown f}{||\bigtriangledown f||} = ||\frac{\partial F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q}||\cos \theta$,

其中,$\theta$为向量$(\frac{\partial F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q})^T$与单位向量$\frac{\bigtriangledown f}{||\bigtriangledown f||}$的夹角。当$\theta = \pi$时,误差平方和下降最快。因此,我们得到,

$\bigtriangledown f = -\rho (\frac{\partial F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q})^T$,

其中,$\rho$为一个大于0的比例系数。上式中的$F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)$关于四元数q的偏导计算为,

$\frac{\partial F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q} = \frac{\partial (f^T(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t))}{\partial q}$

$= [\frac{\partial (f_{11}^2 + f_{21}^2 + \cdots + f_{61}^2)}{\partial q_0}\ \frac{\partial (f_{11}^2 + f_{21}^2 + \cdots + f_{61}^2)}{\partial q_1}\ \frac{\partial (f_{11}^2 + f_{21}^2 + \cdots + f_{61}^2)}{\partial q_2}\ \frac{\partial (f_{11}^2 + f_{21}^2 + \cdots + f_{61}^2)}{\partial q_3}]$

$= [2f_{11}\frac{\partial f_{11}}{\partial q_0} + \cdots + 2f_{61}\frac{\partial f_{61}}{\partial q_0}\ 2f_{11}\frac{\partial f_{11}}{\partial q_1} + \cdots + 2f_{61}\frac{\partial f_{61}}{\partial q_1}\ 2f_{11}\frac{\partial f_{11}}{\partial q_2} + \cdots + 2f_{61}\frac{\partial f_{61}}{\partial q_2}\ 2f_{11}\frac{\partial f_{11}}{\partial q_3} + \cdots + 2f_{61}\frac{\partial f_{61}}{\partial q_3}]$

$= 2f^T(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)\frac{\partial f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)}{\partial q}$,

其中,$f_{ij}$表示$f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)$中第i行第j列元素。$f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)$的表达式为,

$f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t) = \hat{M}_t\ ^E\hat{y}^T - ^S\hat{y}_t^T$

$= \left[
\begin{matrix}
2(q_1q_3 - q_0q_2) - a_x \\
2(q_0q_1 + q_2q_3) - a_y \\
2(\frac{1}{2} - q_1^2 - q_2^2) \\
2b_x(\frac{1}{2} - q_2^2 - q_3^2) + 2b_z(q_1q_3 - q_0q_2) - m_x \\
2b_x(q_1q_2 - q_0q_3) + 2b_z(q_0q_1 + q_1q_3) - m_y \\
2b_x(q_0q_2 + q_1q_3) + 2b_z(\frac{1}{2} - q_1^2 - q_2^2) - m_z
\end{matrix}
\right]$.

而$\frac{\partial f(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q}$是$f(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)$关于四元数的偏导,其雅可比矩阵为,

$\frac{\partial F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q} = J(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t))$

$ = [\frac{\partial f(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q_0}\ \frac{\partial f(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q_1}\ \frac{\partial f(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q_2}\ \frac{\partial f(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)}{\partial q_3}]$

$= \left[
\begin{matrix}
-2q_2 & 2q_3 & -2q_0 & 2q_1 \\
2q_1 & 2q_0 & 2q_3 & 2q_2 \\
0 & -4q_1 & -4q_2 & 0 \\
-2b_zq_2 & 2b_zq_3 & -4b_xq_2 - 2b_zq_0 & -4b_xq_3 + 2b_zq_1 \\
-2b_xq_3 + 2b_zq_1 & 2b_xq_2 + 2b_zq_0 & 2b_xq_1 + 2b_zq_3 & -2b_xq_0 + 2b_zq_2 \\
2b_xq_2 & 2b_xq_3 - 4b_zq_1 & 2b_xq_0 - 4b_zq_2 & 2b_xq_1
\end{matrix}
\right]$,

因此,$\bigtriangledown f$的计算为,

$\bigtriangledown f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t) = -rho(2f^T(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)J(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t))^T$

$= -2\rho J^T(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)$.

进行归一化,除以其范数,得到梯度方向为,

$\frac{\bigtriangledown f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)}{||\bigtriangledown f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)||} = \frac{-2\rho J^T(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)}{||-2\rho J^T(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)||}$

$= \frac{-J^T(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)}{||-J^T(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)||}$,

然后再乘以步长$\mu_t$就得到各个变量要改变的值。因此,各个自变量现有的值加上要修正的量,得到新的$^S_E\hat{q}_t$的估计为,

$^S_E\hat{q}_{t,k} = ^S_E\hat{q}_{t,k-1} + \frac{\bigtriangledown f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)}{||\bigtriangledown f(^{S}_{E}\hat{q}_t, ^E\hat{y}, ^S\hat{y}_t)||}\mu_t$,

其中,$k = 1, 2, \cdots, n$.

如此迭代下去,用上一时刻的姿态$^S_E\hat{q}_{est,t-1}$作为初值$^S_E\hat{q}_{t,0}$,即已知上一次的估计$^S_E\hat{q}_{t,k}$,代入上述公式中,进行多次迭代,直到$F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t)$小于某一阈值,或者经过多次迭代,直到满足$F(^{S}_{E}\hat{q}_{t,k}, ^E\hat{y}, ^S\hat{y}_t) - F(^{S}_{E}\hat{q}_{t,k-1}, ^E\hat{y}, ^S\hat{y}_t) \geq 0$,此时,$^S_E\hat{q}_{t,k}$为$^S_E\hat{q}_{t}$的最佳估计值。

以上迭代公式是计算精度要求较高时采用的。而在实际工程中,不要求如此的高精度而追求计算速度时,可采用以下的近似迭代公式。亦是参考Madgwick算法详细解读

梯度下降法的迭代过程,也可以只用一步来简化,即认为一步就可以近似达到最佳估计值。那就是要设置步长$\mu_t$,使得迭代一次就能够接近最佳估计值,

$\mu_t = \eta||^S_E\dot{q}_{w,t-1,t}||\Delta t$,其中,$\eta \geq 1$,是一个根据实际情况调节的量,用来弥补加速度计和磁力计的测量误差。

因此,简化后的$^S_Eq_{\bigtriangledown, t}$为,

$^S_Eq_{\bigtriangledown, t} = ^S_E\hat{q}_{est,t-1} - \mu_t\frac{\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)}{||\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)||}$

$= ^S_E\hat{q}_{est,t-1} - (\eta||^S_E\dot{q}_{w,t-1,t}||\Delta t)\frac{\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)}{||\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)||}$

$^S_E\hat{q}_{est,t-1} - (\eta||\frac{1}{2}^S_E\hat{q}_{est,t-1}\otimes ^Sw_t||\Delta t)\frac{\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)}{||\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)||}$

2.3 加权融合

互补滤波的方法将两个部分的加权平均,

$^S_Eq_{est,t} = \alpha_1\ ^S_Eq_{w,t} + \alpha_2\ ^S_Eq_{\bigtriangledown,t}$,

其中,$\alpha_1 + \alpha_2 = 1$,$0 \leq \alpha_1 \leq 1$,$0 \leq \alpha_2 \leq 1$。$\alpha_1$和$\alpha_2$是加权系数,它们是由各自的误差占总体误差的比重所决定的,误差所占的比重越小则加权系数越大。

设采样间隔为$\Delta t$,陀螺仪的单位误差$\beta$可以通过查手册得到,那么陀螺仪的误差为$\beta \Delta t$。而加速度计磁场计共同算出的姿态的误差是由计算方法决定的,计算方法如梯度下降法、高斯牛顿迭代法、牛顿法、共轭梯度法等,由于采用的方法是梯度下降法,所以其误差为梯度下降法中所选取的步长$\mu_t$,步长越长则其计算结果的误差越大。所以,总体误差为$\beta \Delta t + \mu_t$。

$\alpha_1$是陀螺仪的姿态加权系数,$\alpha_1 = 1 - \frac{\beta \Delta t}{\beta \Delta t + \mu_t}$。

$\alpha_2$是加速度计和磁力计的姿态加权系数,$\alpha_2 = \alpha_1 = 1 - \frac{\mu_t}{\beta \Delta t + \mu_t}$。

所以,

$^S_Eq_{est,t} = \alpha_1\ ^S_Eq_{w,t} + \alpha_2\ ^S_Eq_{\bigtriangledown,t}$

$= (1-\frac{\beta\Delta t}{\beta\Delta t + \mu_t})(^S_E\hat{q}_{est,t-1} + ^S_E\dot{q}_{w,t-1,t}\Delta t) + (1-\frac{\mu_t}{\beta\Delta t + \mu_t})(^S_E\hat{q}_{est,t-1} - \mu_t\frac{\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)}{||\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)||})$

$= ^S_E\hat{q}_{est,t-1} + \frac{\mu_t}{\beta\Delta t + \mu_t}^S_E\dot{q}_{w,t-1,t}\Delta t - \frac{\beta\Delta t}{\beta\Delta t + \mu_t}\mu_t\frac{\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)}{||\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)||}$

$= ^S_E\hat{q}_{est,t-1} + \frac{\eta||^S_E\dot{q}_{w,t-1,t}||\Delta t}{\beta\Delta t + \eta||^S_E\dot{q}_{w,t-1,t}||\Delta t}^S_E\dot{q}_{w,t-1,t}\Delta t - \frac{\beta\Delta t}{\beta\Delta t + \eta||^S_E\dot{q}_{w,t-1,t}||}\eta||^S_E\dot{q}_{w,t-1,t}||\Delta t\frac{\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)}{||\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)||}$

$= ^S_E\hat{q}_{est,t-1}+ \frac{\eta||^S_E\hat{q}_{est,t-1}||}{\beta + \eta||^S_E\hat{q}_{est,t-1}||}(\frac{1}{2}^S_E\hat{q}_{est,t-1}\otimes ^Sw_t)\Delta t - \frac{\beta}{\beta + \eta||^S_E\hat{q}_{est,t-1}||}\eta||\frac{1}{2}^S_E\hat{q}_{est,t-1}\otimes ^Sw_t||\Delta t\frac{\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)}{||\bigtriangledown f(^{S}_{E}\hat{q}_{est,t-1}, ^E\hat{y}, ^S\hat{y}_t)||}$.

2.4 系统流程图

image-20191208161930373

2.5 磁力计校准

  • 磁力计易受外界地磁干扰,导致地磁测量数据误差较大
  • 磁力计本身也具有误差,例如漂移、制造误差等
  • 如果使用磁力计估计姿态,为了获取精确地姿态,需要对磁力计进行校准

  • 主要思想

    • 利用加速度计来校准磁力计
    • 因为加速度计不收外界干扰,仅有其自身传感器相关误差
    • 采用最优化算法来估计最优姿态,继而校准磁力计

根据论文和参考Madgwick算法详细解读,磁力计校准过程描述如下。

在传感器刚开始运行的时候,即第一帧的时候,传感器可能处于任意一种姿态,几乎不会是水平静止朝北的,所以磁场计的输出$^S\hat{m}_0$几乎不会是$^E\hat{b}_0$。所以,$^E\hat{b}_0$是未知的。此时,可利用加速度计来估计姿态,

$^S\hat{a}_0 = ^S_Eq_0 \otimes ^E\hat{g} \otimes ^S_Eq^*_0$,

用旋转矩阵R表示为,

$^S\hat{a}_0^T = R ^E\hat{g}$.

在上式中,$^S\hat{a}_0$和$^E\hat{g}$是已知的,所以可以由上式再反过来去求出第一帧的姿态$^S_Eq_0$,转换成R,使得f最小,

$\epsilon = R\ ^E\hat{g}^T - ^S\hat{a}^T_0$,

$f = \epsilon^T\epsilon = (R\ ^E\hat{g}^T - ^S\hat{a}_0^T)^T(R\ ^E\hat{g}^T - ^S\hat{a}_0^T)$.

用高斯牛顿迭代法来寻找这个最佳的四元数,其雅克比矩阵为,

$J = [\frac{\partial \epsilon}{\partial q_0}\ \frac{\partial \epsilon}{\partial q_1}\ \frac{\partial \epsilon}{\partial q_2}\ \frac{\partial \epsilon}{\partial q_3}]$.

假设当前四元数各个元素的误差为4行1列的矩阵x,那么$Jx = \epsilon$。采用最小二乘法计算x,

$x = (J^TJ)^{-1}\epsilon$.

所以,现有的四元数的值减去误差,得到新的四元数,

$,^S_E\hat{q}_{0,k+1} = ^S_E\hat{q}_{0,k} - x = ^S_E\hat{q}_{0,k+1} - (J^TJ)^{-1}J^T\epsilon$

其中,$^S_E\hat{q}_{0}$的初始值可以设为$[1\ 0\ 0\ 0]$。

重复上述公式,迭代多次,直到f达到最小值。

于是就得到加速度计估计出的第一帧的姿态$^S_E\hat{q}_{0}$。

根据四元数的坐标系旋转性质,可以把坐标系转到水平的位置上,但并不能保证朝北。对于向量来讲,坐标系逆着四元数转回去,就相当于是向量顺着四元数继续转,得到在这个水平坐标系中的磁场的向量$^E\hat{h}_0$。

其中,$h_x$和$h_y$是$b_x$在这个坐标系中的x轴和y轴上的分量,得到$^E\hat{b}_0 = [0\ \sqrt{h_x^2 + h_y^2}\ 0\ h_z]$.

以上是第一帧的时候得到$^E\hat{b}_0$的方法。

再将加速度计估计出来的姿态$^S_E\hat{q}_{0}$作为初值,将$^E\hat{g}$、$^S\hat{a}_0$、$^E\hat{b}_0$、$^S\hat{m}_0$代入之前公式中,用梯度下降法迭代,得到高精度的第一帧的姿态$^S_E\hat{q}_{0}$。

当物体发生运动之后,由于周围环境的影响,每一帧都要对$^E\hat{b}_t$进行修正,假设上一针的最佳的姿态估计为$^S_R\hat{q}_{est,t-1}$,那么这一帧的$^E\hat{b}_t$为

$^E\hat{h}_t = [0\ h_x\ h_y\ h_z] = ^S_R\hat{q}_{est,t-1} \otimes ^S\hat{m}_t \otimes ^S_R\hat{q}_{est,t-1}^*$,

$^E\hat{b}_t = [0\ \sqrt{h_x^2 + h_y^2}\ 0\ h_z]$.

这样的校准计算可以将外界的电磁干扰影响约束在对传感器指向的方向估计中。而且不需要预先给定地磁方向。

2.6 陀螺仪零点漂移校准

  • 陀螺仪的零飘受温度和运动等因素的影响
  • 本文提出陀螺仪的零飘可通过姿态变化率来进行弥补

修正方法如下:

$^S_E\dot{\hat{q}}_{\epsilon}$代表了陀螺仪在各个轴的角误差,使用前文提到的$^S_E\dot{q}_{w,t} = \frac{1}{2}^S_E\hat{q}_{est,t-1}\otimes ^Sw_t$公式的逆运算可以得出陀螺仪当前度数的可能偏差为$^Sw_{\epsilon, t} = 2^S_E\hat{q}^*_{est,t-1}\otimes ^S_E\dot{\hat{q}}_{\epsilon,t}$。

那么,陀螺仪随着时间的漂移量为$^Sw_{b,t} = \zeta\sum\ ^Sw_{\epsilon,t}\Delta t$。

最后,得到校正后的陀螺仪读数为$^Sw_{\epsilon,t} = ^Sw_{t} - ^Sw_{b,t}$。

2.7 算法的整体流程

image-20191208205128064

3. 实验评测

3.1 实验设置

  • 传感器
    • xsens MTx orientation sensor
    • tri-axis gyroscopes, accelerometers and magnetometers
    • 512 Hz
  • 对比算法
    • Kalman-based orientation filter
  • 真值
    • A Vicon system

3.2 实验结果

image-20191208205742715

image-20191208205816720

image-20191208205959230

image-20191208210044291

5. 代码实现

5.1 论文源码

  • 加速度计和陀螺仪
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <math.h>

// System constants
#define deltat 0.001f // sampling period in seconds (shown as 1 ms)
#define gyroMeasError 3.14159265358979f * (5.0f / 180.0f) // gyroscope measurement error in rad/s (shown as 5 deg/s)
#define beta sqrt(3.0f / 4.0f) * gyroMeasError // compute beta

// Global system variables
float a_x, a_y, a_z; // accelerometer measurements
float w_x, w_y, w_z; // gyroscope measurements in rad/s
float SEq_1 = 1.0f, SEq_2 = 0.0f, SEq_3 = 0.0f, SEq_4 = 0.0f; // estimated orientation quaternion elements with initial conditions

void filterUpdate(float w_x, float w_y, float w_z, float a_x, float a_y, float a_z) {
// Local system variables
float norm; // vector norm
float SEqDot_omega_1, SEqDot_omega_2, SEqDot_omega_3, SEqDot_omega_4; // quaternion derrivative from gyroscopes elements
float f_1, f_2, f_3; // objective function elements
float J_11or24, J_12or23, J_13or22, J_14or21, J_32, J_33; // objective function Jacobian elements
float SEqHatDot_1, SEqHatDot_2, SEqHatDot_3, SEqHatDot_4; // estimated direction of the gyroscope error
// Axulirary variables to avoid reapeated calcualtions
float halfSEq_1 = 0.5f * SEq_1;
float halfSEq_2 = 0.5f * SEq_2;
float halfSEq_3 = 0.5f * SEq_3;
float halfSEq_4 = 0.5f * SEq_4;
float twoSEq_1 = 2.0f * SEq_1;
float twoSEq_2 = 2.0f * SEq_2;
float twoSEq_3 = 2.0f * SEq_3;

// Normalise the accelerometer measurement
norm = sqrt(a_x * a_x + a_y * a_y + a_z * a_z);
a_x /= norm;
a_y /= norm;
a_z /= norm;

// Compute the objective function and Jacobian
f_1 = twoSEq_2 * SEq_4 - twoSEq_1 * SEq_3 - a_x;
f_2 = twoSEq_1 * SEq_2 + twoSEq_3 * SEq_4 - a_y;
f_3 = 1.0f - twoSEq_2 * SEq_2 - twoSEq_3 * SEq_3 - a_z;
J_11or24 = twoSEq_3; // J_11 negated in matrix multiplication
J_12or23 = 2.0f * SEq_4;
J_13or22 = twoSEq_1; // J_12 negated in matrix multiplication
J_14or21 = twoSEq_2;
J_32 = 2.0f * J_14or21; // negated in matrix multiplication
J_33 = 2.0f * J_11or24; // negated in matrix multiplication

// Compute the gradient (matrix multiplication)
SEqHatDot_1 = J_14or21 * f_2 - J_11or24 * f_1;
SEqHatDot_2 = J_12or23 * f_1 + J_13or22 * f_2 - J_32 * f_3;
SEqHatDot_3 = J_12or23 * f_2 - J_33 * f_3 - J_13or22 * f_1;
SEqHatDot_4 = J_14or21 * f_1 + J_11or24 * f_2;

// Normalise the gradient
norm = sqrt(SEqHatDot_1 * SEqHatDot_1 + SEqHatDot_2 * SEqHatDot_2 + SEqHatDot_3 * SEqHatDot_3 + SEqHatDot_4 * SEqHatDot_4);
SEqHatDot_1 /= norm;
SEqHatDot_2 /= norm;
SEqHatDot_3 /= norm;
SEqHatDot_4 /= norm;

// Compute the quaternion derrivative measured by gyroscopes
SEqDot_omega_1 = -halfSEq_2 * w_x - halfSEq_3 * w_y - halfSEq_4 * w_z;
SEqDot_omega_2 = halfSEq_1 * w_x + halfSEq_3 * w_z - halfSEq_4 * w_y;
SEqDot_omega_3 = halfSEq_1 * w_y - halfSEq_2 * w_z + halfSEq_4 * w_x;
SEqDot_omega_4 = halfSEq_1 * w_z + halfSEq_2 * w_y - halfSEq_3 * w_x;

// Compute then integrate the estimated quaternion derrivative
SEq_1 += (SEqDot_omega_1 - (beta * SEqHatDot_1)) * deltat;
SEq_2 += (SEqDot_omega_2 - (beta * SEqHatDot_2)) * deltat;
SEq_3 += (SEqDot_omega_3 - (beta * SEqHatDot_3)) * deltat;
SEq_4 += (SEqDot_omega_4 - (beta * SEqHatDot_4)) * deltat;

// Normalise quaternion
norm = sqrt(SEq_1 * SEq_1 + SEq_2 * SEq_2 + SEq_3 * SEq_3 + SEq_4 * SEq_4);
SEq_1 /= norm;
SEq_2 /= norm;
SEq_3 /= norm;
SEq_4 /= norm;
}
  • 加速度计、陀螺仪和磁力计
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Math library required for `sqrt'
#include <math.h>

// System constants
#define deltat 0.001f // sampling period in seconds (shown as 1 ms)
#define gyroMeasError 3.14159265358979 * (5.0f / 180.0f) // gyroscope measurement error in rad/s (shown as 5 deg/s)
#define gyroMeasDrift 3.14159265358979 * (0.2f / 180.0f) // gyroscope measurement error in rad/s/s (shown as 0.2f deg/s/s)
#define beta sqrt(3.0f / 4.0f) * gyroMeasError // compute beta
#define zeta sqrt(3.0f / 4.0f) * gyroMeasDrift // compute zeta

// Global system variables
float a_x, a_y, a_z; // accelerometer measurements
float w_x, w_y, w_z; // gyroscope measurements in rad/s
float m_x, m_y, m_z; // magnetometer measurements
float SEq_1 = 1, SEq_2 = 0, SEq_3 = 0, SEq_4 = 0; // estimated orientation quaternion elements with initial conditions
float b_x = 1, b_z = 0; // reference direction of flux in earth frame
float w_bx = 0, w_by = 0, w_bz = 0; // estimate gyroscope biases error

void filterUpdate(float w_x, float w_y, float w_z, float a_x, float a_y, float a_z, float m_x, float m_y, float m_z) {
// local system variables
float norm; // vector norm
float SEqDot_omega_1, SEqDot_omega_2, SEqDot_omega_3, SEqDot_omega_4; // quaternion rate from gyroscopes elements
float f_1, f_2, f_3, f_4, f_5, f_6; // objective function elements
float J_11or24, J_12or23, J_13or22, J_14or21, J_32, J_33, // objective function Jacobian elements
J_41, J_42, J_43, J_44, J_51, J_52, J_53, J_54, J_61, J_62, J_63, J_64; //
float SEqHatDot_1, SEqHatDot_2, SEqHatDot_3, SEqHatDot_4; // estimated direction of the gyroscope error
float w_err_x, w_err_y, w_err_z; // estimated direction of the gyroscope error (angular)
float h_x, h_y, h_z; // computed flux in the earth frame

// axulirary variables to avoid reapeated calcualtions
float halfSEq_1 = 0.5f * SEq_1;
float halfSEq_2 = 0.5f * SEq_2;
float halfSEq_3 = 0.5f * SEq_3;
float halfSEq_4 = 0.5f * SEq_4;
float twoSEq_1 = 2.0f * SEq_1;
float twoSEq_2 = 2.0f * SEq_2;
float twoSEq_3 = 2.0f * SEq_3;
float twoSEq_4 = 2.0f * SEq_4;
float twob_x = 2.0f * b_x;
float twob_z = 2.0f * b_z;
float twob_xSEq_1 = 2.0f * b_x * SEq_1;
float twob_xSEq_2 = 2.0f * b_x * SEq_2;
float twob_xSEq_3 = 2.0f * b_x * SEq_3;
float twob_xSEq_4 = 2.0f * b_x * SEq_4;
float twob_zSEq_1 = 2.0f * b_z * SEq_1;
float twob_zSEq_2 = 2.0f * b_z * SEq_2;
float twob_zSEq_3 = 2.0f * b_z * SEq_3;
float twob_zSEq_4 = 2.0f * b_z * SEq_4;
float SEq_1SEq_2;
float SEq_1SEq_3 = SEq_1 * SEq_3;
float SEq_1SEq_4;
float SEq_2SEq_3;
float SEq_2SEq_4 = SEq_2 * SEq_4;
float SEq_3SEq_4;
float twom_x = 2.0f * m_x;
float twom_y = 2.0f * m_y;
float twom_z = 2.0f * m_z;

// normalise the accelerometer measurement
norm = sqrt(a_x * a_x + a_y * a_y + a_z * a_z);
a_x /= norm;
a_y /= norm;
a_z /= norm;

// normalise the magnetometer measurement
norm = sqrt(m_x * m_x + m_y * m_y + m_z * m_z);
m_x /= norm;
m_y /= norm;
m_z /= norm;

// compute the objective function and Jacobian
f_1 = twoSEq_2 * SEq_4 - twoSEq_1 * SEq_3 - a_x;
f_2 = twoSEq_1 * SEq_2 + twoSEq_3 * SEq_4 - a_y;
f_3 = 1.0f - twoSEq_2 * SEq_2 - twoSEq_3 * SEq_3 - a_z;
f_4 = twob_x * (0.5f - SEq_3 * SEq_3 - SEq_4 * SEq_4) + twob_z * (SEq_2SEq_4 - SEq_1SEq_3) - m_x;
f_5 = twob_x * (SEq_2 * SEq_3 - SEq_1 * SEq_4) + twob_z * (SEq_1 * SEq_2 + SEq_3 * SEq_4) - m_y;
f_6 = twob_x * (SEq_1SEq_3 + SEq_2SEq_4) + twob_z * (0.5f - SEq_2 * SEq_2 - SEq_3 * SEq_3) - m_z;
J_11or24 = twoSEq_3; // J_11 negated in matrix multiplication
J_12or23 = 2.0f * SEq_4;
J_13or22 = twoSEq_1; // J_12 negated in matrix multiplication
J_14or21 = twoSEq_2;
J_32 = 2.0f * J_14or21; // negated in matrix multiplication
J_33 = 2.0f * J_11or24; // negated in matrix multiplication
J_41 = twob_zSEq_3; // negated in matrix multiplication
J_42 = twob_zSEq_4;
J_43 = 2.0f * twob_xSEq_3 + twob_zSEq_1; // negated in matrix multiplication
J_44 = 2.0f * twob_xSEq_4 - twob_zSEq_2; // negated in matrix multiplication
J_51 = twob_xSEq_4 - twob_zSEq_2; // negated in matrix multiplication
J_52 = twob_xSEq_3 + twob_zSEq_1;
J_53 = twob_xSEq_2 + twob_zSEq_4;
J_54 = twob_xSEq_1 - twob_zSEq_3; // negated in matrix multiplication
J_61 = twob_xSEq_3;
J_62 = twob_xSEq_4 - 2.0f * twob_zSEq_2;
J_63 = twob_xSEq_1 - 2.0f * twob_zSEq_3;
J_64 = twob_xSEq_2;

// compute the gradient (matrix multiplication)
SEqHatDot_1 = J_14or21 * f_2 - J_11or24 * f_1 - J_41 * f_4 - J_51 * f_5 + J_61 * f_6;
SEqHatDot_2 = J_12or23 * f_1 + J_13or22 * f_2 - J_32 * f_3 + J_42 * f_4 + J_52 * f_5 + J_62 * f_6;
SEqHatDot_3 = J_12or23 * f_2 - J_33 * f_3 - J_13or22 * f_1 - J_43 * f_4 + J_53 * f_5 + J_63 * f_6;
SEqHatDot_4 = J_14or21 * f_1 + J_11or24 * f_2 - J_44 * f_4 - J_54 * f_5 + J_64 * f_6;

// normalise the gradient to estimate direction of the gyroscope error
norm = sqrt(SEqHatDot_1 * SEqHatDot_1 + SEqHatDot_2 * SEqHatDot_2 + SEqHatDot_3 * SEqHatDot_3 + SEqHatDot_4 * SEqHatDot_4);
SEqHatDot_1 = SEqHatDot_1 / norm;
SEqHatDot_2 = SEqHatDot_2 / norm;
SEqHatDot_3 = SEqHatDot_3 / norm;
SEqHatDot_4 = SEqHatDot_4 / norm;

// compute angular estimated direction of the gyroscope error
w_err_x = twoSEq_1 * SEqHatDot_2 - twoSEq_2 * SEqHatDot_1 - twoSEq_3 * SEqHatDot_4 + twoSEq_4 * SEqHatDot_3;
w_err_y = twoSEq_1 * SEqHatDot_3 + twoSEq_2 * SEqHatDot_4 - twoSEq_3 * SEqHatDot_1 - twoSEq_4 * SEqHatDot_2;
w_err_z = twoSEq_1 * SEqHatDot_4 - twoSEq_2 * SEqHatDot_3 + twoSEq_3 * SEqHatDot_2 - twoSEq_4 * SEqHatDot_1;

// compute and remove the gyroscope baises
w_bx += w_err_x * deltat * zeta;
w_by += w_err_y * deltat * zeta;
w_bz += w_err_z * deltat * zeta;
w_x -= w_bx;
w_y -= w_by;
w_z -= w_bz;

// compute the quaternion rate measured by gyroscopes
SEqDot_omega_1 = -halfSEq_2 * w_x - halfSEq_3 * w_y - halfSEq_4 * w_z;
SEqDot_omega_2 = halfSEq_1 * w_x + halfSEq_3 * w_z - halfSEq_4 * w_y;
SEqDot_omega_3 = halfSEq_1 * w_y - halfSEq_2 * w_z + halfSEq_4 * w_x;
SEqDot_omega_4 = halfSEq_1 * w_z + halfSEq_2 * w_y - halfSEq_3 * w_x;

// compute then integrate the estimated quaternion rate
SEq_1 += (SEqDot_omega_1 - (beta * SEqHatDot_1)) * deltat;
SEq_2 += (SEqDot_omega_2 - (beta * SEqHatDot_2)) * deltat;
SEq_3 += (SEqDot_omega_3 - (beta * SEqHatDot_3)) * deltat;
SEq_4 += (SEqDot_omega_4 - (beta * SEqHatDot_4)) * deltat;

// normalise quaternion
norm = sqrt(SEq_1 * SEq_1 + SEq_2 * SEq_2 + SEq_3 * SEq_3 + SEq_4 * SEq_4);
SEq_1 /= norm;
SEq_2 /= norm;
SEq_3 /= norm;
SEq_4 /= norm;

// compute flux in the earth frame
SEq_1SEq_2 = SEq_1 * SEq_2; // recompute axulirary variables
SEq_1SEq_3 = SEq_1 * SEq_3;
SEq_1SEq_4 = SEq_1 * SEq_4;
SEq_3SEq_4 = SEq_3 * SEq_4;
SEq_2SEq_3 = SEq_2 * SEq_3;
SEq_2SEq_4 = SEq_2 * SEq_4;
h_x = twom_x * (0.5f - SEq_3 * SEq_3 - SEq_4 * SEq_4) + twom_y * (SEq_2SEq_3 - SEq_1SEq_4) + twom_z * (SEq_2SEq_4 + SEq_1SEq_3);
h_y = twom_x * (SEq_2SEq_3 + SEq_1SEq_4) + twom_y * (0.5f - SEq_2 * SEq_2 - SEq_4 * SEq_4) + twom_z * (SEq_3SEq_4 - SEq_1SEq_2);
h_z = twom_x * (SEq_2SEq_4 - SEq_1SEq_3) + twom_y * (SEq_3SEq_4 + SEq_1SEq_2) + twom_z * (0.5f - SEq_2 * SEq_2 - SEq_3 * SEq_3);

// normalise the flux vector to have only components in the x and z
b_x = sqrt((h_x * h_x) + (h_y * h_y));
b_z = h_z;
}

5.2 x-io源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//=====================================================================================================
// MadgwickAHRS.h
//=====================================================================================================
//
// Implementation of Madgwick's IMU and AHRS algorithms.
// See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
//
// Date Author Notes
// 29/09/2011 SOH Madgwick Initial release
// 02/10/2011 SOH Madgwick Optimised for reduced CPU load
//
//=====================================================================================================
#ifndef MadgwickAHRS_h
#define MadgwickAHRS_h

//----------------------------------------------------------------------------------------------------
// Variable declaration

extern volatile float beta; // algorithm gain
extern volatile float q0, q1, q2, q3; // quaternion of sensor frame relative to auxiliary frame

//---------------------------------------------------------------------------------------------------
// Function declarations

void MadgwickAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz);
void MadgwickAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az);

#endif
//=====================================================================================================
// End of file
//=====================================================================================================
  • MadgwickAHRS.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//=====================================================================================================
// MadgwickAHRS.c
//=====================================================================================================
//
// Implementation of Madgwick's IMU and AHRS algorithms.
// See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
//
// Date Author Notes
// 29/09/2011 SOH Madgwick Initial release
// 02/10/2011 SOH Madgwick Optimised for reduced CPU load
// 19/02/2012 SOH Madgwick Magnetometer measurement is normalised
//
//=====================================================================================================

//---------------------------------------------------------------------------------------------------
// Header files

#include "MadgwickAHRS.h"
#include <math.h>

//---------------------------------------------------------------------------------------------------
// Definitions

#define sampleFreq 512.0f // sample frequency in Hz
#define betaDef 0.1f // 2 * proportional gain

//---------------------------------------------------------------------------------------------------
// Variable definitions

volatile float beta = betaDef; // 2 * proportional gain (Kp)
volatile float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f; // quaternion of sensor frame relative to auxiliary frame

//---------------------------------------------------------------------------------------------------
// Function declarations

float invSqrt(float x);

//====================================================================================================
// Functions

//---------------------------------------------------------------------------------------------------
// AHRS algorithm update

void MadgwickAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz) {
float recipNorm;
float s0, s1, s2, s3;
float qDot1, qDot2, qDot3, qDot4;
float hx, hy;
float _2q0mx, _2q0my, _2q0mz, _2q1mx, _2bx, _2bz, _4bx, _4bz, _2q0, _2q1, _2q2, _2q3, _2q0q2, _2q2q3, q0q0, q0q1, q0q2, q0q3, q1q1, q1q2, q1q3, q2q2, q2q3, q3q3;

// Use IMU algorithm if magnetometer measurement invalid (avoids NaN in magnetometer normalisation)
if((mx == 0.0f) && (my == 0.0f) && (mz == 0.0f)) {
MadgwickAHRSupdateIMU(gx, gy, gz, ax, ay, az);
return;
}

// Rate of change of quaternion from gyroscope
qDot1 = 0.5f * (-q1 * gx - q2 * gy - q3 * gz);
qDot2 = 0.5f * (q0 * gx + q2 * gz - q3 * gy);
qDot3 = 0.5f * (q0 * gy - q1 * gz + q3 * gx);
qDot4 = 0.5f * (q0 * gz + q1 * gy - q2 * gx);

// Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {

// Normalise accelerometer measurement
recipNorm = invSqrt(ax * ax + ay * ay + az * az);
ax *= recipNorm;
ay *= recipNorm;
az *= recipNorm;

// Normalise magnetometer measurement
recipNorm = invSqrt(mx * mx + my * my + mz * mz);
mx *= recipNorm;
my *= recipNorm;
mz *= recipNorm;

// Auxiliary variables to avoid repeated arithmetic
_2q0mx = 2.0f * q0 * mx;
_2q0my = 2.0f * q0 * my;
_2q0mz = 2.0f * q0 * mz;
_2q1mx = 2.0f * q1 * mx;
_2q0 = 2.0f * q0;
_2q1 = 2.0f * q1;
_2q2 = 2.0f * q2;
_2q3 = 2.0f * q3;
_2q0q2 = 2.0f * q0 * q2;
_2q2q3 = 2.0f * q2 * q3;
q0q0 = q0 * q0;
q0q1 = q0 * q1;
q0q2 = q0 * q2;
q0q3 = q0 * q3;
q1q1 = q1 * q1;
q1q2 = q1 * q2;
q1q3 = q1 * q3;
q2q2 = q2 * q2;
q2q3 = q2 * q3;
q3q3 = q3 * q3;

// Reference direction of Earth's magnetic field
hx = mx * q0q0 - _2q0my * q3 + _2q0mz * q2 + mx * q1q1 + _2q1 * my * q2 + _2q1 * mz * q3 - mx * q2q2 - mx * q3q3;
hy = _2q0mx * q3 + my * q0q0 - _2q0mz * q1 + _2q1mx * q2 - my * q1q1 + my * q2q2 + _2q2 * mz * q3 - my * q3q3;
_2bx = sqrt(hx * hx + hy * hy);
_2bz = -_2q0mx * q2 + _2q0my * q1 + mz * q0q0 + _2q1mx * q3 - mz * q1q1 + _2q2 * my * q3 - mz * q2q2 + mz * q3q3;
_4bx = 2.0f * _2bx;
_4bz = 2.0f * _2bz;

// Gradient decent algorithm corrective step
s0 = -_2q2 * (2.0f * q1q3 - _2q0q2 - ax) + _2q1 * (2.0f * q0q1 + _2q2q3 - ay) - _2bz * q2 * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (-_2bx * q3 + _2bz * q1) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + _2bx * q2 * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz);
s1 = _2q3 * (2.0f * q1q3 - _2q0q2 - ax) + _2q0 * (2.0f * q0q1 + _2q2q3 - ay) - 4.0f * q1 * (1 - 2.0f * q1q1 - 2.0f * q2q2 - az) + _2bz * q3 * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (_2bx * q2 + _2bz * q0) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + (_2bx * q3 - _4bz * q1) * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz);
s2 = -_2q0 * (2.0f * q1q3 - _2q0q2 - ax) + _2q3 * (2.0f * q0q1 + _2q2q3 - ay) - 4.0f * q2 * (1 - 2.0f * q1q1 - 2.0f * q2q2 - az) + (-_4bx * q2 - _2bz * q0) * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (_2bx * q1 + _2bz * q3) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + (_2bx * q0 - _4bz * q2) * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz);
s3 = _2q1 * (2.0f * q1q3 - _2q0q2 - ax) + _2q2 * (2.0f * q0q1 + _2q2q3 - ay) + (-_4bx * q3 + _2bz * q1) * (_2bx * (0.5f - q2q2 - q3q3) + _2bz * (q1q3 - q0q2) - mx) + (-_2bx * q0 + _2bz * q2) * (_2bx * (q1q2 - q0q3) + _2bz * (q0q1 + q2q3) - my) + _2bx * q1 * (_2bx * (q0q2 + q1q3) + _2bz * (0.5f - q1q1 - q2q2) - mz);
recipNorm = invSqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude
s0 *= recipNorm;
s1 *= recipNorm;
s2 *= recipNorm;
s3 *= recipNorm;

// Apply feedback step
qDot1 -= beta * s0;
qDot2 -= beta * s1;
qDot3 -= beta * s2;
qDot4 -= beta * s3;
}

// Integrate rate of change of quaternion to yield quaternion
q0 += qDot1 * (1.0f / sampleFreq);
q1 += qDot2 * (1.0f / sampleFreq);
q2 += qDot3 * (1.0f / sampleFreq);
q3 += qDot4 * (1.0f / sampleFreq);

// Normalise quaternion
recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
q0 *= recipNorm;
q1 *= recipNorm;
q2 *= recipNorm;
q3 *= recipNorm;
}

//---------------------------------------------------------------------------------------------------
// IMU algorithm update

void MadgwickAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az) {
float recipNorm;
float s0, s1, s2, s3;
float qDot1, qDot2, qDot3, qDot4;
float _2q0, _2q1, _2q2, _2q3, _4q0, _4q1, _4q2 ,_8q1, _8q2, q0q0, q1q1, q2q2, q3q3;

// Rate of change of quaternion from gyroscope
qDot1 = 0.5f * (-q1 * gx - q2 * gy - q3 * gz);
qDot2 = 0.5f * (q0 * gx + q2 * gz - q3 * gy);
qDot3 = 0.5f * (q0 * gy - q1 * gz + q3 * gx);
qDot4 = 0.5f * (q0 * gz + q1 * gy - q2 * gx);

// Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {

// Normalise accelerometer measurement
recipNorm = invSqrt(ax * ax + ay * ay + az * az);
ax *= recipNorm;
ay *= recipNorm;
az *= recipNorm;

// Auxiliary variables to avoid repeated arithmetic
_2q0 = 2.0f * q0;
_2q1 = 2.0f * q1;
_2q2 = 2.0f * q2;
_2q3 = 2.0f * q3;
_4q0 = 4.0f * q0;
_4q1 = 4.0f * q1;
_4q2 = 4.0f * q2;
_8q1 = 8.0f * q1;
_8q2 = 8.0f * q2;
q0q0 = q0 * q0;
q1q1 = q1 * q1;
q2q2 = q2 * q2;
q3q3 = q3 * q3;

// Gradient decent algorithm corrective step
s0 = _4q0 * q2q2 + _2q2 * ax + _4q0 * q1q1 - _2q1 * ay;
s1 = _4q1 * q3q3 - _2q3 * ax + 4.0f * q0q0 * q1 - _2q0 * ay - _4q1 + _8q1 * q1q1 + _8q1 * q2q2 + _4q1 * az;
s2 = 4.0f * q0q0 * q2 + _2q0 * ax + _4q2 * q3q3 - _2q3 * ay - _4q2 + _8q2 * q1q1 + _8q2 * q2q2 + _4q2 * az;
s3 = 4.0f * q1q1 * q3 - _2q1 * ax + 4.0f * q2q2 * q3 - _2q2 * ay;
recipNorm = invSqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude
s0 *= recipNorm;
s1 *= recipNorm;
s2 *= recipNorm;
s3 *= recipNorm;

// Apply feedback step
qDot1 -= beta * s0;
qDot2 -= beta * s1;
qDot3 -= beta * s2;
qDot4 -= beta * s3;
}

// Integrate rate of change of quaternion to yield quaternion
q0 += qDot1 * (1.0f / sampleFreq);
q1 += qDot2 * (1.0f / sampleFreq);
q2 += qDot3 * (1.0f / sampleFreq);
q3 += qDot4 * (1.0f / sampleFreq);

// Normalise quaternion
recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
q0 *= recipNorm;
q1 *= recipNorm;
q2 *= recipNorm;
q3 *= recipNorm;
}

//---------------------------------------------------------------------------------------------------
// Fast inverse square-root
// See: http://en.wikipedia.org/wiki/Fast_inverse_square_root

float invSqrt(float x) {
float halfx = 0.5f * x;
float y = x;
long i = *(long*)&y;
i = 0x5f3759df - (i>>1);
y = *(float*)&i;
y = y * (1.5f - (halfx * y * y));
return y;
}

//====================================================================================================
// END OF CODE
//====================================================================================================
  • MahonyAHRS.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//=====================================================================================================
// MahonyAHRS.h
//=====================================================================================================
//
// Madgwick's implementation of Mayhony's AHRS algorithm.
// See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
//
// Date Author Notes
// 29/09/2011 SOH Madgwick Initial release
// 02/10/2011 SOH Madgwick Optimised for reduced CPU load
//
//=====================================================================================================
#ifndef MahonyAHRS_h
#define MahonyAHRS_h

//----------------------------------------------------------------------------------------------------
// Variable declaration

extern volatile float twoKp; // 2 * proportional gain (Kp)
extern volatile float twoKi; // 2 * integral gain (Ki)
extern volatile float q0, q1, q2, q3; // quaternion of sensor frame relative to auxiliary frame

//---------------------------------------------------------------------------------------------------
// Function declarations

void MahonyAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz);
void MahonyAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az);

#endif
//=====================================================================================================
// End of file
//=====================================================================================================
  • MahonyAHRS.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
//=====================================================================================================
// MahonyAHRS.c
//=====================================================================================================
//
// Madgwick's implementation of Mayhony's AHRS algorithm.
// See: http://www.x-io.co.uk/node/8#open_source_ahrs_and_imu_algorithms
//
// Date Author Notes
// 29/09/2011 SOH Madgwick Initial release
// 02/10/2011 SOH Madgwick Optimised for reduced CPU load
//
//=====================================================================================================

//---------------------------------------------------------------------------------------------------
// Header files

#include "MahonyAHRS.h"
#include <math.h>

//---------------------------------------------------------------------------------------------------
// Definitions

#define sampleFreq 512.0f // sample frequency in Hz
#define twoKpDef (2.0f * 0.5f) // 2 * proportional gain
#define twoKiDef (2.0f * 0.0f) // 2 * integral gain

//---------------------------------------------------------------------------------------------------
// Variable definitions

volatile float twoKp = twoKpDef; // 2 * proportional gain (Kp)
volatile float twoKi = twoKiDef; // 2 * integral gain (Ki)
volatile float q0 = 1.0f, q1 = 0.0f, q2 = 0.0f, q3 = 0.0f; // quaternion of sensor frame relative to auxiliary frame
volatile float integralFBx = 0.0f, integralFBy = 0.0f, integralFBz = 0.0f; // integral error terms scaled by Ki

//---------------------------------------------------------------------------------------------------
// Function declarations

float invSqrt(float x);

//====================================================================================================
// Functions

//---------------------------------------------------------------------------------------------------
// AHRS algorithm update

void MahonyAHRSupdate(float gx, float gy, float gz, float ax, float ay, float az, float mx, float my, float mz) {
float recipNorm;
float q0q0, q0q1, q0q2, q0q3, q1q1, q1q2, q1q3, q2q2, q2q3, q3q3;
float hx, hy, bx, bz;
float halfvx, halfvy, halfvz, halfwx, halfwy, halfwz;
float halfex, halfey, halfez;
float qa, qb, qc;

// Use IMU algorithm if magnetometer measurement invalid (avoids NaN in magnetometer normalisation)
if((mx == 0.0f) && (my == 0.0f) && (mz == 0.0f)) {
MahonyAHRSupdateIMU(gx, gy, gz, ax, ay, az);
return;
}

// Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {

// Normalise accelerometer measurement
recipNorm = invSqrt(ax * ax + ay * ay + az * az);
ax *= recipNorm;
ay *= recipNorm;
az *= recipNorm;

// Normalise magnetometer measurement
recipNorm = invSqrt(mx * mx + my * my + mz * mz);
mx *= recipNorm;
my *= recipNorm;
mz *= recipNorm;

// Auxiliary variables to avoid repeated arithmetic
q0q0 = q0 * q0;
q0q1 = q0 * q1;
q0q2 = q0 * q2;
q0q3 = q0 * q3;
q1q1 = q1 * q1;
q1q2 = q1 * q2;
q1q3 = q1 * q3;
q2q2 = q2 * q2;
q2q3 = q2 * q3;
q3q3 = q3 * q3;

// Reference direction of Earth's magnetic field
hx = 2.0f * (mx * (0.5f - q2q2 - q3q3) + my * (q1q2 - q0q3) + mz * (q1q3 + q0q2));
hy = 2.0f * (mx * (q1q2 + q0q3) + my * (0.5f - q1q1 - q3q3) + mz * (q2q3 - q0q1));
bx = sqrt(hx * hx + hy * hy);
bz = 2.0f * (mx * (q1q3 - q0q2) + my * (q2q3 + q0q1) + mz * (0.5f - q1q1 - q2q2));

// Estimated direction of gravity and magnetic field
halfvx = q1q3 - q0q2;
halfvy = q0q1 + q2q3;
halfvz = q0q0 - 0.5f + q3q3;
halfwx = bx * (0.5f - q2q2 - q3q3) + bz * (q1q3 - q0q2);
halfwy = bx * (q1q2 - q0q3) + bz * (q0q1 + q2q3);
halfwz = bx * (q0q2 + q1q3) + bz * (0.5f - q1q1 - q2q2);

// Error is sum of cross product between estimated direction and measured direction of field vectors
halfex = (ay * halfvz - az * halfvy) + (my * halfwz - mz * halfwy);
halfey = (az * halfvx - ax * halfvz) + (mz * halfwx - mx * halfwz);
halfez = (ax * halfvy - ay * halfvx) + (mx * halfwy - my * halfwx);

// Compute and apply integral feedback if enabled
if(twoKi > 0.0f) {
integralFBx += twoKi * halfex * (1.0f / sampleFreq); // integral error scaled by Ki
integralFBy += twoKi * halfey * (1.0f / sampleFreq);
integralFBz += twoKi * halfez * (1.0f / sampleFreq);
gx += integralFBx; // apply integral feedback
gy += integralFBy;
gz += integralFBz;
}
else {
integralFBx = 0.0f; // prevent integral windup
integralFBy = 0.0f;
integralFBz = 0.0f;
}

// Apply proportional feedback
gx += twoKp * halfex;
gy += twoKp * halfey;
gz += twoKp * halfez;
}

// Integrate rate of change of quaternion
gx *= (0.5f * (1.0f / sampleFreq)); // pre-multiply common factors
gy *= (0.5f * (1.0f / sampleFreq));
gz *= (0.5f * (1.0f / sampleFreq));
qa = q0;
qb = q1;
qc = q2;
q0 += (-qb * gx - qc * gy - q3 * gz);
q1 += (qa * gx + qc * gz - q3 * gy);
q2 += (qa * gy - qb * gz + q3 * gx);
q3 += (qa * gz + qb * gy - qc * gx);

// Normalise quaternion
recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
q0 *= recipNorm;
q1 *= recipNorm;
q2 *= recipNorm;
q3 *= recipNorm;
}

//---------------------------------------------------------------------------------------------------
// IMU algorithm update

void MahonyAHRSupdateIMU(float gx, float gy, float gz, float ax, float ay, float az) {
float recipNorm;
float halfvx, halfvy, halfvz;
float halfex, halfey, halfez;
float qa, qb, qc;

// Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation)
if(!((ax == 0.0f) && (ay == 0.0f) && (az == 0.0f))) {

// Normalise accelerometer measurement
recipNorm = invSqrt(ax * ax + ay * ay + az * az);
ax *= recipNorm;
ay *= recipNorm;
az *= recipNorm;

// Estimated direction of gravity and vector perpendicular to magnetic flux
halfvx = q1 * q3 - q0 * q2;
halfvy = q0 * q1 + q2 * q3;
halfvz = q0 * q0 - 0.5f + q3 * q3;

// Error is sum of cross product between estimated and measured direction of gravity
halfex = (ay * halfvz - az * halfvy);
halfey = (az * halfvx - ax * halfvz);
halfez = (ax * halfvy - ay * halfvx);

// Compute and apply integral feedback if enabled
if(twoKi > 0.0f) {
integralFBx += twoKi * halfex * (1.0f / sampleFreq); // integral error scaled by Ki
integralFBy += twoKi * halfey * (1.0f / sampleFreq);
integralFBz += twoKi * halfez * (1.0f / sampleFreq);
gx += integralFBx; // apply integral feedback
gy += integralFBy;
gz += integralFBz;
}
else {
integralFBx = 0.0f; // prevent integral windup
integralFBy = 0.0f;
integralFBz = 0.0f;
}

// Apply proportional feedback
gx += twoKp * halfex;
gy += twoKp * halfey;
gz += twoKp * halfez;
}

// Integrate rate of change of quaternion
gx *= (0.5f * (1.0f / sampleFreq)); // pre-multiply common factors
gy *= (0.5f * (1.0f / sampleFreq));
gz *= (0.5f * (1.0f / sampleFreq));
qa = q0;
qb = q1;
qc = q2;
q0 += (-qb * gx - qc * gy - q3 * gz);
q1 += (qa * gx + qc * gz - q3 * gy);
q2 += (qa * gy - qb * gz + q3 * gx);
q3 += (qa * gz + qb * gy - qc * gx);

// Normalise quaternion
recipNorm = invSqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
q0 *= recipNorm;
q1 *= recipNorm;
q2 *= recipNorm;
q3 *= recipNorm;
}

//---------------------------------------------------------------------------------------------------
// Fast inverse square-root
// See: http://en.wikipedia.org/wiki/Fast_inverse_square_root

float invSqrt(float x) {
float halfx = 0.5f * x;
float y = x;
long i = *(long*)&y;
i = 0x5f3759df - (i>>1);
y = *(float*)&i;
y = y * (1.5f - (halfx * y * y));
return y;
}

//====================================================================================================
// END OF CODE
//====================================================================================================

6. 参考: