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 #include <stdio.h>
10 #include "sensor_fusion.h"
11 #include "fusion.h"
12 
13 /*! \file precisionAccelerometer.c
14     \brief Implements accelerometer calibration routines
15 */
16 
17 // function resets the accelerometer buffer and accelerometer calibration
fInitializeAccelCalibration(struct AccelCalibration * pthisAccelCal,struct AccelBuffer * pthisAccelBuffer,volatile int8 * AccelCalPacketOn)18 void fInitializeAccelCalibration(struct AccelCalibration *pthisAccelCal,
19                                  struct AccelBuffer *pthisAccelBuffer,
20                                  volatile int8 *AccelCalPacketOn)
21 {
22     float   *pFlash;    // pointer into flash memory
23     int8_t  i,
24             j;          // loop counters
25 
26     // set flags to false to denote no precision accelerometer measurements
27     pthisAccelBuffer->iStoreFlags = 0;
28 
29     // perform one transmission of the precision calibration (using invalid value MAX_ACCEL_CAL_ORIENTATIONS for calibration only)
30     *AccelCalPacketOn = MAX_ACCEL_CAL_ORIENTATIONS;
31 
32     // check to see if the stored accelerometer calibration has been erased
33     // the standard value for erased flash is 0xFF in each byte but for portability check against 0x12345678
34 #ifndef SIMULATION
35     pFlash = (float *) (CALIBRATION_NVM_ADDR + ACCEL_NVM_OFFSET);
36     if (*((uint32 *) pFlash++) == 0x12345678)
37     {
38         // a precision accelerometer calibration is present in flash
39         // copy accelerometer calibration elements (21x float total 84 bytes) from flash to RAM
40         for (i = CHX; i <= CHZ; i++) pthisAccelCal->fV[i] = *(pFlash++);
41         for (i = CHX; i <= CHZ; i++)
42             for (j = CHX; j <= CHZ; j++)
43                 pthisAccelCal->finvW[i][j] = *(pFlash++);
44         for (i = CHX; i <= CHZ; i++)
45             for (j = CHX; j <= CHZ; j++)
46                 pthisAccelCal->fR0[i][j] = *(pFlash++);
47     }
48     else
49     {
50 #endif
51         // flash has been erased and no accelerometer calibration is present
52         // initialize the precision accelerometer calibration in RAM to null default
53         pthisAccelCal->fV[CHX] = pthisAccelCal->fV[CHY] = pthisAccelCal->fV[CHZ] = 0.0F;
54         f3x3matrixAeqI(pthisAccelCal->finvW);
55         f3x3matrixAeqI(pthisAccelCal->fR0);
56 #ifndef SIMULATION
57     }
58 #endif
59 
60     // set current averaging location and counter to -1 for invalid
61     pthisAccelBuffer->iStoreLocation = pthisAccelBuffer->iStoreCounter = -1;
62 
63     return;
64 }
65 
fUpdateAccelBuffer(struct AccelCalibration * pthisAccelCal,struct AccelBuffer * pthisAccelBuffer,struct AccelSensor * pthisAccel,volatile int8 * AccelCalPacketOn)66 void fUpdateAccelBuffer(struct AccelCalibration *pthisAccelCal,
67                         struct AccelBuffer *pthisAccelBuffer,
68                         struct AccelSensor *pthisAccel, volatile int8 *AccelCalPacketOn)
69 {
70     int16_t i;          // loop counter
71 
72     // iStoreCounter > 0: precision measurements are still on-going
73     // iStoreCounter = 0: the precision measurement has just finished
74     // iStoreCounter = -1: no precision measurement is in progress
75     // check if a new precision measurement has started and zero sums if one has
76     if (pthisAccelBuffer->iStoreCounter == (ACCEL_CAL_AVERAGING_SECS * FUSION_HZ))
77     {
78         for (i = CHX; i <= CHZ; i++) pthisAccelBuffer->fSumGs[i] = 0.0F;
79     }
80 
81     // accumulate sum if averaging not yet complete
82     if (pthisAccelBuffer->iStoreCounter > 0)
83     {
84         for (i = CHX; i <= CHZ; i++)
85             pthisAccelBuffer->fSumGs[i] += pthisAccel->fGs[i];
86         pthisAccelBuffer->iStoreCounter--;
87     }
88 
89     // check if the measurement accumulation is complete and, if so, store average and set packet transmit flag
90     if (pthisAccelBuffer->iStoreCounter == 0)
91     {
92         // store the measurement, set the relevant flag and decrement the counter down to -1 denoting completion
93         for (i = CHX; i <= CHZ; i++)
94         {
95             pthisAccelBuffer->fGsStored[pthisAccelBuffer->iStoreLocation][i] =
96                     pthisAccelBuffer->fSumGs[i] /
97                 (float) (ACCEL_CAL_AVERAGING_SECS * FUSION_HZ);
98         }
99 
100         pthisAccelBuffer->iStoreFlags |=
101             (
102                 1 <<
103                 pthisAccelBuffer->iStoreLocation
104             );
105         pthisAccelBuffer->iStoreCounter--;
106 
107         // compute the new precision accelerometer calibration including rotation using all measurements
108         fRunAccelCalibration(pthisAccelCal, pthisAccelBuffer, pthisAccel);
109 
110         // and make one packet transmission of this measurement with the new calibration
111         *AccelCalPacketOn = pthisAccelBuffer->iStoreLocation;
112     }
113 
114     return;
115 }
116 
117 // function maps the accelerometer data fGs (g) onto precision calibrated and de-rotated data fGc (g), iGc (counts)
fInvertAccelCal(struct AccelSensor * pthisAccel,struct AccelCalibration * pthisAccelCal)118 void fInvertAccelCal(struct AccelSensor *pthisAccel,
119                      struct AccelCalibration *pthisAccelCal)
120 {
121     // local variables
122     float   ftmp[3];    // temporary array
123     int8_t  i;          // loop counter
124 
125     //subtract the offset vector fV (g): ftmp[]=fGs[]-V[]
126     for (i = CHX; i <= CHZ; i++)
127         ftmp[i] = pthisAccel->fGs[i] - pthisAccelCal->fV[i];
128 
129     // apply the inverse rotation correction matrix finvW: fGc=inv(W)*(fGs[]-V[])
130     for (i = CHX; i <= CHZ; i++)
131     {
132         pthisAccel->fGc[i] = pthisAccelCal->finvW[i][CHX] *
133             ftmp[CHX] +
134             pthisAccelCal->finvW[i][CHY] *
135             ftmp[CHY] +
136             pthisAccelCal->finvW[i][CHZ] *
137             ftmp[CHZ];
138     }
139 
140     // apply the inverse of the forward rotation matrix fR0: fGc=inv(R).inv(W)*(fGs[]-V[])
141     for (i = CHX; i <= CHZ; i++) ftmp[i] = pthisAccel->fGc[i];
142     for (i = CHX; i <= CHZ; i++)
143     {
144         pthisAccel->fGc[i] = pthisAccelCal->fR0[CHX][i] *
145             ftmp[CHX] +
146             pthisAccelCal->fR0[CHY][i] *
147             ftmp[CHY] +
148             pthisAccelCal->fR0[CHZ][i] *
149             ftmp[CHZ];
150         pthisAccel->iGc[i] = (int16_t) (pthisAccel->fGc[i] * pthisAccel->iCountsPerg);
151     }
152 
153     return;
154 }
155 
156 // function runs the precision accelerometer calibration
fRunAccelCalibration(struct AccelCalibration * pthisAccelCal,struct AccelBuffer * pthisAccelBuffer,struct AccelSensor * pthisAccel)157 void fRunAccelCalibration(struct AccelCalibration *pthisAccelCal,
158                           struct AccelBuffer *pthisAccelBuffer,
159                           struct AccelSensor *pthisAccel)
160 {
161     float   fGc0[3];        // calibrated but not de-rotated measurement 0
162     uint8_t iMeasurements;  // number of stored measurements
163     int8_t  i;              // loop counters
164 
165     // calculate how many measurements are present in the accelerometer measurement buffer
166     iMeasurements = 0;
167     for (i = 0; i < MAX_ACCEL_CAL_ORIENTATIONS; i++)
168     {
169         if (pthisAccelBuffer->iStoreFlags & (1 << i)) iMeasurements++;
170     }
171 
172     // perform the highest quality calibration possible given this number
173     if (iMeasurements >= 9)
174     {
175         // perform the 10 element calibration
176         fComputeAccelCalibration10(pthisAccelBuffer, pthisAccelCal, pthisAccel);
177     }
178     else if (iMeasurements >= 6)
179     {
180         // perform the 7 element calibration
181         fComputeAccelCalibration7(pthisAccelBuffer, pthisAccelCal, pthisAccel);
182     }
183     else if (iMeasurements >= 4)
184     {
185         // perform the 4 element calibration
186         fComputeAccelCalibration4(pthisAccelBuffer, pthisAccelCal, pthisAccel);
187     }
188 
189     // calculate the rotation correction matrix to rotate calibrated measurement 0 to flat
190     if (pthisAccelBuffer->iStoreFlags & 1)
191     {
192         // apply offset and gain calibration but not rotation to measurement 0 (flat) if present
193         // set ftmpA3x1 = invW . (fGs - fV)
194         for (i = CHX; i <= CHZ; i++)
195             fGc0[i] = pthisAccelBuffer->fGsStored[0][i] - pthisAccelCal->fV[i];
196         fVeq3x3AxV(fGc0, pthisAccelCal->finvW);
197 
198         // compute the new final rotation matrix if rotation 0 (flat) is present.
199         // multiplying by the transpose of this matrix therefore forces measurement zero to flat.
200 #if THISCOORDSYSTEM == NED
201                 f3DOFTiltNED(pthisAccelCal->fR0, fGc0);
202 #endif
203 #if THISCOORDSYSTEM == ANDROID
204                 f3DOFTiltAndroid(pthisAccelCal->fR0, fGc0);
205 #endif
206 #if THISCOORDSYSTEM == WIN8
207                 f3DOFTiltWin8(pthisAccelCal->fR0, fGc0);
208 #endif
209     }
210 
211     return;
212 }
213 
214 // calculate the 4 element calibration from the available measurements
fComputeAccelCalibration4(struct AccelBuffer * pthisAccelBuffer,struct AccelCalibration * pthisAccelCal,struct AccelSensor * pthisAccel)215 void fComputeAccelCalibration4(struct AccelBuffer *pthisAccelBuffer,
216                                struct AccelCalibration *pthisAccelCal,
217                                struct AccelSensor *pthisAccel)
218 {
219     int32   i,
220             j;      // loop counters
221     float   ftmp;   // scratch
222     int8_t  ierror; // flag from matrix inversion
223 
224     // working arrays for 4x4 matrix inversion
225     float   *pfRows[4];
226     int8_t  iColInd[4];
227     int8_t  iRowInd[4];
228     int8_t  iPivot[4];
229 
230     // zero the 4x4 matrix XTX (in upper left of fmatA) and 4x1 vector XTY (in upper fvecA)
231     for (i = 0; i < 4; i++)
232     {
233         pthisAccelCal->fvecA[i] = 0.0F;
234         for (j = 0; j < 4; j++)
235         {
236             pthisAccelCal->fmatA[i][j] = 0.0F;
237         }
238     }
239 
240     // accumulate fXTY (in fvecA) and fXTX4x4 (in fmatA)
241     for (i = 0; i < MAX_ACCEL_CAL_ORIENTATIONS; i++)
242     {
243         // accumulate vector X^T.Y if this entry is valid
244         if ((pthisAccelBuffer->iStoreFlags) & (1 << i))
245         {
246             ftmp = pthisAccelBuffer->fGsStored[i][CHX] *
247                 pthisAccelBuffer->fGsStored[i][CHX] +
248                 pthisAccelBuffer->fGsStored[i][CHY] *
249                 pthisAccelBuffer->fGsStored[i][CHY] +
250                 pthisAccelBuffer->fGsStored[i][CHZ] *
251                 pthisAccelBuffer->fGsStored[i][CHZ];
252             pthisAccelCal->fvecA[0] += pthisAccelBuffer->fGsStored[i][CHX] * ftmp;
253             pthisAccelCal->fvecA[1] += pthisAccelBuffer->fGsStored[i][CHY] * ftmp;
254             pthisAccelCal->fvecA[2] += pthisAccelBuffer->fGsStored[i][CHZ] * ftmp;
255             pthisAccelCal->fvecA[3] += ftmp;
256 
257             // accumulate above diagonal terms of matrix X^T.X
258             pthisAccelCal->fmatA[CHX][CHX] += pthisAccelBuffer->fGsStored[i][
259                     CHX] *
260                 pthisAccelBuffer->fGsStored[i][CHX];
261             pthisAccelCal->fmatA[CHX][CHY] += pthisAccelBuffer->fGsStored[i][
262                     CHX] *
263                 pthisAccelBuffer->fGsStored[i][CHY];
264             pthisAccelCal->fmatA[CHX][CHZ] += pthisAccelBuffer->fGsStored[i][
265                     CHX] *
266                 pthisAccelBuffer->fGsStored[i][CHZ];
267             pthisAccelCal->fmatA[CHX][3] += pthisAccelBuffer->fGsStored[i][CHX];
268             pthisAccelCal->fmatA[CHY][CHY] += pthisAccelBuffer->fGsStored[i][
269                     CHY] *
270                 pthisAccelBuffer->fGsStored[i][CHY];
271             pthisAccelCal->fmatA[CHY][CHZ] += pthisAccelBuffer->fGsStored[i][
272                     CHY] *
273                 pthisAccelBuffer->fGsStored[i][CHZ];;
274             pthisAccelCal->fmatA[CHY][3] += pthisAccelBuffer->fGsStored[i][CHY];
275             pthisAccelCal->fmatA[CHZ][CHZ] += pthisAccelBuffer->fGsStored[i][
276                     CHZ] *
277                 pthisAccelBuffer->fGsStored[i][CHZ];;
278             pthisAccelCal->fmatA[CHZ][3] += pthisAccelBuffer->fGsStored[i][CHZ];
279             pthisAccelCal->fmatA[3][3] += 1.0F;
280         }
281     }
282 
283     // copy above diagonal elements of fXTX4x4 = X^T.X to below diagonal elements
284     pthisAccelCal->fmatA[CHY][CHX] = pthisAccelCal->fmatA[CHX][CHY];
285     pthisAccelCal->fmatA[CHZ][CHX] = pthisAccelCal->fmatA[CHX][CHZ];
286     pthisAccelCal->fmatA[CHZ][CHY] = pthisAccelCal->fmatA[CHY][CHZ];
287     pthisAccelCal->fmatA[3][CHX] = pthisAccelCal->fmatA[CHX][3];
288     pthisAccelCal->fmatA[3][CHY] = pthisAccelCal->fmatA[CHY][3];
289     pthisAccelCal->fmatA[3][CHZ] = pthisAccelCal->fmatA[CHZ][3];
290 
291     // calculate in situ inverse of X^T.X
292     for (i = 0; i < 4; i++)
293     {
294         pfRows[i] = pthisAccelCal->fmatA[i];
295     }
296 
297     fmatrixAeqInvA(pfRows, iColInd, iRowInd, iPivot, 4, &ierror);
298 
299     // calculate the solution vector fvecB = inv(X^T.X).X^T.Y
300     for (i = 0; i < 4; i++)
301     {
302         pthisAccelCal->fvecB[i] = 0.0F;
303         for (j = 0; j < 4; j++)
304         {
305             pthisAccelCal->fvecB[i] += pthisAccelCal->fmatA[i][j] * pthisAccelCal->fvecA[j];
306         }
307     }
308 
309     // extract the offset vector
310     pthisAccelCal->fV[CHX] = 0.5F * pthisAccelCal->fvecB[CHX];
311     pthisAccelCal->fV[CHY] = 0.5F * pthisAccelCal->fvecB[CHY];
312     pthisAccelCal->fV[CHZ] = 0.5F * pthisAccelCal->fvecB[CHZ];
313 
314     // set ftmp to 1/W where W is the forward gain to fit the 1g sphere
315     ftmp = 1.0F / sqrtf(fabsf(pthisAccelCal->fvecB[3] + pthisAccelCal->fV[CHX] *
316                         pthisAccelCal->fV[CHX] + pthisAccelCal->fV[CHY] *
317                         pthisAccelCal->fV[CHY] + pthisAccelCal->fV[CHZ] *
318                         pthisAccelCal->fV[CHZ]));
319 
320     // copy the inverse gain 1/W to the inverse gain matrix
321     pthisAccelCal->finvW[CHX][CHY] = pthisAccelCal->finvW[CHY][CHX] = 0.0F;
322     pthisAccelCal->finvW[CHX][CHZ] = pthisAccelCal->finvW[CHZ][CHX] = 0.0F;
323     pthisAccelCal->finvW[CHY][CHZ] = pthisAccelCal->finvW[CHZ][CHY] = 0.0F;
324     pthisAccelCal->finvW[CHX][CHX] = pthisAccelCal->finvW[CHY][CHY] = pthisAccelCal->finvW[CHZ][CHZ] = ftmp;
325 
326     return;
327 }
328 
329 // calculate the 7 element calibration from the available measurements
fComputeAccelCalibration7(struct AccelBuffer * pthisAccelBuffer,struct AccelCalibration * pthisAccelCal,struct AccelSensor * pthisAccel)330 void fComputeAccelCalibration7(struct AccelBuffer *pthisAccelBuffer,
331                                struct AccelCalibration *pthisAccelCal,
332                                struct AccelSensor *pthisAccel)
333 {
334     int32   i,
335             j,
336             m,
337             n;      // loop counters
338     float   det;    // matrix determinant
339     float   fg0;    // fitted local gravity magnitude
340 
341     // zero the on and above diagonal elements of the 7x7 symmetric measurement matrix fmatA
342     for (i = 0; i < 7; i++)
343         for (j = i; j < 7; j++) pthisAccelCal->fmatA[i][j] = 0.0F;
344 
345     // last entry of vector fvecA is always 1.0 so move assignment outside the loop
346     pthisAccelCal->fvecA[6] = 1.0F;
347 
348     // loop over all orientations
349     for (i = 0; i < MAX_ACCEL_CAL_ORIENTATIONS; i++)
350     {
351         // accumulate fvecA if this entry is valid
352         if ((pthisAccelBuffer->iStoreFlags) & (1 << i))
353         {
354             // compute the remaining members of the measurement vector fvecA
355             pthisAccelCal->fvecA[0] = pthisAccelBuffer->fGsStored[i][CHX] * pthisAccelBuffer->fGsStored[i][CHX];
356             pthisAccelCal->fvecA[1] = pthisAccelBuffer->fGsStored[i][CHY] * pthisAccelBuffer->fGsStored[i][CHY];
357             pthisAccelCal->fvecA[2] = pthisAccelBuffer->fGsStored[i][CHZ] * pthisAccelBuffer->fGsStored[i][CHZ];
358             pthisAccelCal->fvecA[3] = pthisAccelBuffer->fGsStored[i][CHX];
359             pthisAccelCal->fvecA[4] = pthisAccelBuffer->fGsStored[i][CHY];
360             pthisAccelCal->fvecA[5] = pthisAccelBuffer->fGsStored[i][CHZ];
361 
362             // accumulate the on-and above-diagonal terms of fmatA=Sigma{fvecA^T * fvecA}
363             for (m = 0; m < 7; m++)
364                 for (n = m; n < 7; n++)
365                     pthisAccelCal->fmatA[m][n] += pthisAccelCal->fvecA[m] * pthisAccelCal->fvecA[n];
366         }           // end of test for stored data
367     }               // end of loop over orientations
368 
369     // copy the above diagonal elements of symmetric product matrix fmatA to below the diagonal
370     for (m = 1; m < 7; m++)
371         for (n = 0; n < m; n++)
372             pthisAccelCal->fmatA[m][n] = pthisAccelCal->fmatA[n][m];
373 
374     // set fvecA to the unsorted eigenvalues and fmatB to the unsorted normalized eigenvectors of fmatA
375     fEigenCompute10(pthisAccelCal->fmatA, pthisAccelCal->fvecA,
376                     pthisAccelCal->fmatB, 7);
377 
378     // set ellipsoid matrix A from elements of the solution vector column j with smallest eigenvalue
379     j = 0;
380     for (i = 1; i < 7; i++)
381         if (pthisAccelCal->fvecA[i] < pthisAccelCal->fvecA[j]) j = i;
382 
383     // negate the entire solution vector if ellipsoid matrix has negative determinant
384     det = pthisAccelCal->fmatB[0][j] *
385         pthisAccelCal->fmatB[1][j] *
386         pthisAccelCal->fmatB[2][j];
387     if (det < 0.0F)
388     {
389         for (i = 0; i < 7; i++)
390         {
391             pthisAccelCal->fmatB[i][j] = -pthisAccelCal->fmatB[i][j];
392         }
393     }
394 
395     // compute invW and V and fitted gravity g0 from solution vector j
396     f3x3matrixAeqScalar(pthisAccelCal->finvW, 0.0F);
397     pthisAccelCal->finvW[CHX][CHX] = sqrtf(fabsf(pthisAccelCal->fmatB[0][j]));
398     pthisAccelCal->finvW[CHY][CHY] = sqrtf(fabsf(pthisAccelCal->fmatB[1][j]));
399     pthisAccelCal->finvW[CHZ][CHZ] = sqrtf(fabsf(pthisAccelCal->fmatB[2][j]));
400     pthisAccelCal->fV[CHX] = -0.5F *
401         pthisAccelCal->fmatB[3][j] /
402         pthisAccelCal->fmatB[0][j];
403     pthisAccelCal->fV[CHY] = -0.5F *
404         pthisAccelCal->fmatB[4][j] /
405         pthisAccelCal->fmatB[1][j];
406     pthisAccelCal->fV[CHZ] = -0.5F *
407         pthisAccelCal->fmatB[5][j] /
408         pthisAccelCal->fmatB[2][j];
409     fg0 = sqrtf(fabsf(pthisAccelCal->fmatB[0][j] * pthisAccelCal->fV[CHX] *
410                 pthisAccelCal->fV[CHX] + pthisAccelCal->fmatB[1][j] *
411                 pthisAccelCal->fV[CHY] * pthisAccelCal->fV[CHY] +
412                 pthisAccelCal->fmatB[2][j] * pthisAccelCal->fV[CHZ] *
413                 pthisAccelCal->fV[CHZ] - pthisAccelCal->fmatB[6][j]));
414 
415     // normalize invW to fit the 1g sphere
416     pthisAccelCal->finvW[CHX][CHX] /= fg0;
417     pthisAccelCal->finvW[CHY][CHY] /= fg0;
418     pthisAccelCal->finvW[CHZ][CHZ] /= fg0;
419 
420     return;
421 }
422 
423 // calculate the 10 element calibration from the available measurements
fComputeAccelCalibration10(struct AccelBuffer * pthisAccelBuffer,struct AccelCalibration * pthisAccelCal,struct AccelSensor * pthisAccel)424 void fComputeAccelCalibration10(struct AccelBuffer *pthisAccelBuffer,
425                                 struct AccelCalibration *pthisAccelCal,
426                                 struct AccelSensor *pthisAccel)
427 {
428     int32   i,
429             j,
430             k,
431             l,
432             m,
433             n;                  // loop counters
434     float   det;                // matrix determinant
435     float   ftmp;               // scratch
436     float   fg0;                // fitted local gravity magnitude
437 
438     // zero the on and above diagonal elements of the 10x10 symmetric measurement matrix fmatA
439     for (i = 0; i < 10; i++)
440         for (j = i; j < 10; j++) pthisAccelCal->fmatA[i][j] = 0.0F;
441 
442     // last entry of vector fvecA is always 1.0 so move assignment outside the loop
443     pthisAccelCal->fvecA[9] = 1.0F;
444 
445     // loop over all orientations
446     for (i = 0; i < MAX_ACCEL_CAL_ORIENTATIONS; i++)
447     {
448         // accumulate fvecA if this entry is valid
449         if ((pthisAccelBuffer->iStoreFlags) & (1 << i))
450         {
451             // compute the remaining members of the measurement vector fvecA
452             pthisAccelCal->fvecA[6] = pthisAccelBuffer->fGsStored[i][CHX];
453             pthisAccelCal->fvecA[7] = pthisAccelBuffer->fGsStored[i][CHY];
454             pthisAccelCal->fvecA[8] = pthisAccelBuffer->fGsStored[i][CHZ];
455             pthisAccelCal->fvecA[0] = pthisAccelCal->fvecA[6] * pthisAccelCal->fvecA[6];
456             pthisAccelCal->fvecA[1] = 2.0F *
457                 pthisAccelCal->fvecA[6] *
458                 pthisAccelCal->fvecA[7];
459             pthisAccelCal->fvecA[2] = 2.0F *
460                 pthisAccelCal->fvecA[6] *
461                 pthisAccelCal->fvecA[8];
462             pthisAccelCal->fvecA[3] = pthisAccelCal->fvecA[7] * pthisAccelCal->fvecA[7];
463             pthisAccelCal->fvecA[4] = 2.0F *
464                 pthisAccelCal->fvecA[7] *
465                 pthisAccelCal->fvecA[8];
466             pthisAccelCal->fvecA[5] = pthisAccelCal->fvecA[8] * pthisAccelCal->fvecA[8];
467 
468             // accumulate the on-and above-diagonal terms of fmatA=Sigma{fvecA^T * fvecA}
469             for (m = 0; m < 10; m++)
470             {
471                 for (n = m; n < 10; n++)
472                 {
473                     pthisAccelCal->fmatA[m][n] += pthisAccelCal->fvecA[m] * pthisAccelCal->fvecA[n];
474                 }
475             }
476         }                       // end of test for stored data
477     }                           // end of loop over orientations
478 
479     // copy the above diagonal elements of symmetric product matrix fmatA to below the diagonal
480     for (m = 1; m < 10; m++)
481         for (n = 0; n < m; n++)
482             pthisAccelCal->fmatA[m][n] = pthisAccelCal->fmatA[n][m];
483 
484     // set fvecA to the unsorted eigenvalues and fmatB to the unsorted normalized eigenvectors of fmatA
485     fEigenCompute10(pthisAccelCal->fmatA, pthisAccelCal->fvecA,
486                     pthisAccelCal->fmatB, 10);
487 
488     // set ellipsoid matrix A from elements of the solution vector column j with smallest eigenvalue
489     j = 0;
490     for (i = 1; i < 10; i++)
491     {
492         if (pthisAccelCal->fvecA[i] < pthisAccelCal->fvecA[j])
493         {
494             j = i;
495         }
496     }
497 
498     pthisAccelCal->fA[0][0] = pthisAccelCal->fmatB[0][j];
499     pthisAccelCal->fA[0][1] = pthisAccelCal->fA[1][0] = pthisAccelCal->fmatB[1][j];
500     pthisAccelCal->fA[0][2] = pthisAccelCal->fA[2][0] = pthisAccelCal->fmatB[2][j];
501     pthisAccelCal->fA[1][1] = pthisAccelCal->fmatB[3][j];
502     pthisAccelCal->fA[1][2] = pthisAccelCal->fA[2][1] = pthisAccelCal->fmatB[4][j];
503     pthisAccelCal->fA[2][2] = pthisAccelCal->fmatB[5][j];
504 
505     // negate entire solution if A has negative determinant
506     det = f3x3matrixDetA(pthisAccelCal->fA);
507     if (det < 0.0F)
508     {
509         f3x3matrixAeqMinusA(pthisAccelCal->fA);
510         pthisAccelCal->fmatB[6][j] = -pthisAccelCal->fmatB[6][j];
511         pthisAccelCal->fmatB[7][j] = -pthisAccelCal->fmatB[7][j];
512         pthisAccelCal->fmatB[8][j] = -pthisAccelCal->fmatB[8][j];
513         pthisAccelCal->fmatB[9][j] = -pthisAccelCal->fmatB[9][j];
514         det = -det;
515     }
516 
517     // compute the inverse of the ellipsoid matrix
518     f3x3matrixAeqInvSymB(pthisAccelCal->finvA, pthisAccelCal->fA);
519 
520     // compute the offset vector V
521     for (l = CHX; l <= CHZ; l++)
522     {
523         pthisAccelCal->fV[l] = 0.0F;
524         for (m = CHX; m <= CHZ; m++)
525         {
526             pthisAccelCal->fV[l] += pthisAccelCal->finvA[l][m] * pthisAccelCal->fmatB[m + 6][j];
527         }
528 
529         pthisAccelCal->fV[l] *= -0.5F;
530     }
531 
532     // compute the local gravity fit to these calibration coefficients
533     fg0 = sqrtf(fabsf(pthisAccelCal->fA[0][0] * pthisAccelCal->fV[CHX] *
534                 pthisAccelCal->fV[CHX] + 2.0F * pthisAccelCal->fA[0][1] *
535                 pthisAccelCal->fV[CHX] * pthisAccelCal->fV[CHY] + 2.0F *
536                 pthisAccelCal->fA[0][2] * pthisAccelCal->fV[CHX] *
537                 pthisAccelCal->fV[CHZ] + pthisAccelCal->fA[1][1] *
538                 pthisAccelCal->fV[CHY] * pthisAccelCal->fV[CHY] + 2.0F *
539                 pthisAccelCal->fA[1][2] * pthisAccelCal->fV[CHY] *
540                 pthisAccelCal->fV[CHZ] + pthisAccelCal->fA[2][2] *
541                 pthisAccelCal->fV[CHZ] * pthisAccelCal->fV[CHZ] -
542                 pthisAccelCal->fmatB[9][j]));
543 
544     // compute trial invW from the square root of fA
545     // set fvecA to the unsorted eigenvalues and fmatB to the unsorted eigenvectors of fmatA
546     // where fmatA holds the 3x3 matrix fA in its top left elements
547     for (i = 0; i < 3; i++)
548         for (j = 0; j < 3; j++)
549             pthisAccelCal->fmatA[i][j] = pthisAccelCal->fA[i][j];
550     fEigenCompute10(pthisAccelCal->fmatA, pthisAccelCal->fvecA,
551                     pthisAccelCal->fmatB, 3);
552 
553     // set fmatB to be eigenvectors . diag(sqrt(sqrt(eigenvalues))) = fmatB . diag(sqrt(sqrt(fvecA))
554     for (j = 0; j < 3; j++)     // loop over columns j
555     {
556         ftmp = sqrtf(sqrtf(fabsf(pthisAccelCal->fvecA[j])));
557         for (i = 0; i < 3; i++) // loop over rows i
558         {
559             pthisAccelCal->fmatB[i][j] *= ftmp;
560         }
561     }
562 
563     // set finvW to eigenvectors * diag(sqrt(eigenvalues)) * eigenvectors^T
564     // = fmatB * fmatB^T = sqrt(fA) (guaranteed symmetric)
565     // loop over rows
566     for (i = 0; i < 3; i++)
567     {
568         // loop over on and above diagonal columns
569         for (j = i; j < 3; j++)
570         {
571             pthisAccelCal->finvW[i][j] = 0.0F;
572 
573             // accumulate the matrix product
574             for (k = 0; k < 3; k++)
575             {
576                 pthisAccelCal->finvW[i][j] += pthisAccelCal->fmatB[i][k] * pthisAccelCal->fmatB[j][k];
577             }
578 
579             // copy to below diagonal element
580             pthisAccelCal->finvW[j][i] = pthisAccelCal->finvW[i][j];
581         }
582     }
583 
584     // scale finvW so that the calibrated measurements fit on the 1g sphere
585     for (i = CHX; i <= CHZ; i++)
586     {
587         for (j = CHX; j <= CHZ; j++)
588         {
589             pthisAccelCal->finvW[i][j] /= fg0;
590         }
591     }
592 
593     return;
594 }
595