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 /*! \file magnetic.c
10 \brief Lower level magnetic calibration interface
11
12 Many developers can utilize the NXP Sensor Fusion Library without ever
13 making any adjustment to the lower level magnetic calibration functions
14 defined in this file.
15 */
16
17 #include "sensor_fusion.h"
18 #include "math.h"
19 #include "stdlib.h"
20 #include "time.h"
21
22 #if F_USING_MAG
23 // function resets the magnetometer buffer and magnetic calibration
fInitializeMagCalibration(struct MagCalibration * pthisMagCal,struct MagBuffer * pthisMagBuffer)24 void fInitializeMagCalibration(struct MagCalibration *pthisMagCal,
25 struct MagBuffer *pthisMagBuffer)
26 {
27 float *pFlash; // pointer into flash memory
28 int8 i,
29 j; // loop counters
30
31 // set magnetic buffer index to invalid value -1 to denote no measurement present
32 pthisMagBuffer->iMagBufferCount = 0;
33 for (i = 0; i < MAGBUFFSIZEX; i++)
34 for (j = 0; j < MAGBUFFSIZEY; j++) pthisMagBuffer->index[i][j] = -1;
35
36 // initialize the array of (MAGBUFFSIZEX - 1) elements of 100 * tangents used for buffer indexing
37 // entries cover the range 100 * tan(-PI/2 + PI/MAGBUFFSIZEX), 100 * tan(-PI/2 + 2*PI/MAGBUFFSIZEX) to
38 // 100 * tan(-PI/2 + (MAGBUFFSIZEX - 1) * PI/MAGBUFFSIZEX).
39 // for MAGBUFFSIZEX=12, the entries range in value from -373 to +373
40 for (i = 0; i < (MAGBUFFSIZEX - 1); i++)
41 pthisMagBuffer->tanarray[i] = (int16) (100.0F * tanf(PI * (-0.5F + (float) (i + 1) / MAGBUFFSIZEX)));
42
43 // check to see if the stored magnetic calibration has been erased
44 // the standard value for erased flash is 0xFF in each byte but for portability check against 0x12345678
45 #ifndef SIMULATION
46
47 pFlash = (float *) (CALIBRATION_NVM_ADDR + MAG_NVM_OFFSET);
48 if (*((uint32 *) pFlash++) == 0x12345678)
49 {
50 // a magnetic calibration is present in flash
51 // copy magnetic calibration elements (15x float + 1x int32 total 64 bytes) from flash to RAM
52 for (i = CHX; i <= CHZ; i++) pthisMagCal->fV[i] = *(pFlash++);
53 for (i = CHX; i <= CHZ; i++)
54 for (j = CHX; j <= CHZ; j++)
55 pthisMagCal->finvW[i][j] = *(pFlash++);
56 pthisMagCal->fB = *(pFlash++);
57 pthisMagCal->fBSq = *(pFlash++);
58 pthisMagCal->fFitErrorpc = *(pFlash++);
59 pthisMagCal->iValidMagCal = *((int32 *) pFlash);
60 }
61 else
62 {
63 #endif
64 // flash has been erased and no magnetic calibration is present
65 // initialize the magnetic calibration in RAM to null default
66 pthisMagCal->fV[CHX] = pthisMagCal->fV[CHY] = pthisMagCal->fV[CHZ] = 0.0F;
67 f3x3matrixAeqI(pthisMagCal->finvW);
68 pthisMagCal->fB = DEFAULTB;
69 pthisMagCal->fBSq = DEFAULTB * DEFAULTB;
70 pthisMagCal->fFitErrorpc = 100.0F;
71 pthisMagCal->iValidMagCal = 0;
72 #ifndef SIMULATION
73 }
74 #endif
75
76 // initialize remaining elements of the magnetic calibration structure
77 pthisMagCal->iCalInProgress = 0;
78 pthisMagCal->iInitiateMagCal = 0;
79 pthisMagCal->iNewCalibrationAvailable = 0;
80 pthisMagCal->iMagBufferReadOnly = false;
81 pthisMagCal->i4ElementSolverTried = false;
82 pthisMagCal->i7ElementSolverTried = false;
83 pthisMagCal->i10ElementSolverTried = false;
84
85 return;
86 }
87
88 // function updates the magnetic measurement buffer with most recent magnetic data (typically 200Hz)
89
90 // the uncalibrated measurements iBs are stored in the buffer but the calibrated measurements iBc are used for indexing.
iUpdateMagBuffer(struct MagBuffer * pthisMagBuffer,struct MagSensor * pthisMag,int32 loopcounter)91 void iUpdateMagBuffer(struct MagBuffer *pthisMagBuffer, struct MagSensor *pthisMag,
92 int32 loopcounter)
93 {
94 // local variables
95 int32 idelta; // absolute vector distance
96 int32 i; // counter
97 int16 itanj,
98 itank; // indexing accelerometer ratios
99 int8 j,
100 k,
101 l,
102 m; // counters
103 int8 itooclose; // flag denoting measurement is too close to existing ones
104
105 // calculate the magnetometer buffer bins from the tangent ratios
106 if (pthisMag->iBc[CHZ] == 0) return;
107 itanj = (100 * (int32) pthisMag->iBc[CHX]) / ((int32) pthisMag->iBc[CHZ]);
108 itank = (100 * (int32) pthisMag->iBc[CHY]) / ((int32) pthisMag->iBc[CHZ]);
109
110 // map tangent ratios to bins j and k using equal angle bins: C guarantees left to right execution of the test
111 // and add an offset of MAGBUFFSIZEX bins to k to mimic atan2 on this ratio
112 // j will vary from 0 to MAGBUFFSIZEX - 1 and k from 0 to 2 * MAGBUFFSIZEX - 1
113 j = k = 0;
114 while ((j < (MAGBUFFSIZEX - 1) && (itanj >= pthisMagBuffer->tanarray[j])))
115 j++;
116 while ((k < (MAGBUFFSIZEX - 1) && (itank >= pthisMagBuffer->tanarray[k])))
117 k++;
118 if (pthisMag->iBc[CHX] < 0) k += MAGBUFFSIZEX;
119
120 // case 1: buffer is full and this bin has a measurement: over-write without increasing number of measurements
121 // this is the most common option at run time
122 if ((pthisMagBuffer->iMagBufferCount == MAXMEASUREMENTS) &&
123 (pthisMagBuffer->index[j][k] != -1))
124 {
125 // store the fast (unaveraged at typically 200Hz) integer magnetometer reading into the buffer bin j, k
126 for (i = CHX; i <= CHZ; i++)
127 {
128 pthisMagBuffer->iBs[i][j][k] = pthisMag->iBs[i];
129 }
130
131 pthisMagBuffer->index[j][k] = loopcounter;
132 return;
133 } // end case 1
134
135 // case 2: the buffer is full and this bin does not have a measurement: store and retire the oldest
136 // this is the second most common option at run time
137 if ((pthisMagBuffer->iMagBufferCount == MAXMEASUREMENTS) &&
138 (pthisMagBuffer->index[j][k] == -1))
139 {
140 // store the fast (unaveraged at typically 200Hz) integer magnetometer reading into the buffer bin j, k
141 for (i = CHX; i <= CHZ; i++)
142 {
143 pthisMagBuffer->iBs[i][j][k] = pthisMag->iBs[i];
144 }
145
146 pthisMagBuffer->index[j][k] = loopcounter;
147
148 // set l and m to the oldest active entry and disable it
149 i = loopcounter;
150 l = m = 0; // to avoid compiler complaint
151 for (j = 0; j < MAGBUFFSIZEX; j++)
152 {
153 for (k = 0; k < MAGBUFFSIZEY; k++)
154 {
155 // check if the time stamp is older than the oldest found so far (normally fails this test)
156 if (pthisMagBuffer->index[j][k] < i)
157 {
158 // check if this bin is active (normally passes this test)
159 if (pthisMagBuffer->index[j][k] != -1)
160 {
161 // set l and m to the indices of the oldest entry found so far
162 l = j;
163 m = k;
164
165 // set i to the time stamp of the oldest entry found so far
166 i = pthisMagBuffer->index[l][m];
167 } // end of test for active
168 } // end of test for older
169 } // end of loop over k
170 } // end of loop over j
171
172 // deactivate the oldest measurement (no need to zero the measurement data)
173 pthisMagBuffer->index[l][m] = -1;
174 return;
175 } // end case 2
176
177 // case 3: buffer is not full and this bin is empty: store and increment number of measurements
178 if ((pthisMagBuffer->iMagBufferCount < MAXMEASUREMENTS) &&
179 (pthisMagBuffer->index[j][k] == -1))
180 {
181 // store the fast (unaveraged at typically 200Hz) integer magnetometer reading into the buffer bin j, k
182 for (i = CHX; i <= CHZ; i++)
183 {
184 pthisMagBuffer->iBs[i][j][k] = pthisMag->iBs[i];
185 }
186
187 pthisMagBuffer->index[j][k] = loopcounter;
188 (pthisMagBuffer->iMagBufferCount)++;
189 return;
190 } // end case 3
191
192 // case 4: buffer is not full and this bin has a measurement: over-write if close or try to slot in
193 // elsewhere if not close to the other measurements so as to create a mesh at power up
194 if ((pthisMagBuffer->iMagBufferCount < MAXMEASUREMENTS) &&
195 (pthisMagBuffer->index[j][k] != -1))
196 {
197 // calculate the vector difference between current measurement and the buffer entry
198 idelta = 0;
199 for (i = CHX; i <= CHZ; i++)
200 {
201 idelta += abs((int32) pthisMag->iBs[i] -
202 (int32) pthisMagBuffer->iBs[i][j][k]);
203 }
204
205 // check to see if the current reading is close to this existing magnetic buffer entry
206 if (idelta < MESHDELTACOUNTS)
207 {
208 // simply over-write the measurement and return
209 for (i = CHX; i <= CHZ; i++)
210 {
211 pthisMagBuffer->iBs[i][j][k] = pthisMag->iBs[i];
212 }
213
214 pthisMagBuffer->index[j][k] = loopcounter;
215 }
216 else
217 {
218 // reset the flag denoting that the current measurement is close to any measurement in the buffer
219 itooclose = 0;
220
221 // to avoid compiler warning
222 l = m = 0;
223
224 // loop over the buffer j from 0 potentially up to MAGBUFFSIZEX - 1
225 j = 0;
226 while (!itooclose && (j < MAGBUFFSIZEX))
227 {
228 // loop over the buffer k from 0 potentially up to MAGBUFFSIZEY - 1
229 k = 0;
230 while (!itooclose && (k < MAGBUFFSIZEY))
231 {
232 // check whether this buffer entry already has a measurement or not
233 if (pthisMagBuffer->index[j][k] != -1)
234 {
235 // calculate the vector difference between current measurement and the buffer entry
236 idelta = 0;
237 for (i = CHX; i <= CHZ; i++)
238 {
239 idelta += abs((int32) pthisMag->iBs[i] -
240 (int32) pthisMagBuffer->iBs[i][j][k]);
241 }
242
243 // check to see if the current reading is close to this existing magnetic buffer entry
244 if (idelta < MESHDELTACOUNTS)
245 {
246 // set the flag to abort the search
247 itooclose = 1;
248 }
249 }
250 else
251 {
252 // store the location of this empty bin for future use
253 l = j;
254 m = k;
255 } // end of test for valid measurement in this bin
256
257 k++;
258 } // end of k loop
259
260 j++;
261 } // end of j loop
262
263 // if none too close, store the measurement in the last empty bin found and return
264 // l and m are guaranteed to be set if no entries too close are detected
265 if (!itooclose)
266 {
267 for (i = CHX; i <= CHZ; i++)
268 {
269 pthisMagBuffer->iBs[i][l][m] = pthisMag->iBs[i];
270 }
271
272 pthisMagBuffer->index[l][m] = loopcounter;
273 (pthisMagBuffer->iMagBufferCount)++;
274 }
275 } // end of test for closeness to current buffer entry
276
277 return;
278 } // end case 4
279
280 // this line should be unreachable
281 return;
282 }
283
284 // function maps the uncalibrated magnetometer data fBs (uT) onto calibrated averaged data fBc (uT), iBc (counts)
fInvertMagCal(struct MagSensor * pthisMag,struct MagCalibration * pthisMagCal)285 void fInvertMagCal(struct MagSensor *pthisMag, struct MagCalibration *pthisMagCal)
286 {
287 // local variables
288 float ftmp[3]; // temporary array
289 int8 i; // loop counter
290
291 // remove the computed hard iron offsets (uT): ftmp[]=fBs[]-V[]
292 for (i = CHX; i <= CHZ; i++)
293 {
294 ftmp[i] = pthisMag->fBs[i] - pthisMagCal->fV[i];
295 }
296
297 // remove the computed soft iron offsets (uT and counts): fBc=inv(W)*(fBs[]-V[])
298 for (i = CHX; i <= CHZ; i++)
299 {
300 pthisMag->fBc[i] = pthisMagCal->finvW[i][CHX] *
301 ftmp[CHX] +
302 pthisMagCal->finvW[i][CHY] *
303 ftmp[CHY] +
304 pthisMagCal->finvW[i][CHZ] *
305 ftmp[CHZ];
306 pthisMag->iBc[i] = (int16) (pthisMag->fBc[i] * pthisMag->fCountsPeruT);
307 }
308
309 return;
310 }
311
312 // function runs the magnetic calibration
fRunMagCalibration(struct MagCalibration * pthisMagCal,struct MagBuffer * pthisMagBuffer,struct MagSensor * pthisMag,int32 loopcounter)313 void fRunMagCalibration(struct MagCalibration *pthisMagCal, struct MagBuffer *pthisMagBuffer,
314 struct MagSensor *pthisMag, int32 loopcounter)
315 {
316 int8 i,
317 j; // loop counters
318
319 // determine whether to initiate a new magnetic calibration
320 if (!pthisMagCal->iCalInProgress)
321 {
322 // clear the flag
323 pthisMagCal->iInitiateMagCal = 0;
324
325 // try one calibration attempt with the best model available given the number of measurements
326 if ((pthisMagBuffer->iMagBufferCount >= MINMEASUREMENTS10CAL) &&
327 (!pthisMagCal->i10ElementSolverTried))
328 {
329 pthisMagCal->i10ElementSolverTried = true;
330 pthisMagCal->iInitiateMagCal = 10;
331 }
332 else if ((pthisMagBuffer->iMagBufferCount >= MINMEASUREMENTS7CAL) &&
333 (!pthisMagCal->i7ElementSolverTried))
334 {
335 pthisMagCal->i7ElementSolverTried = true;
336 pthisMagCal->iInitiateMagCal = 7;
337 }
338 else if ((pthisMagBuffer->iMagBufferCount >= MINMEASUREMENTS4CAL) &&
339 (!pthisMagCal->i4ElementSolverTried))
340 {
341 pthisMagCal->i4ElementSolverTried = true;
342 pthisMagCal->iInitiateMagCal = 4;
343 }
344
345 // otherwise start a calibration at regular interval defined by CAL_INTERVAL_SECS
346 else if (!pthisMagCal->iInitiateMagCal &&
347 !(loopcounter % (CAL_INTERVAL_SECS * FUSION_HZ)))
348 {
349 if (pthisMagBuffer->iMagBufferCount >= MINMEASUREMENTS10CAL)
350 {
351 pthisMagCal->i10ElementSolverTried = true;
352 pthisMagCal->iInitiateMagCal = 10;
353 }
354 else if (pthisMagBuffer->iMagBufferCount >= MINMEASUREMENTS7CAL)
355 {
356 pthisMagCal->i7ElementSolverTried = true;
357 pthisMagCal->iInitiateMagCal = 7;
358 }
359 else if (pthisMagBuffer->iMagBufferCount >= MINMEASUREMENTS4CAL)
360 {
361 pthisMagCal->i4ElementSolverTried = true;
362 pthisMagCal->iInitiateMagCal = 4;
363 }
364 }
365
366 // store the selected calibration model (if any) to be run
367 pthisMagCal->iCalInProgress = pthisMagCal->iInitiateMagCal;
368 }
369
370 // on entry each of the calibration functions resets iInitiateMagCal and on completion sets
371 // iCalInProgress=0 and iNewCalibrationAvailable=4,7,10 according to the solver used
372 switch (pthisMagCal->iCalInProgress)
373 {
374 case 0:
375 break;
376
377 case 4:
378 fUpdateMagCalibration4Slice(pthisMagCal, pthisMagBuffer, pthisMag);
379 break;
380
381 case 7:
382 fUpdateMagCalibration7Slice(pthisMagCal, pthisMagBuffer, pthisMag);
383 break;
384
385 case 10:
386 fUpdateMagCalibration10Slice(pthisMagCal, pthisMagBuffer, pthisMag);
387 break;
388
389 default:
390 break;
391 }
392
393 // evaluate the new calibration to determine whether to accept it
394 if (pthisMagCal->iNewCalibrationAvailable)
395 {
396 // the geomagnetic field strength must be in range (earth is 22uT to 67uT) with reasonable fit error
397 if ((pthisMagCal->ftrB >= MINBFITUT) && (pthisMagCal->ftrB <= MAXBFITUT) &&
398 (pthisMagCal->ftrFitErrorpc <= 15.0F))
399 {
400 // the fit error must be improved or be from a more sophisticated solver but still 5 bars (under 3.5% fit error)
401 if ((pthisMagCal->ftrFitErrorpc <= pthisMagCal->fFitErrorpc) || ((
402 pthisMagCal->iNewCalibrationAvailable > pthisMagCal->
403 iValidMagCal) && (pthisMagCal->ftrFitErrorpc <= 3.5F)))
404 {
405 // accept the new calibration
406 pthisMagCal->iValidMagCal = pthisMagCal->iNewCalibrationAvailable;
407 pthisMagCal->fFitErrorpc = pthisMagCal->ftrFitErrorpc;
408 pthisMagCal->fB = pthisMagCal->ftrB;
409 pthisMagCal->fBSq = pthisMagCal->fB * pthisMagCal->fB;
410 for (i = CHX; i <= CHZ; i++)
411 {
412 pthisMagCal->fV[i] = pthisMagCal->ftrV[i];
413 for (j = CHX; j <= CHZ; j++)
414 pthisMagCal->finvW[i][j] = pthisMagCal->ftrinvW[i][j];
415 }
416 }
417 }
418 else if(pthisMagCal->i10ElementSolverTried)
419 {
420 // the magnetic buffer is presumed corrupted so clear out all measurements and restart calibration attempts
421 pthisMagBuffer->iMagBufferCount = 0;
422 for (i = 0; i < MAGBUFFSIZEX; i++)
423 for (j = 0; j < MAGBUFFSIZEY; j++)
424 pthisMagBuffer->index[i][j] = -1;
425 pthisMagCal->i4ElementSolverTried = false;
426 pthisMagCal->i7ElementSolverTried = false;
427 pthisMagCal->i10ElementSolverTried = false;
428 } // end of test for new calibration within field strength and fit error limits
429
430 // reset the new calibration flag
431 pthisMagCal->iNewCalibrationAvailable = 0;
432 } // end of test for new calibration available
433
434 // age the existing fit error very slowly to avoid one good calibration locking out future updates.
435 // this prevents a calibration remaining for ever if a unit is never powered down
436 if (pthisMagCal->iValidMagCal)
437 pthisMagCal->fFitErrorpc += 1.0F / ((float) FUSION_HZ * FITERRORAGINGSECS);
438
439 return;
440 }
441
442 // 4 element calibration using 4x4 matrix inverse
fUpdateMagCalibration4Slice(struct MagCalibration * pthisMagCal,struct MagBuffer * pthisMagBuffer,struct MagSensor * pthisMag)443 void fUpdateMagCalibration4Slice(struct MagCalibration *pthisMagCal,
444 struct MagBuffer *pthisMagBuffer, struct MagSensor *pthisMag)
445 {
446 // local variables
447 int8 i,
448 j,
449 k; // loop counters
450
451 // working arrays for 4x4 matrix inversion
452 float *pfRows[4];
453 int8 iColInd[4];
454 int8 iRowInd[4];
455 int8 iPivot[4];
456
457 // reset the time slice to zero if iInitiateMagCal is set and then clear iInitiateMagCal
458 if (pthisMagCal->iInitiateMagCal)
459 {
460 pthisMagCal->itimeslice = 0;
461 pthisMagCal->iInitiateMagCal = false;
462 pthisMagCal->iMagBufferReadOnly = true;
463 }
464
465 // time slice 0: 18.8K ticks = 0.39ms for 300 measurements on KL25Z
466 // initialize matrices and compute average of measurements in magnetic buffer
467 if (pthisMagCal->itimeslice == 0)
468 {
469 int16 iM; // number of measurements in the buffer
470
471 // zero on and above diagonal matrix X^T.X (in fmatA), vector X^T.Y (in fvecA), scalar Y^T.Y (in fYTY)
472 pthisMagCal->fYTY = 0.0F;
473 for (i = 0; i < 4; i++)
474 {
475 pthisMagCal->fvecA[i] = 0.0F;
476 for (j = i; j < 4; j++) pthisMagCal->fmatA[i][j] = 0.0F;
477 }
478
479 // zero total number of measurements and measurement sums
480 iM = 0;
481 for (i = 0; i < 3; i++) pthisMagCal->iSumBs[i] = 0;
482
483 // compute the sum of measurements in the magnetic buffer
484 for (i = 0; i < MAGBUFFSIZEX; i++)
485 {
486 for (j = 0; j < MAGBUFFSIZEY; j++)
487 {
488 if (pthisMagBuffer->index[i][j] != -1)
489 {
490 iM++;
491 for (k = 0; k < 3; k++)
492 pthisMagCal->iSumBs[k] += (int32) pthisMagBuffer->iBs[k][i][j];
493 }
494 }
495 }
496
497 // compute the magnetic buffer measurement averages with rounding
498 for (i = 0; i < 3; i++)
499 {
500 if (pthisMagCal->iSumBs[i] >= 0)
501 pthisMagCal->iMeanBs[i] =
502 (
503 pthisMagCal->iSumBs[i] +
504 ((int32) iM >> 1)
505 ) /
506 (int32) iM;
507 else
508 pthisMagCal->iMeanBs[i] =
509 (
510 pthisMagCal->iSumBs[i] -
511 ((int32) iM >> 1)
512 ) /
513 (int32) iM;
514 }
515
516 // for defensive programming, re-store the number of active measurements in the buffer
517 pthisMagBuffer->iMagBufferCount = iM;
518
519 // increment the time slice
520 (pthisMagCal->itimeslice)++;
521 } // end of time slice 0
522
523 // time slices 1 to MAGBUFFSIZEX: each up to 81K ticks = 1.7ms
524 // accumulate the matrices fmatA=X^T.X and fvecA=X^T.Y and Y^T.Y from the magnetic buffer
525 else if ((pthisMagCal->itimeslice >= 1) &&
526 (pthisMagCal->itimeslice <= MAGBUFFSIZEX))
527 {
528 float fBsZeroMeanSq; // squared magnetic measurement (counts^2)
529 int32 iBsZeroMean[3]; // zero mean magnetic buffer measurement (counts)
530
531 // accumulate the measurement matrix elements XTX (in fmatA), XTY (in fvecA) and YTY on the zero mean measurements
532 i = pthisMagCal->itimeslice - 1;
533 for (j = 0; j < MAGBUFFSIZEY; j++)
534 {
535 if (pthisMagBuffer->index[i][j] != -1)
536 {
537 // compute zero mean measurements
538 for (k = 0; k < 3; k++)
539 iBsZeroMean[k] = (int32) pthisMagBuffer->iBs[k][i][j] - (int32) pthisMagCal->iMeanBs[k];
540
541 // accumulate the non-zero elements of zero mean XTX (in fmatA)
542 pthisMagCal->fmatA[0][0] += (float) (iBsZeroMean[0] * iBsZeroMean[0]);
543 pthisMagCal->fmatA[0][1] += (float) (iBsZeroMean[0] * iBsZeroMean[1]);
544 pthisMagCal->fmatA[0][2] += (float) (iBsZeroMean[0] * iBsZeroMean[2]);
545 pthisMagCal->fmatA[1][1] += (float) (iBsZeroMean[1] * iBsZeroMean[1]);
546 pthisMagCal->fmatA[1][2] += (float) (iBsZeroMean[1] * iBsZeroMean[2]);
547 pthisMagCal->fmatA[2][2] += (float) (iBsZeroMean[2] * iBsZeroMean[2]);
548
549 // accumulate XTY (in fvecA)
550 fBsZeroMeanSq = (float)
551 (
552 iBsZeroMean[CHX] *
553 iBsZeroMean[CHX] +
554 iBsZeroMean[CHY] *
555 iBsZeroMean[CHY] +
556 iBsZeroMean[CHZ] *
557 iBsZeroMean[CHZ]
558 );
559 for (k = 0; k < 3; k++)
560 pthisMagCal->fvecA[k] += (float) iBsZeroMean[k] * fBsZeroMeanSq;
561 pthisMagCal->fvecA[3] += fBsZeroMeanSq;
562
563 // accumulate fYTY
564 pthisMagCal->fYTY += fBsZeroMeanSq * fBsZeroMeanSq;
565 }
566 }
567
568 // increment the time slice
569 (pthisMagCal->itimeslice)++;
570 } // end of time slices 1 to MAGBUFFSIZEX
571
572 // time slice MAGBUFFSIZEX+1: 18.3K ticks = 0.38ms on KL25Z (constant) (stored in systick[2])
573 // re-enable magnetic buffer for writing and invert fmatB = fmatA = X^T.X in situ
574 else if (pthisMagCal->itimeslice == (MAGBUFFSIZEX + 1))
575 {
576 int8 ierror; // matrix inversion error flag
577
578 // set fmatA[3][3] = X^T.X[3][3] to number of measurements found
579 pthisMagCal->fmatA[3][3] = (float) pthisMagBuffer->iMagBufferCount;
580
581 // enable the magnetic buffer for writing now that the matrices have been computed
582 pthisMagCal->iMagBufferReadOnly = false;
583
584 // set fmatA and fmatB to above diagonal elements of fmatA
585 for (i = 0; i < 4; i++)
586 {
587 for (j = 0; j <= i; j++)
588 pthisMagCal->fmatB[i][j] = pthisMagCal->fmatB[j][i] = pthisMagCal->fmatA[i][j] = pthisMagCal->fmatA[j][i];
589 }
590
591 // set fmatB = inv(fmatB) = inv(X^T.X)
592 for (i = 0; i < 4; i++) pfRows[i] = pthisMagCal->fmatB[i];
593 fmatrixAeqInvA(pfRows, iColInd, iRowInd, iPivot, 4, &ierror);
594
595 // increment the time slice
596 (pthisMagCal->itimeslice)++;
597 } // // end of time slice MAGBUFFSIZEX+1
598
599 // time slice MAGBUFFSIZEX+2: 17.2K ticks = 0.36ms on KL25Z (constant) (stored in systick[3])
600 // compute the solution vector and the calibration coefficients
601 else if (pthisMagCal->itimeslice == (MAGBUFFSIZEX + 2))
602 {
603 float fE; // error function = r^T.r
604 float ftmp; // scratch
605
606 // the trial inverse soft iron matrix invW always equals the identity matrix for 4 element calibration
607 f3x3matrixAeqI(pthisMagCal->ftrinvW);
608
609 // calculate solution vector fvecB = beta (4x1) = inv(X^T.X).X^T.Y = fmatB * fvecA (counts)
610 for (i = 0; i < 4; i++)
611 {
612 pthisMagCal->fvecB[i] = pthisMagCal->fmatB[i][0] * pthisMagCal->fvecA[0];
613 for (j = 1; j < 4; j++)
614 pthisMagCal->fvecB[i] += pthisMagCal->fmatB[i][j] * pthisMagCal->fvecA[j];
615 }
616
617 // compute the hard iron vector (uT) correction for zero mean data
618 ftmp = 0.5F * pthisMag->fuTPerCount;
619 for (i = CHX; i <= CHZ; i++)
620 pthisMagCal->ftrV[i] = ftmp * pthisMagCal->fvecB[i];
621
622 // compute the geomagnetic field strength B (uT)
623 pthisMagCal->ftrB = pthisMagCal->fvecB[3] *
624 pthisMag->fuTPerCount *
625 pthisMag->fuTPerCount;
626 for (i = CHX; i <= CHZ; i++)
627 pthisMagCal->ftrB += pthisMagCal->ftrV[i] * pthisMagCal->ftrV[i];
628 pthisMagCal->ftrB = sqrtf(fabs(pthisMagCal->ftrB));
629
630 // add in the previously subtracted magnetic buffer mean to get true hard iron offset (uT)
631 ftmp = pthisMag->fuTPerCount / (float) pthisMagBuffer->iMagBufferCount;
632 for (i = CHX; i <= CHZ; i++)
633 pthisMagCal->ftrV[i] += (float) pthisMagCal->iSumBs[i] * ftmp;
634
635 // calculate E = r^T.r = Y^T.Y - 2 * beta^T.(X^T.Y) + beta^T.(X^T.X).beta
636 // set E = beta^T.(X^T.Y) = fvecB^T.fvecA
637 fE = pthisMagCal->fvecB[0] * pthisMagCal->fvecA[0];
638 for (i = 1; i < 4; i++)
639 fE += pthisMagCal->fvecB[i] * pthisMagCal->fvecA[i];
640
641 // set E = YTY - 2 * beta^T.(X^T.Y) = YTY - 2 * E;
642 fE = pthisMagCal->fYTY - 2.0F * fE;
643
644 // set fvecA = (X^T.X).beta = fmatA.fvecB
645 for (i = 0; i < 4; i++)
646 {
647 pthisMagCal->fvecA[i] = pthisMagCal->fmatA[i][0] * pthisMagCal->fvecB[0];
648 for (j = 1; j < 4; j++)
649 pthisMagCal->fvecA[i] += pthisMagCal->fmatA[i][j] * pthisMagCal->fvecB[j];
650 }
651
652 // add beta^T.(X^T.X).beta = fvecB^T * fvecA to give fit error E (un-normalized counts^4)
653 for (i = 0; i < 4; i++)
654 fE += pthisMagCal->fvecB[i] * pthisMagCal->fvecA[i];
655
656 // normalize fit error to the number of measurements and take square root to get error in uT^2
657 fE = sqrtf(fabs(fE) / (float) pthisMagBuffer->iMagBufferCount) *
658 pthisMag->fuTPerCount *
659 pthisMag->fuTPerCount;
660
661 // obtain dimensionless error by dividing square square of the geomagnetic field and convert to percent
662 pthisMagCal->ftrFitErrorpc = 50.0F * fE / (pthisMagCal->ftrB * pthisMagCal->ftrB);
663
664 // reset the calibration in progress flag to allow writing to the magnetic buffer and flag
665 // that a new 4 element calibration is available
666 pthisMagCal->iCalInProgress = 0;
667 pthisMagCal->iNewCalibrationAvailable = 4;
668 } // end of time slice MAGBUFFSIZEX+2
669
670 return;
671 }
672
673 // 7 element calibration using direct eigen-decomposition
fUpdateMagCalibration7Slice(struct MagCalibration * pthisMagCal,struct MagBuffer * pthisMagBuffer,struct MagSensor * pthisMag)674 void fUpdateMagCalibration7Slice(struct MagCalibration *pthisMagCal,
675 struct MagBuffer *pthisMagBuffer, struct MagSensor *pthisMag)
676 {
677 // local variables
678 float fresidue; // eigen-decomposition residual sum
679 float ftmp; // scratch variable
680 int8 i,
681 j,
682 k,
683 l; // loop counters
684 #define MATRIX_7_SIZE 7
685 // reset the time slice to zero if iInitiateMagCal is set and then clear iInitiateMagCal
686 if (pthisMagCal->iInitiateMagCal)
687 {
688 pthisMagCal->itimeslice = 0;
689 pthisMagCal->iInitiateMagCal = false;
690 pthisMagCal->iMagBufferReadOnly = true;
691 }
692
693 // time slice 0: 18.1K KL25Z ticks for 300 measurements = 0.38ms on KL25Z (variable) stored in systick[0]
694 // zero measurement matrix and calculate the mean values in the magnetic buffer
695 if (pthisMagCal->itimeslice == 0)
696 {
697 int16 iM; // number of measurements in the magnetic buffer
698
699 // zero the on and above diagonal elements of the 7x7 symmetric measurement matrix fmatA
700 for (i = 0; i < MATRIX_7_SIZE; i++)
701 for (j = i; j < MATRIX_7_SIZE; j++)
702 pthisMagCal->fmatA[i][j] = 0.0F;
703
704 // compute the sum of measurements in the magnetic buffer
705 iM = 0;
706 for (i = 0; i < 3; i++) pthisMagCal->iSumBs[i] = 0;
707 for (i = 0; i < MAGBUFFSIZEX; i++)
708 {
709 for (j = 0; j < MAGBUFFSIZEY; j++)
710 {
711 if (pthisMagBuffer->index[i][j] != -1)
712 {
713 iM++;
714 for (k = 0; k < 3; k++)
715 pthisMagCal->iSumBs[k] += (int32) pthisMagBuffer->iBs[k][i][j];
716 }
717 }
718 }
719
720 // compute the magnetic buffer measurement averages with nearest integer rounding
721 for (i = 0; i < 3; i++)
722 {
723 if (pthisMagCal->iSumBs[i] >= 0)
724 pthisMagCal->iMeanBs[i] =
725 (
726 pthisMagCal->iSumBs[i] +
727 ((int32) iM >> 1)
728 ) /
729 (int32) iM;
730 else
731 pthisMagCal->iMeanBs[i] =
732 (
733 pthisMagCal->iSumBs[i] -
734 ((int32) iM >> 1)
735 ) /
736 (int32) iM;
737 }
738
739 // as defensive programming also ensure the number of measurements found is re-stored
740 pthisMagBuffer->iMagBufferCount = iM;
741
742 // increment the time slice for the next iteration
743 (pthisMagCal->itimeslice)++;
744 } // end of time slice 0
745
746 // time slices 1 to MAGBUFFSIZEX * MAGBUFFSIZEY: accumulate matrices: 8.6K KL25Z ticks = 0.18ms (with max stored in systick[1])
747 else if ((pthisMagCal->itimeslice >= 1) &&
748 (pthisMagCal->itimeslice <= MAGBUFFSIZEX * MAGBUFFSIZEY))
749 {
750 // accumulate the symmetric matrix fmatA on the zero mean measurements
751 i = (pthisMagCal->itimeslice - 1) / MAGBUFFSIZEY; // matrix row i ranges 0 to MAGBUFFSIZEX-1
752 j = (pthisMagCal->itimeslice - 1) % MAGBUFFSIZEY; // matrix column j ranges 0 to MAGBUFFSIZEY-1
753 if (pthisMagBuffer->index[i][j] != -1)
754 {
755 // set fvecA to be vector of zero mean measurements and their squares
756 for (k = 0; k < 3; k++)
757 {
758 pthisMagCal->fvecA[k + 3] = (float)
759 (
760 (int32) pthisMagBuffer->iBs[k][i][j] -
761 (int32) pthisMagCal->iMeanBs[k]
762 );
763 pthisMagCal->fvecA[k] = pthisMagCal->fvecA[k + 3] * pthisMagCal->fvecA[k + 3];
764 }
765
766 // update non-zero elements fmatA[0-2][6] of fmatA ignoring fmatA[6][6] which is set later.
767 // elements fmatA[3-5][6] are zero as a result of subtracting the mean value
768 for (k = 0; k < 3; k++)
769 pthisMagCal->fmatA[k][6] += pthisMagCal->fvecA[k];
770
771 // update the remaining on and above diagonal elements fmatA[0-5][0-5]
772 for (k = 0; k < (MATRIX_7_SIZE - 1); k++)
773 {
774 for (l = k; l < (MATRIX_7_SIZE - 1); l++)
775 pthisMagCal->fmatA[k][l] += pthisMagCal->fvecA[k] * pthisMagCal->fvecA[l];
776 }
777 }
778
779 // increment the time slice for the next iteration
780 (pthisMagCal->itimeslice)++;
781 } // end of time slices 1 to MAGBUFFSIZEX * MAGBUFFSIZEY
782
783 // time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 1: 0.8K ticks on KL25Z = 0.02ms on KL25Z (constant) (stored in systick[2])
784 // re-enable magnetic buffer for writing and prepare fmatA, fmatB, fvecA for eigendecomposition
785 else if (pthisMagCal->itimeslice == (MAGBUFFSIZEX * MAGBUFFSIZEY + 1))
786 {
787 // set fmatA[6][6] to the number of magnetic measurements found
788 pthisMagCal->fmatA[MATRIX_7_SIZE - 1][MATRIX_7_SIZE - 1] = (float) pthisMagBuffer->iMagBufferCount;
789
790 // clear the magnetic buffer read only flag now that the matrices have been computed
791 pthisMagCal->iMagBufferReadOnly = false;
792
793 // set below diagonal elements of 7x7 matrix fmatA to above diagonal elements
794 for (i = 1; i < MATRIX_7_SIZE; i++)
795 for (j = 0; j < i; j++)
796 pthisMagCal->fmatA[i][j] = pthisMagCal->fmatA[j][i];
797
798 // set matrix of eigenvectors fmatB to identity matrix and eigenvalues vector fvecA to diagonal elements of fmatA
799 for (i = 0; i < MATRIX_7_SIZE; i++)
800 {
801 for (j = 0; j < MATRIX_7_SIZE; j++)
802 pthisMagCal->fmatB[i][j] = 0.0F;
803 pthisMagCal->fmatB[i][i] = 1.0F;
804 pthisMagCal->fvecA[i] = pthisMagCal->fmatA[i][i];
805 }
806
807 // increment the time slice for the next iteration
808 (pthisMagCal->itimeslice)++;
809 } // end of time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 1
810
811 // repeating 21 time slices MAGBUFFSIZEX * MAGBUFFSIZEY + 2 to MAGBUFFSIZEX * MAGBUFFSIZEY + 22 inclusive
812 // to perform the eigendecomposition of the measurement matrix fmatA.
813 // 19.6k ticks = 0.41ms on KL25Z (with max stored in systick[3]).
814 // for a 7x7 matrix there are 21 above diagonal elements: 6+5+4+3+2+1+0=21
815 else if ((pthisMagCal->itimeslice >= (MAGBUFFSIZEX * MAGBUFFSIZEY + 2)) &&
816 (pthisMagCal->itimeslice <= (MAGBUFFSIZEX * MAGBUFFSIZEY + 22)))
817 {
818 // set k to the matrix element in range 0 to 20 to be zeroed and used it to set row i and column j
819 k = pthisMagCal->itimeslice - (MAGBUFFSIZEX * MAGBUFFSIZEY + 2);
820 if (k < 6)
821 {
822 i = 0;
823 j = k + 1;
824 }
825 else if (k < 11)
826 {
827 i = 1;
828 j = k - 4;
829 }
830 else if (k < 15)
831 {
832 i = 2;
833 j = k - 8;
834 }
835 else if (k < 18)
836 {
837 i = 3;
838 j = k - 11;
839 }
840 else if (k < 20)
841 {
842 i = 4;
843 j = k - 13;
844 }
845 else
846 {
847 i = 5;
848 j = 6;
849 }
850
851 // only continue if matrix element i, j has not already been zeroed
852 if (fabsf(pthisMagCal->fmatA[i][j]) > 0.0F)
853 fComputeEigSlice(pthisMagCal->fmatA, pthisMagCal->fmatB,
854 pthisMagCal->fvecA, i, j, MATRIX_7_SIZE);
855
856 // increment the time slice for the next iteration
857 (pthisMagCal->itimeslice)++;
858 } // end of time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 2 to MAGBUFFSIZEX * MAGBUFFSIZEY + 22 inclusive
859
860 // time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 23: 2.6k ticks on KL25Z = 0.05ms on KL25Z (constant) (stored in systick[4])
861 // compute the sum of the absolute values of above diagonal elements in fmatA as eigen-decomposition exit criterion
862 else if (pthisMagCal->itimeslice == (MAGBUFFSIZEX * MAGBUFFSIZEY + 23))
863 {
864 // sum residue of all above-diagonal elements
865 fresidue = 0.0F;
866 for (i = 0; i < MATRIX_7_SIZE; i++)
867 for (j = i + 1; j < MATRIX_7_SIZE; j++)
868 fresidue += fabsf(pthisMagCal->fmatA[i][j]);
869
870 // determine whether to re-enter the eigen-decomposition or skip to calculation of the calibration coefficients
871 if (fresidue > 0.0F)
872 // continue the eigen-decomposition
873 (pthisMagCal->itimeslice) = MAGBUFFSIZEX * MAGBUFFSIZEY + 2;
874 else
875 // continue to compute the calibration coefficients since the eigen-decomposition is complete
876 (pthisMagCal->itimeslice)++;
877 } // end of time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 23
878
879 // time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 24: 27.8k ticks = 0.58ms on KL25Z (constant) (stored in systick[5])
880 // compute the calibration coefficients from the solution eigenvector
881 else if (pthisMagCal->itimeslice == (MAGBUFFSIZEX * MAGBUFFSIZEY + 24))
882 {
883 float fdetA; // determinant of ellipsoid matrix A
884 int8 imin; // column of solution eigenvector with minimum eigenvalue
885
886 // set imin to the index of the smallest eigenvalue in fvecA
887 imin = 0;
888 for (i = 1; i < MATRIX_7_SIZE; i++)
889 if (pthisMagCal->fvecA[i] < pthisMagCal->fvecA[imin]) imin = i;
890
891 // the ellipsoid fA must have positive determinant but the eigensolver can equally easily return a negated
892 // normalized eigenvector without changing the eigenvalue. compute the determinant of ellipsoid matrix A
893 // from first three elements of eigenvector and negate the eigenvector if negative.
894 fdetA = pthisMagCal->fmatB[CHX][imin] *
895 pthisMagCal->fmatB[CHY][imin] *
896 pthisMagCal->fmatB[CHZ][imin];
897 if (fdetA < 0.0F)
898 {
899 fdetA = fabs(fdetA);
900 for (i = 0; i < MATRIX_7_SIZE; i++)
901 pthisMagCal->fmatB[i][imin] = -pthisMagCal->fmatB[i][imin];
902 }
903
904 // set diagonal elements of ellipsoid matrix A to the solution vector imin with smallest eigenvalue and
905 // zero off-diagonal elements since these are always zero in the 7 element model
906 // compute the hard iron offset fV for zero mean data (counts)
907 for (i = CHX; i <= CHZ; i++)
908 pthisMagCal->ftrV[i] = -0.5F *
909 pthisMagCal->fmatB[i + 3][imin] /
910 pthisMagCal->fmatB[i][imin];
911
912 // set ftmp to the square of the geomagnetic field strength fB (counts) corresponding to current value of fdetA
913 ftmp = -pthisMagCal->fmatB[6][imin];
914 for (i = CHX; i <= CHZ; i++)
915 ftmp += pthisMagCal->fmatB[i][imin] *
916 pthisMagCal->ftrV[i] *
917 pthisMagCal->ftrV[i];
918
919 // calculate the trial normalized fit error as a percentage normalized by the geomagnetic field strength squared
920 pthisMagCal->ftrFitErrorpc = 50.0F * sqrtf(fabsf(pthisMagCal->fvecA[imin]) /
921 (float) pthisMagBuffer->iMagBufferCount) / fabsf(ftmp);
922
923 // compute the geomagnetic field strength (uT) for current value of fdetA
924 pthisMagCal->ftrB = sqrtf(fabsf(ftmp)) * pthisMag->fuTPerCount;
925
926 // compute the normalized ellipsoid matrix A with unit determinant and derive invW also with unit determinant.
927 ftmp = powf(fabs(fdetA), -(ONETHIRD));
928 for (i = CHX; i <= CHZ; i++)
929 {
930 pthisMagCal->fA[i][i] = pthisMagCal->fmatB[i][imin] * ftmp;
931 pthisMagCal->ftrinvW[i][i] = sqrtf(fabsf(pthisMagCal->fA[i][i]));
932 }
933
934 pthisMagCal->fA[CHX][CHY] = pthisMagCal->fA[CHX][CHZ] = pthisMagCal->fA[CHY][CHZ] = 0.0F;
935 pthisMagCal->ftrinvW[CHX][CHY] = pthisMagCal->ftrinvW[CHX][CHZ] = pthisMagCal->ftrinvW[CHY][CHZ] = 0.0F;
936
937 // each element of fA has been scaled by fdetA^(-1/3) so the square of geomagnetic field strength B^2
938 // must be adjusted by the same amount and B by the square root to keep the ellipsoid equation valid:
939 // (Bk-V)^T.A.(Bk-V) = (Bk-V)^T.(invW)^T.invW.(Bk-V) = B^2
940 pthisMagCal->ftrB *= sqrt(fabs(ftmp));
941
942 // compute the final hard iron offset (uT) by adding in previously subtracted zero mean offset (counts)
943 for (i = CHX; i <= CHZ; i++)
944 pthisMagCal->ftrV[i] =
945 (
946 pthisMagCal->ftrV[i] +
947 (float) pthisMagCal->iMeanBs[i]
948 ) *
949 pthisMag->fuTPerCount;
950
951 // reset the calibration in progress flag to allow writing to the magnetic buffer and flag
952 // that a new 7 element calibration is available
953 pthisMagCal->iCalInProgress = 0;
954 pthisMagCal->iNewCalibrationAvailable = 7;
955 } // end of time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 24
956
957 return;
958 }
959
960 // 10 element calibration using direct eigen-decomposition
fUpdateMagCalibration10Slice(struct MagCalibration * pthisMagCal,struct MagBuffer * pthisMagBuffer,struct MagSensor * pthisMag)961 void fUpdateMagCalibration10Slice(struct MagCalibration *pthisMagCal,
962 struct MagBuffer *pthisMagBuffer, struct MagSensor *pthisMag)
963 {
964 // local variables
965 float fresidue; // eigen-decomposition residual sum
966 float ftmp; // scratch variable
967 int8 i,
968 j,
969 k,
970 l; // loop counters
971 #define MATRIX_10_SIZE 10
972 // reset the time slice to zero if iInitiateMagCal is set and then clear iInitiateMagCal
973 if (pthisMagCal->iInitiateMagCal)
974 {
975 pthisMagCal->itimeslice = 0;
976 pthisMagCal->iInitiateMagCal = false;
977 pthisMagCal->iMagBufferReadOnly = true;
978 }
979
980 // time slice 0: 18.7k KL25Z ticks for 300 measurements = 0.39ms on KL25Z (variable) stored in systick[0]
981 // zero measurement matrix fmatA and calculate the mean values in the magnetic buffer
982 if (pthisMagCal->itimeslice == 0)
983 {
984 int16 iM; // number of measurements in the magnetic buffer
985
986 // zero the on and above diagonal elements of the 10x10 symmetric measurement matrix fmatA
987 for (i = 0; i < MATRIX_10_SIZE; i++)
988 for (j = i; j < MATRIX_10_SIZE; j++)
989 pthisMagCal->fmatA[i][j] = 0.0F;
990
991 // compute the sum of measurements in the magnetic buffer
992 iM = 0;
993 for (i = 0; i < 3; i++) pthisMagCal->iSumBs[i] = 0;
994 for (i = 0; i < MAGBUFFSIZEX; i++)
995 {
996 for (j = 0; j < MAGBUFFSIZEY; j++)
997 {
998 if (pthisMagBuffer->index[i][j] != -1)
999 {
1000 iM++;
1001 for (k = 0; k < 3; k++)
1002 pthisMagCal->iSumBs[k] += (int32) pthisMagBuffer->iBs[k][i][j];
1003 }
1004 }
1005 }
1006
1007 // compute the magnetic buffer measurement averages with nearest integer rounding
1008 for (i = 0; i < 3; i++)
1009 {
1010 if (pthisMagCal->iSumBs[i] >= 0)
1011 pthisMagCal->iMeanBs[i] =
1012 (
1013 pthisMagCal->iSumBs[i] +
1014 ((int32) iM >> 1)
1015 ) /
1016 (int32) iM;
1017 else
1018 pthisMagCal->iMeanBs[i] =
1019 (
1020 pthisMagCal->iSumBs[i] -
1021 ((int32) iM >> 1)
1022 ) /
1023 (int32) iM;
1024 }
1025
1026 // as defensive programming also ensure the number of measurements found is re-stored
1027 pthisMagBuffer->iMagBufferCount = iM;
1028
1029 // increment the time slice for the next iteration
1030 (pthisMagCal->itimeslice)++;
1031 } // end of time slice 0
1032
1033 // time slices 1 to MAGBUFFSIZEX * MAGBUFFSIZEY: accumulate matrices: 17.9k KL25Z ticks = 0.37ms for 300 measurements
1034 // (with max measured stored in systick[1])
1035 else if ((pthisMagCal->itimeslice >= 1) &&
1036 (pthisMagCal->itimeslice <= MAGBUFFSIZEX * MAGBUFFSIZEY))
1037 {
1038 // accumulate the symmetric matrix fmatA on the zero mean measurements
1039 i = (pthisMagCal->itimeslice - 1) / MAGBUFFSIZEY; // matrix row i ranges 0 to MAGBUFFSIZEX-1
1040 j = (pthisMagCal->itimeslice - 1) % MAGBUFFSIZEY; // matrix column j ranges 0 to MAGBUFFSIZEY-1
1041 if (pthisMagBuffer->index[i][j] != -1)
1042 {
1043 // set fvecA[6-8] to the zero mean measurements
1044 for (k = 0; k < 3; k++)
1045 pthisMagCal->fvecA[k + 6] = (float)
1046 (
1047 (int32) pthisMagBuffer->iBs[k][i][j] -
1048 (int32) pthisMagCal->iMeanBs[k]
1049 );
1050
1051 // compute fvecA[0-5] from fvecA[6-8]
1052 pthisMagCal->fvecA[0] = pthisMagCal->fvecA[6] * pthisMagCal->fvecA[6];
1053 pthisMagCal->fvecA[1] = 2.0F *
1054 pthisMagCal->fvecA[6] *
1055 pthisMagCal->fvecA[7];
1056 pthisMagCal->fvecA[2] = 2.0F *
1057 pthisMagCal->fvecA[6] *
1058 pthisMagCal->fvecA[8];
1059 pthisMagCal->fvecA[3] = pthisMagCal->fvecA[7] * pthisMagCal->fvecA[7];
1060 pthisMagCal->fvecA[4] = 2.0F *
1061 pthisMagCal->fvecA[7] *
1062 pthisMagCal->fvecA[8];
1063 pthisMagCal->fvecA[5] = pthisMagCal->fvecA[8] * pthisMagCal->fvecA[8];
1064
1065 // update non-zero elements fmatA[0-5][9] of fmatA ignoring fmatA[9][9] which is set later.
1066 // elements fmatA[6-8][9] are zero as a result of subtracting the mean value
1067 for (k = 0; k < 6; k++)
1068 pthisMagCal->fmatA[k][9] += pthisMagCal->fvecA[k];
1069
1070 // update the remaining on and above diagonal elements fmatA[0-8][0-8]
1071 for (k = 0; k < (MATRIX_10_SIZE - 1); k++)
1072 {
1073 for (l = k; l < (MATRIX_10_SIZE - 1); l++)
1074 pthisMagCal->fmatA[k][l] += pthisMagCal->fvecA[k] * pthisMagCal->fvecA[l];
1075 }
1076 }
1077
1078 // increment the time slice for the next iteration
1079 (pthisMagCal->itimeslice)++;
1080 } // end of time slices 1 to MAGBUFFSIZEX * MAGBUFFSIZEY
1081
1082 // time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 1: 1.2k ticks on KL25Z = 0.025ms on KL25Z (constant) (stored in systick[2])
1083 // re-enable magnetic buffer for writing and prepare fmatA, fmatB, fvecA for eigendecomposition
1084 else if (pthisMagCal->itimeslice == (MAGBUFFSIZEX * MAGBUFFSIZEY + 1))
1085 {
1086 // set fmatA[9][9] to the number of magnetic measurements found
1087 pthisMagCal->fmatA[MATRIX_10_SIZE - 1][MATRIX_10_SIZE - 1] = (float) pthisMagBuffer->iMagBufferCount;
1088
1089 // clear the magnetic buffer read only flag now that the matrices have been computed
1090 pthisMagCal->iMagBufferReadOnly = false;
1091
1092 // set below diagonal elements of 10x10 matrix fmatA to above diagonal elements
1093 for (i = 1; i < MATRIX_10_SIZE; i++)
1094 for (j = 0; j < i; j++)
1095 pthisMagCal->fmatA[i][j] = pthisMagCal->fmatA[j][i];
1096
1097 // set matrix of eigenvectors fmatB to identity matrix and eigenvalues vector fvecA to diagonal elements of fmatA
1098 for (i = 0; i < MATRIX_10_SIZE; i++)
1099 {
1100 for (j = 0; j < MATRIX_10_SIZE; j++)
1101 pthisMagCal->fmatB[i][j] = 0.0F;
1102 pthisMagCal->fmatB[i][i] = 1.0F;
1103 pthisMagCal->fvecA[i] = pthisMagCal->fmatA[i][i];
1104 }
1105
1106 // increment the time slice for the next iteration
1107 (pthisMagCal->itimeslice)++;
1108 } // end of time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 1
1109
1110 // repeating 45 time slices MAGBUFFSIZEX * MAGBUFFSIZEY + 2 to MAGBUFFSIZEX * MAGBUFFSIZEY + 46 inclusive
1111 // to perform the eigendecomposition of the measurement matrix fmatA.
1112 // 28.2k ticks = 0.56ms on KL25Z (with max stored in systick[3]).
1113 // for a 10x10 matrix there are 45 above diagonal elements: 9+8+7+6+5+4+3+2+1+0=45
1114 else if ((pthisMagCal->itimeslice >= (MAGBUFFSIZEX * MAGBUFFSIZEY + 2)) &&
1115 (pthisMagCal->itimeslice <= (MAGBUFFSIZEX * MAGBUFFSIZEY + 46)))
1116 {
1117 // set k to the matrix element of interest in range 0 to 44 to be zeroed and set row i and column j
1118 k = pthisMagCal->itimeslice - (MAGBUFFSIZEX * MAGBUFFSIZEY + 2);
1119 if (k < 9)
1120 {
1121 i = 0;
1122 j = k + 1;
1123 }
1124 else if (k < 17)
1125 {
1126 i = 1;
1127 j = k - 7;
1128 }
1129 else if (k < 24)
1130 {
1131 i = 2;
1132 j = k - 14;
1133 }
1134 else if (k < 30)
1135 {
1136 i = 3;
1137 j = k - 20;
1138 }
1139 else if (k < 35)
1140 {
1141 i = 4;
1142 j = k - 25;
1143 }
1144 else if (k < 39)
1145 {
1146 i = 5;
1147 j = k - 29;
1148 }
1149 else if (k < 42)
1150 {
1151 i = 6;
1152 j = k - 32;
1153 }
1154 else if (k < 44)
1155 {
1156 i = 7;
1157 j = k - 34;
1158 }
1159 else
1160 {
1161 i = 8;
1162 j = 9;
1163 }
1164
1165 // only continue if matrix element i, j has not already been zeroed
1166 if (fabsf(pthisMagCal->fmatA[i][j]) > 0.0F)
1167 fComputeEigSlice(pthisMagCal->fmatA, pthisMagCal->fmatB,
1168 pthisMagCal->fvecA, i, j, MATRIX_10_SIZE);
1169
1170 // increment the time slice for the next iteration
1171 (pthisMagCal->itimeslice)++;
1172 } // end of time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 2 to MAGBUFFSIZEX * MAGBUFFSIZEY + 46 inclusive
1173
1174 // time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 47: 5.6k ticks on KL25Z = 0.12ms on KL25Z (constant) (stored in systick[4])
1175 // compute the sum of the absolute values of above diagonal elements in fmatA as eigen-decomposition exit criterion
1176 else if (pthisMagCal->itimeslice == (MAGBUFFSIZEX * MAGBUFFSIZEY + 47))
1177 {
1178 // sum residue of all above-diagonal elements
1179 fresidue = 0.0F;
1180 for (i = 0; i < MATRIX_10_SIZE; i++)
1181 for (j = i + 1; j < MATRIX_10_SIZE; j++)
1182 fresidue += fabsf(pthisMagCal->fmatA[i][j]);
1183
1184 // determine whether to re-enter the eigen-decomposition or skip to calculation of the calibration coefficients
1185 if (fresidue > 0.0F)
1186 // continue the eigen-decomposition
1187 (pthisMagCal->itimeslice) = MAGBUFFSIZEX * MAGBUFFSIZEY + 2;
1188 else
1189 // continue to compute the calibration coefficients since the eigen-decomposition is complete
1190 (pthisMagCal->itimeslice)++;
1191 } // end of time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 47
1192
1193 // time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 48: 38.5k ticks = 0.80ms on KL25Z (constant) (stored in systick[5])
1194 // compute the calibration coefficients (excluding invW) from the solution eigenvector
1195 else if (pthisMagCal->itimeslice == (MAGBUFFSIZEX * MAGBUFFSIZEY + 48))
1196 {
1197 float fdetA; // determinant of ellipsoid matrix A
1198 int8 imin; // column of solution eigenvector with minimum eigenvalue
1199
1200 // set imin to the index of the smallest eigenvalue in fvecA
1201 imin = 0;
1202 for (i = 1; i < MATRIX_10_SIZE; i++)
1203 if (pthisMagCal->fvecA[i] < pthisMagCal->fvecA[imin]) imin = i;
1204
1205 // set the ellipsoid matrix A from elements 0 to 5 of the solution eigenvector.
1206 pthisMagCal->fA[CHX][CHX] = pthisMagCal->fmatB[0][imin];
1207 pthisMagCal->fA[CHX][CHY] = pthisMagCal->fA[CHY][CHX] = pthisMagCal->fmatB[1][imin];
1208 pthisMagCal->fA[CHX][CHZ] = pthisMagCal->fA[CHZ][CHX] = pthisMagCal->fmatB[2][imin];
1209 pthisMagCal->fA[CHY][CHY] = pthisMagCal->fmatB[3][imin];
1210 pthisMagCal->fA[CHY][CHZ] = pthisMagCal->fA[CHZ][CHY] = pthisMagCal->fmatB[4][imin];
1211 pthisMagCal->fA[CHZ][CHZ] = pthisMagCal->fmatB[5][imin];
1212
1213 // negate A and the entire solution vector A has negative determinant
1214 fdetA = f3x3matrixDetA(pthisMagCal->fA);
1215 if (fdetA < 0.0F)
1216 {
1217 f3x3matrixAeqMinusA(pthisMagCal->fA);
1218 fdetA = fabs(fdetA);
1219 for (i = 0; i < MATRIX_10_SIZE; i++)
1220 pthisMagCal->fmatB[i][imin] = -pthisMagCal->fmatB[i][imin];
1221 }
1222
1223 // set finvA to the inverse of the ellipsoid matrix fA
1224 f3x3matrixAeqInvSymB(pthisMagCal->finvA, pthisMagCal->fA);
1225
1226 // compute the hard iron offset fV for zero mean data (counts)
1227 for (i = CHX; i <= CHZ; i++)
1228 {
1229 pthisMagCal->ftrV[i] = pthisMagCal->finvA[i][0] *
1230 pthisMagCal->fmatB[6][imin] +
1231 pthisMagCal->finvA[i][1] *
1232 pthisMagCal->fmatB[7][imin] +
1233 pthisMagCal->finvA[i][2] *
1234 pthisMagCal->fmatB[8][imin];
1235 pthisMagCal->ftrV[i] *= -0.5F;
1236 }
1237
1238 // compute the geomagnetic field strength B (counts) for current (un-normalized) ellipsoid matrix determinant.
1239 ftmp = pthisMagCal->fA[CHX][CHY] *
1240 pthisMagCal->ftrV[CHX] *
1241 pthisMagCal->ftrV[CHY] +
1242 pthisMagCal->fA[CHX][CHZ] *
1243 pthisMagCal->ftrV[CHX] *
1244 pthisMagCal->ftrV[CHZ] +
1245 pthisMagCal->fA[CHY][CHZ] *
1246 pthisMagCal->ftrV[CHY] *
1247 pthisMagCal->ftrV[CHZ];
1248 ftmp *= 2.0F;
1249 ftmp -= pthisMagCal->fmatB[9][imin];
1250 ftmp += pthisMagCal->fA[CHX][CHX] *
1251 pthisMagCal->ftrV[CHX] *
1252 pthisMagCal->ftrV[CHX];
1253 ftmp += pthisMagCal->fA[CHY][CHY] *
1254 pthisMagCal->ftrV[CHY] *
1255 pthisMagCal->ftrV[CHY];
1256 ftmp += pthisMagCal->fA[CHZ][CHZ] *
1257 pthisMagCal->ftrV[CHZ] *
1258 pthisMagCal->ftrV[CHZ];
1259 pthisMagCal->ftrB = sqrtf(fabsf(ftmp));
1260
1261 // calculate the normalized fit error as a percentage
1262 pthisMagCal->ftrFitErrorpc = 50.0F * sqrtf(fabsf(
1263 pthisMagCal->fvecA[imin] /
1264 (float) pthisMagBuffer->iMagBufferCount
1265 )) / (pthisMagCal->ftrB * pthisMagCal->ftrB);
1266
1267 // convert the trial geomagnetic field strength B from counts to uT for un-normalized soft iron matrix A
1268 pthisMagCal->ftrB *= pthisMag->fuTPerCount;
1269
1270 // compute the final hard iron offset (uT) by adding in previously subtracted zero mean offset (counts)
1271 for (i = CHX; i <= CHZ; i++)
1272 pthisMagCal->ftrV[i] =
1273 (
1274 pthisMagCal->ftrV[i] +
1275 (float) pthisMagCal->iMeanBs[i]
1276 ) *
1277 pthisMag->fuTPerCount;
1278
1279 // normalize the ellipsoid matrix A to unit determinant
1280 ftmp = powf(fabs(fdetA), -(ONETHIRD));
1281 f3x3matrixAeqAxScalar(pthisMagCal->fA, ftmp);
1282
1283 // each element of fA has been scaled by fdetA^(-1/3) so the square of geomagnetic field strength B^2
1284 // must be adjusted by the same amount and B by the square root to keep the ellipsoid equation valid:
1285 // (Bk-V)^T.A.(Bk-V) = (Bk-V)^T.(invW)^T.invW.(Bk-V) = B^2
1286 pthisMagCal->ftrB *= sqrt(fabs(ftmp));
1287
1288 // prepare for eigendecomposition of fA to compute finvW from the square root of fA by setting
1289 // fmatA (upper left) to the 3x3 matrix fA, set fmatB (eigenvectors to identity matrix and
1290 // fvecA (eigenvalues) to diagonal elements of fmatA = fA
1291 for (i = 0; i < 3; i++)
1292 {
1293 for (j = 0; j < 3; j++)
1294 {
1295 pthisMagCal->fmatA[i][j] = pthisMagCal->fA[i][j];
1296 pthisMagCal->fmatB[i][j] = 0.0F;
1297 }
1298
1299 pthisMagCal->fmatB[i][i] = 1.0F;
1300 pthisMagCal->fvecA[i] = pthisMagCal->fmatA[i][i];
1301 }
1302
1303 // increment the time slice for the next iteration
1304 (pthisMagCal->itimeslice)++;
1305 } // end of time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 48
1306
1307 // repeating 3 time slices MAGBUFFSIZEX * MAGBUFFSIZEY + 49 to MAGBUFFSIZEX * MAGBUFFSIZEY + 51 inclusive
1308 // 9.7kticks = 0.2ms on KL25Z (with max stored in systick[6]).
1309 // perform the eigendecomposition of the 3x3 ellipsoid matrix fA which has 3 above diagonal elements: 2+1+0=3
1310 else if ((pthisMagCal->itimeslice >= (MAGBUFFSIZEX * MAGBUFFSIZEY + 49)) &&
1311 (pthisMagCal->itimeslice <= (MAGBUFFSIZEX * MAGBUFFSIZEY + 51)))
1312 {
1313 // set k to the matrix element of interest in range 0 to 2 to be zeroed and set row i and column j
1314 k = pthisMagCal->itimeslice - (MAGBUFFSIZEX * MAGBUFFSIZEY + 49);
1315 if (k == 0)
1316 {
1317 i = 0;
1318 j = 1;
1319 }
1320 else if (k == 1)
1321 {
1322 i = 0;
1323 j = 2;
1324 }
1325 else
1326 {
1327 i = 1;
1328 j = 2;
1329 }
1330
1331 // only continue with eigen-decomposition if matrix element i, j has not already been zeroed
1332 if (fabsf(pthisMagCal->fmatA[i][j]) > 0.0F)
1333 fComputeEigSlice(pthisMagCal->fmatA, pthisMagCal->fmatB,
1334 pthisMagCal->fvecA, i, j, 3);
1335
1336 // increment the time slice for the next iteration
1337 (pthisMagCal->itimeslice)++;
1338 } // end of time slices MAGBUFFSIZEX * MAGBUFFSIZEY + 49 to MAGBUFFSIZEX * MAGBUFFSIZEY + 51 inclusive
1339
1340 // time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 52: 0.3k ticks = 0.006ms on KL25Z (constant) (stored in systick[7])
1341 // determine whether the eigen-decomposition of fA is complete
1342 else if (pthisMagCal->itimeslice == (MAGBUFFSIZEX * MAGBUFFSIZEY + 52))
1343 {
1344 // sum residue of all above-diagonal elements and use to determine whether
1345 // to re-enter the eigen-decomposition or skip to calculation of the calibration coefficients
1346 fresidue = fabsf(pthisMagCal->fmatA[0][1]) + fabsf(pthisMagCal->fmatA[0][2]) + fabsf(pthisMagCal->fmatA[1][2]);
1347 if (fresidue > 0.0F)
1348 // continue the eigen-decomposition
1349 (pthisMagCal->itimeslice) = MAGBUFFSIZEX * MAGBUFFSIZEY + 49;
1350 else
1351 // continue to compute the calibration coefficients since the eigen-decomposition is complete
1352 (pthisMagCal->itimeslice)++;
1353 } // end of time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 52
1354
1355 // time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 53: 9.3k ticks = 0.19ms on KL25Z (constant) (stored in systick[8])
1356 // compute the inverse gain matrix invW from the eigendecomposition of the ellipsoid matrix A
1357 else if (pthisMagCal->itimeslice == (MAGBUFFSIZEX * MAGBUFFSIZEY + 53))
1358 {
1359 // set pthisMagCal->fmatB to be eigenvectors . diag(sqrt(sqrt(eigenvalues))) = fmatB . diag(sqrt(sqrt(fvecA))
1360 for (j = 0; j < 3; j++) // loop over columns j
1361 {
1362 ftmp = sqrtf(sqrtf(fabsf(pthisMagCal->fvecA[j])));
1363 for (i = 0; i < 3; i++) // loop over rows i
1364 pthisMagCal->fmatB[i][j] *= ftmp;
1365 }
1366
1367 // set ftrinvW to eigenvectors * diag(sqrt(eigenvalues)) * eigenvectors^T
1368 // = fmatB * fmatB^T = sqrt(fA) (guaranteed symmetric)
1369 // loop over on and above diagonal elements
1370 for (i = 0; i < 3; i++)
1371 {
1372 for (j = i; j < 3; j++)
1373 {
1374 pthisMagCal->ftrinvW[i][j] = pthisMagCal->ftrinvW[j][i] =
1375 pthisMagCal->fmatB[i][0] *
1376 pthisMagCal->fmatB[j][0] +
1377 pthisMagCal->fmatB[i][1] *
1378 pthisMagCal->fmatB[j][1] +
1379 pthisMagCal->fmatB[i][2] *
1380 pthisMagCal->fmatB[j][2];
1381 }
1382 }
1383
1384 // reset the calibration in progress flag to allow writing to the magnetic buffer and flag
1385 // that a new 10 element calibration is available
1386 pthisMagCal->iCalInProgress = 0;
1387 pthisMagCal->iNewCalibrationAvailable = 10;
1388 } // end of time slice MAGBUFFSIZEX * MAGBUFFSIZEY + 53
1389
1390 return;
1391 }
1392
1393 // 4 element calibration using 4x4 matrix inverse
fComputeMagCalibration4(struct MagCalibration * pthisMagCal,struct MagBuffer * pthisMagBuffer,struct MagSensor * pthisMag)1394 void fComputeMagCalibration4(struct MagCalibration *pthisMagCal,
1395 struct MagBuffer *pthisMagBuffer, struct MagSensor *pthisMag)
1396 {
1397 // local variables
1398 float fBs2; // fBs[CHX]^2+fBs[CHY]^2+fBs[CHZ]^2
1399 float fSumBs4; // sum of fBs2
1400 float fscaling; // set to FUTPERCOUNT * FMATRIXSCALING
1401 float fE; // error function = r^T.r
1402 int16 iOffset[3]; // offset to remove large DC hard iron bias in matrix
1403 int16 iCount; // number of measurements counted
1404 int8 ierror; // matrix inversion error flag
1405 int8 i,
1406 j,
1407 k,
1408 l; // loop counters
1409
1410 // working arrays for 4x4 matrix inversion
1411 float *pfRows[4];
1412 int8 iColInd[4];
1413 int8 iRowInd[4];
1414 int8 iPivot[4];
1415
1416 // compute fscaling to reduce multiplications later
1417 fscaling = pthisMag->fuTPerCount / DEFAULTB;
1418
1419 // the trial inverse soft iron matrix invW always equals the identity matrix for 4 element calibration
1420 f3x3matrixAeqI(pthisMagCal->ftrinvW);
1421
1422 // zero fSumBs4=Y^T.Y, fvecB=X^T.Y (4x1) and on and above diagonal elements of fmatA=X^T*X (4x4)
1423 fSumBs4 = 0.0F;
1424 for (i = 0; i < 4; i++)
1425 {
1426 pthisMagCal->fvecB[i] = 0.0F;
1427 for (j = i; j < 4; j++)
1428 {
1429 pthisMagCal->fmatA[i][j] = 0.0F;
1430 }
1431 }
1432
1433 // the offsets are guaranteed to be set from the first element but to avoid compiler error
1434 iOffset[CHX] = iOffset[CHY] = iOffset[CHZ] = 0;
1435
1436 // use entries from magnetic buffer to compute matrices
1437 iCount = 0;
1438 for (j = 0; j < MAGBUFFSIZEX; j++)
1439 {
1440 for (k = 0; k < MAGBUFFSIZEY; k++)
1441 {
1442 if (pthisMagBuffer->index[j][k] != -1)
1443 {
1444 // use first valid magnetic buffer entry as estimate (in counts) for offset
1445 if (iCount == 0)
1446 {
1447 for (l = CHX; l <= CHZ; l++)
1448 {
1449 iOffset[l] = pthisMagBuffer->iBs[l][j][k];
1450 }
1451 }
1452
1453 // store scaled and offset fBs[XYZ] in fvecA[0-2] and fBs[XYZ]^2 in fvecA[3-5]
1454 for (l = CHX; l <= CHZ; l++)
1455 {
1456 pthisMagCal->fvecA[l] = (float)
1457 (
1458 (int32) pthisMagBuffer->iBs[l][j][k] -
1459 (int32) iOffset[l]
1460 ) * fscaling;
1461 pthisMagCal->fvecA[l + 3] = pthisMagCal->fvecA[l] * pthisMagCal->fvecA[l];
1462 }
1463
1464 // calculate fBs2 = fBs[CHX]^2 + fBs[CHY]^2 + fBs[CHZ]^2 (scaled uT^2)
1465 fBs2 = pthisMagCal->fvecA[3] +
1466 pthisMagCal->fvecA[4] +
1467 pthisMagCal->fvecA[5];
1468
1469 // accumulate fBs^4 over all measurements into fSumBs4=Y^T.Y
1470 fSumBs4 += fBs2 * fBs2;
1471
1472 // now we have fBs2, accumulate fvecB[0-2] = X^T.Y =sum(fBs2.fBs[XYZ])
1473 for (l = CHX; l <= CHZ; l++)
1474 {
1475 pthisMagCal->fvecB[l] += pthisMagCal->fvecA[l] * fBs2;
1476 }
1477
1478 //accumulate fvecB[3] = X^T.Y =sum(fBs2)
1479 pthisMagCal->fvecB[3] += fBs2;
1480
1481 // accumulate on and above-diagonal terms of fmatA = X^T.X ignoring fmatA[3][3]
1482 pthisMagCal->fmatA[0][0] += pthisMagCal->fvecA[CHX + 3];
1483 pthisMagCal->fmatA[0][1] += pthisMagCal->fvecA[CHX] * pthisMagCal->fvecA[CHY];
1484 pthisMagCal->fmatA[0][2] += pthisMagCal->fvecA[CHX] * pthisMagCal->fvecA[CHZ];
1485 pthisMagCal->fmatA[0][3] += pthisMagCal->fvecA[CHX];
1486 pthisMagCal->fmatA[1][1] += pthisMagCal->fvecA[CHY + 3];
1487 pthisMagCal->fmatA[1][2] += pthisMagCal->fvecA[CHY] * pthisMagCal->fvecA[CHZ];
1488 pthisMagCal->fmatA[1][3] += pthisMagCal->fvecA[CHY];
1489 pthisMagCal->fmatA[2][2] += pthisMagCal->fvecA[CHZ + 3];
1490 pthisMagCal->fmatA[2][3] += pthisMagCal->fvecA[CHZ];
1491
1492 // increment the counter for next iteration
1493 iCount++;
1494 }
1495 }
1496 }
1497
1498 // set the last element of the measurement matrix to the number of buffer elements used
1499 pthisMagCal->fmatA[3][3] = (float) iCount;
1500
1501 // store the number of measurements accumulated (defensive programming, should never be needed)
1502 pthisMagBuffer->iMagBufferCount = iCount;
1503
1504 // use above diagonal elements of symmetric fmatA to set both fmatB and fmatA to X^T.X
1505 for (i = 0; i < 4; i++)
1506 {
1507 for (j = i; j < 4; j++)
1508 {
1509 pthisMagCal->fmatB[i][j] = pthisMagCal->fmatB[j][i] = pthisMagCal->fmatA[j][i] = pthisMagCal->fmatA[i][j];
1510 }
1511 }
1512
1513 // calculate in situ inverse of fmatB = inv(X^T.X) (4x4) while fmatA still holds X^T.X
1514 for (i = 0; i < 4; i++)
1515 {
1516 pfRows[i] = pthisMagCal->fmatB[i];
1517 }
1518
1519 fmatrixAeqInvA(pfRows, iColInd, iRowInd, iPivot, 4, &ierror);
1520
1521 // calculate fvecA = solution beta (4x1) = inv(X^T.X).X^T.Y = fmatB * fvecB
1522 for (i = 0; i < 4; i++)
1523 {
1524 pthisMagCal->fvecA[i] = 0.0F;
1525 for (k = 0; k < 4; k++)
1526 {
1527 pthisMagCal->fvecA[i] += pthisMagCal->fmatB[i][k] * pthisMagCal->fvecB[k];
1528 }
1529 }
1530
1531 // calculate E = r^T.r = Y^T.Y - 2 * beta^T.(X^T.Y) + beta^T.(X^T.X).beta
1532 // = fSumBs4 - 2 * fvecA^T.fvecB + fvecA^T.fmatA.fvecA
1533 // first set E = Y^T.Y - 2 * beta^T.(X^T.Y) = fSumBs4 - 2 * fvecA^T.fvecB
1534 fE = 0.0F;
1535 for (i = 0; i < 4; i++)
1536 {
1537 fE += pthisMagCal->fvecA[i] * pthisMagCal->fvecB[i];
1538 }
1539
1540 fE = fSumBs4 - 2.0F * fE;
1541
1542 // set fvecB = (X^T.X).beta = fmatA.fvecA
1543 for (i = 0; i < 4; i++)
1544 {
1545 pthisMagCal->fvecB[i] = 0.0F;
1546 for (k = 0; k < 4; k++)
1547 {
1548 pthisMagCal->fvecB[i] += pthisMagCal->fmatA[i][k] * pthisMagCal->fvecA[k];
1549 }
1550 }
1551
1552 // complete calculation of E by adding beta^T.(X^T.X).beta = fvecA^T * fvecB
1553 for (i = 0; i < 4; i++)
1554 {
1555 fE += pthisMagCal->fvecB[i] * pthisMagCal->fvecA[i];
1556 }
1557
1558 // compute the hard iron vector (in uT but offset and scaled by FMATRIXSCALING)
1559 for (l = CHX; l <= CHZ; l++)
1560 {
1561 pthisMagCal->ftrV[l] = 0.5F * pthisMagCal->fvecA[l];
1562 }
1563
1564 // compute the scaled geomagnetic field strength B (in uT but scaled by FMATRIXSCALING)
1565 pthisMagCal->ftrB = sqrtf(pthisMagCal->fvecA[3] + pthisMagCal->ftrV[CHX] *
1566 pthisMagCal->ftrV[CHX] + pthisMagCal->ftrV[CHY] *
1567 pthisMagCal->ftrV[CHY] + pthisMagCal->ftrV[CHZ] *
1568 pthisMagCal->ftrV[CHZ]);
1569
1570 // calculate the trial fit error (percent) normalized to number of measurements and scaled geomagnetic field strength
1571 pthisMagCal->ftrFitErrorpc = sqrtf(fE / (float) pthisMagBuffer->iMagBufferCount) * 100.0F / (2.0F * pthisMagCal->ftrB * pthisMagCal->ftrB);
1572
1573 // correct the hard iron estimate for FMATRIXSCALING and the offsets applied (result in uT)
1574 for (l = CHX; l <= CHZ; l++)
1575 {
1576 pthisMagCal->ftrV[l] = pthisMagCal->ftrV[l] *
1577 DEFAULTB +
1578 (float) iOffset[l] *
1579 pthisMag->fuTPerCount;
1580 }
1581
1582 // correct the geomagnetic field strength B to correct scaling (result in uT)
1583 pthisMagCal->ftrB *= DEFAULTB;
1584
1585 return;
1586 }
1587
1588 // 7 element calibration using direct eigen-decomposition
fComputeMagCalibration7(struct MagCalibration * pthisMagCal,struct MagBuffer * pthisMagBuffer,struct MagSensor * pthisMag)1589 void fComputeMagCalibration7(struct MagCalibration *pthisMagCal,
1590 struct MagBuffer *pthisMagBuffer, struct MagSensor *pthisMag)
1591 {
1592 // local variables
1593 float det; // matrix determinant
1594 float fscaling; // set to FUTPERCOUNT * FMATRIXSCALING
1595 float ftmp; // scratch variable
1596 int16 iOffset[3]; // offset to remove large DC hard iron bias
1597 int16 iCount; // number of measurements counted
1598 int8 i,
1599 j,
1600 k,
1601 l,
1602 m,
1603 n; // loop counters
1604
1605 // compute fscaling to reduce multiplications later
1606 fscaling = pthisMag->fuTPerCount / DEFAULTB;
1607
1608 // the offsets are guaranteed to be set from the first element but to avoid compiler error
1609 iOffset[CHX] = iOffset[CHY] = iOffset[CHZ] = 0;
1610
1611 // zero the on and above diagonal elements of the 7x7 symmetric measurement matrix fmatA
1612 for (m = 0; m < 7; m++)
1613 {
1614 for (n = m; n < 7; n++)
1615 {
1616 pthisMagCal->fmatA[m][n] = 0.0F;
1617 }
1618 }
1619
1620 // add megnetic buffer entries into product matrix fmatA
1621 iCount = 0;
1622 for (j = 0; j < MAGBUFFSIZEX; j++)
1623 {
1624 for (k = 0; k < MAGBUFFSIZEY; k++)
1625 {
1626 if (pthisMagBuffer->index[j][k] != -1)
1627 {
1628 // use first valid magnetic buffer entry as offset estimate (bit counts)
1629 if (iCount == 0)
1630 {
1631 for (l = CHX; l <= CHZ; l++)
1632 {
1633 iOffset[l] = pthisMagBuffer->iBs[l][j][k];
1634 }
1635 }
1636
1637 // apply the offset and scaling and store in fvecA
1638 for (l = CHX; l <= CHZ; l++)
1639 {
1640 pthisMagCal->fvecA[l + 3] = (float)
1641 (
1642 (int32) pthisMagBuffer->iBs[l][j][k] -
1643 (int32) iOffset[l]
1644 ) * fscaling;
1645 pthisMagCal->fvecA[l] = pthisMagCal->fvecA[l + 3] * pthisMagCal->fvecA[l + 3];
1646 }
1647
1648 // accumulate the on-and above-diagonal terms of pthisMagCal->fmatA=Sigma{fvecA^T * fvecA}
1649 // with the exception of fmatA[6][6] which will sum to the number of measurements
1650 // and remembering that fvecA[6] equals 1.0F
1651 // update the right hand column [6] of fmatA except for fmatA[6][6]
1652 for (m = 0; m < 6; m++)
1653 {
1654 pthisMagCal->fmatA[m][6] += pthisMagCal->fvecA[m];
1655 }
1656
1657 // update the on and above diagonal terms except for right hand column 6
1658 for (m = 0; m < 6; m++)
1659 {
1660 for (n = m; n < 6; n++)
1661 {
1662 pthisMagCal->fmatA[m][n] += pthisMagCal->fvecA[m] * pthisMagCal->fvecA[n];
1663 }
1664 }
1665
1666 // increment the measurement counter for the next iteration
1667 iCount++;
1668 }
1669 }
1670 }
1671
1672 // finally set the last element fmatA[6][6] to the number of measurements
1673 pthisMagCal->fmatA[6][6] = (float) iCount;
1674
1675 // store the number of measurements accumulated (defensive programming, should never be needed)
1676 pthisMagBuffer->iMagBufferCount = iCount;
1677
1678 // copy the above diagonal elements of fmatA to below the diagonal
1679 for (m = 1; m < 7; m++)
1680 {
1681 for (n = 0; n < m; n++)
1682 {
1683 pthisMagCal->fmatA[m][n] = pthisMagCal->fmatA[n][m];
1684 }
1685 }
1686
1687 // set tmpA7x1 to the unsorted eigenvalues and fmatB to the unsorted eigenvectors of fmatA
1688 fEigenCompute10(pthisMagCal->fmatA, pthisMagCal->fvecA, pthisMagCal->fmatB,
1689 7);
1690
1691 // find the smallest eigenvalue
1692 j = 0;
1693 for (i = 1; i < 7; i++)
1694 {
1695 if (pthisMagCal->fvecA[i] < pthisMagCal->fvecA[j])
1696 {
1697 j = i;
1698 }
1699 }
1700
1701 // set ellipsoid matrix A to the solution vector with smallest eigenvalue, compute its determinant
1702 // and the hard iron offset (scaled and offset)
1703 f3x3matrixAeqScalar(pthisMagCal->fA, 0.0F);
1704 det = 1.0F;
1705 for (l = CHX; l <= CHZ; l++)
1706 {
1707 pthisMagCal->fA[l][l] = pthisMagCal->fmatB[l][j];
1708 det *= pthisMagCal->fA[l][l];
1709 pthisMagCal->ftrV[l] = -0.5F *
1710 pthisMagCal->fmatB[l + 3][j] /
1711 pthisMagCal->fA[l][l];
1712 }
1713
1714 // negate A if it has negative determinant
1715 if (det < 0.0F)
1716 {
1717 f3x3matrixAeqMinusA(pthisMagCal->fA);
1718 pthisMagCal->fmatB[6][j] = -pthisMagCal->fmatB[6][j];
1719 det = -det;
1720 }
1721
1722 // set ftmp to the square of the trial geomagnetic field strength B (counts times FMATRIXSCALING)
1723 ftmp = -pthisMagCal->fmatB[6][j];
1724 for (l = CHX; l <= CHZ; l++)
1725 {
1726 ftmp += pthisMagCal->fA[l][l] *
1727 pthisMagCal->ftrV[l] *
1728 pthisMagCal->ftrV[l];
1729 }
1730
1731 // normalize the ellipsoid matrix A to unit determinant
1732 f3x3matrixAeqAxScalar(pthisMagCal->fA, powf(det, -(ONETHIRD)));
1733
1734 // convert the geomagnetic field strength B into uT for normalized soft iron matrix A and normalize
1735 pthisMagCal->ftrB = sqrtf(fabsf(ftmp)) * DEFAULTB * powf(det, -(ONESIXTH));
1736
1737 // compute trial invW from the square root of A also with normalized determinant and hard iron offset in uT
1738 f3x3matrixAeqI(pthisMagCal->ftrinvW);
1739 for (l = CHX; l <= CHZ; l++)
1740 {
1741 pthisMagCal->ftrinvW[l][l] = sqrtf(fabsf(pthisMagCal->fA[l][l]));
1742 pthisMagCal->ftrV[l] = pthisMagCal->ftrV[l] *
1743 DEFAULTB +
1744 (float) iOffset[l] *
1745 pthisMag->fuTPerCount;
1746 }
1747
1748 return;
1749 }
1750
1751 // 10 element calibration using direct eigen-decomposition
fComputeMagCalibration10(struct MagCalibration * pthisMagCal,struct MagBuffer * pthisMagBuffer,struct MagSensor * pthisMag)1752 void fComputeMagCalibration10(struct MagCalibration *pthisMagCal,
1753 struct MagBuffer *pthisMagBuffer, struct MagSensor *pthisMag)
1754 {
1755 // local variables
1756 float det; // matrix determinant
1757 float fscaling; // set to FUTPERCOUNT * FMATRIXSCALING
1758 float ftmp; // scratch variable
1759 int16 iOffset[3]; // offset to remove large DC hard iron bias in matrix
1760 int16 iCount; // number of measurements counted
1761 int8 i,
1762 j,
1763 k,
1764 l,
1765 m,
1766 n; // loop counters
1767
1768 // compute fscaling to reduce multiplications later
1769 fscaling = pthisMag->fuTPerCount / DEFAULTB;
1770
1771 // the offsets are guaranteed to be set from the first element but to avoid compiler error
1772 iOffset[CHX] = iOffset[CHY] = iOffset[CHZ] = 0;
1773
1774 // zero the on and above diagonal elements of the 10x10 symmetric measurement matrix fmatA
1775 for (m = 0; m < 10; m++)
1776 {
1777 for (n = m; n < 10; n++)
1778 {
1779 pthisMagCal->fmatA[m][n] = 0.0F;
1780 }
1781 }
1782
1783 // add magnetic buffer entries into the 10x10 product matrix fmatA
1784 iCount = 0;
1785 for (j = 0; j < MAGBUFFSIZEX; j++)
1786 {
1787 for (k = 0; k < MAGBUFFSIZEY; k++)
1788 {
1789 if (pthisMagBuffer->index[j][k] != -1)
1790 {
1791 // use first valid magnetic buffer entry as estimate for offset to help solution (bit counts)
1792 if (iCount == 0)
1793 {
1794 for (l = CHX; l <= CHZ; l++)
1795 {
1796 iOffset[l] = pthisMagBuffer->iBs[l][j][k];
1797 }
1798 }
1799
1800 // apply the fixed offset and scaling and enter into fvecA[6-8]
1801 for (l = CHX; l <= CHZ; l++)
1802 {
1803 pthisMagCal->fvecA[l + 6] = (float)
1804 (
1805 (int32) pthisMagBuffer->iBs[l][j][k] -
1806 (int32) iOffset[l]
1807 ) * fscaling;
1808 }
1809
1810 // compute measurement vector elements fvecA[0-5] from fvecA[6-8]
1811 pthisMagCal->fvecA[0] = pthisMagCal->fvecA[6] * pthisMagCal->fvecA[6];
1812 pthisMagCal->fvecA[1] = 2.0F *
1813 pthisMagCal->fvecA[6] *
1814 pthisMagCal->fvecA[7];
1815 pthisMagCal->fvecA[2] = 2.0F *
1816 pthisMagCal->fvecA[6] *
1817 pthisMagCal->fvecA[8];
1818 pthisMagCal->fvecA[3] = pthisMagCal->fvecA[7] * pthisMagCal->fvecA[7];
1819 pthisMagCal->fvecA[4] = 2.0F *
1820 pthisMagCal->fvecA[7] *
1821 pthisMagCal->fvecA[8];
1822 pthisMagCal->fvecA[5] = pthisMagCal->fvecA[8] * pthisMagCal->fvecA[8];
1823
1824 // accumulate the on-and above-diagonal terms of fmatA=Sigma{fvecA^T * fvecA}
1825 // with the exception of fmatA[9][9] which equals the number of measurements
1826 // update the right hand column [9] of fmatA[0-8][9] ignoring fmatA[9][9]
1827 for (m = 0; m < 9; m++)
1828 {
1829 pthisMagCal->fmatA[m][9] += pthisMagCal->fvecA[m];
1830 }
1831
1832 // update the on and above diagonal terms of fmatA ignoring right hand column 9
1833 for (m = 0; m < 9; m++)
1834 {
1835 for (n = m; n < 9; n++)
1836 {
1837 pthisMagCal->fmatA[m][n] += pthisMagCal->fvecA[m] * pthisMagCal->fvecA[n];
1838 }
1839 }
1840
1841 // increment the measurement counter for the next iteration
1842 iCount++;
1843 }
1844 }
1845 }
1846
1847 // set the last element fmatA[9][9] to the number of measurements
1848 pthisMagCal->fmatA[9][9] = (float) iCount;
1849
1850 // store the number of measurements accumulated (defensive programming, should never be needed)
1851 pthisMagBuffer->iMagBufferCount = iCount;
1852
1853 // copy the above diagonal elements of symmetric product matrix fmatA to below the diagonal
1854 for (m = 1; m < 10; m++)
1855 {
1856 for (n = 0; n < m; n++)
1857 {
1858 pthisMagCal->fmatA[m][n] = pthisMagCal->fmatA[n][m];
1859 }
1860 }
1861
1862 // set pthisMagCal->fvecA to the unsorted eigenvalues and fmatB to the unsorted normalized eigenvectors of fmatA
1863 fEigenCompute10(pthisMagCal->fmatA, pthisMagCal->fvecA, pthisMagCal->fmatB,
1864 10);
1865
1866 // set ellipsoid matrix A from elements of the solution vector column j with smallest eigenvalue
1867 j = 0;
1868 for (i = 1; i < 10; i++)
1869 {
1870 if (pthisMagCal->fvecA[i] < pthisMagCal->fvecA[j])
1871 {
1872 j = i;
1873 }
1874 }
1875
1876 pthisMagCal->fA[0][0] = pthisMagCal->fmatB[0][j];
1877 pthisMagCal->fA[0][1] = pthisMagCal->fA[1][0] = pthisMagCal->fmatB[1][j];
1878 pthisMagCal->fA[0][2] = pthisMagCal->fA[2][0] = pthisMagCal->fmatB[2][j];
1879 pthisMagCal->fA[1][1] = pthisMagCal->fmatB[3][j];
1880 pthisMagCal->fA[1][2] = pthisMagCal->fA[2][1] = pthisMagCal->fmatB[4][j];
1881 pthisMagCal->fA[2][2] = pthisMagCal->fmatB[5][j];
1882
1883 // negate entire solution if A has negative determinant
1884 det = f3x3matrixDetA(pthisMagCal->fA);
1885 if (det < 0.0F)
1886 {
1887 f3x3matrixAeqMinusA(pthisMagCal->fA);
1888 pthisMagCal->fmatB[6][j] = -pthisMagCal->fmatB[6][j];
1889 pthisMagCal->fmatB[7][j] = -pthisMagCal->fmatB[7][j];
1890 pthisMagCal->fmatB[8][j] = -pthisMagCal->fmatB[8][j];
1891 pthisMagCal->fmatB[9][j] = -pthisMagCal->fmatB[9][j];
1892 det = -det;
1893 }
1894
1895 // compute the inverse of the ellipsoid matrix
1896 f3x3matrixAeqInvSymB(pthisMagCal->finvA, pthisMagCal->fA);
1897
1898 // compute the trial hard iron vector in offset bit counts times FMATRIXSCALING
1899 for (l = CHX; l <= CHZ; l++)
1900 {
1901 pthisMagCal->ftrV[l] = 0.0F;
1902 for (m = CHX; m <= CHZ; m++)
1903 {
1904 pthisMagCal->ftrV[l] += pthisMagCal->finvA[l][m] * pthisMagCal->fmatB[m + 6][j];
1905 }
1906
1907 pthisMagCal->ftrV[l] *= -0.5F;
1908 }
1909
1910 // compute the trial geomagnetic field strength B in bit counts times FMATRIXSCALING
1911 pthisMagCal->ftrB = sqrtf(fabsf(pthisMagCal->fA[0][0] *
1912 pthisMagCal->ftrV[CHX] * pthisMagCal->ftrV[CHX] +
1913 2.0F * pthisMagCal->fA[0][1] *
1914 pthisMagCal->ftrV[CHX] * pthisMagCal->ftrV[CHY] +
1915 2.0F * pthisMagCal->fA[0][2] *
1916 pthisMagCal->ftrV[CHX] * pthisMagCal->ftrV[CHZ] +
1917 pthisMagCal->fA[1][1] * pthisMagCal->ftrV[CHY] *
1918 pthisMagCal->ftrV[CHY] + 2.0F *
1919 pthisMagCal->fA[1][2] * pthisMagCal->ftrV[CHY] *
1920 pthisMagCal->ftrV[CHZ] + pthisMagCal->fA[2][2] *
1921 pthisMagCal->ftrV[CHZ] * pthisMagCal->ftrV[CHZ] -
1922 pthisMagCal->fmatB[9][j]));
1923
1924 // calculate the trial normalized fit error as a percentage
1925 pthisMagCal->ftrFitErrorpc = 50.0F * sqrtf(fabsf(pthisMagCal->fvecA[j]) /
1926 (float) pthisMagBuffer->iMagBufferCount) / (pthisMagCal->ftrB * pthisMagCal->ftrB);
1927
1928 // correct for the measurement matrix offset and scaling and get the computed hard iron offset in uT
1929 for (l = CHX; l <= CHZ; l++)
1930 {
1931 pthisMagCal->ftrV[l] = pthisMagCal->ftrV[l] *
1932 DEFAULTB +
1933 (float) iOffset[l] *
1934 pthisMag->fuTPerCount;
1935 }
1936
1937 // convert the trial geomagnetic field strength B into uT for un-normalized soft iron matrix A
1938 pthisMagCal->ftrB *= DEFAULTB;
1939
1940 // normalize the ellipsoid matrix A to unit determinant and correct B by root of this multiplicative factor
1941 f3x3matrixAeqAxScalar(pthisMagCal->fA, powf(det, -(ONETHIRD)));
1942 pthisMagCal->ftrB *= powf(det, -(ONESIXTH));
1943
1944 // compute trial invW from the square root of fA (both with normalized determinant)
1945 // set fvecA to the unsorted eigenvalues and fmatB to the unsorted eigenvectors of fmatA
1946 // where fmatA holds the 3x3 matrix fA in its top left elements
1947 for (i = 0; i < 3; i++)
1948 {
1949 for (j = 0; j < 3; j++)
1950 {
1951 pthisMagCal->fmatA[i][j] = pthisMagCal->fA[i][j];
1952 }
1953 }
1954
1955 fEigenCompute10(pthisMagCal->fmatA, pthisMagCal->fvecA, pthisMagCal->fmatB,
1956 3);
1957
1958 // set pthisMagCal->fmatB to be eigenvectors . diag(sqrt(sqrt(eigenvalues))) = fmatB . diag(sqrt(sqrt(fvecA))
1959 for (j = 0; j < 3; j++) // loop over columns j
1960 {
1961 ftmp = sqrtf(sqrtf(fabsf(pthisMagCal->fvecA[j])));
1962 for (i = 0; i < 3; i++) // loop over rows i
1963 {
1964 pthisMagCal->fmatB[i][j] *= ftmp;
1965 }
1966 }
1967
1968 // set ftrinvW to eigenvectors * diag(sqrt(eigenvalues)) * eigenvectors^T
1969 // = fmatB * fmatB^T = sqrt(fA) (guaranteed symmetric)
1970 // loop over rows
1971 for (i = 0; i < 3; i++)
1972 {
1973 // loop over on and above diagonal columns
1974 for (j = i; j < 3; j++)
1975 {
1976 pthisMagCal->ftrinvW[i][j] = 0.0F;
1977
1978 // accumulate the matrix product
1979 for (k = 0; k < 3; k++)
1980 {
1981 pthisMagCal->ftrinvW[i][j] += pthisMagCal->fmatB[i][k] * pthisMagCal->fmatB[j][k];
1982 }
1983
1984 // copy to below diagonal element
1985 pthisMagCal->ftrinvW[j][i] = pthisMagCal->ftrinvW[i][j];
1986 }
1987 }
1988
1989 return;
1990 }
1991 #endif
1992