Sensor Fusion Library 0.6.1
Orientation sensing for Espressif (ESP32, ESP8266) processors
Loading...
Searching...
No Matches
fusion.c
Go to the documentation of this file.
1/*
2 * Copyright (c) 2015, Freescale Semiconductor, Inc.
3 * Copyright 2016-2017 NXP
4 * All rights reserved.
5 *
6 * SPDX-License-Identifier: BSD-3-Clause
7 */
8
9// This is the file that contains the fusion routines. It is STRONGLY RECOMMENDED
10// that the casual developer NOT TOUCH THIS FILE.
11
16#include <math.h>
17
18#include "sensor_fusion.h"
19#include "approximations.h"
20#include "calibration_storage.h"
21#include "fusion.h"
22#include "hal_timer.h" // Hardware Abstraction Layer timer functions
23#include "matrix.h"
24#include "orientation.h"
25
27// intialization functions for the sensor fusion algorithms
29
30// function initializes the sensor fusion and magnetic calibration and sets loopcounter to zero
31void fInitializeFusion(SensorFusionGlobals *sfg)
32{
33 // reset the quaternion type to the default packet type
34 // sfg->pControlSubsystem->QuaternionPacketType = sfg->pControlSubsystem->DefaultQuaternionPacketType;
35
36 // force a reset of all the algorithms next time they execute
37 // the initialization will result in the default and current quaternion being set to the most sophisticated
38 // algorithm supported by the build
39#if F_1DOF_P_BASIC
40 sfg->SV_1DOF_P_BASIC.resetflag = true;
41#endif
42#if F_3DOF_B_BASIC
43 sfg->SV_3DOF_B_BASIC.resetflag = true;
44#endif
45#if F_3DOF_G_BASIC
46 sfg->SV_3DOF_G_BASIC.resetflag = true;
47#endif
48#if F_3DOF_Y_BASIC
49 sfg->SV_3DOF_Y_BASIC.resetflag = true;
50#endif
51#if F_6DOF_GB_BASIC
52 sfg->SV_6DOF_GB_BASIC.resetflag = true;
53#endif
54#if F_6DOF_GY_KALMAN
55 sfg->SV_6DOF_GY_KALMAN.resetflag = true;
56#endif
57#if F_9DOF_GBY_KALMAN
58 sfg->SV_9DOF_GBY_KALMAN.resetflag = true;
59#endif
60
61 // reset the loop counter to zero for first iteration
62 sfg->loopcounter = 0;
63 return;
64}
65
66void fFuseSensors(struct SV_1DOF_P_BASIC *pthisSV_1DOF_P_BASIC,
67 struct SV_3DOF_G_BASIC *pthisSV_3DOF_G_BASIC,
68 struct SV_3DOF_B_BASIC *pthisSV_3DOF_B_BASIC,
69 struct SV_3DOF_Y_BASIC *pthisSV_3DOF_Y_BASIC,
70 struct SV_6DOF_GB_BASIC *pthisSV_6DOF_GB_BASIC,
71 struct SV_6DOF_GY_KALMAN *pthisSV_6DOF_GY_KALMAN,
72 struct SV_9DOF_GBY_KALMAN *pthisSV_9DOF_GBY_KALMAN,
73 struct AccelSensor *pthisAccel,
74 struct MagSensor *pthisMag,
75 struct GyroSensor *pthisGyro,
76 struct PressureSensor *pthisPressure,
77 struct MagCalibration *pthisMagCal)
78{
79 // 1DOF Pressure: call the low pass filter algorithm
80#if F_1DOF_P_BASIC
81 if (pthisSV_1DOF_P_BASIC)
82 {
83 SystickStartCount(&(pthisSV_1DOF_P_BASIC->systick));
84 fRun_1DOF_P_BASIC(pthisSV_1DOF_P_BASIC, pthisPressure);
85 pthisSV_1DOF_P_BASIC->systick = SystickElapsedMicros(pthisSV_1DOF_P_BASIC->systick);
86 }
87#endif
88
89 // 3DOF Accel Basic: call the tilt algorithm
90#if F_3DOF_G_BASIC
91 if (pthisSV_3DOF_G_BASIC)
92 {
93 SystickStartCount(&(pthisSV_3DOF_G_BASIC->systick));
94 fRun_3DOF_G_BASIC(pthisSV_3DOF_G_BASIC, pthisAccel);
95 pthisSV_3DOF_G_BASIC->systick = SystickElapsedMicros(pthisSV_3DOF_G_BASIC->systick);
96 }
97#endif
98
99 // 3DOF Magnetometer Basic: call the 2D vehicle compass algorithm
100#if F_3DOF_B_BASIC
101 if (pthisSV_3DOF_B_BASIC)
102 {
103 SystickStartCount(&(pthisSV_3DOF_B_BASIC->systick));
104 fRun_3DOF_B_BASIC(pthisSV_3DOF_B_BASIC, pthisMag);
105 pthisSV_3DOF_B_BASIC->systick = SystickElapsedMicros(pthisSV_3DOF_B_BASIC->systick);
106 }
107#endif
108
109 // 3DOF Gyro Basic: call the gyro integration algorithm
110#if F_3DOF_Y_BASIC
111 if (pthisSV_3DOF_Y_BASIC)
112 {
113 SystickStartCount(&(pthisSV_3DOF_Y_BASIC->systick));
114 fRun_3DOF_Y_BASIC(pthisSV_3DOF_Y_BASIC, pthisGyro);
115 pthisSV_3DOF_Y_BASIC->systick = SystickElapsedMicros(pthisSV_3DOF_Y_BASIC->systick);
116 }
117#endif
118
119 // 6DOF Accel / Mag: Basic: call the eCompass orientation algorithm
120#if F_6DOF_GB_BASIC
121 if (pthisSV_6DOF_GB_BASIC)
122 {
123 SystickStartCount(&(pthisSV_6DOF_GB_BASIC->systick));
124 fRun_6DOF_GB_BASIC(pthisSV_6DOF_GB_BASIC, pthisMag, pthisAccel);
125 pthisSV_6DOF_GB_BASIC->systick = SystickElapsedMicros(pthisSV_6DOF_GB_BASIC->systick);
126 }
127#endif
128
129 // 6DOF Accel / Gyro: call the Kalman filter orientation algorithm
130#if F_6DOF_GY_KALMAN
131 if (pthisSV_6DOF_GY_KALMAN)
132 {
133 SystickStartCount(&(pthisSV_6DOF_GY_KALMAN->systick));
134 fRun_6DOF_GY_KALMAN(pthisSV_6DOF_GY_KALMAN, pthisAccel, pthisGyro);
135 pthisSV_6DOF_GY_KALMAN->systick = SystickElapsedMicros(pthisSV_6DOF_GY_KALMAN->systick);
136 }
137#endif
138
139 // 9DOF Accel / Mag / Gyro: call the Kalman filter orientation algorithm
140#if F_9DOF_GBY_KALMAN
141 if (pthisSV_9DOF_GBY_KALMAN)
142 {
143 SystickStartCount(&(pthisSV_9DOF_GBY_KALMAN->systick));
144 fRun_9DOF_GBY_KALMAN(pthisSV_9DOF_GBY_KALMAN, pthisAccel, pthisMag,
145 pthisGyro, pthisMagCal);
146 pthisSV_9DOF_GBY_KALMAN->systick = SystickElapsedMicros(pthisSV_9DOF_GBY_KALMAN->systick);
147 }
148#endif
149 return;
150}
151
152void fInit_1DOF_P_BASIC(struct SV_1DOF_P_BASIC *pthisSV,
153 struct PressureSensor *pthisPressure, float flpftimesecs)
154{
155 // set algorithm sampling interval (typically 40Hz) and low pass filter
156 // Note: the MPL3115 sensor only updates its output every 1s and is therefore repeatedly oversampled at 40Hz
157 // but executing the exponenial filter at the 40Hz rate also performs an interpolation giving smoother output.
158 // set algorithm sampling interval (typically 40Hz)
159 pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
160
161 // set low pass filter constant with maximum value 1.0 (all pass) decreasing to 0.0 (increasing low pass)
162 if (flpftimesecs > pthisSV->fdeltat)
163 pthisSV->flpf = pthisSV->fdeltat / flpftimesecs;
164 else
165 pthisSV->flpf = 1.0F;
166
167 // initialize the low pass filters to current measurement
168 pthisSV->fLPH = pthisPressure->fH;
169 pthisSV->fLPT = pthisPressure->fT;
170
171 // clear the reset flag
172 pthisSV->resetflag = false;
173
174 return;
175} // end fInit_1DOF_P_BASIC
176
177void fInit_3DOF_G_BASIC(struct SV_3DOF_G_BASIC *pthisSV,
178 struct AccelSensor *pthisAccel, float flpftimesecs)
179{
180 // set algorithm sampling interval (typically 40Hz)
181 pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
182
183 // set low pass filter constant with maximum value 1.0 (all pass) decreasing to 0.0 (increasing low pass)
184 if (flpftimesecs > pthisSV->fdeltat)
185 pthisSV->flpf = pthisSV->fdeltat / flpftimesecs;
186 else
187 pthisSV->flpf = 1.0F;
188
189 // apply the tilt estimation algorithm to initialize the low pass orientation matrix and quaternion
190#if THISCOORDSYSTEM == NED
191 f3DOFTiltNED(pthisSV->fLPR, pthisAccel->fGc);
192#elif THISCOORDSYSTEM == ANDROID
193 f3DOFTiltAndroid(pthisSV->fLPR, pthisAccel->fGc);
194#else // WIN8
195 f3DOFTiltWin8(pthisSV->fLPR, pthisAccel->fGc);
196#endif
197 fQuaternionFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPq));
198
199 // clear the reset flag
200 pthisSV->resetflag = false;
201
202 return;
203} // end fInit_3DOF_G_BASIC
204
205void fInit_3DOF_B_BASIC(struct SV_3DOF_B_BASIC *pthisSV,
206 struct MagSensor *pthisMag, float flpftimesecs)
207{
208 // set algorithm sampling interval (typically 40Hz)
209 pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
210
211 // set low pass filter constant with maximum value 1.0 (all pass) decreasing to 0.0 (increasing low pass)
212 if (flpftimesecs > pthisSV->fdeltat)
213 pthisSV->flpf = pthisSV->fdeltat / flpftimesecs;
214 else
215 pthisSV->flpf = 1.0F;
216
217 // initialize the low pass filtered magnetometer orientation matrix and quaternion using fBc
218#if THISCOORDSYSTEM == NED
219 f3DOFMagnetometerMatrixNED(pthisSV->fLPR, pthisMag->fBc);
220#elif THISCOORDSYSTEM == ANDROID
221 f3DOFMagnetometerMatrixAndroid(pthisSV->fLPR, pthisMag->fBc);
222#else // WIN8
223 f3DOFMagnetometerMatrixWin8(pthisSV->fLPR, pthisMag->fBc);
224#endif
225 fQuaternionFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPq));
226
227 // clear the reset flag
228 pthisSV->resetflag = false;
229
230 return;
231} // end fInit_3DOF_B_BASIC
232
233void fInit_3DOF_Y_BASIC(struct SV_3DOF_Y_BASIC *pthisSV)
234{
235 // compute the sampling time intervals (secs)
236 pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
237
238 // initialize orientation estimate to flat
239 f3x3matrixAeqI(pthisSV->fR);
240 fqAeq1(&(pthisSV->fq));
241
242 // clear the reset flag
243 pthisSV->resetflag = false;
244
245 return;
246} // end fInit_3DOF_Y_BASIC
247
248void fInit_6DOF_GB_BASIC(struct SV_6DOF_GB_BASIC *pthisSV,
249 struct AccelSensor *pthisAccel,
250 struct MagSensor *pthisMag, float flpftimesecs)
251{
252 float ftmp;
253
254 // set algorithm sampling interval (typically 40Hz)
255 pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
256
257 // set low pass filter constant with maximum value 1.0 (all pass) decreasing to 0.0 (increasing low pass)
258 if (flpftimesecs > pthisSV->fdeltat)
259 pthisSV->flpf = pthisSV->fdeltat / flpftimesecs;
260 else
261 pthisSV->flpf = 1.0F;
262
263 // initialize the instantaneous orientation matrix, inclination angle and quaternion
264#if THISCOORDSYSTEM == NED
265 feCompassNED(pthisSV->fLPR, &(pthisSV->fLPDelta), &ftmp, &ftmp, pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp );
266#elif THISCOORDSYSTEM == ANDROID
267 feCompassAndroid(pthisSV->fLPR, &(pthisSV->fLPDelta), &ftmp, &ftmp, pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
268#else // WIN8
269 feCompassWin8(pthisSV->fLPR, &(pthisSV->fLPDelta), &ftmp, &ftmp, pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
270#endif
271 fQuaternionFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPq));
272
273 // clear the reset flag
274 pthisSV->resetflag = false;
275
276 return;
277} // end fInit_6DOF_GB_BASIC
278
279// function initalizes the 6DOF accel + gyro Kalman filter algorithm
280void fInit_6DOF_GY_KALMAN(struct SV_6DOF_GY_KALMAN *pthisSV,
281 struct AccelSensor *pthisAccel,
282 struct GyroSensor *pthisGyro)
283{
284 int8_t i; // loop counter
285
286 // compute and store useful product terms to save floating point calculations later
287 pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
288 pthisSV->fQwbOver3 = FQWB_6DOF_GY_KALMAN / 3.0F;
289 pthisSV->fAlphaOver2 = FPIOVER180 * pthisSV->fdeltat / 2.0F;
290 pthisSV->fAlphaSqOver4 = pthisSV->fAlphaOver2 * pthisSV->fAlphaOver2;
291 pthisSV->fAlphaQwbOver6 = pthisSV->fAlphaOver2 * pthisSV->fQwbOver3;
293 pthisSV->fMaxGyroOffsetChange = sqrtf(fabs(FQWB_6DOF_GY_KALMAN)) / (float)FUSION_HZ;
294
295 // zero the a posteriori gyro offset and error vectors
296 for (i = CHX; i <= CHZ; i++)
297 {
298 pthisSV->fqgErrPl[i] = 0.0F;
299 pthisSV->fbErrPl[i] = 0.0F;
300 }
301
302 // check to see if a gyro calibration exists in flash, and load it
303#ifndef SIMULATION
304 float pFlash[3]; // pointer to flash float words
305 if (GetGyroCalibrationFromNVM(pFlash))
306 {
307 // copy the gyro calibration from flash into the state vector
308 for (i = CHX; i <= CHZ; i++) pthisSV->fbPl[i] = pFlash[i];
309 }
310 else
311 {
312#endif
313 // set the gyro offset to the current measurement if within limits
314 for (i = CHX; i <= CHZ; i++)
315 {
316 if ((pthisGyro->fYs[i] >= FMIN_6DOF_GY_BPL) &&
317 (pthisGyro->fYs[i] <= FMAX_6DOF_GY_BPL))
318 pthisSV->fbPl[i] = pthisGyro->fYs[i];
319 else
320 pthisSV->fbPl[i] = 0.0F;
321 }
322#ifndef SIMULATION
323 }
324#endif
325 // initialize the a posteriori orientation state vector to the tilt orientation
326#if THISCOORDSYSTEM == NED
327 f3DOFTiltNED(pthisSV->fRPl, pthisAccel->fGc);
328#elif THISCOORDSYSTEM == ANDROID
329 f3DOFTiltAndroid(pthisSV->fRPl, pthisAccel->fGc);
330#else // Win8
331 f3DOFTiltWin8(pthisSV->fRPl, pthisAccel->fGc);
332#endif
333 fQuaternionFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fqPl));
334
335 // clear the reset flag
336 pthisSV->resetflag = false;
337
338 return;
339} // end fInit_6DOF_GY_KALMAN
340
341// function initializes the 9DOF Kalman filter
342void fInit_9DOF_GBY_KALMAN(struct SV_9DOF_GBY_KALMAN *pthisSV, struct AccelSensor *pthisAccel, struct MagSensor *pthisMag,
343 struct GyroSensor *pthisGyro, struct MagCalibration *pthisMagCal)
344{
345 float ftmp;// scratch
346 int8_t i;// loop counter
347
348 // compute and store useful product terms to save floating point calculations later
349 pthisSV->fdeltat = 1.0F / (float) FUSION_HZ;
350 pthisSV->fgdeltat = GTOMSEC2 * pthisSV->fdeltat;
351 pthisSV->fQwbOver3 = FQWB_9DOF_GBY_KALMAN / 3.0F;
352 pthisSV->fAlphaOver2 = FPIOVER180 * pthisSV->fdeltat / 2.0F;
353 pthisSV->fAlphaSqOver4 = pthisSV->fAlphaOver2 * pthisSV->fAlphaOver2;
354 pthisSV->fAlphaQwbOver6 = pthisSV->fAlphaOver2 * pthisSV->fQwbOver3;
356 pthisSV->fMaxGyroOffsetChange = sqrtf(fabs(FQWB_9DOF_GBY_KALMAN)) / (float)FUSION_HZ;
357
358 // zero the a posteriori error vectors and inertial outputs
359 for (i = CHX; i <= CHZ; i++) {
360 pthisSV->fqgErrPl[i] = 0.0F;
361 pthisSV->fqmErrPl[i] = 0.0F;
362 pthisSV->fbErrPl[i] = 0.0F;
363 pthisSV->fVelGl[i] = 0.0F;
364 pthisSV->fDisGl[i] = 0.0F;
365 }
366
367 // check to see if a gyro calibration exists in flash
368#ifndef SIMULATION
369 float pFlash[3]; // pointer to flash float words
370 if (GetGyroCalibrationFromNVM(pFlash))
371 {
372 // copy the gyro calibration from flash into the state vector
373 for (i = CHX; i <= CHZ; i++) pthisSV->fbPl[i] = pFlash[i];
374 } else {
375#endif
376 // set the gyro offset to the current measurement if within limits
377 for (i = CHX; i <= CHZ; i++) {
378 if ((pthisGyro->fYs[i] >= FMIN_9DOF_GBY_BPL) && (pthisGyro->fYs[i] <= FMAX_9DOF_GBY_BPL))
379 pthisSV->fbPl[i] = pthisGyro->fYs[i];
380 else
381 pthisSV->fbPl[i] = 0.0F;
382 }
383#ifndef SIMULATION
384 }
385#endif
386
387 // initialize the posteriori orientation state vector to the instantaneous eCompass orientation
388 pthisSV->iFirstAccelMagLock = false;
389#if THISCOORDSYSTEM == NED
390 feCompassNED(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
391 pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
392#elif THISCOORDSYSTEM == ANDROID
393 feCompassAndroid(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
394 pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
395#else // WIN8
396 feCompassWin8(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
397 pthisMag->fBc, pthisAccel->fGc, &ftmp, &ftmp);
398#endif
399 fQuaternionFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fqPl));
400
401 // clear the reset flag
402 pthisSV->resetflag = false;
403
404 return;
405} // end fInit_9DOF_GBY_KALMAN
406
408
409// run time functions for the sensor fusion algorithms
411
412// 1DOF pressure function
413void fRun_1DOF_P_BASIC(struct SV_1DOF_P_BASIC *pthisSV,
414 struct PressureSensor *pthisPressure)
415{
416 // if requested, do a reset and return
417 if (pthisSV->resetflag)
418 {
419 fInit_1DOF_P_BASIC(pthisSV, pthisPressure, FLPFSECS_1DOF_P_BASIC);
420 return;
421 }
422
423 // exponentially low pass filter the pressure and temperature.
424 // when executed at (typically) 40Hz, this filter interpolates the raw signals which are
425 // output every 1s in Auto Acquisition mode.
426 pthisSV->fLPH += pthisSV->flpf * (pthisPressure->fH - pthisSV->fLPH);
427 pthisSV->fLPT += pthisSV->flpf * (pthisPressure->fT - pthisSV->fLPT);
428
429 return;
430} // end fRun_1DOF_P_BASIC
431
432// 3DOF orientation function which calls tilt functions and implements low pass filters
433void fRun_3DOF_G_BASIC(struct SV_3DOF_G_BASIC *pthisSV,
434 struct AccelSensor *pthisAccel)
435{
436 // if requested, do a reset and return
437 if (pthisSV->resetflag)
438 {
439 fInit_3DOF_G_BASIC(pthisSV, pthisAccel, FLPFSECS_3DOF_G_BASIC);
440 return;
441 }
442
443 // apply the tilt estimation algorithm to get the instantaneous orientation matrix
444#if THISCOORDSYSTEM == NED
445 f3DOFTiltNED(pthisSV->fR, pthisAccel->fGc);
446#elif THISCOORDSYSTEM == ANDROID
447 f3DOFTiltAndroid(pthisSV->fR, pthisAccel->fGc);
448#else // WIN8
449 f3DOFTiltWin8(pthisSV->fR, pthisAccel->fGc);
450#endif
451
452 // compute the instantaneous quaternion and low pass filter
453 fQuaternionFromRotationMatrix(pthisSV->fR, &(pthisSV->fq));
454 fLPFOrientationQuaternion(&(pthisSV->fq), &(pthisSV->fLPq), pthisSV->flpf,
455 pthisSV->fdeltat, pthisSV->fOmega);
456
457 // compute the low pass rotation matrix and rotation vector from low pass filtered quaternion
458 fRotationMatrixFromQuaternion(pthisSV->fLPR, &(pthisSV->fLPq));
459 fRotationVectorDegFromQuaternion(&(pthisSV->fLPq), pthisSV->fLPRVec);
460
461 // calculate the Euler angles from the low pass orientation matrix
462#if THISCOORDSYSTEM == NED
463 fNEDAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
464 &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
465 &(pthisSV->fLPRho), &(pthisSV->fLPChi));
466#elif THISCOORDSYSTEM == ANDROID
467 fAndroidAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
468 &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
469 &(pthisSV->fLPRho), &(pthisSV->fLPChi));
470#else // WIN8
471 fWin8AnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
472 &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
473 &(pthisSV->fLPRho), &(pthisSV->fLPChi));
474#endif
475
476 // force the yaw and compass angles to zero
477 pthisSV->fLPPsi = pthisSV->fLPRho = 0.0F;
478
479 return;
480} // end fRun_3DOF_G_BASIC
481
482// 2D automobile eCompass
483void fRun_3DOF_B_BASIC(struct SV_3DOF_B_BASIC *pthisSV, struct MagSensor *pthisMag)
484{
485 // if requested, do a reset and return
486 if (pthisSV->resetflag)
487 {
488 fInit_3DOF_B_BASIC(pthisSV, pthisMag, FLPFSECS_3DOF_B_BASIC);
489 return;
490 }
491
492 // calculate the 3DOF magnetometer orientation matrix from fBc
493#if THISCOORDSYSTEM == NED
494 f3DOFMagnetometerMatrixNED(pthisSV->fR, pthisMag->fBc);
495#elif THISCOORDSYSTEM == ANDROID
496 f3DOFMagnetometerMatrixAndroid(pthisSV->fR, pthisMag->fBc);
497#else // WIN8
498 f3DOFMagnetometerMatrixWin8(pthisSV->fR, pthisMag->fBc);
499#endif
500
501 // compute the instantaneous quaternion and low pass filter
502 fQuaternionFromRotationMatrix(pthisSV->fR, &(pthisSV->fq));
503 fLPFOrientationQuaternion(&(pthisSV->fq), &(pthisSV->fLPq), pthisSV->flpf,
504 pthisSV->fdeltat, pthisSV->fOmega);
505
506 // compute the low pass rotation matrix and rotation vector from low pass filtered quaternion
507 fRotationMatrixFromQuaternion(pthisSV->fLPR, &(pthisSV->fLPq));
508 fRotationVectorDegFromQuaternion(&(pthisSV->fLPq), pthisSV->fLPRVec);
509
510 // calculate the Euler angles from the low pass orientation matrix
511#if THISCOORDSYSTEM == NED
512 fNEDAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
513 &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
514 &(pthisSV->fLPRho), &(pthisSV->fLPChi));
515#elif THISCOORDSYSTEM == ANDROID
516 fAndroidAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
517 &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
518 &(pthisSV->fLPRho), &(pthisSV->fLPChi));
519#else // WIN8
520 fWin8AnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
521 &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
522 &(pthisSV->fLPRho), &(pthisSV->fLPChi));
523#endif
524 return;
525}
526
527// basic gyro integration function
528void fRun_3DOF_Y_BASIC(struct SV_3DOF_Y_BASIC *pthisSV,
529 struct GyroSensor *pthisGyro)
530{
531 Quaternion ftmpq; // scratch quaternion
532 int8_t i; // loop counter
533
534 // if requested, do a reset and return
535 if (pthisSV->resetflag)
536 {
537 fInit_3DOF_Y_BASIC(pthisSV);
538 return;
539 }
540
541 // perform an approximate gyro integration for this algorithm using the average of all gyro measurments
542 // in the FIFO rather than computing the products of the individual quaternions.
543 // the reason is this algorithm does not estimate and subtract the gyro offset so any loss of integration accuracy
544 // from using the average gyro measurement is irrelevant.
545 // calculate the angular velocity (deg/s) and rotation vector from the average measurement
546 for (i = CHX; i <= CHZ; i++) pthisSV->fOmega[i] = pthisGyro->fYs[i];
547
548 // compute the incremental rotation quaternion ftmpq, rotate the orientation quaternion fq
549 // and re-normalize fq to prevent error propagation
550 fQuaternionFromRotationVectorDeg(&ftmpq, pthisSV->fOmega, pthisSV->fdeltat);
551 qAeqAxB(&(pthisSV->fq), &ftmpq);
552 fqAeqNormqA(&(pthisSV->fq));
553
554 // get the rotation matrix and rotation vector from the orientation quaternion fq
555 fRotationMatrixFromQuaternion(pthisSV->fR, &(pthisSV->fq));
556 fRotationVectorDegFromQuaternion(&(pthisSV->fq), pthisSV->fRVec);
557
558 // compute the Euler angles from the orientation matrix
559#if THISCOORDSYSTEM == NED
560 fNEDAnglesDegFromRotationMatrix(pthisSV->fR, &(pthisSV->fPhi),
561 &(pthisSV->fThe), &(pthisSV->fPsi),
562 &(pthisSV->fRho), &(pthisSV->fChi));
563#elif THISCOORDSYSTEM == ANDROID
564 fAndroidAnglesDegFromRotationMatrix(pthisSV->fR, &(pthisSV->fPhi),
565 &(pthisSV->fThe), &(pthisSV->fPsi),
566 &(pthisSV->fRho), &(pthisSV->fChi));
567#else // WIN8
568 fWin8AnglesDegFromRotationMatrix(pthisSV->fR, &(pthisSV->fPhi),
569 &(pthisSV->fThe), &(pthisSV->fPsi),
570 &(pthisSV->fRho), &(pthisSV->fChi));
571#endif
572 return;
573} // end fRun_3DOF_Y_BASIC
574
575// 6DOF eCompass orientation function
576void fRun_6DOF_GB_BASIC(struct SV_6DOF_GB_BASIC *pthisSV,
577 struct MagSensor *pthisMag, struct AccelSensor *pthisAccel)
578{
579 float ftmp1, ftmp2, ftmp3, ftmp4;
580
581 // if requested, do a reset and return
582 if (pthisSV->resetflag)
583 {
584 fInit_6DOF_GB_BASIC(pthisSV, pthisAccel, pthisMag,
585 FLPFSECS_6DOF_GB_BASIC);
586 return;
587 }
588
589 // call the eCompass algorithm to get the instantaneous orientation matrix and inclination angle
590#if THISCOORDSYSTEM == NED
591 feCompassNED(pthisSV->fR, &(pthisSV->fDelta), &ftmp1, &ftmp2, pthisMag->fBc, pthisAccel->fGc, &ftmp3, &ftmp4);
592#elif THISCOORDSYSTEM == ANDROID
593 feCompassAndroid(pthisSV->fR, &(pthisSV->fDelta), &ftmp1, &ftmp2, pthisMag->fBc, pthisAccel->fGc, &ftmp3, &ftmp4);
594#else // WIN8
595 feCompassWin8(pthisSV->fR, &(pthisSV->fDelta), &ftmp1, &ftmp2, pthisMag->fBc, pthisAccel->fGc, &ftmp3, &ftmp4);
596#endif
597
598 // compute the instantaneous quaternion and low pass filter
599 fQuaternionFromRotationMatrix(pthisSV->fR, &(pthisSV->fq));
600 fLPFOrientationQuaternion(&(pthisSV->fq), &(pthisSV->fLPq), pthisSV->flpf,
601 pthisSV->fdeltat, pthisSV->fOmega);
602
603 // compute the low pass rotation matrix and rotation vector from low pass filtered quaternion
604 fRotationMatrixFromQuaternion(pthisSV->fLPR, &(pthisSV->fLPq));
605 fRotationVectorDegFromQuaternion(&(pthisSV->fLPq), pthisSV->fLPRVec);
606
607 // calculate the Euler angles from the low pass orientation matrix
608#if THISCOORDSYSTEM == NED
609 fNEDAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
610 &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
611 &(pthisSV->fLPRho), &(pthisSV->fLPChi));
612#elif THISCOORDSYSTEM == ANDROID
613 fAndroidAnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
614 &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
615 &(pthisSV->fLPRho), &(pthisSV->fLPChi));
616#else // WIN8
617 fWin8AnglesDegFromRotationMatrix(pthisSV->fLPR, &(pthisSV->fLPPhi),
618 &(pthisSV->fLPThe), &(pthisSV->fLPPsi),
619 &(pthisSV->fLPRho), &(pthisSV->fLPChi));
620#endif
621
622 // low pass filter the geomagnetic inclination angle with a simple exponential filter
623 pthisSV->fLPDelta += pthisSV->flpf * (pthisSV->fDelta - pthisSV->fLPDelta);
624
625 return;
626} // end fRun_6DOF_GB_BASIC
627
628// 6DOF accelerometer+gyroscope orientation function implemented using indirect complementary Kalman filter
629void fRun_6DOF_GY_KALMAN(struct SV_6DOF_GY_KALMAN *pthisSV,
630 struct AccelSensor *pthisAccel,
631 struct GyroSensor *pthisGyro)
632{
633 // local scalars and arrays
634 float ftmpMi3x1[3]; // temporary vector used for a priori calculations
635 float ftmp3DOF3x1[3]; // temporary vector used for 3DOF calculations
636 float ftmpA3x3[3][3]; // scratch 3x3 matrix
637 float fQvGQa; // accelerometer noise covariance to 1g sphere
638 float fC3x6ik; // element i, k of measurement matrix C
639 float fC3x6jk; // element j, k of measurement matrix C
640 float fmodGc; // modulus of fGc[]
641 Quaternion fqMi; // a priori orientation quaternion
642 Quaternion ftmpq; // scratch quaternion
643 float ftmp; // scratch float
644 int8_t ierror; // matrix inversion error flag
645 int8_t i,
646 j,
647 k; // loop counters
648
649 // working arrays for 3x3 matrix inversion
650 float *pfRows[3];
651 int8_t iColInd[3];
652 int8_t iRowInd[3];
653 int8_t iPivot[3];
654
655 // if requested, do a reset initialization with no further processing
656 if (pthisSV->resetflag)
657 {
658 fInit_6DOF_GY_KALMAN(pthisSV, pthisAccel, pthisGyro);
659 return;
660 }
661
662 // compute the average angular velocity (used for display only) from the average measurement minus gyro offset
663 for (i = CHX; i <= CHZ; i++)
664 pthisSV->fOmega[i] = (float) pthisGyro->iYs[i] *
665 pthisGyro->fDegPerSecPerCount -
666 pthisSV->fbPl[i];
667
668 // initialize the a priori orientation quaternion fqMi to the previous iteration's a posteriori estimate
669 // and incrementally rotate fqMi by the contents of the gyro FIFO buffer
670 fqMi = pthisSV->fqPl;
671 if (pthisGyro->iFIFOCount > 0)
672 {
673 // set ftmp to the interval between the FIFO gyro measurements
674 ftmp = pthisSV->fdeltat / (float) pthisGyro->iFIFOCount;
675
676 // normal case, loop over all the buffered gyroscope measurements
677 for (j = 0; j < pthisGyro->iFIFOCount; j++)
678 {
679 // calculate the instantaneous angular velocity subtracting the gyro offset
680 for (i = CHX; i <= CHZ; i++)
681 ftmpMi3x1[i] = (float) pthisGyro->iYsFIFO[j][i] *
682 pthisGyro->fDegPerSecPerCount -
683 pthisSV->fbPl[i];
684
685 // compute the incremental rotation quaternion ftmpq and integrate the a priori orientation quaternion fqMi
686 fQuaternionFromRotationVectorDeg(&ftmpq, ftmpMi3x1, ftmp);
687 qAeqAxB(&fqMi, &ftmpq);
688 }
689 }
690 else
691 {
692 // special case with no new FIFO measurements, use the previous iteration's average gyro reading to compute
693 // the incremental rotation quaternion ftmpq and integrate the a priori orientation quaternion fqMi
695 pthisSV->fdeltat);
696 qAeqAxB(&fqMi, &ftmpq);
697 }
698
699 // set ftmp3DOF3x1 to the 3DOF gravity vector in the sensor frame
700 fmodGc = sqrtf(fabs(pthisAccel->fGc[CHX] * pthisAccel->fGc[CHX] +
701 pthisAccel->fGc[CHY] * pthisAccel->fGc[CHY] +
702 pthisAccel->fGc[CHZ] * pthisAccel->fGc[CHZ]));
703 if (fmodGc != 0.0F)
704 {
705 // normal non-freefall case
706 ftmp = 1.0F / fmodGc;
707 ftmp3DOF3x1[CHX] = pthisAccel->fGc[CHX] * ftmp;
708 ftmp3DOF3x1[CHY] = pthisAccel->fGc[CHY] * ftmp;
709 ftmp3DOF3x1[CHZ] = pthisAccel->fGc[CHZ] * ftmp;
710 }
711 else
712 {
713 // use zero tilt in case of freefall
714 ftmp3DOF3x1[CHX] = 0.0F;
715 ftmp3DOF3x1[CHY] = 0.0F;
716 ftmp3DOF3x1[CHZ] = 1.0F;
717 }
718
719 // correct accelerometer gravity vector for different coordinate systems
720#if THISCOORDSYSTEM == NED
721 // +1g in accelerometer z axis (z down) when PCB is flat so no correction needed
722#elif THISCOORDSYSTEM == ANDROID
723 // +1g in accelerometer z axis (z up) when PCB is flat so negate the vector to obtain gravity
724 ftmp3DOF3x1[CHX] = -ftmp3DOF3x1[CHX];
725 ftmp3DOF3x1[CHY] = -ftmp3DOF3x1[CHY];
726 ftmp3DOF3x1[CHZ] = -ftmp3DOF3x1[CHZ];
727#else // WIN8
728 // -1g in accelerometer z axis (z up) when PCB is flat so no correction needed
729#endif
730
731 // set ftmpMi3x1 to the a priori gravity vector in the sensor frame from the a priori quaternion
732 ftmpMi3x1[CHX] = 2.0F * (fqMi.q1 * fqMi.q3 - fqMi.q0 * fqMi.q2);
733 ftmpMi3x1[CHY] = 2.0F * (fqMi.q2 * fqMi.q3 + fqMi.q0 * fqMi.q1);
734 ftmpMi3x1[CHZ] = 2.0F * (fqMi.q0 * fqMi.q0 + fqMi.q3 * fqMi.q3) - 1.0F;
735
736 // correct a priori gravity vector for different coordinate systems
737#if THISCOORDSYSTEM == NED
738 // z axis is down (NED) so no correction needed
739#else // ANDROID and WIN8
740 // z axis is up (ANDROID and WIN8 ENU) so no negate the vector to obtain gravity
741 ftmpMi3x1[CHX] = -ftmpMi3x1[CHX];
742 ftmpMi3x1[CHY] = -ftmpMi3x1[CHY];
743 ftmpMi3x1[CHZ] = -ftmpMi3x1[CHZ];
744#endif
745
746 // calculate the rotation quaternion between 3DOF and a priori gravity vectors (ignored minus signs cancel here)
747 // and copy the quaternion vector components to the measurement error vector fZErr
748 fveqconjgquq(&ftmpq, ftmp3DOF3x1, ftmpMi3x1);
749 pthisSV->fZErr[CHX] = ftmpq.q1;
750 pthisSV->fZErr[CHY] = ftmpq.q2;
751 pthisSV->fZErr[CHZ] = ftmpq.q3;
752
753 // update Qw using the a posteriori error vectors from the previous iteration.
754 // as Qv increases or Qw decreases, K -> 0 and the Kalman filter is weighted towards the a priori prediction
755 // as Qv decreases or Qw increases, KC -> I and the Kalman filter is weighted towards the measurement.
756 // initialize Qw to all zeroes
757 for (i = 0; i < 6; i++)
758 for (j = 0; j < 6; j++) pthisSV->fQw6x6[i][j] = 0.0F;
759
760 // partial diagonal gyro offset terms
761 pthisSV->fQw6x6[3][3] = pthisSV->fbErrPl[CHX] * pthisSV->fbErrPl[CHX];
762 pthisSV->fQw6x6[4][4] = pthisSV->fbErrPl[CHY] * pthisSV->fbErrPl[CHY];
763 pthisSV->fQw6x6[5][5] = pthisSV->fbErrPl[CHZ] * pthisSV->fbErrPl[CHZ];
764
765 // diagonal gravity vector components
766 pthisSV->fQw6x6[0][0] = pthisSV->fqgErrPl[CHX] *
767 pthisSV->fqgErrPl[CHX] +
768 pthisSV->fAlphaSqQvYQwbOver12 +
769 pthisSV->fAlphaSqOver4 *
770 pthisSV->fQw6x6[3][3];
771 pthisSV->fQw6x6[1][1] = pthisSV->fqgErrPl[CHY] *
772 pthisSV->fqgErrPl[CHY] +
773 pthisSV->fAlphaSqQvYQwbOver12 +
774 pthisSV->fAlphaSqOver4 *
775 pthisSV->fQw6x6[4][4];
776 pthisSV->fQw6x6[2][2] = pthisSV->fqgErrPl[CHZ] *
777 pthisSV->fqgErrPl[CHZ] +
778 pthisSV->fAlphaSqQvYQwbOver12 +
779 pthisSV->fAlphaSqOver4 *
780 pthisSV->fQw6x6[5][5];
781
782 // remaining diagonal gyro offset components
783 pthisSV->fQw6x6[3][3] += pthisSV->fQwbOver3;
784 pthisSV->fQw6x6[4][4] += pthisSV->fQwbOver3;
785 pthisSV->fQw6x6[5][5] += pthisSV->fQwbOver3;
786
787 // off diagonal gravity and gyro offset components
788 pthisSV->fQw6x6[0][3] = pthisSV->fQw6x6[3][0] = pthisSV->fqgErrPl[CHX] *
789 pthisSV->fbErrPl[CHX] -
790 pthisSV->fAlphaOver2 *
791 pthisSV->fQw6x6[3][3];
792 pthisSV->fQw6x6[1][4] = pthisSV->fQw6x6[4][1] = pthisSV->fqgErrPl[CHY] *
793 pthisSV->fbErrPl[CHY] -
794 pthisSV->fAlphaOver2 *
795 pthisSV->fQw6x6[4][4];
796 pthisSV->fQw6x6[2][5] = pthisSV->fQw6x6[5][2] = pthisSV->fqgErrPl[CHZ] *
797 pthisSV->fbErrPl[CHZ] -
798 pthisSV->fAlphaOver2 *
799 pthisSV->fQw6x6[5][5];
800
801 // calculate the vector fQv containing the diagonal elements of the measurement covariance matrix Qv
802 ftmp = fmodGc - 1.0F;
803 fQvGQa = 3.0F * ftmp * ftmp;
804 if (fQvGQa < FQVG_6DOF_GY_KALMAN) fQvGQa = FQVG_6DOF_GY_KALMAN;
805 pthisSV->fQv = ONEOVER12 * fQvGQa + pthisSV->fAlphaSqQvYQwbOver12;
806
807 // calculate the 6x3 Kalman gain matrix K = Qw * C^T * inv(C * Qw * C^T + Qv)
808 // set fQwCT6x3 = Qw.C^T where Qw has size 6x6 and C^T has size 6x3
809 for (i = 0; i < 6; i++) // loop over rows
810 {
811 for (j = 0; j < 3; j++) // loop over columns
812 {
813 pthisSV->fQwCT6x3[i][j] = 0.0F;
814
815 // accumulate matrix sum
816 for (k = 0; k < 6; k++)
817 {
818 // determine fC3x6[j][k] since the matrix is highly sparse
819 fC3x6jk = 0.0F;
820 if (k == j) fC3x6jk = 1.0F;
821 if (k == (j + 3)) fC3x6jk = -pthisSV->fAlphaOver2;
822
823 // accumulate fQwCT6x3[i][j] += Qw6x6[i][k] * C[j][k]
824 if ((pthisSV->fQw6x6[i][k] != 0.0F) && (fC3x6jk != 0.0F))
825 {
826 if (fC3x6jk == 1.0F)
827 pthisSV->fQwCT6x3[i][j] += pthisSV->fQw6x6[i][k];
828 else
829 pthisSV->fQwCT6x3[i][j] += pthisSV->fQw6x6[i][k] * fC3x6jk;
830 }
831 }
832 }
833 }
834
835 // set symmetric ftmpA3x3 = C.(Qw.C^T) + Qv = C.fQwCT6x3 + Qv
836 for (i = 0; i < 3; i++) // loop over rows
837 {
838 for (j = i; j < 3; j++) // loop over on and above diagonal columns
839 {
840 // zero off diagonal and set diagonal to Qv
841 if (i == j)
842 ftmpA3x3[i][j] = pthisSV->fQv;
843 else
844 ftmpA3x3[i][j] = 0.0F;
845
846 // accumulate matrix sum
847 for (k = 0; k < 6; k++)
848 {
849 // determine fC3x6[i][k]
850 fC3x6ik = 0.0F;
851 if (k == i) fC3x6ik = 1.0F;
852 if (k == (i + 3)) fC3x6ik = -pthisSV->fAlphaOver2;
853
854 // accumulate ftmpA3x3[i][j] += C[i][k] & fQwCT6x3[k][j]
855 if ((fC3x6ik != 0.0F) && (pthisSV->fQwCT6x3[k][j] != 0.0F))
856 {
857 if (fC3x6ik == 1.0F)
858 ftmpA3x3[i][j] += pthisSV->fQwCT6x3[k][j];
859 else
860 ftmpA3x3[i][j] += fC3x6ik * pthisSV->fQwCT6x3[k][j];
861 }
862 }
863 }
864 }
865
866 // set ftmpA3x3 below diagonal elements to above diagonal elements
867 ftmpA3x3[1][0] = ftmpA3x3[0][1];
868 ftmpA3x3[2][0] = ftmpA3x3[0][2];
869 ftmpA3x3[2][1] = ftmpA3x3[1][2];
870
871 // invert ftmpA3x3 in situ to give ftmpA3x3 = inv(C * Qw * C^T + Qv) = inv(ftmpA3x3)
872 for (i = 0; i < 3; i++) pfRows[i] = ftmpA3x3[i];
873 fmatrixAeqInvA(pfRows, iColInd, iRowInd, iPivot, 3, &ierror);
874
875 // on successful inversion set Kalman gain matrix fK6x3 = Qw * C^T * inv(C * Qw * C^T + Qv) = fQwCT6x3 * ftmpA3x3
876 if (!ierror)
877 {
878 // normal case
879 for (i = 0; i < 6; i++) // loop over rows
880 {
881 for (j = 0; j < 3; j++) // loop over columns
882 {
883 pthisSV->fK6x3[i][j] = 0.0F;
884 for (k = 0; k < 3; k++)
885 {
886 if ((pthisSV->fQwCT6x3[i][k] != 0.0F) &&
887 (ftmpA3x3[k][j] != 0.0F))
888 {
889 pthisSV->fK6x3[i][j] += pthisSV->fQwCT6x3[i][k] * ftmpA3x3[k][j];
890 }
891 }
892 }
893 }
894 }
895 else
896 {
897 // ftmpA3x3 was singular so set Kalman gain matrix fK6x3 to zero
898 for (i = 0; i < 6; i++) // loop over rows
899 {
900 for (j = 0; j < 3; j++) // loop over columns
901 {
902 pthisSV->fK6x3[i][j] = 0.0F;
903 }
904 }
905 }
906
907 // calculate the a posteriori gravity and geomagnetic tilt quaternion errors and gyro offset error vector
908 // from the Kalman matrix fK6x3 and from the measurement error vector fZErr.
909 for (i = CHX; i <= CHZ; i++)
910 {
911 // gravity tilt vector error component
912 if (pthisSV->fK6x3[i][CHX] != 0.0F)
913 pthisSV->fqgErrPl[i] = pthisSV->fK6x3[i][CHX] * pthisSV->fZErr[CHX];
914 else
915 pthisSV->fqgErrPl[i] = 0.0F;
916 if (pthisSV->fK6x3[i][CHY] != 0.0F)
917 pthisSV->fqgErrPl[i] += pthisSV->fK6x3[i][CHY] * pthisSV->fZErr[CHY];
918 if (pthisSV->fK6x3[i][CHZ] != 0.0F)
919 pthisSV->fqgErrPl[i] += pthisSV->fK6x3[i][CHZ] * pthisSV->fZErr[CHZ];
920
921 // gyro offset vector error component
922 if (pthisSV->fK6x3[i + 3][CHX] != 0.0F)
923 pthisSV->fbErrPl[i] = pthisSV->fK6x3[i + 3][CHX] * pthisSV->fZErr[CHX];
924 else
925 pthisSV->fbErrPl[i] = 0.0F;
926 if (pthisSV->fK6x3[i + 3][CHY] != 0.0F)
927 pthisSV->fbErrPl[i] += pthisSV->fK6x3[i + 3][CHY] * pthisSV->fZErr[CHY];
928 if (pthisSV->fK6x3[i + 3][CHZ] != 0.0F)
929 pthisSV->fbErrPl[i] += pthisSV->fK6x3[i + 3][CHZ] * pthisSV->fZErr[CHZ];
930 }
931
932 // set ftmpq to the gravity tilt correction (conjugate) quaternion
933 ftmpq.q1 = -pthisSV->fqgErrPl[CHX];
934 ftmpq.q2 = -pthisSV->fqgErrPl[CHY];
935 ftmpq.q3 = -pthisSV->fqgErrPl[CHZ];
936 ftmp = ftmpq.q1 * ftmpq.q1 + ftmpq.q2 * ftmpq.q2 + ftmpq.q3 * ftmpq.q3;
937
938 // determine the scalar component q0 to enforce normalization
939 if (ftmp <= 1.0F)
940 {
941 // normal case
942 ftmpq.q0 = sqrtf(fabsf(1.0F - ftmp));
943 }
944 else
945 {
946 // if vector component exceeds unity then set to 180 degree rotation and force normalization
947 ftmp = 1.0F / sqrtf(ftmp);
948 ftmpq.q0 = 0.0F;
949 ftmpq.q1 *= ftmp;
950 ftmpq.q2 *= ftmp;
951 ftmpq.q3 *= ftmp;
952 }
953
954 // apply the gravity tilt correction quaternion so fqPl = fqMi.(fqgErrPl)* = fqMi.ftmpq and normalize
955 qAeqBxC(&(pthisSV->fqPl), &fqMi, &ftmpq);
956
957 // normalize the a posteriori quaternion and compute the a posteriori rotation matrix and rotation vector
958 fqAeqNormqA(&(pthisSV->fqPl));
959 fRotationMatrixFromQuaternion(pthisSV->fRPl, &(pthisSV->fqPl));
960 fRotationVectorDegFromQuaternion(&(pthisSV->fqPl), pthisSV->fRVecPl);
961
962 // update the a posteriori gyro offset vector: b+[k] = b-[k] - be+[k] = b+[k] - be+[k] (deg/s)
963 // limiting the correction to the maximum permitted by the random walk model
964 for (i = CHX; i <= CHZ; i++)
965 {
966 if (pthisSV->fbErrPl[i] > pthisSV->fMaxGyroOffsetChange)
967 pthisSV->fbPl[i] -= pthisSV->fMaxGyroOffsetChange;
968 else if (pthisSV->fbErrPl[i] < -pthisSV->fMaxGyroOffsetChange)
969 pthisSV->fbPl[i] += pthisSV->fMaxGyroOffsetChange;
970 else
971 pthisSV->fbPl[i] -= pthisSV->fbErrPl[i];
972 }
973
974 // compute the linear acceleration fAccGl in the global frame
975 // first de-rotate the accelerometer measurement fGc from the sensor to global frame
976 // using the transpose (inverse) of the orientation matrix fRPl
977 fveqRu(pthisSV->fAccGl, pthisSV->fRPl, pthisAccel->fGc, 1);
978
979 // sutract the fixed gravity vector in the global frame leaving linear acceleration
980#if THISCOORDSYSTEM == NED
981 // gravity positive NED
982 pthisSV->fAccGl[CHX] = -pthisSV->fAccGl[CHX];
983 pthisSV->fAccGl[CHY] = -pthisSV->fAccGl[CHY];
984 pthisSV->fAccGl[CHZ] = -(pthisSV->fAccGl[CHZ] - 1.0F);
985#elif THISCOORDSYSTEM == ANDROID
986 // acceleration positive ENU
987 pthisSV->fAccGl[CHZ] = pthisSV->fAccGl[CHZ] - 1.0F;
988#else // WIN8
989 // gravity positive ENU
990 pthisSV->fAccGl[CHX] = -pthisSV->fAccGl[CHX];
991 pthisSV->fAccGl[CHY] = -pthisSV->fAccGl[CHY];
992 pthisSV->fAccGl[CHZ] = -(pthisSV->fAccGl[CHZ] + 1.0F);
993#endif
994
995 // compute the a posteriori Euler angles from the a posteriori orientation matrix fRPl
996#if THISCOORDSYSTEM == NED
997 fNEDAnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl),
998 &(pthisSV->fThePl), &(pthisSV->fPsiPl),
999 &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1000#elif THISCOORDSYSTEM == ANDROID
1001 fAndroidAnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl),
1002 &(pthisSV->fThePl), &(pthisSV->fPsiPl),
1003 &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1004#else // WIN8
1005 fWin8AnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl),
1006 &(pthisSV->fThePl), &(pthisSV->fPsiPl),
1007 &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1008#endif
1009 return;
1010} // end fRun_6DOF_GY_KALMAN
1011#if F_9DOF_GBY_KALMAN
1012// 9DOF accelerometer+magnetometer+gyroscope orientation function implemented using indirect complementary Kalman filter
1013void fRun_9DOF_GBY_KALMAN(struct SV_9DOF_GBY_KALMAN *pthisSV,
1014 struct AccelSensor *pthisAccel,
1015 struct MagSensor *pthisMag,
1016 struct GyroSensor *pthisGyro,
1017 struct MagCalibration *pthisMagCal)
1018{
1019 // local scalars and arrays
1020 float ftmpA6x6[6][6]; // scratch 6x6 matrix
1021 float fRMi[3][3]; // a priori orientation matrix
1022 float fR6DOF[3][3]; // eCompass (6DOF accelerometer+magnetometer) orientation matrix
1023 float fgMi[3]; // a priori estimate of the gravity vector (sensor frame)
1024 float fmMi[3]; // a priori estimate of the geomagnetic vector (sensor frame)
1025 float fgPl[3]; // a posteriori estimate of the gravity vector (sensor frame)
1026 float fmPl[3]; // a posteriori estimate of the geomagnetic vector (sensor frame)
1027 float ftmpA3x3[3][3]; // scratch 3x3 matrix
1028 float ftmpA3x1[3]; // scratch 3x1 vector
1029 float fQvGQa; // accelerometer noise covariance to 1g sphere
1030 float fQvBQd; // magnetometer noise covariance to geomagnetic sphere
1031 float fC6x9ik; // element i, k of measurement matrix C
1032 float fC6x9jk; // element j, k of measurement matrix C
1033 Quaternion fqMi; // a priori orientation quaternion
1034 Quaternion fq6DOF; // eCompass (6DOF accelerometer+magnetometer) orientation quaternion
1035 Quaternion ftmpq; // scratch quaternion used for gyro integration
1036 float fDelta6DOF; // geomagnetic inclination angle computed from accelerometer and magnetometer (deg)
1037 float fsinDelta6DOF; // sin(fDelta6DOF)
1038 float fcosDelta6DOF; // cos(fDelta6DOF)
1039 float fmodGc; // modulus of calibrated accelerometer measurement (g)
1040 float fmodBc; // modulus of calibrated magnetometer measurement (uT)
1041 float ftmp; // scratch float
1042 int8_t ierror; // matrix inversion error flag
1043 int8_t i,
1044 j,
1045 k; // loop counters
1046
1047 // working arrays for 6x6 matrix inversion
1048 float *pfRows[6];
1049 int8_t iColInd[6];
1050 int8_t iRowInd[6];
1051 int8_t iPivot[6];
1052
1053 // if requested, do a reset initialization with no further processing
1054 if (pthisSV->resetflag) {
1055 fInit_9DOF_GBY_KALMAN(pthisSV, pthisAccel, pthisMag, pthisGyro, pthisMagCal);
1056 return;
1057 }
1058
1059 // compute the average angular velocity (used for display only) from the average measurement minus gyro offset
1060 for (i = CHX; i <= CHZ; i++) pthisSV->fOmega[i] = (float)pthisGyro->iYs[i] * pthisGyro->fDegPerSecPerCount - pthisSV->fbPl[i];
1061
1062 // initialize the a priori orientation quaternion fqMi to the previous iteration's a posteriori estimate fqPl
1063 // and incrementally rotate fqMi by the contents of the gyro FIFO buffer
1064 fqMi = pthisSV->fqPl;
1065 if (pthisGyro->iFIFOCount > 0) {
1066 // set ftmp to the average interval between FIFO gyro measurements
1067 ftmp = pthisSV->fdeltat / (float)pthisGyro->iFIFOCount;
1068
1069 // normal case, loop over all the buffered gyroscope measurements
1070 for (j = 0; j < pthisGyro->iFIFOCount; j++) {
1071 // calculate the instantaneous angular velocity subtracting the gyro offset
1072 for (i = CHX; i <= CHZ; i++) ftmpA3x1[i] = (float)pthisGyro->iYsFIFO[j][i] * pthisGyro->fDegPerSecPerCount - pthisSV->fbPl[i];
1073 // compute the incremental rotation quaternion ftmpq and integrate the a priori orientation quaternion fqMi
1074 fQuaternionFromRotationVectorDeg(&ftmpq, ftmpA3x1, ftmp);
1075 qAeqAxB(&fqMi, &ftmpq);
1076 }
1077 } else {
1078 // special case with no new FIFO measurements, use the previous iteration's average gyro reading to compute
1079 // the incremental rotation quaternion ftmpq and integrate the a priori orientation quaternion fqMi
1080 fQuaternionFromRotationVectorDeg(&ftmpq, pthisSV->fOmega, pthisSV->fdeltat);
1081 qAeqAxB(&fqMi, &ftmpq);
1082 }
1083
1084 // compute the a priori orientation matrix fRMi from the new a priori orientation quaternion fqMi
1085 fRotationMatrixFromQuaternion(fRMi, &fqMi);
1086
1087 // compute the 6DOF orientation matrix fR6DOF, inclination angle fDelta6DOF and the squared
1088 // deviations of the accelerometer and magnetometer measurements from the 1g gravity and geomagnetic spheres.
1089#if THISCOORDSYSTEM == NED
1090 feCompassNED(fR6DOF, &fDelta6DOF, &fsinDelta6DOF, &fcosDelta6DOF, pthisMag->fBc, pthisAccel->fGc, &fmodBc, &fmodGc);
1091#elif THISCOORDSYSTEM == ANDROID
1092 feCompassAndroid(fR6DOF, &fDelta6DOF, &fsinDelta6DOF, &fcosDelta6DOF, pthisMag->fBc, pthisAccel->fGc, &fmodBc, &fmodGc);
1093#else // WIN8
1094 feCompassWin8(fR6DOF, &fDelta6DOF, &fsinDelta6DOF, &fcosDelta6DOF, pthisMag->fBc, pthisAccel->fGc, &fmodBc, &fmodGc);
1095#endif
1096
1097 // compute the 6DOF orientation quaternion fq6DOF from the 6DOF orientation matrix fR6OF
1098 fQuaternionFromRotationMatrix(fR6DOF, &fq6DOF);
1099
1100 // calculate the acceleration noise variance relative to 1g sphere
1101 ftmp = fmodGc - 1.0F;
1102 fQvGQa = 3.0F * ftmp * ftmp;
1103 if (fQvGQa < FQVG_9DOF_GBY_KALMAN)
1104 fQvGQa = FQVG_9DOF_GBY_KALMAN;
1105
1106 // calculate magnetic noise variance relative to geomagnetic sphere
1107 ftmp = fmodBc - pthisMagCal->fB;
1108 fQvBQd = 3.0F * ftmp * ftmp;
1109 if (fQvBQd < FQVB_9DOF_GBY_KALMAN)
1110 fQvBQd = FQVB_9DOF_GBY_KALMAN;
1111
1112 // do a once-only orientation lock immediately after the first valid magnetic calibration by:
1113 // i) setting the a priori and a posteriori orientations to the 6DOF eCompass orientation
1114 // ii) setting the geomagnetic inclination angle fDeltaPl now that the first calibrated 6DOF estimate is available
1115 if (pthisMagCal->iValidMagCal && !pthisSV->iFirstAccelMagLock) {
1116 fqMi = pthisSV->fqPl = fq6DOF;
1117 f3x3matrixAeqB(fRMi, fR6DOF);
1118 pthisSV->fDeltaPl = fDelta6DOF;
1119 pthisSV->fsinDeltaPl = fsinDelta6DOF;
1120 pthisSV->fcosDeltaPl = fcosDelta6DOF;
1121 pthisSV->iFirstAccelMagLock = true;
1122 }
1123
1124 // set ftmpA3x1 to the normalized 6DOF gravity vector and set fgMi to the normalized a priori gravity vector
1125 // with both estimates computed in the sensor frame
1126#if THISCOORDSYSTEM == NED
1127 ftmpA3x1[CHX] = fR6DOF[CHX][CHZ];
1128 ftmpA3x1[CHY] = fR6DOF[CHY][CHZ];
1129 ftmpA3x1[CHZ] = fR6DOF[CHZ][CHZ];
1130 fgMi[CHX] = fRMi[CHX][CHZ];
1131 fgMi[CHY] = fRMi[CHY][CHZ];
1132 fgMi[CHZ] = fRMi[CHZ][CHZ];
1133#else // ANDROID and WIN8 (ENU gravity positive)
1134 ftmpA3x1[CHX] = -fR6DOF[CHX][CHZ];
1135 ftmpA3x1[CHY] = -fR6DOF[CHY][CHZ];
1136 ftmpA3x1[CHZ] = -fR6DOF[CHZ][CHZ];
1137 fgMi[CHX] = -fRMi[CHX][CHZ];
1138 fgMi[CHY] = -fRMi[CHY][CHZ];
1139 fgMi[CHZ] = -fRMi[CHZ][CHZ];
1140#endif
1141
1142 // set ftmpq to the quaternion that rotates the 6DOF gravity tilt vector ftmpA3x1 to the a priori estimate fgMi
1143 // and copy its vector components into the measurement error vector fZErr[0-2].
1144 fveqconjgquq(&ftmpq, ftmpA3x1, fgMi);
1145 pthisSV->fZErr[0] = ftmpq.q1;
1146 pthisSV->fZErr[1] = ftmpq.q2;
1147 pthisSV->fZErr[2] = ftmpq.q3;
1148
1149 // set ftmpA3x1 to the normalized 6DOF geomagnetic vector and set fmMi to the normalized a priori geomagnetic vector
1150 // with both estimates computed in the sensor frame
1151#if THISCOORDSYSTEM == NED
1152 ftmpA3x1[CHX] = fR6DOF[CHX][CHX] * fcosDelta6DOF + fR6DOF[CHX][CHZ] * fsinDelta6DOF;
1153 ftmpA3x1[CHY] = fR6DOF[CHY][CHX] * fcosDelta6DOF + fR6DOF[CHY][CHZ] * fsinDelta6DOF;
1154 ftmpA3x1[CHZ] = fR6DOF[CHZ][CHX] * fcosDelta6DOF + fR6DOF[CHZ][CHZ] * fsinDelta6DOF;
1155 fmMi[CHX] = fRMi[CHX][CHX] * pthisSV->fcosDeltaPl + fRMi[CHX][CHZ] * pthisSV->fsinDeltaPl;
1156 fmMi[CHY] = fRMi[CHY][CHX] * pthisSV->fcosDeltaPl + fRMi[CHY][CHZ] * pthisSV->fsinDeltaPl;
1157 fmMi[CHZ] = fRMi[CHZ][CHX] * pthisSV->fcosDeltaPl + fRMi[CHZ][CHZ] * pthisSV->fsinDeltaPl;
1158#else // ANDROID and WIN8 (both ENU coordinate systems)
1159 ftmpA3x1[CHX] = fR6DOF[CHX][CHY] * fcosDelta6DOF - fR6DOF[CHX][CHZ] * fsinDelta6DOF;
1160 ftmpA3x1[CHY] = fR6DOF[CHY][CHY] * fcosDelta6DOF - fR6DOF[CHY][CHZ] * fsinDelta6DOF;
1161 ftmpA3x1[CHZ] = fR6DOF[CHZ][CHY] * fcosDelta6DOF - fR6DOF[CHZ][CHZ] * fsinDelta6DOF;
1162 fmMi[CHX] = fRMi[CHX][CHY] * pthisSV->fcosDeltaPl - fRMi[CHX][CHZ] * pthisSV->fsinDeltaPl;
1163 fmMi[CHY] = fRMi[CHY][CHY] * pthisSV->fcosDeltaPl - fRMi[CHY][CHZ] * pthisSV->fsinDeltaPl;
1164 fmMi[CHZ] = fRMi[CHZ][CHY] * pthisSV->fcosDeltaPl - fRMi[CHZ][CHZ] * pthisSV->fsinDeltaPl;
1165#endif
1166
1167 // set ftmpq to the quaternion that rotates the 6DOF geomagnetic tilt vector ftmpA3x1 to the a priori estimate fmMi
1168 // and copy its vector components into the measurement error vector fZErr[3-5].
1169 fveqconjgquq(&ftmpq, ftmpA3x1, fmMi);
1170 pthisSV->fZErr[3] = ftmpq.q1;
1171 pthisSV->fZErr[4] = ftmpq.q2;
1172 pthisSV->fZErr[5] = ftmpq.q3;
1173
1174 // update Qw using the a posteriori error vectors from the previous iteration.
1175 // as Qv increases or Qw decreases, K -> 0 and the Kalman filter is weighted towards the a priori prediction
1176 // as Qv decreases or Qw increases, KC -> I and the Kalman filter is weighted towards the measurement.
1177 // initialize on and above diagonal elements of Qw to zero
1178 for (i = 0; i < 9; i++)
1179 for (j = i; j < 9; j++)
1180 pthisSV->fQw9x9[i][j] = 0.0F;
1181 // partial diagonal gyro offset terms
1182 pthisSV->fQw9x9[6][6] = pthisSV->fbErrPl[CHX] * pthisSV->fbErrPl[CHX];
1183 pthisSV->fQw9x9[7][7] = pthisSV->fbErrPl[CHY] * pthisSV->fbErrPl[CHY];
1184 pthisSV->fQw9x9[8][8] = pthisSV->fbErrPl[CHZ] * pthisSV->fbErrPl[CHZ];
1185 // set ftmpA3x1 to alpha^2 / 4 * fbErrPl.fbErrPl + fAlphaSqQvYQwbOver12
1186 ftmpA3x1[0] = pthisSV->fAlphaSqOver4 * pthisSV->fQw9x9[6][6] + pthisSV->fAlphaSqQvYQwbOver12;
1187 ftmpA3x1[1] = pthisSV->fAlphaSqOver4 * pthisSV->fQw9x9[7][7] + pthisSV->fAlphaSqQvYQwbOver12;
1188 ftmpA3x1[2] = pthisSV->fAlphaSqOver4 * pthisSV->fQw9x9[8][8] + pthisSV->fAlphaSqQvYQwbOver12;
1189 // diagonal gravity vector components
1190 pthisSV->fQw9x9[0][0] = pthisSV->fqgErrPl[CHX] * pthisSV->fqgErrPl[CHX] + ftmpA3x1[0];
1191 pthisSV->fQw9x9[1][1] = pthisSV->fqgErrPl[CHY] * pthisSV->fqgErrPl[CHY] + ftmpA3x1[1];
1192 pthisSV->fQw9x9[2][2] = pthisSV->fqgErrPl[CHZ] * pthisSV->fqgErrPl[CHZ] + ftmpA3x1[2];
1193 // diagonal geomagnetic vector components
1194 pthisSV->fQw9x9[3][3] = pthisSV->fqmErrPl[CHX] * pthisSV->fqmErrPl[CHX] + ftmpA3x1[0];
1195 pthisSV->fQw9x9[4][4] = pthisSV->fqmErrPl[CHY] * pthisSV->fqmErrPl[CHY] + ftmpA3x1[1];
1196 pthisSV->fQw9x9[5][5] = pthisSV->fqmErrPl[CHZ] * pthisSV->fqmErrPl[CHZ] + ftmpA3x1[2];
1197 // diagonal gyro offset components
1198 pthisSV->fQw9x9[6][6] += pthisSV->fQwbOver3;
1199 pthisSV->fQw9x9[7][7] += pthisSV->fQwbOver3;;
1200 pthisSV->fQw9x9[8][8] += pthisSV->fQwbOver3;;
1201 // set ftmpA3x1 to alpha / 2 * fQw9x9[6-8][6-8]
1202 ftmpA3x1[0] = pthisSV->fAlphaOver2 * pthisSV->fQw9x9[6][6];
1203 ftmpA3x1[1] = pthisSV->fAlphaOver2 * pthisSV->fQw9x9[7][7];
1204 ftmpA3x1[2] = pthisSV->fAlphaOver2 * pthisSV->fQw9x9[8][8];
1205 // off diagonal gravity and gyro offset components
1206 pthisSV->fQw9x9[0][6] = pthisSV->fqgErrPl[CHX] * pthisSV->fbErrPl[CHX] - ftmpA3x1[0];
1207 pthisSV->fQw9x9[1][7] = pthisSV->fqgErrPl[CHY] * pthisSV->fbErrPl[CHY] - ftmpA3x1[1];
1208 pthisSV->fQw9x9[2][8] = pthisSV->fqgErrPl[CHZ] * pthisSV->fbErrPl[CHZ] - ftmpA3x1[2];
1209 // off diagonal geomagnetic and gyro offset components
1210 pthisSV->fQw9x9[3][6] = pthisSV->fqmErrPl[CHX] * pthisSV->fbErrPl[CHX] - ftmpA3x1[0];
1211 pthisSV->fQw9x9[4][7] = pthisSV->fqmErrPl[CHY] * pthisSV->fbErrPl[CHY] - ftmpA3x1[1];
1212 pthisSV->fQw9x9[5][8] = pthisSV->fqmErrPl[CHZ] * pthisSV->fbErrPl[CHZ] - ftmpA3x1[2];
1213 // set below diagonal elements of Qw to above diagonal elements
1214 for (i = 1; i < 9; i++)
1215 for (j = 0; j < i; j++)
1216 pthisSV->fQw9x9[i][j] = pthisSV->fQw9x9[j][i];
1217
1218 // calculate the vector fQv6x1 containing the diagonal elements of the measurement covariance matrix Qv
1219 pthisSV->fQv6x1[0] = pthisSV->fQv6x1[1] = pthisSV->fQv6x1[2] = ONEOVER12 * fQvGQa + pthisSV->fAlphaSqQvYQwbOver12;
1220 pthisSV->fQv6x1[3] = pthisSV->fQv6x1[4] = pthisSV->fQv6x1[5] = ONEOVER12 * fQvBQd / pthisMagCal->fBSq + pthisSV->fAlphaSqQvYQwbOver12;
1221
1222 // calculate the Kalman gain matrix K = Qw * C^T * inv(C * Qw * C^T + Qv)
1223 // set fQwCT9x6 = Qw.C^T where Qw has size 9x9 and C^T has size 9x6
1224 for (i = 0; i < 9; i++) { // loop over rows
1225 for (j = 0; j < 6; j++) { // loop over columns
1226 pthisSV->fQwCT9x6[i][j] = 0.0F;
1227 // accumulate matrix sum
1228 for (k = 0; k < 9; k++) {
1229 // determine fC6x9[j][k] since the matrix is highly sparse
1230 fC6x9jk = 0.0F;
1231 // handle rows 0 to 2
1232 if (j < 3) {
1233 if (k == j) fC6x9jk = 1.0F;
1234 if (k == (j + 6)) fC6x9jk = -pthisSV->fAlphaOver2;
1235 } else if (j < 6) {
1236 // handle rows 3 to 5
1237 if (k == j) fC6x9jk = 1.0F;
1238 if (k == (j + 3)) fC6x9jk = -pthisSV->fAlphaOver2;
1239 }
1240
1241 // accumulate fQwCT9x6[i][j] += Qw9x9[i][k] * C[j][k]
1242 if ((pthisSV->fQw9x9[i][k] != 0.0F) && (fC6x9jk != 0.0F)) {
1243 if (fC6x9jk == 1.0F) pthisSV->fQwCT9x6[i][j] += pthisSV->fQw9x9[i][k];
1244 else pthisSV->fQwCT9x6[i][j] += pthisSV->fQw9x9[i][k] * fC6x9jk;
1245 }
1246 }
1247 }
1248 }
1249
1250 // set symmetric ftmpA6x6 = C.(Qw.C^T) + Qv = C.fQwCT9x6 + Qv
1251 for (i = 0; i < 6; i++) { // loop over rows
1252 for (j = i; j < 6; j++) { // loop over on and above diagonal columns
1253 // zero off diagonal and set diagonal to Qv
1254 if (i == j) ftmpA6x6[i][j] = pthisSV->fQv6x1[i];
1255 else ftmpA6x6[i][j] = 0.0F;
1256 // accumulate matrix sum
1257 for (k = 0; k < 9; k++) {
1258 // determine fC6x9[i][k]
1259 fC6x9ik = 0.0F;
1260 // handle rows 0 to 2
1261 if (i < 3) {
1262 if (k == i) fC6x9ik = 1.0F;
1263 if (k == (i + 6)) fC6x9ik = -pthisSV->fAlphaOver2;
1264 } else if (i < 6) {
1265 // handle rows 3 to 5
1266 if (k == i) fC6x9ik = 1.0F;
1267 if (k == (i + 3)) fC6x9ik = -pthisSV->fAlphaOver2;
1268 }
1269
1270 // accumulate ftmpA6x6[i][j] += C[i][k] & fQwCT9x6[k][j]
1271 if ((fC6x9ik != 0.0F) && (pthisSV->fQwCT9x6[k][j] != 0.0F)) {
1272 if (fC6x9ik == 1.0F) ftmpA6x6[i][j] += pthisSV->fQwCT9x6[k][j];
1273 else ftmpA6x6[i][j] += fC6x9ik * pthisSV->fQwCT9x6[k][j];
1274 }
1275 }
1276 }
1277 }
1278 // set ftmpA6x6 below diagonal elements to above diagonal elements
1279 for (i = 1; i < 6; i++) // loop over rows
1280 for (j = 0; j < i; j++) // loop over below diagonal columns
1281 ftmpA6x6[i][j] = ftmpA6x6[j][i];
1282
1283 // invert ftmpA6x6 in situ to give ftmpA6x6 = inv(C * Qw * C^T + Qv) = inv(ftmpA6x6)
1284 for (i = 0; i < 6; i++)
1285 pfRows[i] = ftmpA6x6[i];
1286 fmatrixAeqInvA(pfRows, iColInd, iRowInd, iPivot, 6, &ierror);
1287
1288 // on successful inversion set Kalman gain matrix K9x6 = Qw * C^T * inv(C * Qw * C^T + Qv) = fQwCT9x6 * ftmpA6x6
1289 if (!ierror) {
1290 // normal case
1291 for (i = 0; i < 9; i++) // loop over rows
1292 for (j = 0; j < 6; j++) { // loop over columns
1293 pthisSV->fK9x6[i][j] = 0.0F;
1294 for (k = 0; k < 6; k++) {
1295 if ((pthisSV->fQwCT9x6[i][k] != 0.0F) && (ftmpA6x6[k][j] != 0.0F))
1296 pthisSV->fK9x6[i][j] += pthisSV->fQwCT9x6[i][k] * ftmpA6x6[k][j];
1297 }
1298 }
1299 } else {
1300 // ftmpA6x6 was singular so set Kalman gain matrix to zero
1301 for (i = 0; i < 9; i++) // loop over rows
1302 for (j = 0; j < 6; j++) // loop over columns
1303 pthisSV->fK9x6[i][j] = 0.0F;
1304 }
1305
1306 // calculate the a posteriori gravity and geomagnetic tilt quaternion errors and gyro offset error vector
1307 // from the Kalman matrix fK9x6 and the measurement error vector fZErr.
1308 for (i = CHX; i <= CHZ; i++) {
1309 pthisSV->fqgErrPl[i] = pthisSV->fqmErrPl[i] = pthisSV->fbErrPl[i] = 0.0F;
1310 for (j = 0; j < 6; j++) {
1311 // calculate gravity tilt quaternion vector error component fqgErrPl
1312 if (pthisSV->fK9x6[i][j] != 0.0F)
1313 pthisSV->fqgErrPl[i] += pthisSV->fK9x6[i][j] * pthisSV->fZErr[j];
1314
1315 // calculate geomagnetic tilt quaternion vector error component fqmErrPl
1316 if (pthisSV->fK9x6[i + 3][j] != 0.0F)
1317 pthisSV->fqmErrPl[i] += pthisSV->fK9x6[i + 3][j] * pthisSV->fZErr[j];
1318
1319 // calculate gyro offset vector error component fbErrPl
1320 if (pthisSV->fK9x6[i + 6][j] != 0.0F)
1321 pthisSV->fbErrPl[i] += pthisSV->fK9x6[i + 6][j] * pthisSV->fZErr[j];
1322 }
1323 }
1324
1325 // set ftmpq to the a posteriori gravity tilt correction (conjugate) quaternion
1326 ftmpq.q1 = -pthisSV->fqgErrPl[CHX];
1327 ftmpq.q2 = -pthisSV->fqgErrPl[CHY];
1328 ftmpq.q3 = -pthisSV->fqgErrPl[CHZ];
1329 ftmpq.q0 = sqrtf(fabs(1.0F - ftmpq.q1 * ftmpq.q1 - ftmpq.q2 * ftmpq.q2 - ftmpq.q3 * ftmpq.q3));
1330
1331 // set ftmpA3x3 to the gravity tilt correction matrix and rotate the normalized a priori estimate of the
1332 // gravity vector fgMi to obtain the normalized a posteriori estimate of the gravity vector fgPl
1333 fRotationMatrixFromQuaternion(ftmpA3x3, &ftmpq);
1334 fveqRu(fgPl, ftmpA3x3, fgMi, 0);
1335
1336 // set ftmpq to the a posteriori geomagnetic tilt correction (conjugate) quaternion
1337 ftmpq.q1 = -pthisSV->fqmErrPl[CHX];
1338 ftmpq.q2 = -pthisSV->fqmErrPl[CHY];
1339 ftmpq.q3 = -pthisSV->fqmErrPl[CHZ];
1340 ftmpq.q0 = sqrtf(fabs(1.0F - ftmpq.q1 * ftmpq.q1 - ftmpq.q2 * ftmpq.q2 - ftmpq.q3 * ftmpq.q3));
1341
1342 // set ftmpA3x3 to the geomagnetic tilt correction matrix and rotate the normalized a priori estimate of the
1343 // geomagnetic vector fmMi to obtain the normalized a posteriori estimate of the geomagnetic vector fmPl
1344 fRotationMatrixFromQuaternion(ftmpA3x3, &ftmpq);
1345 fveqRu(fmPl, ftmpA3x3, fmMi, 0);
1346
1347 // compute the a posteriori orientation matrix fRPl from the vector product of the a posteriori gravity fgPl
1348 // and geomagnetic fmPl vectors both of which are normalized
1349#if THISCOORDSYSTEM == NED// gravity vector is +z and accel measurement is +z when flat so don't negate
1350 feCompassNED(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
1351 fmPl, fgPl, &fmodBc, &fmodGc);
1352#elif THISCOORDSYSTEM == ANDROID // gravity vector is -z and accel measurement is +z when flat so negate
1353 ftmpA3x1[CHX] = -fgPl[CHX];
1354 ftmpA3x1[CHY] = -fgPl[CHY];
1355 ftmpA3x1[CHZ] = -fgPl[CHZ];
1356 feCompassAndroid(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
1357 fmPl, ftmpA3x1, &fmodBc, &fmodGc);
1358#else // WIN8// gravity vector is -z and accel measurement is -1g when flat so don't negate
1359 feCompassWin8(pthisSV->fRPl, &(pthisSV->fDeltaPl), &(pthisSV->fsinDeltaPl), &(pthisSV->fcosDeltaPl),
1360 fmPl, fgPl, &fmodBc, &fmodGc);
1361#endif
1362
1363 // compute the a posteriori quaternion fqPl and rotation vector fRVecPl from fRPl
1364 fQuaternionFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fqPl));
1365 fRotationVectorDegFromQuaternion(&(pthisSV->fqPl), pthisSV->fRVecPl);
1366
1367 // update the a posteriori gyro offset vector: b+[k] = b-[k] - be+[k] = b+[k] - be+[k] (deg/s)
1368 for (i = CHX; i <= CHZ; i++) {
1369 // restrict the gyro offset correction to the maximum permitted by the random walk model
1370 if (pthisSV->fbErrPl[i] > pthisSV->fMaxGyroOffsetChange)
1371 pthisSV->fbPl[i] -= pthisSV->fMaxGyroOffsetChange;
1372 else if (pthisSV->fbErrPl[i] < -pthisSV->fMaxGyroOffsetChange)
1373 pthisSV->fbPl[i] += pthisSV->fMaxGyroOffsetChange;
1374 else
1375 pthisSV->fbPl[i] -= pthisSV->fbErrPl[i];
1376 // restrict gyro offset between specified limits
1377 if (pthisSV->fbPl[i] > FMAX_9DOF_GBY_BPL) pthisSV->fbPl[i] = FMAX_9DOF_GBY_BPL;
1378 if (pthisSV->fbPl[i] < FMIN_9DOF_GBY_BPL) pthisSV->fbPl[i] = FMIN_9DOF_GBY_BPL;
1379 }
1380
1381 // compute the linear acceleration fAccGl in the global frame
1382 // first de-rotate the accelerometer measurement fGc from the sensor to global frame
1383 // using the transpose (inverse) of the orientation matrix fRPl
1384 fveqRu(pthisSV->fAccGl, pthisSV->fRPl, pthisAccel->fGc, 1);
1385
1386 // subtract the fixed gravity vector in the global frame leaving linear acceleration
1387#if THISCOORDSYSTEM == NED
1388 // gravity positive NED
1389 pthisSV->fAccGl[CHX] = -pthisSV->fAccGl[CHX];
1390 pthisSV->fAccGl[CHY] = -pthisSV->fAccGl[CHY];
1391 pthisSV->fAccGl[CHZ] = -(pthisSV->fAccGl[CHZ] - 1.0F);
1392#elif THISCOORDSYSTEM == ANDROID
1393 // acceleration positive ENU
1394 pthisSV->fAccGl[CHZ] = pthisSV->fAccGl[CHZ] - 1.0F;
1395#else // WIN8
1396 // gravity positive ENU
1397 pthisSV->fAccGl[CHX] = -pthisSV->fAccGl[CHX];
1398 pthisSV->fAccGl[CHY] = -pthisSV->fAccGl[CHY];
1399 pthisSV->fAccGl[CHZ] = -(pthisSV->fAccGl[CHZ] + 1.0F);
1400#endif
1401
1402 // integrate the acceleration to velocity and displacement in the global frame.
1403 // Note: integration errors accumulate without limit over time and this code should only be
1404 // used for inertial integration of the order of seconds.
1405 for (i = CHX; i <= CHZ; i++) {
1406 // integrate acceleration (in g) to velocity in m/s
1407 pthisSV->fVelGl[i] += pthisSV->fAccGl[i] * pthisSV->fgdeltat;
1408 // integrate velocity (in m/s) to displacement (m)
1409 pthisSV->fDisGl[i] += pthisSV->fVelGl[i] * pthisSV->fdeltat;
1410 }
1411
1412 // compute the a posteriori Euler angles from the a posteriori orientation matrix fRPl
1413#if THISCOORDSYSTEM == NED
1414 fNEDAnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl), &(pthisSV->fThePl), &(pthisSV->fPsiPl), &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1415#elif THISCOORDSYSTEM == ANDROID
1416 fAndroidAnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl), &(pthisSV->fThePl), &(pthisSV->fPsiPl), &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1417#else // WIN8
1418 fWin8AnglesDegFromRotationMatrix(pthisSV->fRPl, &(pthisSV->fPhiPl), &(pthisSV->fThePl), &(pthisSV->fPsiPl), &(pthisSV->fRhoPl), &(pthisSV->fChiPl));
1419#endif
1420
1421 return;
1422} // end fRun_9DOF_GBY_KALMAN
1423#endif // #if F_9DOF_GBY_KALMAN
Math approximations file.
#define FUSION_HZ
(int) rate of fusion algorithm execution
Definition build.h:85
Provides functions to store calibration to NVM, which on ESP devices is provided by EEPROM.
Lower level sensor fusion interface.
#define FQVY_6DOF_GY_KALMAN
gyro sensor noise variance units (deg/s)^2
Definition fusion.h:50
#define FMAX_9DOF_GBY_BPL
maximum permissible power on gyro offsets (deg/s)
Definition fusion.h:66
#define FMIN_9DOF_GBY_BPL
minimum permissible power on gyro offsets (deg/s)
Definition fusion.h:65
#define FQWB_9DOF_GBY_KALMAN
gyro offset random walk units (deg/s)^2
Definition fusion.h:64
#define FQVY_9DOF_GBY_KALMAN
gyro sensor noise variance units (deg/s)^2
Definition fusion.h:61
#define FQWB_6DOF_GY_KALMAN
gyro offset random walk units (deg/s)^2
Definition fusion.h:52
#define FLPFSECS_3DOF_B_BASIC
2D eCompass orientation low pass filter time constant (s)
Definition fusion.h:40
#define FMAX_6DOF_GY_BPL
maximum permissible power on gyro offsets (deg/s)
Definition fusion.h:54
#define FQVB_9DOF_GBY_KALMAN
magnetometer sensor noise variance units uT^2 defining minimum deviation from geomagnetic sphere.
Definition fusion.h:63
#define FQVG_6DOF_GY_KALMAN
accelerometer sensor noise variance units g^2
Definition fusion.h:51
#define FLPFSECS_1DOF_P_BASIC
pressure low pass filter time constant (s)
Definition fusion.h:30
#define FMIN_6DOF_GY_BPL
minimum permissible power on gyro offsets (deg/s)
Definition fusion.h:53
#define FQVG_9DOF_GBY_KALMAN
accelerometer sensor noise variance units g^2 defining minimum deviation from 1g sphere
Definition fusion.h:62
#define FLPFSECS_3DOF_G_BASIC
tilt orientation low pass filter time constant (s)
Definition fusion.h:35
Wrapper for Hardware Abstraction Layer (HAL) Contains replacements for hardware-specific functions Cu...
void f3x3matrixAeqI(float A[][3])
function sets the 3x3 matrix A to the identity matrix
Definition matrix.c:26
void fmatrixAeqInvA(float *A[], int8_t iColInd[], int8_t iRowInd[], int8_t iPivot[], int8_t isize, int8_t *pierror)
Definition matrix.c:647
void f3x3matrixAeqB(float A[][3], float B[][3])
function sets 3x3 matrix A to 3x3 matrix B
Definition matrix.c:47
void fveqRu(float fv[], float fR[][3], float fu[], int8_t itranspose)
Definition matrix.c:801
Matrix manipulation functions.
void fQuaternionFromRotationMatrix(float R[][3], Quaternion *pq)
compute the orientation quaternion from a 3x3 rotation matrix
void qAeqBxC(Quaternion *pqA, const Quaternion *pqB, const Quaternion *pqC)
function compute the quaternion product qB * qC
void fWin8AnglesDegFromRotationMatrix(float R[][3], float *pfPhiDeg, float *pfTheDeg, float *pfPsiDeg, float *pfRhoDeg, float *pfChiDeg)
extract the Windows 8 angles in degrees from the Windows 8 rotation matrix
void qAeqAxB(Quaternion *pqA, const Quaternion *pqB)
function compute the quaternion product qA = qA * qB
void f3DOFMagnetometerMatrixNED(float fR[][3], float fBc[])
Aerospace NED magnetometer 3DOF flat eCompass function, computing rotation matrix fR.
void fqAeqNormqA(Quaternion *pqA)
function normalizes a rotation quaternion and ensures q0 is non-negative
void feCompassAndroid(float fR[][3], float *pfDelta, float *pfsinDelta, float *pfcosDelta, float fBc[], float fGc[], float *pfmodBc, float *pfmodGc)
Android: basic 6DOF e-Compass function, computing rotation matrix fR and magnetic inclination angle f...
void fveqconjgquq(Quaternion *pfq, float fu[], float fv[])
void fqAeq1(Quaternion *pqA)
set a quaternion to the unit quaternion
void f3DOFMagnetometerMatrixAndroid(float fR[][3], float fBc[])
Android magnetometer 3DOF flat eCompass function, computing rotation matrix fR.
void fAndroidAnglesDegFromRotationMatrix(float R[][3], float *pfPhiDeg, float *pfTheDeg, float *pfPsiDeg, float *pfRhoDeg, float *pfChiDeg)
extract the Android angles in degrees from the Android rotation matrix
void f3DOFMagnetometerMatrixWin8(float fR[][3], float fBc[])
Windows 8 magnetometer 3DOF flat eCompass function, computing rotation matrix fR.
void fQuaternionFromRotationVectorDeg(Quaternion *pq, const float rvecdeg[], float fscaling)
computes normalized rotation quaternion from a rotation vector (deg)
void fRotationVectorDegFromQuaternion(Quaternion *pq, float rvecdeg[])
computes rotation vector (deg) from rotation quaternion
void fNEDAnglesDegFromRotationMatrix(float R[][3], float *pfPhiDeg, float *pfTheDeg, float *pfPsiDeg, float *pfRhoDeg, float *pfChiDeg)
extract the NED angles in degrees from the NED rotation matrix
void feCompassWin8(float fR[][3], float *pfDelta, float *pfsinDelta, float *pfcosDelta, float fBc[], float fGc[], float *pfmodBc, float *pfmodGc)
Win8: basic 6DOF e-Compass function, computing rotation matrix fR and magnetic inclination angle fDel...
void fRotationMatrixFromQuaternion(float R[][3], const Quaternion *pq)
compute the rotation matrix from an orientation quaternion
void fLPFOrientationQuaternion(Quaternion *pq, Quaternion *pLPq, float flpf, float fdeltat, float fOmega[])
function low pass filters an orientation quaternion and computes virtual gyro rotation rate
void feCompassNED(float fR[][3], float *pfDelta, float *pfsinDelta, float *pfcosDelta, float fBc[], float fGc[], float *pfmodBc, float *pfmodGc)
NED: basic 6DOF e-Compass function, computing rotation matrix fR and magnetic inclination angle fDelt...
Functions to convert between various orientation representations.
void f3DOFTiltWin8(float fR[][3], float fGc[])
Windows 8 accelerometer 3DOF tilt function computing, rotation matrix fR.
void f3DOFTiltAndroid(float fR[][3], float fGc[])
Android accelerometer 3DOF tilt function computing, rotation matrix fR.
void f3DOFTiltNED(float fR[][3], float fGc[])
Aerospace NED accelerometer 3DOF tilt function, computing rotation matrix fR.
The sensor_fusion.h file implements the top level programming interface.
#define CHX
Used to access X-channel entries in various data data structures.
#define GTOMSEC2
standard gravity in m/s2
#define CHY
Used to access Y-channel entries in various data data structures.
#define CHZ
Used to access Z-channel entries in various data data structures.
#define ONEOVER12
1 / 12
#define FPIOVER180
degrees to radians conversion = pi / 180
The AccelSensor structure stores raw and processed measurements for a 3-axis accelerometer.
float fGc[3]
averaged precision calibrated measurement (g)
The GyroSensor structure stores raw and processed measurements for a 3-axis gyroscope.
int16_t iYs[3]
average measurement (counts)
int16_t iYsFIFO[GYRO_FIFO_SIZE][3]
FIFO measurements (counts)
uint8_t iFIFOCount
number of measurements read from FIFO
float fDegPerSecPerCount
deg/s per count
float fYs[3]
averaged measurement (deg/s)
Magnetic Calibration Structure.
Definition magnetic.h:61
float fB
current geomagnetic field magnitude (uT)
Definition magnetic.h:65
float fBSq
square of fB (uT^2)
Definition magnetic.h:66
int32_t iValidMagCal
solver used: 0 (no calibration) or 4, 7, 10 element
Definition magnetic.h:68
The MagSensor structure stores raw and processed measurements for a 3-axis magnetic sensor.
float fBc[3]
averaged calibrated measurement (uT)
The PressureSensor structure stores raw and processed measurements for an altimeter.
float fT
most recent unaveraged temperature (C)
float fH
most recent unaveraged height (m)
quaternion structure definition
Definition orientation.h:25
float q3
z vector component
Definition orientation.h:29
float q0
scalar component
Definition orientation.h:26
float q1
x vector component
Definition orientation.h:27
float q2
y vector component
Definition orientation.h:28
The SV_1DOF_P_BASIC structure contains state information for a pressure sensor/altimeter.
float fdeltat
fusion time interval (s)
float flpf
low pass filter coefficient
int8_t resetflag
flag to request re-initialization on next pass
int32_t systick
systick timer
float fLPH
low pass filtered height (m)
float fLPT
low pass filtered temperature (C)
This is the 3DOF basic magnetometer state vector structure/.
float fLPChi
low pass tilt from vertical (deg)
int32_t systick
systick timer
Quaternion fq
unfiltered orientation quaternion
int8_t resetflag
flag to request re-initialization on next pass
float fLPPsi
low pass yaw (deg)
float fOmega[3]
angular velocity (deg/s)
float fR[3][3]
unfiltered orientation matrix
float flpf
low pass filter coefficient
Quaternion fLPq
low pass filtered orientation quaternion
float fdeltat
fusion time interval (s)
float fLPRVec[3]
rotation vector
float fLPThe
low pass pitch (deg)
float fLPRho
low pass compass (deg)
float fLPPhi
low pass roll (deg)
float fLPR[3][3]
low pass filtered orientation matrix
This is the 3DOF basic accelerometer state vector structure.
float fR[3][3]
unfiltered orientation matrix
Quaternion fq
unfiltered orientation quaternion
float fdeltat
fusion time interval (s)
float fLPThe
low pass pitch (deg)
float fLPPsi
low pass yaw (deg)
float fLPR[3][3]
low pass filtered orientation matrix
Quaternion fLPq
low pass filtered orientation quaternion
float fLPRho
low pass compass (deg)
int32_t systick
systick timer
float fLPRVec[3]
rotation vector
float flpf
low pass filter coefficient
float fLPChi
low pass tilt from vertical (deg)
int8_t resetflag
flag to request re-initialization on next pass
float fOmega[3]
angular velocity (deg/s)
float fLPPhi
low pass roll (deg)
SV_3DOF_Y_BASIC structure is the 3DOF basic gyroscope state vector structure.
float fRVec[3]
rotation vector
int32_t systick
systick timer
Quaternion fq
unfiltered orientation quaternion
float fOmega[3]
angular velocity (deg/s)
float fPhi
roll (deg)
float fPsi
yaw (deg)
float fR[3][3]
unfiltered orientation matrix
int8_t resetflag
flag to request re-initialization on next pass
float fThe
pitch (deg)
float fChi
tilt from vertical (deg)
float fRho
compass (deg)
float fdeltat
fusion filter sampling interval (s)
SV_6DOF_GB_BASIC is the 6DOF basic accelerometer and magnetometer state vector structure.
Quaternion fq
unfiltered orientation quaternion
float fLPPhi
low pass roll (deg)
Quaternion fLPq
low pass filtered orientation quaternion
float fLPR[3][3]
low pass filtered orientation matrix
int8_t resetflag
flag to request re-initialization on next pass
float fLPDelta
low pass filtered inclination angle (deg)
float fdeltat
fusion time interval (s)
float fR[3][3]
unfiltered orientation matrix
float fLPChi
low pass tilt from vertical (deg)
float flpf
low pass filter coefficient
float fDelta
unfiltered inclination angle (deg)
float fOmega[3]
virtual gyro angular velocity (deg/s)
float fLPPsi
low pass yaw (deg)
float fLPThe
low pass pitch (deg)
float fLPRho
low pass compass (deg)
float fLPRVec[3]
rotation vector
int32_t systick
systick timer
SV_6DOF_GY_KALMAN is the 6DOF Kalman filter accelerometer and gyroscope state vector structure.
float fQw6x6[6][6]
covariance matrix Qw
float fOmega[3]
average angular velocity (deg/s)
float fPsiPl
yaw (deg)
float fAccGl[3]
linear acceleration (g) in global frame
float fAlphaOver2
PI / 180 * fdeltat / 2.
float fqgErrPl[3]
gravity vector tilt orientation quaternion error (dimensionless)
float fbPl[3]
gyro offset (deg/s)
float fAlphaSqOver4
(PI / 180 * fdeltat)^2 / 4
float fQwCT6x3[6][3]
Qw.C^T matrix.
float fRPl[3][3]
a posteriori orientation matrix
float fK6x3[6][3]
kalman filter gain matrix K
float fAlphaQwbOver6
(PI / 180 * fdeltat) * Qwb / 6
float fMaxGyroOffsetChange
maximum permissible gyro offset change per iteration (deg/s)
float fRhoPl
compass (deg)
float fThePl
pitch (deg)
Quaternion fqPl
a posteriori orientation quaternion
float fdeltat
sensor fusion interval (s)
float fChiPl
tilt from vertical (deg)
float fPhiPl
roll (deg)
float fQwbOver3
Qwb / 3.
float fZErr[3]
measurement error vector
int32_t systick
systick timer;
float fbErrPl[3]
gyro offset error (deg/s)
int8_t resetflag
flag to request re-initialization on next pass
float fAlphaSqQvYQwbOver12
(PI / 180 * fdeltat)^2 * (QvY + Qwb) / 12
float fQv
measurement noise covariance matrix leading diagonal
float fRVecPl[3]
rotation vector
SV_9DOF_GBY_KALMAN is the 9DOF Kalman filter accelerometer, magnetometer and gyroscope state vector s...
int32_t systick
systick timer;
float fPhiPl
roll (deg)
float fThePl
pitch (deg)
float fAlphaOver2
PI / 180 * fdeltat / 2.
float fChiPl
tilt from vertical (deg)
float fDeltaPl
a posteriori inclination angle from Kalman filter (deg)
float fqmErrPl[3]
geomagnetic vector tilt orientation quaternion error (dimensionless)
float fqgErrPl[3]
gravity vector tilt orientation quaternion error (dimensionless)
float fbErrPl[3]
gyro offset error (deg/s)
float fcosDeltaPl
cos(fDeltaPl)
float fsinDeltaPl
sin(fDeltaPl)
float fPsiPl
yaw (deg)
float fRhoPl
compass (deg)
float fK9x6[9][6]
kalman filter gain matrix K
float fAlphaSqOver4
(PI / 180 * fdeltat)^2 / 4
float fQw9x9[9][9]
covariance matrix Qw
Quaternion fqPl
a posteriori orientation quaternion
float fAlphaSqQvYQwbOver12
(PI / 180 * fdeltat)^2 * (QvY + Qwb) / 12
float fRVecPl[3]
rotation vector
float fgdeltat
g (m/s2) * fdeltat
float fVelGl[3]
velocity (m/s) in global frame
float fAlphaQwbOver6
(PI / 180 * fdeltat) * Qwb / 6
float fDisGl[3]
displacement (m) in global frame
float fMaxGyroOffsetChange
maximum permissible gyro offset change per iteration (deg/s)
float fOmega[3]
average angular velocity (deg/s)
int8_t resetflag
flag to request re-initialization on next pass
float fAccGl[3]
linear acceleration (g) in global frame
float fbPl[3]
gyro offset (deg/s)
float fZErr[6]
measurement error vector
float fRPl[3][3]
a posteriori orientation matrix
float fdeltat
sensor fusion interval (s)
float fQwCT9x6[9][6]
Qw.C^T matrix.
float fQv6x1[6]
measurement noise covariance matrix leading diagonal
int8_t iFirstAccelMagLock
denotes that 9DOF orientation has locked to 6DOF eCompass
float fQwbOver3
Qwb / 3.
The top level fusion structure.
int32_t loopcounter
counter incrementing each iteration of sensor fusion (typically 25Hz)