| /* |
| $License: |
| Copyright 2011 InvenSense, Inc. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| $ |
| */ |
| /******************************************************************************* |
| * |
| * $Id: compass.c 5641 2011-06-14 02:10:02Z mcaramello $ |
| * |
| *******************************************************************************/ |
| |
| /** |
| * @defgroup COMPASSDL |
| * @brief Motion Library - Compass Driver Layer. |
| * Provides the interface to setup and handle an compass |
| * connected to either the primary or the seconday I2C interface |
| * of the gyroscope. |
| * |
| * @{ |
| * @file compass.c |
| * @brief Compass setup and handling methods. |
| */ |
| |
| /* ------------------ */ |
| /* - Include Files. - */ |
| /* ------------------ */ |
| |
| #include <string.h> |
| #include <stdlib.h> |
| #include "compass.h" |
| |
| #include "ml.h" |
| #include "mlinclude.h" |
| #include "dmpKey.h" |
| #include "mlFIFO.h" |
| #include "mldl.h" |
| #include "mldl_cfg.h" |
| #include "mlMathFunc.h" |
| #include "mlsl.h" |
| #include "mlos.h" |
| |
| #include "log.h" |
| #undef MPL_LOG_TAG |
| #define MPL_LOG_TAG "MPL-compass" |
| |
| #define COMPASS_DEBUG 0 |
| |
| /* --------------------- */ |
| /* - Global Variables. - */ |
| /* --------------------- */ |
| |
| /* --------------------- */ |
| /* - Static Variables. - */ |
| /* --------------------- */ |
| |
| /* --------------- */ |
| /* - Prototypes. - */ |
| /* --------------- */ |
| |
| /* -------------- */ |
| /* - Externs. - */ |
| /* -------------- */ |
| |
| /* -------------- */ |
| /* - Functions. - */ |
| /* -------------- */ |
| |
| static float square(float data) |
| { |
| return data * data; |
| } |
| |
| static void adaptive_filter_init(struct yas_adaptive_filter *adap_filter, int len, float noise) |
| { |
| int i; |
| |
| adap_filter->num = 0; |
| adap_filter->index = 0; |
| adap_filter->noise = noise; |
| adap_filter->len = len; |
| |
| for (i = 0; i < adap_filter->len; ++i) { |
| adap_filter->sequence[i] = 0; |
| } |
| } |
| |
| static int cmpfloat(const void *p1, const void *p2) |
| { |
| return *(float*)p1 - *(float*)p2; |
| } |
| |
| |
| static float adaptive_filter_filter(struct yas_adaptive_filter *adap_filter, float in) |
| { |
| float avg, sum, median, sorted[YAS_DEFAULT_FILTER_LEN]; |
| int i; |
| |
| if (adap_filter->len <= 1) { |
| return in; |
| } |
| if (adap_filter->num < adap_filter->len) { |
| adap_filter->sequence[adap_filter->index++] = in; |
| adap_filter->num++; |
| return in; |
| } |
| if (adap_filter->len <= adap_filter->index) { |
| adap_filter->index = 0; |
| } |
| adap_filter->sequence[adap_filter->index++] = in; |
| |
| avg = 0; |
| for (i = 0; i < adap_filter->len; i++) { |
| avg += adap_filter->sequence[i]; |
| } |
| avg /= adap_filter->len; |
| |
| memcpy(sorted, adap_filter->sequence, adap_filter->len * sizeof(float)); |
| qsort(&sorted, adap_filter->len, sizeof(float), cmpfloat); |
| median = sorted[adap_filter->len/2]; |
| |
| sum = 0; |
| for (i = 0; i < adap_filter->len; i++) { |
| sum += square(avg - adap_filter->sequence[i]); |
| } |
| sum /= adap_filter->len; |
| |
| if (sum <= adap_filter->noise) { |
| return median; |
| } |
| |
| return ((in - avg) * (sum - adap_filter->noise) / sum + avg); |
| } |
| |
| static void thresh_filter_init(struct yas_thresh_filter *thresh_filter, float threshold) |
| { |
| thresh_filter->threshold = threshold; |
| thresh_filter->last = 0; |
| } |
| |
| static float thresh_filter_filter(struct yas_thresh_filter *thresh_filter, float in) |
| { |
| if (in < thresh_filter->last - thresh_filter->threshold |
| || thresh_filter->last + thresh_filter->threshold < in) { |
| thresh_filter->last = in; |
| return in; |
| } |
| else { |
| return thresh_filter->last; |
| } |
| } |
| |
| static int init(yas_filter_handle_t *t) |
| { |
| float noise[] = { |
| YAS_DEFAULT_FILTER_NOISE, |
| YAS_DEFAULT_FILTER_NOISE, |
| YAS_DEFAULT_FILTER_NOISE, |
| }; |
| int i; |
| |
| if (t == NULL) { |
| return -1; |
| } |
| |
| for (i = 0; i < 3; i++) { |
| adaptive_filter_init(&t->adap_filter[i], YAS_DEFAULT_FILTER_LEN, noise[i]); |
| thresh_filter_init(&t->thresh_filter[i], YAS_DEFAULT_FILTER_THRESH); |
| } |
| |
| return 0; |
| } |
| |
| static int update(yas_filter_handle_t *t, float *input, float *output) |
| { |
| int i; |
| |
| if (t == NULL || input == NULL || output == NULL) { |
| return -1; |
| } |
| |
| for (i = 0; i < 3; i++) { |
| output[i] = adaptive_filter_filter(&t->adap_filter[i], input[i]); |
| output[i] = thresh_filter_filter(&t->thresh_filter[i], output[i]); |
| } |
| |
| return 0; |
| } |
| |
| int yas_filter_init(yas_filter_if_s *f) |
| { |
| if (f == NULL) { |
| return -1; |
| } |
| f->init = init; |
| f->update = update; |
| |
| return 0; |
| } |
| |
| /** |
| * @brief Used to determine if a compass is |
| * configured and used by the MPL. |
| * @return INV_SUCCESS if the compass is present. |
| */ |
| unsigned char inv_compass_present(void) |
| { |
| INVENSENSE_FUNC_START; |
| struct mldl_cfg *mldl_cfg = inv_get_dl_config(); |
| if (NULL != mldl_cfg->compass && |
| NULL != mldl_cfg->compass->resume && |
| mldl_cfg->requested_sensors & INV_THREE_AXIS_COMPASS) |
| return TRUE; |
| else |
| return FALSE; |
| } |
| |
| /** |
| * @brief Query the compass slave address. |
| * @return The 7-bit compass slave address. |
| */ |
| unsigned char inv_get_compass_slave_addr(void) |
| { |
| INVENSENSE_FUNC_START; |
| struct mldl_cfg *mldl_cfg = inv_get_dl_config(); |
| if (NULL != mldl_cfg->pdata) |
| return mldl_cfg->pdata->compass.address; |
| else |
| return 0; |
| } |
| |
| /** |
| * @brief Get the ID of the compass in use. |
| * @return ID of the compass in use. |
| */ |
| unsigned short inv_get_compass_id(void) |
| { |
| INVENSENSE_FUNC_START; |
| struct mldl_cfg *mldl_cfg = inv_get_dl_config(); |
| if (NULL != mldl_cfg->compass) { |
| return mldl_cfg->compass->id; |
| } |
| return ID_INVALID; |
| } |
| |
| /** |
| * @brief Get a sample of compass data from the device. |
| * @param data |
| * the buffer to store the compass raw data for |
| * X, Y, and Z axes. |
| * @return INV_SUCCESS or a non-zero error code. |
| */ |
| inv_error_t inv_get_compass_data(long *data) |
| { |
| inv_error_t result; |
| int ii; |
| struct mldl_cfg *mldl_cfg = inv_get_dl_config(); |
| |
| unsigned char *tmp = inv_obj.compass_raw_data; |
| |
| if (mldl_cfg->compass->read_len > sizeof(inv_obj.compass_raw_data)) { |
| LOG_RESULT_LOCATION(INV_ERROR_INVALID_CONFIGURATION); |
| return INV_ERROR_INVALID_CONFIGURATION; |
| } |
| |
| if (mldl_cfg->pdata->compass.bus == EXT_SLAVE_BUS_PRIMARY || |
| !(mldl_cfg->requested_sensors & INV_DMP_PROCESSOR)) { |
| /*--- read the compass sensor data. |
| The compass read function may return an INV_ERROR_COMPASS_* errors |
| when the data is not ready (read/refresh frequency mismatch) or |
| the internal data sampling timing of the device was not respected. |
| Returning the error code will make the sensor fusion supervisor |
| ignore this compass data sample. ---*/ |
| result = (inv_error_t) inv_mpu_read_compass(mldl_cfg, |
| inv_get_serial_handle(), |
| inv_get_serial_handle(), |
| tmp); |
| if (result) { |
| if (COMPASS_DEBUG) { |
| MPL_LOGV("inv_mpu_read_compass returned %d\n", result); |
| } |
| return result; |
| } |
| for (ii = 0; ii < 3; ii++) { |
| if (EXT_SLAVE_BIG_ENDIAN == mldl_cfg->compass->endian) |
| data[ii] = |
| ((long)((signed char)tmp[2 * ii]) << 8) + tmp[2 * ii + 1]; |
| else |
| data[ii] = |
| ((long)((signed char)tmp[2 * ii + 1]) << 8) + tmp[2 * ii]; |
| } |
| |
| inv_obj.compass_overunder = (int)tmp[6]; |
| |
| } else { |
| #if defined CONFIG_MPU_SENSORS_MPU6050A2 || \ |
| defined CONFIG_MPU_SENSORS_MPU6050B1 |
| result = inv_get_external_sensor_data(data, 3); |
| if (result) { |
| LOG_RESULT_LOCATION(result); |
| return result; |
| } |
| #if defined CONFIG_MPU_SENSORS_MPU6050A2 |
| { |
| static unsigned char first = TRUE; |
| // one-off write to AKM |
| if (first) { |
| unsigned char regs[] = { |
| // beginning Mantis register for one-off slave R/W |
| MPUREG_I2C_SLV4_ADDR, |
| // the slave to write to |
| mldl_cfg->pdata->compass.address, |
| // the register to write to |
| /*mldl_cfg->compass->trigger->reg */ 0x0A, |
| // the value to write |
| /*mldl_cfg->compass->trigger->value */ 0x01, |
| // enable the write |
| 0xC0 |
| }; |
| result = |
| inv_serial_write(inv_get_serial_handle(), mldl_cfg->addr, |
| ARRAY_SIZE(regs), regs); |
| first = FALSE; |
| } else { |
| unsigned char regs[] = { |
| MPUREG_I2C_SLV4_CTRL, |
| 0xC0 |
| }; |
| result = |
| inv_serial_write(inv_get_serial_handle(), mldl_cfg->addr, |
| ARRAY_SIZE(regs), regs); |
| } |
| } |
| #endif |
| #else |
| return INV_ERROR_INVALID_CONFIGURATION; |
| #endif // CONFIG_MPU_SENSORS_xxxx |
| } |
| data[0] = inv_q30_mult(data[0], inv_obj.compass_asa[0]); |
| data[1] = inv_q30_mult(data[1], inv_obj.compass_asa[1]); |
| data[2] = inv_q30_mult(data[2], inv_obj.compass_asa[2]); |
| |
| return INV_SUCCESS; |
| } |
| |
| /** |
| * @brief Sets the compass bias. |
| * @param bias |
| * Compass bias, length 3. Scale is micro Tesla's * 2^16. |
| * Frame is mount frame which may be different from body frame. |
| * @return INV_SUCCESS = 0 if successful. A non-zero error code otherwise. |
| */ |
| inv_error_t inv_set_compass_bias(long *bias) |
| { |
| inv_error_t result = INV_SUCCESS; |
| long biasC[3]; |
| struct mldl_cfg *mldlCfg = inv_get_dl_config(); |
| |
| inv_obj.compass_bias[0] = bias[0]; |
| inv_obj.compass_bias[1] = bias[1]; |
| inv_obj.compass_bias[2] = bias[2]; |
| |
| // Find Bias in units of the raw data scaled by 2^16 in chip mounting frame |
| biasC[0] = |
| (long)(bias[0] * (1LL << 30) / inv_obj.compass_sens) + |
| inv_obj.init_compass_bias[0] * (1L << 16); |
| biasC[1] = |
| (long)(bias[1] * (1LL << 30) / inv_obj.compass_sens) + |
| inv_obj.init_compass_bias[1] * (1L << 16); |
| biasC[2] = |
| (long)(bias[2] * (1LL << 30) / inv_obj.compass_sens) + |
| inv_obj.init_compass_bias[2] * (1L << 16); |
| |
| if (inv_dmpkey_supported(KEY_CPASS_BIAS_X)) { |
| unsigned char reg[4]; |
| long biasB[3]; |
| signed char *orC = mldlCfg->pdata->compass.orientation; |
| |
| // Now transform to body frame |
| biasB[0] = biasC[0] * orC[0] + biasC[1] * orC[1] + biasC[2] * orC[2]; |
| biasB[1] = biasC[0] * orC[3] + biasC[1] * orC[4] + biasC[2] * orC[5]; |
| biasB[2] = biasC[0] * orC[6] + biasC[1] * orC[7] + biasC[2] * orC[8]; |
| |
| result = |
| inv_set_mpu_memory(KEY_CPASS_BIAS_X, 4, |
| inv_int32_to_big8(biasB[0], reg)); |
| result = |
| inv_set_mpu_memory(KEY_CPASS_BIAS_Y, 4, |
| inv_int32_to_big8(biasB[1], reg)); |
| result = |
| inv_set_mpu_memory(KEY_CPASS_BIAS_Z, 4, |
| inv_int32_to_big8(biasB[2], reg)); |
| } |
| return result; |
| } |
| |
| /** |
| * @brief Write a single register on the compass slave device, regardless |
| * of the bus it is connected to and the MPU's configuration. |
| * @param reg |
| * the register to write to on the slave compass device. |
| * @param val |
| * the value to write. |
| * @return INV_SUCCESS = 0 if successful. A non-zero error code otherwise. |
| */ |
| inv_error_t inv_compass_write_reg(unsigned char reg, unsigned char val) |
| { |
| struct ext_slave_config config; |
| unsigned char data[2]; |
| inv_error_t result; |
| |
| data[0] = reg; |
| data[1] = val; |
| |
| config.key = MPU_SLAVE_WRITE_REGISTERS; |
| config.len = 2; |
| config.apply = TRUE; |
| config.data = data; |
| |
| result = inv_mpu_config_compass(inv_get_dl_config(), |
| inv_get_serial_handle(), |
| inv_get_serial_handle(), &config); |
| if (result) { |
| LOG_RESULT_LOCATION(result); |
| return result; |
| } |
| return result; |
| } |
| |
| /** |
| * @brief Read values from the compass slave device registers, regardless |
| * of the bus it is connected to and the MPU's configuration. |
| * @param reg |
| * the register to read from on the slave compass device. |
| * @param val |
| * a buffer of 3 elements to store the values read from the |
| * compass device. |
| * @return INV_SUCCESS = 0 if successful. A non-zero error code otherwise. |
| */ |
| inv_error_t inv_compass_read_reg(unsigned char reg, unsigned char *val) |
| { |
| struct ext_slave_config config; |
| unsigned char data[2]; |
| inv_error_t result; |
| |
| data[0] = reg; |
| |
| config.key = MPU_SLAVE_READ_REGISTERS; |
| config.len = 2; |
| config.apply = TRUE; |
| config.data = data; |
| |
| result = inv_mpu_get_compass_config(inv_get_dl_config(), |
| inv_get_serial_handle(), |
| inv_get_serial_handle(), &config); |
| if (result) { |
| LOG_RESULT_LOCATION(result); |
| return result; |
| } |
| *val = data[1]; |
| return result; |
| } |
| |
| /** |
| * @brief Read values from the compass slave device scale registers, regardless |
| * of the bus it is connected to and the MPU's configuration. |
| * @param reg |
| * the register to read from on the slave compass device. |
| * @param val |
| * a buffer of 3 elements to store the values read from the |
| * compass device. |
| * @return INV_SUCCESS = 0 if successful. A non-zero error code otherwise. |
| */ |
| inv_error_t inv_compass_read_scale(long *val) |
| { |
| struct ext_slave_config config; |
| unsigned char data[3]; |
| inv_error_t result; |
| |
| config.key = MPU_SLAVE_READ_SCALE; |
| config.len = 3; |
| config.apply = TRUE; |
| config.data = data; |
| |
| result = inv_mpu_get_compass_config(inv_get_dl_config(), |
| inv_get_serial_handle(), |
| inv_get_serial_handle(), &config); |
| if (result) { |
| LOG_RESULT_LOCATION(result); |
| return result; |
| } |
| val[0] = ((data[0] - 128) << 22) + (1L << 30); |
| val[1] = ((data[1] - 128) << 22) + (1L << 30); |
| val[2] = ((data[2] - 128) << 22) + (1L << 30); |
| return result; |
| } |
| |
| inv_error_t inv_set_compass_offset(void) |
| { |
| struct ext_slave_config config; |
| unsigned char data[3]; |
| inv_error_t result; |
| |
| config.key = MPU_SLAVE_OFFSET_VALS; |
| config.len = 3; |
| config.apply = TRUE; |
| config.data = data; |
| |
| if(inv_obj.flags[INV_COMPASS_OFFSET_VALID]) { |
| /* push stored values */ |
| data[0] = (char)inv_obj.compass_offsets[0]; |
| data[1] = (char)inv_obj.compass_offsets[1]; |
| data[2] = (char)inv_obj.compass_offsets[2]; |
| MPL_LOGI("push compass offsets %hhd, %hhd, %hhd", data[0], data[1], data[2]); |
| result = inv_mpu_config_compass(inv_get_dl_config(), |
| inv_get_serial_handle(), |
| inv_get_serial_handle(), &config); |
| } else { |
| /* compute new values and store them */ |
| result = inv_mpu_get_compass_config(inv_get_dl_config(), |
| inv_get_serial_handle(), |
| inv_get_serial_handle(), &config); |
| MPL_LOGI("pulled compass offsets %hhd %hhd %hhd", data[0], data[1], data[2]); |
| if(result == INV_SUCCESS) { |
| inv_obj.flags[INV_COMPASS_OFFSET_VALID] = 1; |
| inv_obj.compass_offsets[0] = data[0]; |
| inv_obj.compass_offsets[1] = data[1]; |
| inv_obj.compass_offsets[2] = data[2]; |
| } |
| } |
| |
| if (result) { |
| LOG_RESULT_LOCATION(result); |
| return result; |
| } |
| |
| return result; |
| } |
| |
| inv_error_t inv_compass_check_range(void) |
| { |
| struct ext_slave_config config; |
| unsigned char data[3]; |
| inv_error_t result; |
| |
| config.key = MPU_SLAVE_RANGE_CHECK; |
| config.len = 3; |
| config.apply = TRUE; |
| config.data = data; |
| |
| result = inv_mpu_get_compass_config(inv_get_dl_config(), |
| inv_get_serial_handle(), |
| inv_get_serial_handle(), &config); |
| if (result) { |
| LOG_RESULT_LOCATION(result); |
| return result; |
| } |
| |
| if(data[0] || data[1] || data[2]) { |
| /* some value clipped */ |
| return INV_ERROR_COMPASS_DATA_ERROR; |
| } |
| return INV_SUCCESS; |
| } |
| |
| /** |
| * @} |
| */ |