2 * Copyright (C) 2014-2015 Intel Corporation.
6 #include <hardware/sensors.h>
9 #include "calibration.h"
10 #include "matrix-ops.h"
11 #include "description.h"
15 #define COMPASS_CALIBRATION_PATH "/data/compass.conf"
16 #define EPSILON 0.000000001
18 #define MAGNETIC_LOW 960 /* 31 micro tesla squared */
20 #define CAL_VERSION 1.0
22 /* We'll have multiple calibration levels so that we can provide an estimation as fast as possible */
23 static const float min_diffs [CAL_STEPS] = {0.2, 0.25, 0.4, 0.6, 1.0};
24 static const float max_sqr_errs [CAL_STEPS] = {10.0, 10.0, 8.0, 5.0, 3.5};
25 static const unsigned int lookback_counts [CAL_STEPS] = {2, 3, 4, 5, 6 };
28 /* Reset calibration algorithm */
29 static void reset_sample (compass_cal_t* data)
32 data->sample_count = 0;
33 for (i = 0; i < MAGN_DS_SIZE; i++)
35 data->sample[i][j] = 0;
37 data->average[0] = data->average[1] = data->average[2] = 0;
41 static double calc_square_err (compass_cal_t* data)
44 double raw[3][1], result[3][1], mat_diff[3][1];
46 float stdev[3] = {0,0,0};
49 for (i = 0; i < MAGN_DS_SIZE; i++) {
50 raw[0][0] = data->sample[i][0];
51 raw[1][0] = data->sample[i][1];
52 raw[2][0] = data->sample[i][2];
54 stdev[0] += (raw[0][0] - data->average[0]) * (raw[0][0] - data->average[0]);
55 stdev[1] += (raw[1][0] - data->average[1]) * (raw[1][0] - data->average[1]);
56 stdev[2] += (raw[2][0] - data->average[2]) * (raw[2][0] - data->average[2]);
58 substract (3, 1, raw, data->offset, mat_diff);
59 multiply(3, 3, 1, data->w_invert, mat_diff, result);
61 diff = sqrt(result[0][0] * result[0][0] + result[1][0] * result[1][0] + result[2][0] * result[2][0]) - data->bfield;
66 stdev[0] = sqrt(stdev[0] / MAGN_DS_SIZE);
67 stdev[1] = sqrt(stdev[1] / MAGN_DS_SIZE);
68 stdev[2] = sqrt(stdev[2] / MAGN_DS_SIZE);
70 /* A sanity check - if we have too little variation for an axis it's best to reject the calibration than risking a wrong calibration */
71 if (stdev[0] <= 1 || stdev[1] <= 1 || stdev[2] <= 1)
72 return max_sqr_errs[0];
79 /* Given an real symmetric 3x3 matrix A, compute the eigenvalues */
80 static void compute_eigenvalues (double mat[3][3], double* eig1, double* eig2, double* eig3)
82 double p = mat[0][1] * mat[0][1] + mat[0][2] * mat[0][2] + mat[1][2] * mat[1][2];
91 double q = (mat[0][0] + mat[1][1] + mat[2][2]) / 3;
92 double temp1 = mat[0][0] - q;
93 double temp2 = mat[1][1] - q;
94 double temp3 = mat[2][2] - q;
96 p = temp1 * temp1 + temp2 * temp2 + temp3 * temp3 + 2 * p;
100 assign(3, 3, mat, mat2);
104 multiply_scalar_inplace(3, 3, mat2, 1/p);
106 double r = (mat2[0][0] * mat2[1][1] * mat2[2][2] + mat2[0][1] * mat2[1][2] * mat2[2][0]
107 + mat2[0][2] * mat2[1][0] * mat2[2][1] - mat2[0][2] * mat2[1][1] * mat2[2][0]
108 - mat2[0][0] * mat2[1][2] * mat2[2][1] - mat2[0][1] * mat2[1][0] * mat2[2][2]) / 2;
118 *eig3 = q + 2 * p * cos(phi);
119 *eig1 = q + 2 * p * cos(phi + 2 * M_PI / 3);
120 *eig2 = 3 * q - *eig1 - *eig3;
124 static void calc_evector (double mat[3][3], double eig, double vec[3][1])
128 assign(3, 3, mat, h);
139 assign(2, 2, x_tmp, x);
141 double temp1 = x[0][0] * (-h[1][0]) + x[0][1] * (-h[2][0]);
142 double temp2 = x[1][0] * (-h[1][0]) + x[1][1] * (-h[2][0]);
143 double norm = sqrt(1 + temp1 * temp1 + temp2 * temp2);
145 vec[0][0] = 1.0 / norm;
146 vec[1][0] = temp1 / norm;
147 vec[2][0] = temp2 / norm;
151 static int ellipsoid_fit (mat_input_t m, double offset[3][1], double w_invert[3][3], double* bfield)
154 double h[MAGN_DS_SIZE][9];
155 double w[MAGN_DS_SIZE][1];
156 double h_trans[9][MAGN_DS_SIZE];
157 double p_temp1[9][9];
158 double p_temp2[9][MAGN_DS_SIZE];
159 double temp1[3][3], temp[3][3];
160 double temp1_inv[3][3];
164 double a[3][3], sqrt_evals[3][3], evecs[3][3], evecs_trans[3][3];
165 double evec1[3][1], evec2[3][1], evec3[3][1];
167 for (i = 0; i < MAGN_DS_SIZE; i++) {
168 w[i][0] = m[i][0] * m[i][0];
172 h[i][3] = -1 * m[i][0] * m[i][1];
173 h[i][4] = -1 * m[i][0] * m[i][2];
174 h[i][5] = -1 * m[i][1] * m[i][2];
175 h[i][6] = -1 * m[i][1] * m[i][1];
176 h[i][7] = -1 * m[i][2] * m[i][2];
180 transpose (MAGN_DS_SIZE, 9, h, h_trans);
181 multiply (9, MAGN_DS_SIZE, 9, h_trans, h, result);
182 invert (9, result, p_temp1);
183 multiply (9, 9, MAGN_DS_SIZE, p_temp1, h_trans, p_temp2);
184 multiply (9, MAGN_DS_SIZE, 1, p_temp2, w, p);
187 temp1[0][1] = p[3][0];
188 temp1[0][2] = p[4][0];
189 temp1[1][0] = p[3][0];
190 temp1[1][1] = 2 * p[6][0];
191 temp1[1][2] = p[5][0];
192 temp1[2][0] = p[4][0];
193 temp1[2][1] = p[5][0];
194 temp1[2][2] = 2 * p[7][0];
196 temp2[0][0] = p[0][0];
197 temp2[1][0] = p[1][0];
198 temp2[2][0] = p[2][0];
200 invert(3, temp1, temp1_inv);
201 multiply(3, 3, 1, temp1_inv, temp2, offset);
202 double off_x = offset[0][0];
203 double off_y = offset[1][0];
204 double off_z = offset[2][0];
206 a[0][0] = 1.0 / (p[8][0] + off_x * off_x + p[6][0] * off_y * off_y
207 + p[7][0] * off_z * off_z + p[3][0] * off_x * off_y
208 + p[4][0] * off_x * off_z + p[5][0] * off_y * off_z);
210 a[0][1] = p[3][0] * a[0][0] / 2;
211 a[0][2] = p[4][0] * a[0][0] / 2;
212 a[1][2] = p[5][0] * a[0][0] / 2;
213 a[1][1] = p[6][0] * a[0][0];
214 a[2][2] = p[7][0] * a[0][0];
219 double eig1 = 0, eig2 = 0, eig3 = 0;
220 compute_eigenvalues(a, &eig1, &eig2, &eig3);
222 if (eig1 <=0 || eig2 <= 0 || eig3 <= 0)
225 sqrt_evals[0][0] = sqrt(eig1);
226 sqrt_evals[1][0] = 0;
227 sqrt_evals[2][0] = 0;
228 sqrt_evals[0][1] = 0;
229 sqrt_evals[1][1] = sqrt(eig2);
230 sqrt_evals[2][1] = 0;
231 sqrt_evals[0][2] = 0;
232 sqrt_evals[1][2] = 0;
233 sqrt_evals[2][2] = sqrt(eig3);
235 calc_evector(a, eig1, evec1);
236 calc_evector(a, eig2, evec2);
237 calc_evector(a, eig3, evec3);
239 evecs[0][0] = evec1[0][0];
240 evecs[1][0] = evec1[1][0];
241 evecs[2][0] = evec1[2][0];
242 evecs[0][1] = evec2[0][0];
243 evecs[1][1] = evec2[1][0];
244 evecs[2][1] = evec2[2][0];
245 evecs[0][2] = evec3[0][0];
246 evecs[1][2] = evec3[1][0];
247 evecs[2][2] = evec3[2][0];
249 multiply (3, 3, 3, evecs, sqrt_evals, temp1);
250 transpose(3, 3, evecs, evecs_trans);
251 multiply (3, 3, 3, temp1, evecs_trans, temp);
252 transpose (3, 3, temp, w_invert);
254 *bfield = pow(sqrt(1/eig1) * sqrt(1/eig2) * sqrt(1/eig3), 1.0/3.0);
259 multiply_scalar_inplace(3, 3, w_invert, *bfield);
265 static void compass_cal_init (FILE* data_file, sensor_info_t* info)
267 compass_cal_t* cal_data = (compass_cal_t*) info->cal_data;
268 int cal_steps = (info->max_cal_level && info->max_cal_level <= CAL_STEPS) ? info->max_cal_level : CAL_STEPS;
271 if (cal_data == NULL)
275 reset_sample(cal_data);
277 if (!info->cal_level && data_file != NULL) {
278 int ret = fscanf(data_file, "%f %d %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf %lf",
279 &version, &info->cal_level,
280 &cal_data->offset[0][0], &cal_data->offset[1][0], &cal_data->offset[2][0],
281 &cal_data->w_invert[0][0], &cal_data->w_invert[0][1], &cal_data->w_invert[0][2],
282 &cal_data->w_invert[1][0], &cal_data->w_invert[1][1], &cal_data->w_invert[1][2],
283 &cal_data->w_invert[2][0], &cal_data->w_invert[2][1], &cal_data->w_invert[2][2],
286 if (ret != data_count || info->cal_level >= cal_steps || version != CAL_VERSION)
290 if (info->cal_level) {
291 ALOGV("CompassCalibration: load old data, caldata: %f %f %f %f %f %f %f %f %f %f %f %f %f",
292 cal_data->offset[0][0], cal_data->offset[1][0], cal_data->offset[2][0],
293 cal_data->w_invert[0][0], cal_data->w_invert[0][1], cal_data->w_invert[0][2], cal_data->w_invert[1][0],
294 cal_data->w_invert[1][1], cal_data->w_invert[1][2], cal_data->w_invert[2][0], cal_data->w_invert[2][1],
295 cal_data->w_invert[2][2], cal_data->bfield);
297 cal_data->offset[0][0] = 0;
298 cal_data->offset[1][0] = 0;
299 cal_data->offset[2][0] = 0;
301 cal_data->w_invert[0][0] = 1;
302 cal_data->w_invert[1][0] = 0;
303 cal_data->w_invert[2][0] = 0;
304 cal_data->w_invert[0][1] = 0;
305 cal_data->w_invert[1][1] = 1;
306 cal_data->w_invert[2][1] = 0;
307 cal_data->w_invert[0][2] = 0;
308 cal_data->w_invert[1][2] = 0;
309 cal_data->w_invert[2][2] = 1;
311 cal_data->bfield = 0;
316 static void compass_store_result (FILE* data_file, sensor_info_t* info)
318 compass_cal_t* cal_data = (compass_cal_t*) info->cal_data;
320 if (data_file == NULL || cal_data == NULL)
323 int ret = fprintf(data_file, "%f %d %f %f %f %f %f %f %f %f %f %f %f %f %f\n",
324 CAL_VERSION, info->cal_level,
325 cal_data->offset[0][0], cal_data->offset[1][0], cal_data->offset[2][0],
326 cal_data->w_invert[0][0], cal_data->w_invert[0][1], cal_data->w_invert[0][2],
327 cal_data->w_invert[1][0], cal_data->w_invert[1][1], cal_data->w_invert[1][2],
328 cal_data->w_invert[2][0], cal_data->w_invert[2][1], cal_data->w_invert[2][2],
332 ALOGE ("Compass calibration - store data failed!");
336 static int compass_collect (sensors_event_t* event, sensor_info_t* info)
338 float data[3] = {event->magnetic.x, event->magnetic.y, event->magnetic.z};
339 unsigned int index,j;
340 unsigned int lookback_count;
343 compass_cal_t* cal_data = (compass_cal_t*) info->cal_data;
345 if (cal_data == NULL)
348 /* Discard the point if not valid */
349 if (data[0] == 0 || data[1] == 0 || data[2] == 0)
352 lookback_count = lookback_counts[info->cal_level];
353 min_diff = min_diffs[info->cal_level];
355 /* For the current point to be accepted, each x/y/z value must be different enough to the last several collected points */
356 if (cal_data->sample_count > 0 && cal_data->sample_count < MAGN_DS_SIZE) {
357 unsigned int lookback = lookback_count < cal_data->sample_count ? lookback_count : cal_data->sample_count;
358 for (index = 0; index < lookback; index++)
359 for (j = 0; j < 3; j++)
360 if (fabsf(data[j] - cal_data->sample[cal_data->sample_count-1-index][j]) < min_diff) {
361 ALOGV("CompassCalibration:point reject: [%f,%f,%f], selected_count=%d", data[0], data[1], data[2], cal_data->sample_count);
366 if (cal_data->sample_count < MAGN_DS_SIZE) {
367 memcpy(cal_data->sample[cal_data->sample_count], data, sizeof(float) * 3);
368 cal_data->sample_count++;
369 cal_data->average[0] += data[0];
370 cal_data->average[1] += data[1];
371 cal_data->average[2] += data[2];
372 ALOGV("CompassCalibration:point collected [%f,%f,%f], selected_count=%d", (double)data[0], (double)data[1], (double)data[2], cal_data->sample_count);
378 static void scale_event (sensors_event_t* event)
381 float sanity_norm = 0;
384 sqr_norm = (event->magnetic.x * event->magnetic.x +
385 event->magnetic.y * event->magnetic.y +
386 event->magnetic.z * event->magnetic.z);
388 if (sqr_norm < MAGNETIC_LOW)
389 sanity_norm = MAGNETIC_LOW;
391 if (sanity_norm && sqr_norm) {
392 scale = sanity_norm / sqr_norm;
394 event->magnetic.x = event->magnetic.x * scale;
395 event->magnetic.y = event->magnetic.y * scale;
396 event->magnetic.z = event->magnetic.z * scale;
401 static void compass_compute_cal (sensors_event_t* event, sensor_info_t* info)
403 compass_cal_t* cal_data = (compass_cal_t*) info->cal_data;
404 double result[3][1], raw[3][1], diff[3][1];
406 if (!info->cal_level || cal_data == NULL)
409 raw[0][0] = event->magnetic.x;
410 raw[1][0] = event->magnetic.y;
411 raw[2][0] = event->magnetic.z;
413 substract(3, 1, raw, cal_data->offset, diff);
414 multiply (3, 3, 1, cal_data->w_invert, diff, result);
416 event->magnetic.x = event->data[0] = result[0][0];
417 event->magnetic.y = event->data[1] = result[1][0];
418 event->magnetic.z = event->data[2] = result[2][0];
424 static int compass_ready (sensor_info_t* info)
430 compass_cal_t* cal_data = (compass_cal_t*) info->cal_data;
431 compass_cal_t new_cal_data;
434 * Some sensors take unrealistically long to calibrate at higher levels. We'll use a max_cal_level if we have such a property setup,
435 * or go with the default settings if not.
437 int cal_steps = (info->max_cal_level && info->max_cal_level <= CAL_STEPS) ? info->max_cal_level : CAL_STEPS;
439 if (cal_data->sample_count < MAGN_DS_SIZE)
440 return info->cal_level;
442 max_sqr_err = max_sqr_errs[info->cal_level];
444 /* Enough points have been collected, do the ellipsoid calibration */
446 /* Compute average per axis */
447 cal_data->average[0] /= MAGN_DS_SIZE;
448 cal_data->average[1] /= MAGN_DS_SIZE;
449 cal_data->average[2] /= MAGN_DS_SIZE;
451 for (i = 0; i < MAGN_DS_SIZE; i++) {
452 mat[i][0] = cal_data->sample[i][0];
453 mat[i][1] = cal_data->sample[i][1];
454 mat[i][2] = cal_data->sample[i][2];
457 /* Check if result is good. The sample data must remain the same */
458 new_cal_data = *cal_data;
460 if (ellipsoid_fit(mat, new_cal_data.offset, new_cal_data.w_invert, &new_cal_data.bfield)) {
461 double new_err = calc_square_err (&new_cal_data);
462 ALOGI("new err is %f, max sqr err id %f", new_err,max_sqr_err);
463 if (new_err < max_sqr_err) {
464 double err = calc_square_err(cal_data);
466 /* New cal data is better, so we switch to the new */
467 memcpy(cal_data->offset, new_cal_data.offset, sizeof(cal_data->offset));
468 memcpy(cal_data->w_invert, new_cal_data.w_invert, sizeof(cal_data->w_invert));
469 cal_data->bfield = new_cal_data.bfield;
470 if (info->cal_level < (cal_steps - 1))
472 ALOGV("CompassCalibration: ready check success, caldata: %f %f %f %f %f %f %f %f %f %f %f %f %f, err %f",
473 cal_data->offset[0][0], cal_data->offset[1][0], cal_data->offset[2][0], cal_data->w_invert[0][0],
474 cal_data->w_invert[0][1], cal_data->w_invert[0][2], cal_data->w_invert[1][0], cal_data->w_invert[1][1],
475 cal_data->w_invert[1][2], cal_data->w_invert[2][0], cal_data->w_invert[2][1], cal_data->w_invert[2][2],
476 cal_data->bfield, new_err);
480 reset_sample(cal_data);
481 return info->cal_level;
485 void calibrate_compass (int s, sensors_event_t* event)
489 /* Calibration is continuous */
490 compass_collect (event, &sensor[s]);
492 cal_level = compass_ready(&sensor[s]);
497 event->magnetic.status = SENSOR_STATUS_UNRELIABLE;
501 compass_compute_cal (event, &sensor[s]);
502 event->magnetic.status = SENSOR_STATUS_ACCURACY_LOW;
506 compass_compute_cal (event, &sensor[s]);
507 event->magnetic.status = SENSOR_STATUS_ACCURACY_MEDIUM;
511 compass_compute_cal (event, &sensor[s]);
512 event->magnetic.status = SENSOR_STATUS_ACCURACY_HIGH;
517 void compass_read_data (int s)
519 FILE* data_file = fopen (COMPASS_CALIBRATION_PATH, "r");
521 compass_cal_init(data_file, &sensor[s]);
528 void compass_store_data (int s)
530 FILE* data_file = fopen (COMPASS_CALIBRATION_PATH, "w");
532 compass_store_result(data_file, &sensor[s]);