1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "calibration/magnetometer/mag_cal.h" 18 #include <errno.h> 19 #include <nanohub_math.h> 20 #include <string.h> 21 22 #ifdef MAG_CAL_ORIGINAL_TUNING 23 #define MAX_EIGEN_RATIO 25.0f 24 #define MAX_EIGEN_MAG 80.0f // uT 25 #define MIN_EIGEN_MAG 10.0f // uT 26 #define MAX_FIT_MAG 80.0f 27 #define MIN_FIT_MAG 10.0f 28 #define MIN_BATCH_WINDOW 1000000UL // 1 sec 29 #define MAX_BATCH_WINDOW 15000000UL // 15 sec 30 #define MIN_BATCH_SIZE 25 // samples 31 #else 32 #define MAX_EIGEN_RATIO 15.0f 33 #define MAX_EIGEN_MAG 70.0f // uT 34 #define MIN_EIGEN_MAG 20.0f // uT 35 #define MAX_FIT_MAG 70.0f 36 #define MIN_FIT_MAG 20.0f 37 #define MIN_BATCH_WINDOW 3000000UL // 3 sec 38 #define MAX_BATCH_WINDOW 15000000UL // 15 sec 39 #define MIN_BATCH_SIZE 25 // samples 40 #endif 41 42 #ifdef DIVERSITY_CHECK_ENABLED 43 #define MAX_DISTANCE_VIOLATIONS 2 44 #endif 45 46 // eigen value magnitude and ratio test 47 static int moc_eigen_test(struct KasaFit *kasa) { 48 // covariance matrix 49 struct Mat33 S; 50 S.elem[0][0] = kasa->acc_xx - kasa->acc_x * kasa->acc_x; 51 S.elem[0][1] = S.elem[1][0] = kasa->acc_xy - kasa->acc_x * kasa->acc_y; 52 S.elem[0][2] = S.elem[2][0] = kasa->acc_xz - kasa->acc_x * kasa->acc_z; 53 S.elem[1][1] = kasa->acc_yy - kasa->acc_y * kasa->acc_y; 54 S.elem[1][2] = S.elem[2][1] = kasa->acc_yz - kasa->acc_y * kasa->acc_z; 55 S.elem[2][2] = kasa->acc_zz - kasa->acc_z * kasa->acc_z; 56 57 struct Vec3 eigenvals; 58 struct Mat33 eigenvecs; 59 mat33GetEigenbasis(&S, &eigenvals, &eigenvecs); 60 61 float evmax = (eigenvals.x > eigenvals.y) ? eigenvals.x : eigenvals.y; 62 evmax = (eigenvals.z > evmax) ? eigenvals.z : evmax; 63 64 float evmin = (eigenvals.x < eigenvals.y) ? eigenvals.x : eigenvals.y; 65 evmin = (eigenvals.z < evmin) ? eigenvals.z : evmin; 66 67 float evmag = sqrtf(eigenvals.x + eigenvals.y + eigenvals.z); 68 69 int eigen_pass = (evmin * MAX_EIGEN_RATIO > evmax) && 70 (evmag > MIN_EIGEN_MAG) && (evmag < MAX_EIGEN_MAG); 71 72 return eigen_pass; 73 } 74 75 // Kasa sphere fitting with normal equation 76 int magKasaFit(struct KasaFit *kasa, struct Vec3 *bias, float *radius) { 77 // A * out = b 78 // (4 x 4) (4 x 1) (4 x 1) 79 struct Mat44 A; 80 A.elem[0][0] = kasa->acc_xx; 81 A.elem[0][1] = kasa->acc_xy; 82 A.elem[0][2] = kasa->acc_xz; 83 A.elem[0][3] = kasa->acc_x; 84 A.elem[1][0] = kasa->acc_xy; 85 A.elem[1][1] = kasa->acc_yy; 86 A.elem[1][2] = kasa->acc_yz; 87 A.elem[1][3] = kasa->acc_y; 88 A.elem[2][0] = kasa->acc_xz; 89 A.elem[2][1] = kasa->acc_yz; 90 A.elem[2][2] = kasa->acc_zz; 91 A.elem[2][3] = kasa->acc_z; 92 A.elem[3][0] = kasa->acc_x; 93 A.elem[3][1] = kasa->acc_y; 94 A.elem[3][2] = kasa->acc_z; 95 A.elem[3][3] = 1.0f; 96 97 struct Vec4 b; 98 initVec4(&b, -kasa->acc_xw, -kasa->acc_yw, -kasa->acc_zw, -kasa->acc_w); 99 100 struct Size4 pivot; 101 mat44DecomposeLup(&A, &pivot); 102 103 struct Vec4 out; 104 mat44Solve(&A, &out, &b, &pivot); 105 106 // sphere: (x - xc)^2 + (y - yc)^2 + (z - zc)^2 = r^2 107 // 108 // xc = -out[0] / 2, yc = -out[1] / 2, zc = -out[2] / 2 109 // r = sqrt(xc^2 + yc^2 + zc^2 - out[3]) 110 111 struct Vec3 v; 112 initVec3(&v, out.x, out.y, out.z); 113 vec3ScalarMul(&v, -0.5f); 114 115 float r = sqrtf(vec3Dot(&v, &v) - out.w); 116 117 initVec3(bias, v.x, v.y, v.z); 118 *radius = r; 119 120 int success = 0; 121 if (r > MIN_FIT_MAG && r < MAX_FIT_MAG) { 122 success = 1; 123 } 124 125 return success; 126 } 127 128 void magKasaReset(struct KasaFit *kasa) { 129 kasa->acc_x = kasa->acc_y = kasa->acc_z = kasa->acc_w = 0.0f; 130 kasa->acc_xx = kasa->acc_xy = kasa->acc_xz = kasa->acc_xw = 0.0f; 131 kasa->acc_yy = kasa->acc_yz = kasa->acc_yw = 0.0f; 132 kasa->acc_zz = kasa->acc_zw = 0.0f; 133 134 kasa->nsamples = 0; 135 } 136 137 void magCalReset(struct MagCal *moc) { 138 magKasaReset(&moc->kasa); 139 #ifdef DIVERSITY_CHECK_ENABLED 140 diversityCheckerReset(&moc->diversity_checker); 141 #endif 142 moc->start_time = 0; 143 } 144 145 static int moc_batch_complete(struct MagCal *moc, uint64_t sample_time_us) { 146 int complete = 0; 147 148 if ((sample_time_us - moc->start_time > MIN_BATCH_WINDOW) && 149 (moc->kasa.nsamples > MIN_BATCH_SIZE)) { 150 complete = 1; 151 152 } else if (sample_time_us - moc->start_time > MAX_BATCH_WINDOW) { 153 // not enough samples collected in MAX_BATCH_WINDOW or too many 154 // maximum distance violations detected. 155 magCalReset(moc); 156 } 157 158 return complete; 159 } 160 161 void initKasa(struct KasaFit *kasa) { 162 magKasaReset(kasa); 163 } 164 165 void initMagCal(struct MagCal *moc, float x_bias, float y_bias, float z_bias, 166 float c00, float c01, float c02, float c10, float c11, 167 float c12, float c20, float c21, float c22 168 #ifdef DIVERSITY_CHECK_ENABLED 169 ,size_t min_num_diverse_vectors 170 ,size_t max_num_max_distance 171 ,float var_threshold 172 ,float max_min_threshold 173 ,float local_field 174 ,float threshold_tuning_param 175 ,float max_distance_tuning_param 176 #endif 177 ) { 178 magCalReset(moc); 179 moc->update_time = 0; 180 moc->radius = 0.0f; 181 182 moc->x_bias = x_bias; 183 moc->y_bias = y_bias; 184 moc->z_bias = z_bias; 185 186 moc->c00 = c00; 187 moc->c01 = c01; 188 moc->c02 = c02; 189 moc->c10 = c10; 190 moc->c11 = c11; 191 moc->c12 = c12; 192 moc->c20 = c20; 193 moc->c21 = c21; 194 moc->c22 = c22; 195 196 #ifdef DIVERSITY_CHECK_ENABLED 197 // Diversity Checker Init 198 diversityCheckerInit(&moc->diversity_checker, 199 min_num_diverse_vectors, 200 max_num_max_distance, 201 var_threshold, 202 max_min_threshold, 203 local_field, 204 threshold_tuning_param, 205 max_distance_tuning_param); 206 #endif 207 } 208 209 void magCalDestroy(struct MagCal *moc) { (void)moc; } 210 211 bool magCalUpdate(struct MagCal *moc, uint64_t sample_time_us, float x, float y, 212 float z) { 213 bool new_bias = false; 214 215 #ifdef DIVERSITY_CHECK_ENABLED 216 // Diversity Checker Update. 217 diversityCheckerUpdate(&moc->diversity_checker, x, y, z); 218 #endif 219 220 // 1. run accumulators 221 float w = x * x + y * y + z * z; 222 223 moc->kasa.acc_x += x; 224 moc->kasa.acc_y += y; 225 moc->kasa.acc_z += z; 226 moc->kasa.acc_w += w; 227 228 moc->kasa.acc_xx += x * x; 229 moc->kasa.acc_xy += x * y; 230 moc->kasa.acc_xz += x * z; 231 moc->kasa.acc_xw += x * w; 232 233 moc->kasa.acc_yy += y * y; 234 moc->kasa.acc_yz += y * z; 235 moc->kasa.acc_yw += y * w; 236 237 moc->kasa.acc_zz += z * z; 238 moc->kasa.acc_zw += z * w; 239 240 if (++moc->kasa.nsamples == 1) { 241 moc->start_time = sample_time_us; 242 } 243 244 // 2. batch has enough samples? 245 if (moc_batch_complete(moc, sample_time_us)) { 246 float inv = 1.0f / moc->kasa.nsamples; 247 248 moc->kasa.acc_x *= inv; 249 moc->kasa.acc_y *= inv; 250 moc->kasa.acc_z *= inv; 251 moc->kasa.acc_w *= inv; 252 253 moc->kasa.acc_xx *= inv; 254 moc->kasa.acc_xy *= inv; 255 moc->kasa.acc_xz *= inv; 256 moc->kasa.acc_xw *= inv; 257 258 moc->kasa.acc_yy *= inv; 259 moc->kasa.acc_yz *= inv; 260 moc->kasa.acc_yw *= inv; 261 262 moc->kasa.acc_zz *= inv; 263 moc->kasa.acc_zw *= inv; 264 265 // 3. eigen test 266 if (moc_eigen_test(&moc->kasa)) { 267 struct Vec3 bias; 268 float radius; 269 // 4. Kasa sphere fitting 270 if (magKasaFit(&moc->kasa, &bias, &radius)) { 271 #ifdef DIVERSITY_CHECK_ENABLED 272 diversityCheckerLocalFieldUpdate(&moc->diversity_checker, 273 radius); 274 if (diversityCheckerNormQuality(&moc->diversity_checker, 275 bias.x, 276 bias.y, 277 bias.z) && 278 moc->diversity_checker.num_max_dist_violations 279 <= MAX_DISTANCE_VIOLATIONS) { 280 #endif 281 moc->x_bias = bias.x; 282 moc->y_bias = bias.y; 283 moc->z_bias = bias.z; 284 285 moc->radius = radius; 286 moc->update_time = sample_time_us; 287 288 new_bias = true; 289 #ifdef DIVERSITY_CHECK_ENABLED 290 } 291 #endif 292 } 293 } 294 295 // 5. reset for next batch 296 magCalReset(moc); 297 } 298 299 return new_bias; 300 } 301 302 void magCalGetBias(struct MagCal *moc, float *x, float *y, float *z) { 303 *x = moc->x_bias; 304 *y = moc->y_bias; 305 *z = moc->z_bias; 306 } 307 308 void magCalAddBias(struct MagCal *moc, float x, float y, float z) { 309 moc->x_bias += x; 310 moc->y_bias += y; 311 moc->z_bias += z; 312 } 313 314 void magCalRemoveBias(struct MagCal *moc, float xi, float yi, float zi, 315 float *xo, float *yo, float *zo) { 316 *xo = xi - moc->x_bias; 317 *yo = yi - moc->y_bias; 318 *zo = zi - moc->z_bias; 319 } 320 321 void magCalSetSoftiron(struct MagCal *moc, float c00, float c01, float c02, 322 float c10, float c11, float c12, float c20, float c21, 323 float c22) { 324 moc->c00 = c00; 325 moc->c01 = c01; 326 moc->c02 = c02; 327 moc->c10 = c10; 328 moc->c11 = c11; 329 moc->c12 = c12; 330 moc->c20 = c20; 331 moc->c21 = c21; 332 moc->c22 = c22; 333 } 334 335 void magCalRemoveSoftiron(struct MagCal *moc, float xi, float yi, float zi, 336 float *xo, float *yo, float *zo) { 337 *xo = moc->c00 * xi + moc->c01 * yi + moc->c02 * zi; 338 *yo = moc->c10 * xi + moc->c11 * yi + moc->c12 * zi; 339 *zo = moc->c20 * xi + moc->c21 * yi + moc->c22 * zi; 340 } 341