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