1 /* 2 $License: 3 Copyright 2011 InvenSense, Inc. 4 5 Licensed under the Apache License, Version 2.0 (the "License"); 6 you may not use this file except in compliance with the License. 7 You may obtain a copy of the License at 8 9 http://www.apache.org/licenses/LICENSE-2.0 10 11 Unless required by applicable law or agreed to in writing, software 12 distributed under the License is distributed on an "AS IS" BASIS, 13 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 See the License for the specific language governing permissions and 15 limitations under the License. 16 $ 17 */ 18 /******************************************************************************* 19 * 20 * $Id: compass.c 5641 2011-06-14 02:10:02Z mcaramello $ 21 * 22 *******************************************************************************/ 23 24 /** 25 * @defgroup COMPASSDL 26 * @brief Motion Library - Compass Driver Layer. 27 * Provides the interface to setup and handle an compass 28 * connected to either the primary or the seconday I2C interface 29 * of the gyroscope. 30 * 31 * @{ 32 * @file compass.c 33 * @brief Compass setup and handling methods. 34 */ 35 36 /* ------------------ */ 37 /* - Include Files. - */ 38 /* ------------------ */ 39 40 #include <string.h> 41 #include <stdlib.h> 42 #include "compass.h" 43 44 #include "ml.h" 45 #include "mlinclude.h" 46 #include "dmpKey.h" 47 #include "mlFIFO.h" 48 #include "mldl.h" 49 #include "mldl_cfg.h" 50 #include "mlMathFunc.h" 51 #include "mlsl.h" 52 #include "mlos.h" 53 54 #include "log.h" 55 #undef MPL_LOG_TAG 56 #define MPL_LOG_TAG "MPL-compass" 57 58 #define COMPASS_DEBUG 0 59 60 /* --------------------- */ 61 /* - Global Variables. - */ 62 /* --------------------- */ 63 64 /* --------------------- */ 65 /* - Static Variables. - */ 66 /* --------------------- */ 67 68 /* --------------- */ 69 /* - Prototypes. - */ 70 /* --------------- */ 71 72 /* -------------- */ 73 /* - Externs. - */ 74 /* -------------- */ 75 76 /* -------------- */ 77 /* - Functions. - */ 78 /* -------------- */ 79 80 static float square(float data) 81 { 82 return data * data; 83 } 84 85 static void adaptive_filter_init(struct yas_adaptive_filter *adap_filter, int len, float noise) 86 { 87 int i; 88 89 adap_filter->num = 0; 90 adap_filter->index = 0; 91 adap_filter->noise = noise; 92 adap_filter->len = len; 93 94 for (i = 0; i < adap_filter->len; ++i) { 95 adap_filter->sequence[i] = 0; 96 } 97 } 98 99 static int cmpfloat(const void *p1, const void *p2) 100 { 101 return *(float*)p1 - *(float*)p2; 102 } 103 104 105 static float adaptive_filter_filter(struct yas_adaptive_filter *adap_filter, float in) 106 { 107 float avg, sum, median, sorted[YAS_DEFAULT_FILTER_LEN]; 108 int i; 109 110 if (adap_filter->len <= 1) { 111 return in; 112 } 113 if (adap_filter->num < adap_filter->len) { 114 adap_filter->sequence[adap_filter->index++] = in; 115 adap_filter->num++; 116 return in; 117 } 118 if (adap_filter->len <= adap_filter->index) { 119 adap_filter->index = 0; 120 } 121 adap_filter->sequence[adap_filter->index++] = in; 122 123 avg = 0; 124 for (i = 0; i < adap_filter->len; i++) { 125 avg += adap_filter->sequence[i]; 126 } 127 avg /= adap_filter->len; 128 129 memcpy(sorted, adap_filter->sequence, adap_filter->len * sizeof(float)); 130 qsort(&sorted, adap_filter->len, sizeof(float), cmpfloat); 131 median = sorted[adap_filter->len/2]; 132 133 sum = 0; 134 for (i = 0; i < adap_filter->len; i++) { 135 sum += square(avg - adap_filter->sequence[i]); 136 } 137 sum /= adap_filter->len; 138 139 if (sum <= adap_filter->noise) { 140 return median; 141 } 142 143 return ((in - avg) * (sum - adap_filter->noise) / sum + avg); 144 } 145 146 static void thresh_filter_init(struct yas_thresh_filter *thresh_filter, float threshold) 147 { 148 thresh_filter->threshold = threshold; 149 thresh_filter->last = 0; 150 } 151 152 static float thresh_filter_filter(struct yas_thresh_filter *thresh_filter, float in) 153 { 154 if (in < thresh_filter->last - thresh_filter->threshold 155 || thresh_filter->last + thresh_filter->threshold < in) { 156 thresh_filter->last = in; 157 return in; 158 } 159 else { 160 return thresh_filter->last; 161 } 162 } 163 164 static int init(yas_filter_handle_t *t) 165 { 166 float noise[] = { 167 YAS_DEFAULT_FILTER_NOISE, 168 YAS_DEFAULT_FILTER_NOISE, 169 YAS_DEFAULT_FILTER_NOISE, 170 }; 171 int i; 172 173 if (t == NULL) { 174 return -1; 175 } 176 177 for (i = 0; i < 3; i++) { 178 adaptive_filter_init(&t->adap_filter[i], YAS_DEFAULT_FILTER_LEN, noise[i]); 179 thresh_filter_init(&t->thresh_filter[i], YAS_DEFAULT_FILTER_THRESH); 180 } 181 182 return 0; 183 } 184 185 static int update(yas_filter_handle_t *t, float *input, float *output) 186 { 187 int i; 188 189 if (t == NULL || input == NULL || output == NULL) { 190 return -1; 191 } 192 193 for (i = 0; i < 3; i++) { 194 output[i] = adaptive_filter_filter(&t->adap_filter[i], input[i]); 195 output[i] = thresh_filter_filter(&t->thresh_filter[i], output[i]); 196 } 197 198 return 0; 199 } 200 201 int yas_filter_init(yas_filter_if_s *f) 202 { 203 if (f == NULL) { 204 return -1; 205 } 206 f->init = init; 207 f->update = update; 208 209 return 0; 210 } 211 212 /** 213 * @brief Used to determine if a compass is 214 * configured and used by the MPL. 215 * @return INV_SUCCESS if the compass is present. 216 */ 217 unsigned char inv_compass_present(void) 218 { 219 INVENSENSE_FUNC_START; 220 struct mldl_cfg *mldl_cfg = inv_get_dl_config(); 221 if (NULL != mldl_cfg->compass && 222 NULL != mldl_cfg->compass->resume && 223 mldl_cfg->requested_sensors & INV_THREE_AXIS_COMPASS) 224 return TRUE; 225 else 226 return FALSE; 227 } 228 229 /** 230 * @brief Query the compass slave address. 231 * @return The 7-bit compass slave address. 232 */ 233 unsigned char inv_get_compass_slave_addr(void) 234 { 235 INVENSENSE_FUNC_START; 236 struct mldl_cfg *mldl_cfg = inv_get_dl_config(); 237 if (NULL != mldl_cfg->pdata) 238 return mldl_cfg->pdata->compass.address; 239 else 240 return 0; 241 } 242 243 /** 244 * @brief Get the ID of the compass in use. 245 * @return ID of the compass in use. 246 */ 247 unsigned short inv_get_compass_id(void) 248 { 249 INVENSENSE_FUNC_START; 250 struct mldl_cfg *mldl_cfg = inv_get_dl_config(); 251 if (NULL != mldl_cfg->compass) { 252 return mldl_cfg->compass->id; 253 } 254 return ID_INVALID; 255 } 256 257 /** 258 * @brief Get a sample of compass data from the device. 259 * @param data 260 * the buffer to store the compass raw data for 261 * X, Y, and Z axes. 262 * @return INV_SUCCESS or a non-zero error code. 263 */ 264 inv_error_t inv_get_compass_data(long *data) 265 { 266 inv_error_t result; 267 int ii; 268 struct mldl_cfg *mldl_cfg = inv_get_dl_config(); 269 270 unsigned char *tmp = inv_obj.compass_raw_data; 271 272 if (mldl_cfg->compass->read_len > sizeof(inv_obj.compass_raw_data)) { 273 LOG_RESULT_LOCATION(INV_ERROR_INVALID_CONFIGURATION); 274 return INV_ERROR_INVALID_CONFIGURATION; 275 } 276 277 if (mldl_cfg->pdata->compass.bus == EXT_SLAVE_BUS_PRIMARY || 278 !(mldl_cfg->requested_sensors & INV_DMP_PROCESSOR)) { 279 /*--- read the compass sensor data. 280 The compass read function may return an INV_ERROR_COMPASS_* errors 281 when the data is not ready (read/refresh frequency mismatch) or 282 the internal data sampling timing of the device was not respected. 283 Returning the error code will make the sensor fusion supervisor 284 ignore this compass data sample. ---*/ 285 result = (inv_error_t) inv_mpu_read_compass(mldl_cfg, 286 inv_get_serial_handle(), 287 inv_get_serial_handle(), 288 tmp); 289 if (result) { 290 if (COMPASS_DEBUG) { 291 MPL_LOGV("inv_mpu_read_compass returned %d\n", result); 292 } 293 return result; 294 } 295 for (ii = 0; ii < 3; ii++) { 296 if (EXT_SLAVE_BIG_ENDIAN == mldl_cfg->compass->endian) 297 data[ii] = 298 ((long)((signed char)tmp[2 * ii]) << 8) + tmp[2 * ii + 1]; 299 else 300 data[ii] = 301 ((long)((signed char)tmp[2 * ii + 1]) << 8) + tmp[2 * ii]; 302 } 303 304 inv_obj.compass_overunder = (int)tmp[6]; 305 306 } else { 307 #if defined CONFIG_MPU_SENSORS_MPU6050A2 || \ 308 defined CONFIG_MPU_SENSORS_MPU6050B1 309 result = inv_get_external_sensor_data(data, 3); 310 if (result) { 311 LOG_RESULT_LOCATION(result); 312 return result; 313 } 314 #if defined CONFIG_MPU_SENSORS_MPU6050A2 315 { 316 static unsigned char first = TRUE; 317 // one-off write to AKM 318 if (first) { 319 unsigned char regs[] = { 320 // beginning Mantis register for one-off slave R/W 321 MPUREG_I2C_SLV4_ADDR, 322 // the slave to write to 323 mldl_cfg->pdata->compass.address, 324 // the register to write to 325 /*mldl_cfg->compass->trigger->reg */ 0x0A, 326 // the value to write 327 /*mldl_cfg->compass->trigger->value */ 0x01, 328 // enable the write 329 0xC0 330 }; 331 result = 332 inv_serial_write(inv_get_serial_handle(), mldl_cfg->addr, 333 ARRAY_SIZE(regs), regs); 334 first = FALSE; 335 } else { 336 unsigned char regs[] = { 337 MPUREG_I2C_SLV4_CTRL, 338 0xC0 339 }; 340 result = 341 inv_serial_write(inv_get_serial_handle(), mldl_cfg->addr, 342 ARRAY_SIZE(regs), regs); 343 } 344 } 345 #endif 346 #else 347 return INV_ERROR_INVALID_CONFIGURATION; 348 #endif // CONFIG_MPU_SENSORS_xxxx 349 } 350 data[0] = inv_q30_mult(data[0], inv_obj.compass_asa[0]); 351 data[1] = inv_q30_mult(data[1], inv_obj.compass_asa[1]); 352 data[2] = inv_q30_mult(data[2], inv_obj.compass_asa[2]); 353 354 return INV_SUCCESS; 355 } 356 357 /** 358 * @brief Sets the compass bias. 359 * @param bias 360 * Compass bias, length 3. Scale is micro Tesla's * 2^16. 361 * Frame is mount frame which may be different from body frame. 362 * @return INV_SUCCESS = 0 if successful. A non-zero error code otherwise. 363 */ 364 inv_error_t inv_set_compass_bias(long *bias) 365 { 366 inv_error_t result = INV_SUCCESS; 367 long biasC[3]; 368 struct mldl_cfg *mldlCfg = inv_get_dl_config(); 369 370 inv_obj.compass_bias[0] = bias[0]; 371 inv_obj.compass_bias[1] = bias[1]; 372 inv_obj.compass_bias[2] = bias[2]; 373 374 // Find Bias in units of the raw data scaled by 2^16 in chip mounting frame 375 biasC[0] = 376 (long)(bias[0] * (1LL << 30) / inv_obj.compass_sens) + 377 inv_obj.init_compass_bias[0] * (1L << 16); 378 biasC[1] = 379 (long)(bias[1] * (1LL << 30) / inv_obj.compass_sens) + 380 inv_obj.init_compass_bias[1] * (1L << 16); 381 biasC[2] = 382 (long)(bias[2] * (1LL << 30) / inv_obj.compass_sens) + 383 inv_obj.init_compass_bias[2] * (1L << 16); 384 385 if (inv_dmpkey_supported(KEY_CPASS_BIAS_X)) { 386 unsigned char reg[4]; 387 long biasB[3]; 388 signed char *orC = mldlCfg->pdata->compass.orientation; 389 390 // Now transform to body frame 391 biasB[0] = biasC[0] * orC[0] + biasC[1] * orC[1] + biasC[2] * orC[2]; 392 biasB[1] = biasC[0] * orC[3] + biasC[1] * orC[4] + biasC[2] * orC[5]; 393 biasB[2] = biasC[0] * orC[6] + biasC[1] * orC[7] + biasC[2] * orC[8]; 394 395 result = 396 inv_set_mpu_memory(KEY_CPASS_BIAS_X, 4, 397 inv_int32_to_big8(biasB[0], reg)); 398 result = 399 inv_set_mpu_memory(KEY_CPASS_BIAS_Y, 4, 400 inv_int32_to_big8(biasB[1], reg)); 401 result = 402 inv_set_mpu_memory(KEY_CPASS_BIAS_Z, 4, 403 inv_int32_to_big8(biasB[2], reg)); 404 } 405 return result; 406 } 407 408 /** 409 * @brief Write a single register on the compass slave device, regardless 410 * of the bus it is connected to and the MPU's configuration. 411 * @param reg 412 * the register to write to on the slave compass device. 413 * @param val 414 * the value to write. 415 * @return INV_SUCCESS = 0 if successful. A non-zero error code otherwise. 416 */ 417 inv_error_t inv_compass_write_reg(unsigned char reg, unsigned char val) 418 { 419 struct ext_slave_config config; 420 unsigned char data[2]; 421 inv_error_t result; 422 423 data[0] = reg; 424 data[1] = val; 425 426 config.key = MPU_SLAVE_WRITE_REGISTERS; 427 config.len = 2; 428 config.apply = TRUE; 429 config.data = data; 430 431 result = inv_mpu_config_compass(inv_get_dl_config(), 432 inv_get_serial_handle(), 433 inv_get_serial_handle(), &config); 434 if (result) { 435 LOG_RESULT_LOCATION(result); 436 return result; 437 } 438 return result; 439 } 440 441 /** 442 * @brief Read values from the compass slave device registers, regardless 443 * of the bus it is connected to and the MPU's configuration. 444 * @param reg 445 * the register to read from on the slave compass device. 446 * @param val 447 * a buffer of 3 elements to store the values read from the 448 * compass device. 449 * @return INV_SUCCESS = 0 if successful. A non-zero error code otherwise. 450 */ 451 inv_error_t inv_compass_read_reg(unsigned char reg, unsigned char *val) 452 { 453 struct ext_slave_config config; 454 unsigned char data[2]; 455 inv_error_t result; 456 457 data[0] = reg; 458 459 config.key = MPU_SLAVE_READ_REGISTERS; 460 config.len = 2; 461 config.apply = TRUE; 462 config.data = data; 463 464 result = inv_mpu_get_compass_config(inv_get_dl_config(), 465 inv_get_serial_handle(), 466 inv_get_serial_handle(), &config); 467 if (result) { 468 LOG_RESULT_LOCATION(result); 469 return result; 470 } 471 *val = data[1]; 472 return result; 473 } 474 475 /** 476 * @brief Read values from the compass slave device scale registers, regardless 477 * of the bus it is connected to and the MPU's configuration. 478 * @param reg 479 * the register to read from on the slave compass device. 480 * @param val 481 * a buffer of 3 elements to store the values read from the 482 * compass device. 483 * @return INV_SUCCESS = 0 if successful. A non-zero error code otherwise. 484 */ 485 inv_error_t inv_compass_read_scale(long *val) 486 { 487 struct ext_slave_config config; 488 unsigned char data[3]; 489 inv_error_t result; 490 491 config.key = MPU_SLAVE_READ_SCALE; 492 config.len = 3; 493 config.apply = TRUE; 494 config.data = data; 495 496 result = inv_mpu_get_compass_config(inv_get_dl_config(), 497 inv_get_serial_handle(), 498 inv_get_serial_handle(), &config); 499 if (result) { 500 LOG_RESULT_LOCATION(result); 501 return result; 502 } 503 val[0] = ((data[0] - 128) << 22) + (1L << 30); 504 val[1] = ((data[1] - 128) << 22) + (1L << 30); 505 val[2] = ((data[2] - 128) << 22) + (1L << 30); 506 return result; 507 } 508 509 inv_error_t inv_set_compass_offset(void) 510 { 511 struct ext_slave_config config; 512 unsigned char data[3]; 513 inv_error_t result; 514 515 config.key = MPU_SLAVE_OFFSET_VALS; 516 config.len = 3; 517 config.apply = TRUE; 518 config.data = data; 519 520 if(inv_obj.flags[INV_COMPASS_OFFSET_VALID]) { 521 /* push stored values */ 522 data[0] = (char)inv_obj.compass_offsets[0]; 523 data[1] = (char)inv_obj.compass_offsets[1]; 524 data[2] = (char)inv_obj.compass_offsets[2]; 525 MPL_LOGI("push compass offsets %hhd, %hhd, %hhd", data[0], data[1], data[2]); 526 result = inv_mpu_config_compass(inv_get_dl_config(), 527 inv_get_serial_handle(), 528 inv_get_serial_handle(), &config); 529 } else { 530 /* compute new values and store them */ 531 result = inv_mpu_get_compass_config(inv_get_dl_config(), 532 inv_get_serial_handle(), 533 inv_get_serial_handle(), &config); 534 MPL_LOGI("pulled compass offsets %hhd %hhd %hhd", data[0], data[1], data[2]); 535 if(result == INV_SUCCESS) { 536 inv_obj.flags[INV_COMPASS_OFFSET_VALID] = 1; 537 inv_obj.compass_offsets[0] = data[0]; 538 inv_obj.compass_offsets[1] = data[1]; 539 inv_obj.compass_offsets[2] = data[2]; 540 } 541 } 542 543 if (result) { 544 LOG_RESULT_LOCATION(result); 545 return result; 546 } 547 548 return result; 549 } 550 551 inv_error_t inv_compass_check_range(void) 552 { 553 struct ext_slave_config config; 554 unsigned char data[3]; 555 inv_error_t result; 556 557 config.key = MPU_SLAVE_RANGE_CHECK; 558 config.len = 3; 559 config.apply = TRUE; 560 config.data = data; 561 562 result = inv_mpu_get_compass_config(inv_get_dl_config(), 563 inv_get_serial_handle(), 564 inv_get_serial_handle(), &config); 565 if (result) { 566 LOG_RESULT_LOCATION(result); 567 return result; 568 } 569 570 if(data[0] || data[1] || data[2]) { 571 /* some value clipped */ 572 return INV_ERROR_COMPASS_DATA_ERROR; 573 } 574 return INV_SUCCESS; 575 } 576 577 /** 578 * @} 579 */ 580