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