2 ******************************************************************************
7 * @brief INSGPS is a joint attitude and position estimation EKF
10 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
11 * Tau Labs, http://github.com/TauLabs Copyright (C) 2012-2013.
12 * The LibrePilot Project, http://www.librepilot.org Copyright (C) 2016.
13 * @brief An INS/GPS algorithm implemented with an EKF.
15 * @see The GNU Public License (GPL) Version 3
17 *****************************************************************************/
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 3 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful, but
25 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
26 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
29 * You should have received a copy of the GNU General Public License along
30 * with this program; if not, write to the Free Software Foundation, Inc.,
31 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
38 #include <pios_math.h>
40 #include <pios_constants.h>
42 // constants/macros/typdefs
43 #define NUMX 14 // number of states, X is the state vector
44 #define NUMW 10 // number of plant noise inputs, w is disturbance noise vector
45 #define NUMV 10 // number of measurements, v is the measurement noise vector
46 #define NUMU 6 // number of deterministic inputs, U is the input vector
47 #pragma GCC optimize "O3"
49 void CovariancePrediction(float F
[NUMX
][NUMX
], float G
[NUMX
][NUMW
],
50 float Q
[NUMW
], float dT
, float P
[NUMX
][NUMX
]);
51 static void SerialUpdate(float H
[NUMV
][NUMX
], float R
[NUMV
], float Z
[NUMV
],
52 float Y
[NUMV
], float P
[NUMX
][NUMX
], float X
[NUMX
],
53 uint16_t SensorsUsed
);
54 static void RungeKutta(float X
[NUMX
], float U
[NUMU
], float dT
);
55 static void StateEq(float X
[NUMX
], float U
[NUMU
], float Xdot
[NUMX
]);
56 static void LinearizeFG(float X
[NUMX
], float U
[NUMU
], float F
[NUMX
][NUMX
],
58 static void MeasurementEq(float X
[NUMX
], float Be
[3], float Y
[NUMV
]);
59 static void LinearizeH(float X
[NUMX
], float Be
[3], float H
[NUMV
][NUMX
]);
63 // speed optimizations, describe matrix sparsity
64 // derived from state equations in
65 // LinearizeFG() and LinearizeH():
67 // usage F: usage G: usage H:
68 // -0123456789abcd 0123456789 0123456789abcd
69 // 0...X.......... .......... X.............
70 // 1....X......... .......... .X............
71 // 2.....X........ .......... ..X...........
72 // 3......XXXX...X ...XXX.... ...X..........
73 // 4......XXXX...X ...XXX.... ....X.........
74 // 5......XXXX...X ...XXX.... .....X........
75 // 6.....ooXXXXXX. XXX....... ......XXXX....
76 // 7.....oXoXXXXX. XXX....... ......XXXX....
77 // 8.....oXXoXXXX. XXX....... ......XXXX....
78 // 9.....oXXXoXXX. XXX....... ..X...........
79 // a.............. ..........
80 // b.............. ..........
81 // c.............. ..........
82 // d.............. ..........
84 static int8_t FrowMin
[NUMX
] = { 3, 4, 5, 6, 6, 6, 5, 5, 5, 5, 14, 14, 14, 14 };
85 static int8_t FrowMax
[NUMX
] = { 3, 4, 5, 13, 13, 13, 12, 12, 12, 12, -1, -1, -1, -1 };
87 static int8_t GrowMin
[NUMX
] = { 10, 10, 10, 3, 3, 3, 0, 0, 0, 0, 10, 10, 10, 10 };
88 static int8_t GrowMax
[NUMX
] = { -1, -1, -1, 5, 5, 5, 2, 2, 2, 2, -1, -1, -1, -1 };
90 static int8_t HrowMin
[NUMV
] = { 0, 1, 2, 3, 4, 5, 6, 6, 6, 2 };
91 static int8_t HrowMax
[NUMV
] = { 0, 1, 2, 3, 4, 5, 9, 9, 9, 2 };
93 static struct EKFData
{
96 float H
[NUMV
][NUMX
]; // linearized system matrices
97 // global to init to zero and maintain zero elements
98 float Be
[3]; // local magnetic unit vector in NED frame
100 float X
[NUMX
]; // covariance matrix and state vector
102 float R
[NUMV
]; // input noise and measurement noise variances
103 float K
[NUMX
][NUMV
]; // feedback gain matrix
107 struct NavStruct Nav
;
109 // ************* Exposed Functions ****************
110 // *************************************************
112 uint16_t ins_get_num_states()
121 ekf
.Be
[2] = 0; // local magnetic unit vector
123 for (int i
= 0; i
< NUMX
; i
++) {
124 for (int j
= 0; j
< NUMX
; j
++) {
125 ekf
.P
[i
][j
] = 0.0f
; // zero all terms
129 for (int j
= 0; j
< NUMW
; j
++) {
133 for (int j
= 0; j
< NUMV
; j
++) {
140 for (int i
= 0; i
< NUMW
; i
++) {
143 for (int i
= 0; i
< NUMV
; i
++) {
147 ekf
.P
[0][0] = ekf
.P
[1][1] = ekf
.P
[2][2] = 25.0f
; // initial position variance (m^2)
148 ekf
.P
[3][3] = ekf
.P
[4][4] = ekf
.P
[5][5] = 5.0f
; // initial velocity variance (m/s)^2
149 ekf
.P
[6][6] = ekf
.P
[7][7] = ekf
.P
[8][8] = ekf
.P
[9][9] = 1e-5f
; // initial quaternion variance
150 ekf
.P
[10][10] = ekf
.P
[11][11] = ekf
.P
[12][12] = 1e-6f
; // initial gyro bias variance (rad/s)^2
151 ekf
.P
[13][13] = 1e-5f
; // initial accel bias variance (deg/s)^2
153 ekf
.X
[0] = ekf
.X
[1] = ekf
.X
[2] = ekf
.X
[3] = ekf
.X
[4] = ekf
.X
[5] = 0.0f
; // initial pos and vel (m)
155 ekf
.X
[7] = ekf
.X
[8] = ekf
.X
[9] = 0.0f
; // initial quaternion (level and North) (m/s)
156 ekf
.X
[10] = ekf
.X
[11] = ekf
.X
[12] = 0.0f
; // initial gyro bias (rad/s)
157 ekf
.X
[13] = 0.0f
; // initial accel bias
159 ekf
.Q
[0] = ekf
.Q
[1] = ekf
.Q
[2] = 1e-5f
; // gyro noise variance (rad/s)^2
160 ekf
.Q
[3] = ekf
.Q
[4] = ekf
.Q
[5] = 1e-5f
; // accelerometer noise variance (m/s^2)^2
161 ekf
.Q
[6] = ekf
.Q
[7] = 1e-6f
; // gyro x and y bias random walk variance (rad/s^2)^2
162 ekf
.Q
[8] = 1e-6f
; // gyro z bias random walk variance (rad/s^2)^2
163 ekf
.Q
[9] = 5e-4f
; // accel bias random walk variance (m/s^3)^2
165 ekf
.R
[0] = ekf
.R
[1] = 0.004f
; // High freq GPS horizontal position noise variance (m^2)
166 ekf
.R
[2] = 0.036f
; // High freq GPS vertical position noise variance (m^2)
167 ekf
.R
[3] = ekf
.R
[4] = 0.004f
; // High freq GPS horizontal velocity noise variance (m/s)^2
168 ekf
.R
[5] = 0.004f
; // High freq GPS vertical velocity noise variance (m/s)^2
169 ekf
.R
[6] = ekf
.R
[7] = ekf
.R
[8] = 0.005f
; // magnetometer unit vector noise variance
170 ekf
.R
[9] = .05f
; // High freq altimeter noise variance (m^2)
173 // ! Set the current flight state
174 void INSSetArmed(bool armed
)
178 // Speed up convergence of accel and gyro bias when not armed
189 * Get the current state estimate (null input skips that get)
190 * @param[out] pos The position in NED space (m)
191 * @param[out] vel The velocity in NED (m/s)
192 * @param[out] attitude Quaternion representation of attitude
193 * @param[out] gyros_bias Estimate of gyro bias (rad/s)
194 * @param[out] accel_bias Estiamte of the accel bias (m/s^2)
196 void INSGetState(float *pos
, float *vel
, float *attitude
, float *gyro_bias
, float *accel_bias
)
211 attitude
[0] = ekf
.X
[6];
212 attitude
[1] = ekf
.X
[7];
213 attitude
[2] = ekf
.X
[8];
214 attitude
[3] = ekf
.X
[9];
218 gyro_bias
[0] = ekf
.X
[10];
219 gyro_bias
[1] = ekf
.X
[11];
220 gyro_bias
[2] = ekf
.X
[12];
224 accel_bias
[0] = 0.0f
;
225 accel_bias
[1] = 0.0f
;
226 accel_bias
[2] = ekf
.X
[13];
231 * Get the variance, for visualizing the filter performance
232 * @param[out var_out The variances
234 void INSGetVariance(float *var_out
)
236 for (uint32_t i
= 0; i
< NUMX
; i
++) {
237 var_out
[i
] = ekf
.P
[i
][i
];
241 void INSResetP(const float *PDiag
)
245 // if PDiag[i] nonzero then clear row and column and set diagonal element
246 for (i
= 0; i
< NUMX
; i
++) {
248 for (j
= 0; j
< NUMX
; j
++) {
249 ekf
.P
[i
][j
] = ekf
.P
[j
][i
] = 0.0f
;
251 ekf
.P
[i
][i
] = PDiag
[i
];
256 void INSSetState(const float pos
[3], const float vel
[3], const float q
[4], const float gyro_bias
[3], const float accel_bias
[3])
268 ekf
.X
[10] = gyro_bias
[0];
269 ekf
.X
[11] = gyro_bias
[1];
270 ekf
.X
[12] = gyro_bias
[2];
271 ekf
.X
[13] = accel_bias
[2];
274 void INSPosVelReset(const float pos
[3], const float vel
[3])
276 for (int i
= 0; i
< 6; i
++) {
277 for (int j
= i
; j
< NUMX
; j
++) {
278 ekf
.P
[i
][j
] = 0.0f
; // zero the first 6 rows and columns
283 ekf
.P
[0][0] = ekf
.P
[1][1] = ekf
.P
[2][2] = 25.0f
; // initial position variance (m^2)
284 ekf
.P
[3][3] = ekf
.P
[4][4] = ekf
.P
[5][5] = 5.0f
; // initial velocity variance (m/s)^2
293 void INSSetPosVelVar(const float PosVar
[3], const float VelVar
[3])
295 ekf
.R
[0] = PosVar
[0];
296 ekf
.R
[1] = PosVar
[1];
297 ekf
.R
[2] = PosVar
[2];
298 ekf
.R
[3] = VelVar
[0];
299 ekf
.R
[4] = VelVar
[1];
300 ekf
.R
[5] = VelVar
[2]; // Don't change vertical velocity, not measured
303 void INSSetGyroBias(const float gyro_bias
[3])
305 ekf
.X
[10] = gyro_bias
[0];
306 ekf
.X
[11] = gyro_bias
[1];
307 ekf
.X
[12] = gyro_bias
[2];
310 void INSSetAccelBias(const float accel_bias
[3])
312 ekf
.X
[13] = accel_bias
[2];
315 void INSSetAccelVar(const float accel_var
[3])
317 ekf
.Q
[3] = accel_var
[0];
318 ekf
.Q
[4] = accel_var
[1];
319 ekf
.Q
[5] = accel_var
[2];
322 void INSSetGyroVar(const float gyro_var
[3])
324 ekf
.Q
[0] = gyro_var
[0];
325 ekf
.Q
[1] = gyro_var
[1];
326 ekf
.Q
[2] = gyro_var
[2];
329 void INSSetGyroBiasVar(const float gyro_bias_var
[3])
331 ekf
.Q
[6] = gyro_bias_var
[0];
332 ekf
.Q
[7] = gyro_bias_var
[1];
333 ekf
.Q
[8] = gyro_bias_var
[2];
336 void INSSetMagVar(const float scaled_mag_var
[3])
338 ekf
.R
[6] = scaled_mag_var
[0];
339 ekf
.R
[7] = scaled_mag_var
[1];
340 ekf
.R
[8] = scaled_mag_var
[2];
343 void INSSetBaroVar(const float baro_var
)
348 void INSSetMagNorth(const float B
[3])
357 // The Z accel bias should never wander too much. This helps ensure the filter
359 if (ekf
.X
[13] > 0.1f
) {
361 } else if (ekf
.X
[13] < -0.1f
) {
365 // Make sure no gyro bias gets to more than 10 deg / s. This should be more than
366 // enough for well behaving sensors.
367 const float GYRO_BIAS_LIMIT
= DEG2RAD(10);
368 for (int i
= 10; i
< 13; i
++) {
369 if (ekf
.X
[i
] < -GYRO_BIAS_LIMIT
) {
370 ekf
.X
[i
] = -GYRO_BIAS_LIMIT
;
371 } else if (ekf
.X
[i
] > GYRO_BIAS_LIMIT
) {
372 ekf
.X
[i
] = GYRO_BIAS_LIMIT
;
377 void INSStatePrediction(const float gyro_data
[3], const float accel_data
[3], float dT
)
382 // rate gyro inputs in units of rad/s
387 // accelerometer inputs in units of m/s
388 U
[3] = accel_data
[0];
389 U
[4] = accel_data
[1];
390 U
[5] = accel_data
[2];
392 // EKF prediction step
393 LinearizeFG(ekf
.X
, U
, ekf
.F
, ekf
.G
);
394 RungeKutta(ekf
.X
, U
, dT
);
395 invqmag
= invsqrtf(ekf
.X
[6] * ekf
.X
[6] + ekf
.X
[7] * ekf
.X
[7] + ekf
.X
[8] * ekf
.X
[8] + ekf
.X
[9] * ekf
.X
[9]);
401 // Update Nav solution structure
402 Nav
.Pos
[0] = ekf
.X
[0];
403 Nav
.Pos
[1] = ekf
.X
[1];
404 Nav
.Pos
[2] = ekf
.X
[2];
405 Nav
.Vel
[0] = ekf
.X
[3];
406 Nav
.Vel
[1] = ekf
.X
[4];
407 Nav
.Vel
[2] = ekf
.X
[5];
412 Nav
.gyro_bias
[0] = ekf
.X
[10];
413 Nav
.gyro_bias
[1] = ekf
.X
[11];
414 Nav
.gyro_bias
[2] = ekf
.X
[12];
415 Nav
.accel_bias
[0] = 0.0f
;
416 Nav
.accel_bias
[1] = 0.0f
;
417 Nav
.accel_bias
[2] = ekf
.X
[13];
420 void INSCovariancePrediction(float dT
)
422 CovariancePrediction(ekf
.F
, ekf
.G
, ekf
.Q
, dT
, ekf
.P
);
425 void INSCorrection(const float mag_data
[3], const float Pos
[3], const float Vel
[3],
426 const float BaroAlt
, uint16_t SensorsUsed
)
431 // GPS Position in meters and in local NED frame
436 // GPS Velocity in meters and in local NED frame
441 if (SensorsUsed
& MAG_SENSORS
) {
442 // magnetometer data in any units (use unit vector) and in body frame
448 float k1
= 1.0f
/ sqrtf(powf(q0
* q1
* 2.0f
+ q2
* q3
* 2.0f
, 2.0f
) + powf(q0
* q0
- q1
* q1
- q2
* q2
+ q3
* q3
, 2.0f
));
449 float k2
= sqrtf(-powf(q0
* q2
* 2.0f
- q1
* q3
* 2.0f
, 2.0f
) + 1.0f
);
453 Rbe_a
[0][2] = q0
* q2
* -2.0f
+ q1
* q3
* 2.0f
;
454 Rbe_a
[1][0] = k1
* (q0
* q1
* 2.0f
+ q2
* q3
* 2.0f
) * (q0
* q2
* 2.0f
- q1
* q3
* 2.0f
);
455 Rbe_a
[1][1] = k1
* (q0
* q0
- q1
* q1
- q2
* q2
+ q3
* q3
);
456 Rbe_a
[1][2] = k1
* sqrtf(-powf(q0
* q2
* 2.0f
- q1
* q3
* 2.0f
, 2.0f
) + 1.0f
) * (q0
* q1
* 2.0f
+ q2
* q3
* 2.0f
);
457 Rbe_a
[2][0] = k1
* (q0
* q2
* 2.0f
- q1
* q3
* 2.0f
) * (q0
* q0
- q1
* q1
- q2
* q2
+ q3
* q3
);
458 Rbe_a
[2][1] = -k1
* (q0
* q1
* 2.0f
+ q2
* q3
* 2.0f
);
459 Rbe_a
[2][2] = k1
* k2
* (q0
* q0
- q1
* q1
- q2
* q2
+ q3
* q3
);
461 Z
[6] = Rbe_a
[0][0] * mag_data
[0] + Rbe_a
[1][0] * mag_data
[1] + Rbe_a
[2][0] * mag_data
[2];
462 Z
[7] = Rbe_a
[0][1] * mag_data
[0] + Rbe_a
[1][1] * mag_data
[1] + Rbe_a
[2][1] * mag_data
[2];
463 Z
[8] = Rbe_a
[0][2] * mag_data
[0] + Rbe_a
[1][2] * mag_data
[1] + Rbe_a
[2][2] * mag_data
[2];
464 if (!(IS_REAL(Z
[6]) && IS_REAL(Z
[7]) && IS_REAL(Z
[8]))) {
465 SensorsUsed
= SensorsUsed
& ~MAG_SENSORS
;
469 // barometric altimeter in meters and in local NED frame
472 // EKF correction step
473 LinearizeH(ekf
.X
, ekf
.Be
, ekf
.H
);
474 MeasurementEq(ekf
.X
, ekf
.Be
, Y
);
475 SerialUpdate(ekf
.H
, ekf
.R
, Z
, Y
, ekf
.P
, ekf
.X
, SensorsUsed
);
476 invqmag
= invsqrtf(ekf
.X
[6] * ekf
.X
[6] + ekf
.X
[7] * ekf
.X
[7] + ekf
.X
[8] * ekf
.X
[8] + ekf
.X
[9] * ekf
.X
[9]);
485 // ************* CovariancePrediction *************
486 // Does the prediction step of the Kalman filter for the covariance matrix
487 // Output, Pnew, overwrites P, the input covariance
488 // Pnew = (I+F*T)*P*(I+F*T)' + T^2*G*Q*G'
489 // Q is the discrete time covariance of process noise
490 // Q is vector of the diagonal for a square matrix with
491 // dimensions equal to the number of disturbance noise variables
492 // The General Method is very inefficient,not taking advantage of the sparse F and G
493 // The first Method is very specific to this implementation
494 // ************************************************
496 void CovariancePrediction(float F
[NUMX
][NUMX
], float G
[NUMX
][NUMW
],
497 float Q
[NUMW
], float dT
, float P
[NUMX
][NUMX
])
499 // Pnew = (I+F*T)*P*(I+F*T)' + (T^2)*G*Q*G' = (T^2)[(P/T + F*P)*(I/T + F') + G*Q*G')]
501 const float dT1
= 1.0f
/ dT
; // multiplication is faster than division on fpu.
502 const float dTsq
= dT
* dT
;
504 float Dummy
[NUMX
][NUMX
];
508 for (i
= 0; i
< NUMX
; i
++) { // Calculate Dummy = (P/T +F*P)
511 float *Dirow
= Dummy
[i
];
512 const int8_t Fistart
= FrowMin
[i
];
513 const int8_t Fiend
= FrowMax
[i
];
516 for (j
= 0; j
< NUMX
; j
++) {
517 Dirow
[j
] = Pirow
[j
] * dT1
; // Dummy = P / T ...
519 for (k
= Fistart
; k
<= Fiend
; k
++) {
520 for (j
= 0; j
< NUMX
; j
++) {
521 Dirow
[j
] += Firow
[k
] * P
[k
][j
]; // [] + F * P
525 for (i
= 0; i
< NUMX
; i
++) { // Calculate Pnew = (T^2) [Dummy/T + Dummy*F' + G*Qw*G']
526 float *Dirow
= Dummy
[i
];
529 const int8_t Gistart
= GrowMin
[i
];
530 const int8_t Giend
= GrowMax
[i
];
534 for (j
= i
; j
< NUMX
; j
++) { // Use symmetry, ie only find upper triangular
535 float Ptmp
= Dirow
[j
] * dT1
; // Pnew = Dummy / T ...
537 const float *Fjrow
= F
[j
];
538 int8_t Fjstart
= FrowMin
[j
];
539 int8_t Fjend
= FrowMax
[j
];
542 while (k
<= Fjend
- 3) {
543 Ptmp
+= Dirow
[k
] * Fjrow
[k
]; // [] + Dummy*F' ...
544 Ptmp
+= Dirow
[k
+ 1] * Fjrow
[k
+ 1];
545 Ptmp
+= Dirow
[k
+ 2] * Fjrow
[k
+ 2];
546 Ptmp
+= Dirow
[k
+ 3] * Fjrow
[k
+ 3];
550 Ptmp
+= Dirow
[k
] * Fjrow
[k
];
555 const int8_t Gjstart
= MAX(Gistart
, GrowMin
[j
]);
556 const int8_t Gjend
= MIN(Giend
, GrowMax
[j
]);
558 while (k
<= Gjend
- 2) {
559 Ptmp
+= Q
[k
] * Girow
[k
] * Gjrow
[k
]; // [] + G*Q*G' ...
560 Ptmp
+= Q
[k
+ 1] * Girow
[k
+ 1] * Gjrow
[k
+ 1];
561 Ptmp
+= Q
[k
+ 2] * Girow
[k
+ 2] * Gjrow
[k
+ 2];
565 Ptmp
+= Q
[k
] * Girow
[k
] * Gjrow
[k
];
566 if (k
<= Gjend
- 1) {
567 Ptmp
+= Q
[k
+ 1] * Girow
[k
+ 1] * Gjrow
[k
+ 1];
571 P
[j
][i
] = Pirow
[j
] = Ptmp
* dTsq
; // [] * (T^2)
576 // ************* SerialUpdate *******************
577 // Does the update step of the Kalman filter for the covariance and estimate
578 // Outputs are Xnew & Pnew, and are written over P and X
579 // Z is actual measurement, Y is predicted measurement
580 // Xnew = X + K*(Z-Y), Pnew=(I-K*H)*P,
581 // where K=P*H'*inv[H*P*H'+R]
582 // NOTE the algorithm assumes R (measurement covariance matrix) is diagonal
583 // i.e. the measurment noises are uncorrelated.
584 // It therefore uses a serial update that requires no matrix inversion by
585 // processing the measurements one at a time.
586 // Algorithm - see Grewal and Andrews, "Kalman Filtering,2nd Ed" p.121 & p.253
587 // - or see Simon, "Optimal State Estimation," 1st Ed, p.150
588 // The SensorsUsed variable is a bitwise mask indicating which sensors
589 // should be used in the update.
590 // ************************************************
591 void SerialUpdate(float H
[NUMV
][NUMX
], float R
[NUMV
], float Z
[NUMV
],
592 float Y
[NUMV
], float P
[NUMX
][NUMX
], float X
[NUMX
],
593 uint16_t SensorsUsed
)
595 float HP
[NUMX
], HPHR
, Error
;
599 // Iterate through all the possible measurements and apply the
600 // appropriate corrections
601 for (m
= 0; m
< NUMV
; m
++) {
602 if (SensorsUsed
& (0x01 << m
)) { // use this sensor for update
603 for (j
= 0; j
< NUMX
; j
++) { // Find Hp = H*P
607 for (k
= HrowMin
[m
]; k
<= HrowMax
[m
]; k
++) {
608 for (j
= 0; j
< NUMX
; j
++) { // Find Hp = H*P
609 HP
[j
] += H
[m
][k
] * P
[k
][j
];
612 HPHR
= R
[m
]; // Find HPHR = H*P*H' + R
613 for (k
= HrowMin
[m
]; k
<= HrowMax
[m
]; k
++) {
614 HPHR
+= HP
[k
] * H
[m
][k
];
616 float invHPHR
= 1.0f
/ HPHR
;
617 for (k
= 0; k
< NUMX
; k
++) {
618 Km
[k
] = HP
[k
] * invHPHR
; // find K = HP/HPHR
620 for (i
= 0; i
< NUMX
; i
++) { // Find P(m)= P(m-1) + K*HP
621 for (j
= i
; j
< NUMX
; j
++) {
622 P
[i
][j
] = P
[j
][i
] = P
[i
][j
] - Km
[i
] * HP
[j
];
627 for (i
= 0; i
< NUMX
; i
++) { // Find X(m)= X(m-1) + K*Error
628 X
[i
] = X
[i
] + Km
[i
] * Error
;
634 // ************* RungeKutta **********************
635 // Does a 4th order Runge Kutta numerical integration step
636 // Output, Xnew, is written over X
637 // NOTE the algorithm assumes time invariant state equations and
638 // constant inputs over integration step
639 // ************************************************
641 void RungeKutta(float X
[NUMX
], float U
[NUMU
], float dT
)
643 const float dT2
= dT
/ 2.0f
;
644 float K1
[NUMX
], K2
[NUMX
], K3
[NUMX
], K4
[NUMX
], Xlast
[NUMX
];
647 for (i
= 0; i
< NUMX
; i
++) {
648 Xlast
[i
] = X
[i
]; // make a working copy
650 StateEq(X
, U
, K1
); // k1 = f(x,u)
651 for (i
= 0; i
< NUMX
; i
++) {
652 X
[i
] = Xlast
[i
] + dT2
* K1
[i
];
654 StateEq(X
, U
, K2
); // k2 = f(x+0.5*dT*k1,u)
655 for (i
= 0; i
< NUMX
; i
++) {
656 X
[i
] = Xlast
[i
] + dT2
* K2
[i
];
658 StateEq(X
, U
, K3
); // k3 = f(x+0.5*dT*k2,u)
659 for (i
= 0; i
< NUMX
; i
++) {
660 X
[i
] = Xlast
[i
] + dT
* K3
[i
];
662 StateEq(X
, U
, K4
); // k4 = f(x+dT*k3,u)
664 // Xnew = X + dT*(k1+2*k2+2*k3+k4)/6
665 for (i
= 0; i
< NUMX
; i
++) {
667 Xlast
[i
] + dT
* (K1
[i
] + 2.0f
* K2
[i
] + 2.0f
* K3
[i
] +
668 K4
[i
]) * (1.0f
/ 6.0f
);
672 // ************* Model Specific Stuff ***************************
673 // *** StateEq, MeasurementEq, LinerizeFG, and LinearizeH ********
675 // State Variables = [Pos Vel Quaternion GyroBias AccelBias]
676 // Deterministic Inputs = [AngularVel Accel]
677 // Disturbance Noise = [GyroNoise AccelNoise GyroRandomWalkNoise AccelRandomWalkNoise]
679 // Measurement Variables = [Pos Vel BodyFrameMagField Altimeter]
680 // Inputs to Measurement = [EarthFrameMagField]
682 // Notes: Pos and Vel in earth frame
683 // AngularVel and Accel in body frame
684 // MagFields are unit vectors
685 // Xdot is output of StateEq()
686 // F and G are outputs of LinearizeFG(), all elements not set should be zero
687 // y is output of OutputEq()
688 // H is output of LinearizeH(), all elements not set should be zero
689 // ************************************************
691 void StateEq(float X
[NUMX
], float U
[NUMU
], float Xdot
[NUMX
])
693 const float wx
= U
[0] - X
[10];
694 const float wy
= U
[1] - X
[11];
695 const float wz
= U
[2] - X
[12]; // subtract the biases on gyros
696 const float ax
= U
[3];
697 const float ay
= U
[4];
698 const float az
= U
[5] - X
[13]; // subtract the biases on accels
699 const float q0
= X
[6];
700 const float q1
= X
[7];
701 const float q2
= X
[8];
702 const float q3
= X
[9];
711 (q0
* q0
+ q1
* q1
- q2
* q2
- q3
* q3
) * ax
+ 2.0f
* (q1
* q2
-
713 ay
+ 2.0f
* (q1
* q3
+ q0
* q2
) * az
;
715 2.0f
* (q1
* q2
+ q0
* q3
) * ax
+ (q0
* q0
- q1
* q1
+ q2
* q2
-
716 q3
* q3
) * ay
+ 2.0f
* (q2
* q3
-
720 2.0f
* (q1
* q3
- q0
* q2
) * ax
+ 2.0f
* (q2
* q3
+ q0
* q1
) * ay
+
721 (q0
* q0
- q1
* q1
- q2
* q2
+ q3
* q3
) * az
+ PIOS_CONST_MKS_GRAV_ACCEL_F
;
724 Xdot
[6] = (-q1
* wx
- q2
* wy
- q3
* wz
) / 2.0f
;
725 Xdot
[7] = (q0
* wx
- q3
* wy
+ q2
* wz
) / 2.0f
;
726 Xdot
[8] = (q3
* wx
+ q0
* wy
- q1
* wz
) / 2.0f
;
727 Xdot
[9] = (-q2
* wx
+ q1
* wy
+ q0
* wz
) / 2.0f
;
729 // best guess is that bias stays constant
730 Xdot
[10] = Xdot
[11] = Xdot
[12] = 0;
732 // For accels to make sure things stay stable, assume bias always walks weakly
733 // towards zero for the horizontal axis. This prevents drifting around an
734 // unobservable manifold of possible attitudes and gyro biases. The z-axis
735 // we assume no drift because this is the one we want to estimate most accurately.
740 * Linearize the state equations around the current state estimate.
741 * @param[in] X the current state estimate
742 * @param[in] U the control inputs
743 * @param[out] F the linearized natural dynamics
744 * @param[out] G the linearized influence of disturbance model
746 * so the prediction of the next state is
747 * Xdot = F * X + G * U
748 * where X is the current state and U is the current input
750 * For reference the state order (in F) is pos, vel, attitude, gyro bias, accel bias
751 * and the input order is gyro, bias
753 void LinearizeFG(float X
[NUMX
], float U
[NUMU
], float F
[NUMX
][NUMX
],
756 const float wx
= U
[0] - X
[10];
757 const float wy
= U
[1] - X
[11];
758 const float wz
= U
[2] - X
[12]; // subtract the biases on gyros
759 const float ax
= U
[3];
760 const float ay
= U
[4];
761 const float az
= U
[5] - X
[13]; // subtract the biases on accels
762 const float q0
= X
[6];
763 const float q1
= X
[7];
764 const float q2
= X
[8];
765 const float q3
= X
[9];
768 F
[0][3] = F
[1][4] = F
[2][5] = 1.0f
;
771 F
[3][6] = 2.0f
* (q0
* ax
- q3
* ay
+ q2
* az
);
772 F
[3][7] = 2.0f
* (q1
* ax
+ q2
* ay
+ q3
* az
);
773 F
[3][8] = 2.0f
* (-q2
* ax
+ q1
* ay
+ q0
* az
);
774 F
[3][9] = 2.0f
* (-q3
* ax
- q0
* ay
+ q1
* az
);
775 F
[4][6] = 2.0f
* (q3
* ax
+ q0
* ay
- q1
* az
);
776 F
[4][7] = 2.0f
* (q2
* ax
- q1
* ay
- q0
* az
);
777 F
[4][8] = 2.0f
* (q1
* ax
+ q2
* ay
+ q3
* az
);
778 F
[4][9] = 2.0f
* (q0
* ax
- q3
* ay
+ q2
* az
);
779 F
[5][6] = 2.0f
* (-q2
* ax
+ q1
* ay
+ q0
* az
);
780 F
[5][7] = 2.0f
* (q3
* ax
+ q0
* ay
- q1
* az
);
781 F
[5][8] = 2.0f
* (-q0
* ax
+ q3
* ay
- q2
* az
);
782 F
[5][9] = 2.0f
* (q1
* ax
+ q2
* ay
+ q3
* az
);
784 // dVdot/dabias & dVdot/dna - the equations for how the accel input and accel bias influence velocity are the same
785 F
[3][13] = G
[3][5] = -2.0f
* (q1
* q3
+ q0
* q2
);
786 F
[4][13] = G
[4][5] = 2.0f
* (-q2
* q3
+ q0
* q1
);
787 F
[5][13] = G
[5][5] = -q0
* q0
+ q1
* q1
+ q2
* q2
- q3
* q3
;
791 F
[6][7] = -wx
/ 2.0f
;
792 F
[6][8] = -wy
/ 2.0f
;
793 F
[6][9] = -wz
/ 2.0f
;
797 F
[7][9] = -wy
/ 2.0f
;
799 F
[8][7] = -wz
/ 2.0f
;
804 F
[9][8] = -wx
/ 2.0f
;
808 F
[6][10] = q1
/ 2.0f
;
809 F
[6][11] = q2
/ 2.0f
;
810 F
[6][12] = q3
/ 2.0f
;
811 F
[7][10] = -q0
/ 2.0f
;
812 F
[7][11] = q3
/ 2.0f
;
813 F
[7][12] = -q2
/ 2.0f
;
814 F
[8][10] = -q3
/ 2.0f
;
815 F
[8][11] = -q0
/ 2.0f
;
816 F
[8][12] = q1
/ 2.0f
;
817 F
[9][10] = q2
/ 2.0f
;
818 F
[9][11] = -q1
/ 2.0f
;
819 F
[9][12] = -q0
/ 2.0f
;
822 G
[3][3] = -q0
* q0
- q1
* q1
+ q2
* q2
+ q3
* q3
;
823 G
[3][4] = 2 * (-q1
* q2
+ q0
* q3
);
824 // G[3][5] = -2 * (q1 * q3 + q0 * q2); // already assigned above
825 G
[4][3] = -2 * (q1
* q2
+ q0
* q3
);
826 G
[4][4] = -q0
* q0
+ q1
* q1
- q2
* q2
+ q3
* q3
;
827 // G[4][5] = 2 * (-q2 * q3 + q0 * q1); // already assigned above
828 G
[5][3] = 2 * (-q1
* q3
+ q0
* q2
);
829 G
[5][4] = -2 * (q2
* q3
+ q0
* q1
);
830 // G[5][5] = -q0 * q0 + q1 * q1 + q2 * q2 - q3 * q3; // already assigned above
836 G
[7][0] = -q0
/ 2.0f
;
838 G
[7][2] = -q2
/ 2.0f
;
839 G
[8][0] = -q3
/ 2.0f
;
840 G
[8][1] = -q0
/ 2.0f
;
843 G
[9][1] = -q1
/ 2.0f
;
844 G
[9][2] = -q0
/ 2.0f
;
848 * Predicts the measurements from the current state. Note
849 * that this is very similar to @ref LinearizeH except this
850 * directly computes the outputs instead of a matrix that
851 * you transform the state by
853 void MeasurementEq(float X
[NUMX
], float Be
[3], float Y
[NUMV
])
855 const float q0
= X
[6];
856 const float q1
= X
[7];
857 const float q2
= X
[8];
858 const float q3
= X
[9];
860 // first six outputs are P and V
868 // Rotate Be by only the yaw heading
869 const float a1
= 2 * q0
* q3
+ 2 * q1
* q2
;
870 const float a2
= q0
* q0
+ q1
* q1
- q2
* q2
- q3
* q3
;
871 const float r
= sqrtf(a1
* a1
+ a2
* a2
);
872 const float cP
= a2
/ r
;
873 const float sP
= a1
/ r
;
874 Y
[6] = Be
[0] * cP
+ Be
[1] * sP
;
875 Y
[7] = -Be
[0] * sP
+ Be
[1] * cP
;
876 Y
[8] = 0; // don't care
883 * Linearize the measurement around the current state estiamte
884 * so the predicted measurements are
887 void LinearizeH(float X
[NUMX
], float Be
[3], float H
[NUMV
][NUMX
])
889 const float q0
= X
[6];
890 const float q1
= X
[7];
891 const float q2
= X
[8];
892 const float q3
= X
[9];
894 // dP/dP=I; (expect position to measure the position)
895 H
[0][0] = H
[1][1] = H
[2][2] = 1.0f
;
896 // dV/dV=I; (expect velocity to measure the velocity)
897 H
[3][3] = H
[4][4] = H
[5][5] = 1.0f
;
899 // dBb/dq (expected magnetometer readings in the horizontal plane)
900 // these equations were generated by Rhb(q)*Be which is the matrix that
901 // rotates the earth magnetic field into the horizontal plane, and then
902 // taking the partial derivative wrt each term in q. Maniuplated in
903 // matlab symbolic toolbox
904 const float Be_0
= Be
[0];
905 const float Be_1
= Be
[1];
906 const float a1
= q0
* q3
* 2.0f
+ q1
* q2
* 2.0f
;
907 const float a1s
= a1
* a1
;
908 const float a2
= q0
* q0
+ q1
* q1
- q2
* q2
- q3
* q3
;
909 const float a2s
= a2
* a2
;
910 const float a3
= 1.0f
/ powf(a1s
+ a2s
, 3.0f
/ 2.0f
) * (1.0f
/ 2.0f
);
912 const float k1
= 1.0f
/ sqrtf(a1s
+ a2s
);
913 const float k3
= a3
* a2
;
914 const float k4
= a2
* 4.0f
;
915 const float k5
= a1
* 4.0f
;
916 const float k6
= a3
* a1
;
918 H
[6][6] = Be_0
* q0
* k1
* 2.0f
+ Be_1
* q3
* k1
* 2.0f
- Be_0
* (q0
* k4
+ q3
* k5
) * k3
- Be_1
* (q0
* k4
+ q3
* k5
) * k6
;
919 H
[6][7] = Be_0
* q1
* k1
* 2.0f
+ Be_1
* q2
* k1
* 2.0f
- Be_0
* (q1
* k4
+ q2
* k5
) * k3
- Be_1
* (q1
* k4
+ q2
* k5
) * k6
;
920 H
[6][8] = Be_0
* q2
* k1
* -2.0f
+ Be_1
* q1
* k1
* 2.0f
+ Be_0
* (q2
* k4
- q1
* k5
) * k3
+ Be_1
* (q2
* k4
- q1
* k5
) * k6
;
921 H
[6][9] = Be_1
* q0
* k1
* 2.0f
- Be_0
* q3
* k1
* 2.0f
+ Be_0
* (q3
* k4
- q0
* k5
) * k3
+ Be_1
* (q3
* k4
- q0
* k5
) * k6
;
922 H
[7][6] = Be_1
* q0
* k1
* 2.0f
- Be_0
* q3
* k1
* 2.0f
- Be_1
* (q0
* k4
+ q3
* k5
) * k3
+ Be_0
* (q0
* k4
+ q3
* k5
) * k6
;
923 H
[7][7] = Be_0
* q2
* k1
* -2.0f
+ Be_1
* q1
* k1
* 2.0f
- Be_1
* (q1
* k4
+ q2
* k5
) * k3
+ Be_0
* (q1
* k4
+ q2
* k5
) * k6
;
924 H
[7][8] = Be_0
* q1
* k1
* -2.0f
- Be_1
* q2
* k1
* 2.0f
+ Be_1
* (q2
* k4
- q1
* k5
) * k3
- Be_0
* (q2
* k4
- q1
* k5
) * k6
;
925 H
[7][9] = Be_0
* q0
* k1
* -2.0f
- Be_1
* q3
* k1
* 2.0f
+ Be_1
* (q3
* k4
- q0
* k5
) * k3
- Be_0
* (q3
* k4
- q0
* k5
) * k6
;
930 // dAlt/dPz = -1 (expected baro readings)