/* $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: mputest.c 5637 2011-06-14 01:13:53Z mcaramello $ * *****************************************************************************/ /* * MPU Self Test functions * Version 2.4 * May 13th, 2011 */ /** * @defgroup MPU_SELF_TEST * @brief MPU Self Test functions * * These functions provide an in-site test of the MPU 3xxx chips. The main * entry point is the inv_mpu_test function. * This runs the tests (as described in the accompanying documentation) and * writes a configuration file containing initial calibration data. * inv_mpu_test returns INV_SUCCESS if the chip passes the tests. * Otherwise, an error code is returned. * The functions in this file rely on MLSL and MLOS: refer to the MPL * documentation for more information regarding the system interface * files. * * @{ * @file mputest.c * @brief MPU Self Test routines for assessing gyro sensor status * after surface mount has happened on the target host platform. */ #include <stdio.h> #include <time.h> #include <string.h> #include <math.h> #include <stdlib.h> #ifdef LINUX #include <unistd.h> #endif #include "mpu.h" #include "mldl.h" #include "mldl_cfg.h" #include "accel.h" #include "mlFIFO.h" #include "slave.h" #include "ml.h" #include "ml_stored_data.h" #include "checksum.h" #include "mlsl.h" #include "mlos.h" #include "log.h" #undef MPL_LOG_TAG #define MPL_LOG_TAG "MPL-mpust" #ifdef __cplusplus extern "C" { #endif /* Defines */ #define VERBOSE_OUT 0 /*#define TRACK_IDS*/ /*--- Test parameters defaults. See set_test_parameters for more details ---*/ #define DEF_MPU_ADDR (0x68) /* I2C address of the mpu */ #if (USE_SENSE_PATH_TEST == 1) /* gyro full scale dps */ #define DEF_GYRO_FULLSCALE (2000) #else #define DEF_GYRO_FULLSCALE (250) #endif #define DEF_GYRO_SENS (32768.f / DEF_GYRO_FULLSCALE) /* gyro sensitivity LSB/dps */ #define DEF_PACKET_THRESH (75) /* 600 ms / 8ms / sample */ #define DEF_TOTAL_TIMING_TOL (.03f) /* 3% = 2 pkts + 1% proc tol. */ #define DEF_BIAS_THRESH (40 * DEF_GYRO_SENS) /* 40 dps in LSBs */ #define DEF_RMS_THRESH (0.4f * DEF_GYRO_SENS) /* 0.4 dps-rms in LSB-rms */ #define DEF_SP_SHIFT_THRESH_CUST (.05f) /* 5% */ #define DEF_TEST_TIME_PER_AXIS (600) /* ms of time spent collecting data for each axis, multiple of 600ms */ #define DEF_N_ACCEL_SAMPLES (20) /* num of accel samples to average from, if applic. */ #define ML_INIT_CAL_LEN (36) /* length in bytes of calibration data file */ /* Macros */ #define CHECK_TEST_ERROR(x) \ if (x) { \ MPL_LOGI("error %d @ %s|%d\n", x, __func__, __LINE__); \ return (-1); \ } #define SHORT_TO_TEMP_C(shrt) (((shrt+13200.f)/280.f)+35.f) #define USHORT_TO_CHARS(chr,shrt) (chr)[0]=(unsigned char)(shrt>>8); \ (chr)[1]=(unsigned char)(shrt); #define UINT_TO_CHARS(chr,ui) (chr)[0]=(unsigned char)(ui>>24); \ (chr)[1]=(unsigned char)(ui>>16); \ (chr)[2]=(unsigned char)(ui>>8); \ (chr)[3]=(unsigned char)(ui); #define FLOAT_TO_SHORT(f) ( \ (fabs(f-(short)f)>=0.5) ? ( \ ((short)f)+(f<0?(-1):(+1))) : \ ((short)f) \ ) #define CHARS_TO_SHORT(d) ((((short)(d)[0])<<8)+(d)[1]) #define CHARS_TO_SHORT_SWAPPED(d) ((((short)(d)[1])<<8)+(d)[0]) #define ACCEL_UNPACK(d) d[0], d[1], d[2], d[3], d[4], d[5] #define CHECK_NACKS(d) ( \ d[0]==0xff && d[1]==0xff && \ d[2]==0xff && d[3]==0xff && \ d[4]==0xff && d[5]==0xff \ ) /* Prototypes */ static inv_error_t test_get_data( void *mlsl_handle, struct mldl_cfg *mputestCfgPtr, short *vals); /* Types */ typedef struct { float gyro_sens; int gyro_fs; int packet_thresh; float total_timing_tol; int bias_thresh; float rms_threshSq; float sp_shift_thresh; unsigned int test_time_per_axis; unsigned short accel_samples; } tTestSetup; /* Global variables */ static unsigned char dataout[20]; static unsigned char dataStore[ML_INIT_CAL_LEN]; static tTestSetup test_setup = { DEF_GYRO_SENS, DEF_GYRO_FULLSCALE, DEF_PACKET_THRESH, DEF_TOTAL_TIMING_TOL, (int)DEF_BIAS_THRESH, DEF_RMS_THRESH * DEF_RMS_THRESH, DEF_SP_SHIFT_THRESH_CUST, DEF_TEST_TIME_PER_AXIS, DEF_N_ACCEL_SAMPLES }; static float adjGyroSens; static char a_name[3][2] = {"X", "Y", "Z"}; /* NOTE : modify get_slave_descr parameter below to reflect the DEFAULT accelerometer in use. The accelerometer in use can be modified at run-time using the inv_test_setup_accel API. NOTE : modify the expected z axis orientation (Z axis pointing upward or downward) */ signed char g_z_sign = +1; struct mldl_cfg *mputestCfgPtr = NULL; #ifndef LINUX /** * @internal * @brief usec precision sleep function. * @param number of micro seconds (us) to sleep for. */ static void usleep(unsigned long t) { unsigned long start = inv_get_tick_count(); while (inv_get_tick_count()-start < t / 1000); } #endif /** * @brief Modify the self test limits from their default values. * * @param slave_addr * the slave address the MPU device is setup to respond at. * The default is DEF_MPU_ADDR = 0x68. * @param sensitivity * the read sensitivity of the device in LSB/dps as it is trimmed. * NOTE : if using the self test as part of the MPL, the * sensitivity the different sensitivity trims are already * taken care of. * @param p_thresh * number of packets expected to be received in a 600 ms period. * Depends on the sampling frequency of choice (set by default to * 125 Hz) and low pass filter cut-off frequency selection (set * to 42 Hz). * The default is DEF_PACKET_THRESH = 75 packets. * @param total_time_tol * time skew tolerance, taking into account imprecision in turning * the FIFO on and off and the processor time imprecision (for * 1 GHz processor). * The default is DEF_TOTAL_TIMING_TOL = 3 %, about 2 packets. * @param bias_thresh * bias level threshold, the maximun acceptable no motion bias * for a production quality part. * The default is DEF_BIAS_THRESH = 40 dps. * @param rms_thresh * the limit standard deviation (=~ RMS) set to assess whether * the noise level on the part is acceptable. * The default is DEF_RMS_THRESH = 0.2 dps-rms. * @param sp_shift_thresh * the limit shift applicable to the Sense Path self test * calculation. */ void inv_set_test_parameters(unsigned int slave_addr, float sensitivity, int p_thresh, float total_time_tol, int bias_thresh, float rms_thresh, float sp_shift_thresh, unsigned short accel_samples) { mputestCfgPtr->addr = slave_addr; test_setup.gyro_sens = sensitivity; test_setup.gyro_fs = (int)(32768.f / sensitivity); test_setup.packet_thresh = p_thresh; test_setup.total_timing_tol = total_time_tol; test_setup.bias_thresh = bias_thresh; test_setup.rms_threshSq = rms_thresh * rms_thresh; test_setup.sp_shift_thresh = sp_shift_thresh; test_setup.accel_samples = accel_samples; } #define X (0) #define Y (1) #define Z (2) #ifdef CONFIG_MPU_SENSORS_MPU3050 /** * @brief Test the gyroscope sensor. * Implements the core logic of the MPU Self Test. * Produces the PASS/FAIL result. Loads the calculated gyro biases * and temperature datum into the corresponding pointers. * @param mlsl_handle * serial interface handle to allow serial communication with the * device, both gyro and accelerometer. * @param gyro_biases * output pointer to store the initial bias calculation provided * by the MPU Self Test. Requires 3 elements for gyro X, Y, * and Z. * @param temp_avg * output pointer to store the initial average temperature as * provided by the MPU Self Test. * @param perform_full_test * If 1: * calculates offset, drive frequency, and noise and compare it * against set thresholds. * Report also the final result using a bit-mask like error code * as explained in return value description. * When 0: * skip the noise and drive frequency calculation and pass/fail * assessment; simply calculates the gyro biases. * * @return 0 on success. * On error, the return value is a bitmask representing: * 0, 1, 2 Failures with PLLs on X, Y, Z gyros respectively * (decimal values will be 1, 2, 4 respectively). * 3, 4, 5 Excessive offset with X, Y, Z gyros respectively * (decimal values will be 8, 16, 32 respectively). * 6, 7, 8 Excessive noise with X, Y, Z gyros respectively * (decimal values will be 64, 128, 256 respectively). * 9 If any of the RMS noise values is zero, it could be * due to a non-functional gyro or FIFO/register failure. * (decimal value will be 512). * (decimal values will be 1024, 2048, 4096 respectively). */ int inv_test_gyro_3050(void *mlsl_handle, short gyro_biases[3], short *temp_avg, uint_fast8_t perform_full_test) { int retVal = 0; inv_error_t result; int total_count = 0; int total_count_axis[3] = {0, 0, 0}; int packet_count; short x[DEF_TEST_TIME_PER_AXIS / 8 * 4] = {0}; short y[DEF_TEST_TIME_PER_AXIS / 8 * 4] = {0}; short z[DEF_TEST_TIME_PER_AXIS / 8 * 4] = {0}; unsigned char regs[7]; int temperature; float Avg[3]; float RMS[3]; int i, j, tmp; char tmpStr[200]; temperature = 0; /* sample rate = 8ms */ result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_SMPLRT_DIV, 0x07); CHECK_TEST_ERROR(result); regs[0] = 0x03; /* filter = 42Hz, analog_sample rate = 1 KHz */ switch (DEF_GYRO_FULLSCALE) { case 2000: regs[0] |= 0x18; break; case 1000: regs[0] |= 0x10; break; case 500: regs[0] |= 0x08; break; case 250: default: regs[0] |= 0x00; break; } result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_DLPF_FS_SYNC, regs[0]); CHECK_TEST_ERROR(result); result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_INT_CFG, 0x00); CHECK_TEST_ERROR(result); /* 1st, timing test */ for (j = 0; j < 3; j++) { MPL_LOGI("Collecting gyro data from %s gyro PLL\n", a_name[j]); /* turn on all gyros, use gyro X for clocking Set to Y and Z for 2nd and 3rd iteration */ result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_PWR_MGM, j + 1); CHECK_TEST_ERROR(result); /* wait for 2 ms after switching clock source */ usleep(2000); /* we will enable XYZ gyro in FIFO and nothing else */ result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_FIFO_EN2, 0x00); CHECK_TEST_ERROR(result); /* enable/reset FIFO */ result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_USER_CTRL, BIT_FIFO_EN | BIT_FIFO_RST); CHECK_TEST_ERROR(result); tmp = (int)(test_setup.test_time_per_axis / 600); while (tmp-- > 0) { /* enable XYZ gyro in FIFO and nothing else */ result = inv_serial_single_write(mlsl_handle, mputestCfgPtr->addr, MPUREG_FIFO_EN1, BIT_GYRO_XOUT | BIT_GYRO_YOUT | BIT_GYRO_ZOUT); CHECK_TEST_ERROR(result); /* wait for 600 ms for data */ usleep(600000); /* stop storing gyro in the FIFO */ result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_FIFO_EN1, 0x00); CHECK_TEST_ERROR(result); /* Getting number of bytes in FIFO */ result = inv_serial_read( mlsl_handle, mputestCfgPtr->addr, MPUREG_FIFO_COUNTH, 2, dataout); CHECK_TEST_ERROR(result); /* number of 6 B packets in the FIFO */ packet_count = CHARS_TO_SHORT(dataout) / 6; sprintf(tmpStr, "Packet Count: %d - ", packet_count); if ( abs(packet_count - test_setup.packet_thresh) <= /* Within +/- total_timing_tol % range */ test_setup.total_timing_tol * test_setup.packet_thresh) { for (i = 0; i < packet_count; i++) { /* getting FIFO data */ result = inv_serial_read_fifo(mlsl_handle, mputestCfgPtr->addr, 6, dataout); CHECK_TEST_ERROR(result); x[total_count + i] = CHARS_TO_SHORT(&dataout[0]); y[total_count + i] = CHARS_TO_SHORT(&dataout[2]); z[total_count + i] = CHARS_TO_SHORT(&dataout[4]); if (VERBOSE_OUT) { MPL_LOGI("Gyros %-4d : %+13d %+13d %+13d\n", total_count + i, x[total_count + i], y[total_count + i], z[total_count + i]); } } total_count += packet_count; total_count_axis[j] += packet_count; sprintf(tmpStr, "%sOK", tmpStr); } else { if (perform_full_test) retVal |= 1 << j; sprintf(tmpStr, "%sNOK - samples ignored", tmpStr); } MPL_LOGI("%s\n", tmpStr); } /* remove gyros from FIFO */ result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_FIFO_EN1, 0x00); CHECK_TEST_ERROR(result); /* Read Temperature */ result = inv_serial_read(mlsl_handle, mputestCfgPtr->addr, MPUREG_TEMP_OUT_H, 2, dataout); CHECK_TEST_ERROR(result); temperature += (short)CHARS_TO_SHORT(dataout); } MPL_LOGI("\n"); MPL_LOGI("Total %d samples\n", total_count); MPL_LOGI("\n"); /* 2nd, check bias from X and Y PLL clock source */ tmp = total_count != 0 ? total_count : 1; for (i = 0, Avg[X] = .0f, Avg[Y] = .0f, Avg[Z] = .0f; i < total_count; i++) { Avg[X] += 1.f * x[i] / tmp; Avg[Y] += 1.f * y[i] / tmp; Avg[Z] += 1.f * z[i] / tmp; } MPL_LOGI("bias : %+13.3f %+13.3f %+13.3f (LSB)\n", Avg[X], Avg[Y], Avg[Z]); if (VERBOSE_OUT) { MPL_LOGI(" : %+13.3f %+13.3f %+13.3f (dps)\n", Avg[X] / adjGyroSens, Avg[Y] / adjGyroSens, Avg[Z] / adjGyroSens); } if(perform_full_test) { for (j = 0; j < 3; j++) { if (fabs(Avg[j]) > test_setup.bias_thresh) { MPL_LOGI("%s-Gyro bias (%.0f) exceeded threshold " "(threshold = %d)\n", a_name[j], Avg[j], test_setup.bias_thresh); retVal |= 1 << (3+j); } } } /* 3rd, check RMS */ if (perform_full_test) { for (i = 0, RMS[X] = 0.f, RMS[Y] = 0.f, RMS[Z] = 0.f; i < total_count; i++) { RMS[X] += (x[i] - Avg[X]) * (x[i] - Avg[X]); RMS[Y] += (y[i] - Avg[Y]) * (y[i] - Avg[Y]); RMS[Z] += (z[i] - Avg[Z]) * (z[i] - Avg[Z]); } for (j = 0; j < 3; j++) { if (RMS[j] > test_setup.rms_threshSq * total_count) { MPL_LOGI("%s-Gyro RMS (%.2f) exceeded threshold " "(threshold = %.2f)\n", a_name[j], sqrt(RMS[j] / total_count), sqrt(test_setup.rms_threshSq)); retVal |= 1 << (6+j); } } MPL_LOGI("RMS : %+13.3f %+13.3f %+13.3f (LSB-rms)\n", sqrt(RMS[X] / total_count), sqrt(RMS[Y] / total_count), sqrt(RMS[Z] / total_count)); if (VERBOSE_OUT) { MPL_LOGI("RMS ^ 2 : %+13.3f %+13.3f %+13.3f\n", RMS[X] / total_count, RMS[Y] / total_count, RMS[Z] / total_count); } if (RMS[X] == 0 || RMS[Y] == 0 || RMS[Z] == 0) { /* If any of the RMS noise value returns zero, then we might have dead gyro or FIFO/register failure, the part is sleeping, or the part is not responsive */ retVal |= 1 << 9; } } /* 4th, temperature average */ temperature /= 3; if (VERBOSE_OUT) MPL_LOGI("Temperature : %+13.3f %13s %13s (deg. C)\n", SHORT_TO_TEMP_C(temperature), "", ""); /* load into final storage */ *temp_avg = (short)temperature; gyro_biases[X] = FLOAT_TO_SHORT(Avg[X]); gyro_biases[Y] = FLOAT_TO_SHORT(Avg[Y]); gyro_biases[Z] = FLOAT_TO_SHORT(Avg[Z]); return retVal; } #else /* CONFIG_MPU_SENSORS_MPU3050 */ /** * @brief Test the gyroscope sensor. * Implements the core logic of the MPU Self Test but does not provide * a PASS/FAIL output as in the MPU-3050 implementation. * @param mlsl_handle * serial interface handle to allow serial communication with the * device, both gyro and accelerometer. * @param gyro_biases * output pointer to store the initial bias calculation provided * by the MPU Self Test. Requires 3 elements for gyro X, Y, * and Z. * @param temp_avg * output pointer to store the initial average temperature as * provided by the MPU Self Test. * * @return 0 on success. * A non-zero error code on error. */ int inv_test_gyro_6050(void *mlsl_handle, short gyro_biases[3], short *temp_avg) { inv_error_t result; int total_count = 0; int total_count_axis[3] = {0, 0, 0}; int packet_count; short x[DEF_TEST_TIME_PER_AXIS / 8 * 4] = {0}; short y[DEF_TEST_TIME_PER_AXIS / 8 * 4] = {0}; short z[DEF_TEST_TIME_PER_AXIS / 8 * 4] = {0}; unsigned char regs[7]; int temperature = 0; float Avg[3]; int i, j, tmp; char tmpStr[200]; /* sample rate = 8ms */ result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_SMPLRT_DIV, 0x07); if (result) { LOG_RESULT_LOCATION(result); return result; } regs[0] = 0x03; /* filter = 42Hz, analog_sample rate = 1 KHz */ switch (DEF_GYRO_FULLSCALE) { case 2000: regs[0] |= 0x18; break; case 1000: regs[0] |= 0x10; break; case 500: regs[0] |= 0x08; break; case 250: default: regs[0] |= 0x00; break; } result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_CONFIG, regs[0]); if (result) { LOG_RESULT_LOCATION(result); return result; } result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_INT_ENABLE, 0x00); if (result) { LOG_RESULT_LOCATION(result); return result; } /* 1st, timing test */ for (j = 0; j < 3; j++) { MPL_LOGI("Collecting gyro data from %s gyro PLL\n", a_name[j]); /* turn on all gyros, use gyro X for clocking Set to Y and Z for 2nd and 3rd iteration */ #ifdef CONFIG_MPU_SENSORS_MPU6050A2 result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_PWR_MGMT_1, BITS_PWRSEL | (j + 1)); #else result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_PWR_MGMT_1, j + 1); #endif if (result) { LOG_RESULT_LOCATION(result); return result; } /* wait for 2 ms after switching clock source */ usleep(2000); /* enable/reset FIFO */ result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_USER_CTRL, BIT_FIFO_EN | BIT_FIFO_RST); if (result) { LOG_RESULT_LOCATION(result); return result; } tmp = (int)(test_setup.test_time_per_axis / 600); while (tmp-- > 0) { /* enable XYZ gyro in FIFO and nothing else */ result = inv_serial_single_write(mlsl_handle, mputestCfgPtr->addr, MPUREG_FIFO_EN, BIT_GYRO_XOUT | BIT_GYRO_YOUT | BIT_GYRO_ZOUT); if (result) { LOG_RESULT_LOCATION(result); return result; } /* wait for 600 ms for data */ usleep(600000); /* stop storing gyro in the FIFO */ result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_FIFO_EN, 0x00); if (result) { LOG_RESULT_LOCATION(result); return result; } /* Getting number of bytes in FIFO */ result = inv_serial_read( mlsl_handle, mputestCfgPtr->addr, MPUREG_FIFO_COUNTH, 2, dataout); if (result) { LOG_RESULT_LOCATION(result); return result; } /* number of 6 B packets in the FIFO */ packet_count = CHARS_TO_SHORT(dataout) / 6; sprintf(tmpStr, "Packet Count: %d - ", packet_count); if (abs(packet_count - test_setup.packet_thresh) <= /* Within +/- total_timing_tol % range */ test_setup.total_timing_tol * test_setup.packet_thresh) { for (i = 0; i < packet_count; i++) { /* getting FIFO data */ result = inv_serial_read_fifo(mlsl_handle, mputestCfgPtr->addr, 6, dataout); if (result) { LOG_RESULT_LOCATION(result); return result; } x[total_count + i] = CHARS_TO_SHORT(&dataout[0]); y[total_count + i] = CHARS_TO_SHORT(&dataout[2]); z[total_count + i] = CHARS_TO_SHORT(&dataout[4]); if (VERBOSE_OUT) { MPL_LOGI("Gyros %-4d : %+13d %+13d %+13d\n", total_count + i, x[total_count + i], y[total_count + i], z[total_count + i]); } } total_count += packet_count; total_count_axis[j] += packet_count; sprintf(tmpStr, "%sOK", tmpStr); } else { sprintf(tmpStr, "%sNOK - samples ignored", tmpStr); } MPL_LOGI("%s\n", tmpStr); } /* remove gyros from FIFO */ result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_FIFO_EN, 0x00); if (result) { LOG_RESULT_LOCATION(result); return result; } /* Read Temperature */ result = inv_serial_read(mlsl_handle, mputestCfgPtr->addr, MPUREG_TEMP_OUT_H, 2, dataout); if (result) { LOG_RESULT_LOCATION(result); return result; } temperature += (short)CHARS_TO_SHORT(dataout); } MPL_LOGI("\n"); MPL_LOGI("Total %d samples\n", total_count); MPL_LOGI("\n"); /* 2nd, check bias from X and Y PLL clock source */ tmp = total_count != 0 ? total_count : 1; for (i = 0, Avg[X] = .0f, Avg[Y] = .0f, Avg[Z] = .0f; i < total_count; i++) { Avg[X] += 1.f * x[i] / tmp; Avg[Y] += 1.f * y[i] / tmp; Avg[Z] += 1.f * z[i] / tmp; } MPL_LOGI("bias : %+13.3f %+13.3f %+13.3f (LSB)\n", Avg[X], Avg[Y], Avg[Z]); if (VERBOSE_OUT) { MPL_LOGI(" : %+13.3f %+13.3f %+13.3f (dps)\n", Avg[X] / adjGyroSens, Avg[Y] / adjGyroSens, Avg[Z] / adjGyroSens); } temperature /= 3; if (VERBOSE_OUT) MPL_LOGI("Temperature : %+13.3f %13s %13s (deg. C)\n", SHORT_TO_TEMP_C(temperature), "", ""); /* load into final storage */ *temp_avg = (short)temperature; gyro_biases[X] = FLOAT_TO_SHORT(Avg[X]); gyro_biases[Y] = FLOAT_TO_SHORT(Avg[Y]); gyro_biases[Z] = FLOAT_TO_SHORT(Avg[Z]); return INV_SUCCESS; } #endif /* CONFIG_MPU_SENSORS_MPU3050 */ #ifdef TRACK_IDS /** * @internal * @brief Retrieve the unique MPU device identifier from the internal OTP * bank 0 memory. * @param mlsl_handle * serial interface handle to allow serial communication with the * device, both gyro and accelerometer. * @return 0 on success, a non-zero error code from the serial layer on error. */ static inv_error_t test_get_mpu_id(void *mlsl_handle) { inv_error_t result; unsigned char otp0[8]; result = inv_serial_read_mem(mlsl_handle, mputestCfgPtr->addr, (BIT_PRFTCH_EN | BIT_CFG_USER_BANK | MPU_MEM_OTP_BANK_0) << 8 | 0x00, 6, otp0); if (result) goto close; MPL_LOGI("\n"); MPL_LOGI("DIE_ID : %06X\n", ((int)otp0[1] << 8 | otp0[0]) & 0x1fff); MPL_LOGI("WAFER_ID : %06X\n", (((int)otp0[2] << 8 | otp0[1]) & 0x03ff ) >> 5); MPL_LOGI("A_LOT_ID : %06X\n", ( ((int)otp0[4] << 16 | (int)otp0[3] << 8 | otp0[2]) & 0x3ffff) >> 2); MPL_LOGI("W_LOT_ID : %06X\n", ( ((int)otp0[5] << 8 | otp0[4]) & 0x3fff) >> 2); MPL_LOGI("WP_ID : %06X\n", ( ((int)otp0[6] << 8 | otp0[5]) & 0x03ff) >> 7); MPL_LOGI("REV_ID : %06X\n", otp0[6] >> 2); MPL_LOGI("\n"); close: result = inv_serial_single_write(mlsl_handle, mputestCfgPtr->addr, MPUREG_BANK_SEL, 0x00); return result; } #endif /* TRACK_IDS */ /** * @brief If requested via inv_test_setup_accel(), test the accelerometer biases * and calculate the necessary bias correction. * @param mlsl_handle * serial interface handle to allow serial communication with the * device, both gyro and accelerometer. * @param bias * output pointer to store the initial bias calculation provided * by the MPU Self Test. Requires 3 elements to store accel X, Y, * and Z axis bias. * @param perform_full_test * If 1: * calculates offsets and noise and compare it against set * thresholds. The final exist status will reflect if any of the * value is outside of the expected range. * When 0; * skip the noise calculation and pass/fail assessment; simply * calculates the accel biases. * * @return 0 on success. A non-zero error code on error. */ int inv_test_accel(void *mlsl_handle, short *bias, long gravity, uint_fast8_t perform_full_test) { int i; short *p_vals; float x = 0.f, y = 0.f, z = 0.f, zg = 0.f; float RMS[3]; float accelRmsThresh = 1000000.f; /* enourmous so that the test always passes - future deployment */ unsigned short res; unsigned long orig_requested_sensors; struct mpu_platform_data *mputestPData = mputestCfgPtr->pdata; p_vals = (short*)inv_malloc(sizeof(short) * 3 * test_setup.accel_samples); /* load the slave descr from the getter */ if (mputestPData->accel.get_slave_descr == NULL) { MPL_LOGI("\n"); MPL_LOGI("No accelerometer configured\n"); return 0; } if (mputestCfgPtr->accel == NULL) { MPL_LOGI("\n"); MPL_LOGI("No accelerometer configured\n"); return 0; } /* resume the accel */ orig_requested_sensors = mputestCfgPtr->requested_sensors; mputestCfgPtr->requested_sensors = INV_THREE_AXIS_ACCEL | INV_THREE_AXIS_GYRO; res = inv_mpu_resume(mputestCfgPtr, mlsl_handle, NULL, NULL, NULL, mputestCfgPtr->requested_sensors); if(res != INV_SUCCESS) goto accel_error; /* wait at least a sample cycle for the accel data to be retrieved by MPU */ inv_sleep(inv_mpu_get_sampling_period_us(mputestCfgPtr) / 1000); /* collect the samples */ for(i = 0; i < test_setup.accel_samples; i++) { short *vals = &p_vals[3 * i]; if (test_get_data(mlsl_handle, mputestCfgPtr, vals)) { goto accel_error; } x += 1.f * vals[X] / test_setup.accel_samples; y += 1.f * vals[Y] / test_setup.accel_samples; z += 1.f * vals[Z] / test_setup.accel_samples; } mputestCfgPtr->requested_sensors = orig_requested_sensors; res = inv_mpu_suspend(mputestCfgPtr, mlsl_handle, NULL, NULL, NULL, INV_ALL_SENSORS); if (res != INV_SUCCESS) goto accel_error; MPL_LOGI("Accel biases : %+13.3f %+13.3f %+13.3f (LSB)\n", x, y, z); if (VERBOSE_OUT) { MPL_LOGI("Accel biases : %+13.3f %+13.3f %+13.3f (gee)\n", x / gravity, y / gravity, z / gravity); } bias[0] = FLOAT_TO_SHORT(x); bias[1] = FLOAT_TO_SHORT(y); zg = z - g_z_sign * gravity; bias[2] = FLOAT_TO_SHORT(zg); MPL_LOGI("Accel correct.: %+13d %+13d %+13d (LSB)\n", bias[0], bias[1], bias[2]); if (VERBOSE_OUT) { MPL_LOGI("Accel correct.: " "%+13.3f %+13.3f %+13.3f (gee)\n", 1.f * bias[0] / gravity, 1.f * bias[1] / gravity, 1.f * bias[2] / gravity); } if (perform_full_test) { /* accel RMS - for now the threshold is only indicative */ for (i = 0, RMS[X] = 0.f, RMS[Y] = 0.f, RMS[Z] = 0.f; i < test_setup.accel_samples; i++) { short *vals = &p_vals[3 * i]; RMS[X] += (vals[X] - x) * (vals[X] - x); RMS[Y] += (vals[Y] - y) * (vals[Y] - y); RMS[Z] += (vals[Z] - z) * (vals[Z] - z); } for (i = 0; i < 3; i++) { if (RMS[i] > accelRmsThresh * accelRmsThresh * test_setup.accel_samples) { MPL_LOGI("%s-Accel RMS (%.2f) exceeded threshold " "(threshold = %.2f)\n", a_name[i], sqrt(RMS[i] / test_setup.accel_samples), accelRmsThresh); goto accel_error; } } MPL_LOGI("RMS : %+13.3f %+13.3f %+13.3f (LSB-rms)\n", sqrt(RMS[X] / DEF_N_ACCEL_SAMPLES), sqrt(RMS[Y] / DEF_N_ACCEL_SAMPLES), sqrt(RMS[Z] / DEF_N_ACCEL_SAMPLES)); } return 0; /* success */ accel_error: /* error */ bias[0] = bias[1] = bias[2] = 0; return 1; } /** * @brief an user-space substitute of the power management function(s) * in mldl_cfg.c for self test usage. * Wake up and sleep the device, whether that is MPU3050, MPU6050A2, * or MPU6050B1. * @param mlsl_handle * a file handle for the serial communication port used to * communicate with the MPU device. * @param power_level * the power state to change the device into. Currently only 0 or * 1 are supported, for sleep and full-power respectively. * @return 0 on success; a non-zero error code on error. */ static inv_error_t inv_device_power_mgmt(void *mlsl_handle, uint_fast8_t power_level) { inv_error_t result; static unsigned char pwr_mgm; unsigned char b; if (power_level != 0 && power_level != 1) { return INV_ERROR_INVALID_PARAMETER; } if (power_level) { result = inv_serial_read( mlsl_handle, mputestCfgPtr->addr, MPUREG_PWR_MGM, 1, &pwr_mgm); if (result) { LOG_RESULT_LOCATION(result); return result; } /* reset */ result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_PWR_MGM, pwr_mgm | BIT_H_RESET); #ifndef CONFIG_MPU_SENSORS_MPU6050A2 if (result) { LOG_RESULT_LOCATION(result); return result; } #endif inv_sleep(5); /* re-read power mgmt reg - it may have reset after H_RESET is applied */ result = inv_serial_read( mlsl_handle, mputestCfgPtr->addr, MPUREG_PWR_MGM, 1, &b); if (result) { LOG_RESULT_LOCATION(result); return result; } /* wake up */ #ifdef CONFIG_MPU_SENSORS_MPU6050A2 if ((b & BITS_PWRSEL) != BITS_PWRSEL) { result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_PWR_MGM, BITS_PWRSEL); if (result) { LOG_RESULT_LOCATION(result); return result; } } #else if (pwr_mgm & BIT_SLEEP) { result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_PWR_MGM, 0x00); if (result) { LOG_RESULT_LOCATION(result); return result; } } #endif inv_sleep(60); #if defined(CONFIG_MPU_SENSORS_MPU6050A2) || \ defined(CONFIG_MPU_SENSORS_MPU6050B1) result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_INT_PIN_CFG, BIT_BYPASS_EN); if (result) { LOG_RESULT_LOCATION(result); return result; } #endif } else { /* restore the power state the part was found in */ #ifdef CONFIG_MPU_SENSORS_MPU6050A2 if ((pwr_mgm & BITS_PWRSEL) != BITS_PWRSEL) { result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_PWR_MGM, pwr_mgm); if (result) { LOG_RESULT_LOCATION(result); return result; } } #else if (pwr_mgm & BIT_SLEEP) { result = inv_serial_single_write( mlsl_handle, mputestCfgPtr->addr, MPUREG_PWR_MGM, pwr_mgm); if (result) { LOG_RESULT_LOCATION(result); return result; } } #endif } return INV_SUCCESS; } /** * @brief The main entry point of the MPU Self Test, triggering the run of * the single tests, for gyros and accelerometers. * Prepares the MPU for the test, taking the device out of low power * state if necessary, switching the MPU secondary I2C interface into * bypass mode and restoring the original power state at the end of * the test. * This function is also responsible for encoding the output of each * test in the correct format as it is stored on the file/medium of * choice (according to inv_serial_write_cal() function). * The format needs to stay perfectly consistent with the one expected * by the corresponding loader in ml_stored_data.c; currectly the * loaded in use is inv_load_cal_V1 (record type 1 - initial * calibration). * * @param mlsl_handle * serial interface handle to allow serial communication with the * device, both gyro and accelerometer. * @param provide_result * If 1: * perform and analyze the offset, drive frequency, and noise * calculation and compare it against set threshouds. Report * also the final result using a bit-mask like error code as * described in the inv_test_gyro() function. * When 0: * skip the noise and drive frequency calculation and pass/fail * assessment. It simply calculates the gyro and accel biases. * NOTE: for MPU6050 devices, this parameter is currently * ignored. * * @return 0 on success. A non-zero error code on error. * Propagates the errors from the tests up to the caller. */ int inv_mpu_test(void *mlsl_handle, uint_fast8_t provide_result) { int result = 0; short temp_avg; short gyro_biases[3] = {0, 0, 0}; short accel_biases[3] = {0, 0, 0}; unsigned long testStart = inv_get_tick_count(); long accelSens[3] = {0}; int ptr; int tmp; long long lltmp; uint32_t chk; MPL_LOGI("Collecting %d groups of 600 ms samples for each axis\n", DEF_TEST_TIME_PER_AXIS / 600); MPL_LOGI("\n"); result = inv_device_power_mgmt(mlsl_handle, TRUE); #ifdef TRACK_IDS result = test_get_mpu_id(mlsl_handle); if (result != INV_SUCCESS) { MPL_LOGI("Could not read the device's unique ID\n"); MPL_LOGI("\n"); return result; } #endif /* adjust the gyro sensitivity according to the gyro_sens_trim value */ adjGyroSens = test_setup.gyro_sens * mputestCfgPtr->gyro_sens_trim / 131.f; test_setup.gyro_fs = (int)(32768.f / adjGyroSens); /* collect gyro and temperature data */ #ifdef CONFIG_MPU_SENSORS_MPU3050 result = inv_test_gyro_3050(mlsl_handle, gyro_biases, &temp_avg, provide_result); #else MPL_LOGW_IF(provide_result, "Self Test for MPU-6050 devices is for bias correction only: " "no test PASS/FAIL result will be provided\n"); result = inv_test_gyro_6050(mlsl_handle, gyro_biases, &temp_avg); #endif MPL_LOGI("\n"); MPL_LOGI("Test time : %ld ms\n", inv_get_tick_count() - testStart); if (result) return result; /* collect accel data. if this step is skipped, ensure the array still contains zeros. */ if (mputestCfgPtr->accel != NULL) { float fs; RANGE_FIXEDPOINT_TO_FLOAT(mputestCfgPtr->accel->range, fs); accelSens[0] = (long)(32768L / fs); accelSens[1] = (long)(32768L / fs); accelSens[2] = (long)(32768L / fs); #if defined CONFIG_MPU_SENSORS_MPU6050B1 if (MPL_PROD_KEY(mputestCfgPtr->product_id, mputestCfgPtr->product_revision) == MPU_PRODUCT_KEY_B1_E1_5) { accelSens[2] /= 2; } else { unsigned short trim_corr = 16384 / mputestCfgPtr->accel_sens_trim; accelSens[0] /= trim_corr; accelSens[1] /= trim_corr; accelSens[2] /= trim_corr; } #endif } else { /* would be 0, but 1 to avoid divide-by-0 below */ accelSens[0] = accelSens[1] = accelSens[2] = 1; } #ifdef CONFIG_MPU_SENSORS_MPU3050 result = inv_test_accel(mlsl_handle, accel_biases, accelSens[2], provide_result); #else result = inv_test_accel(mlsl_handle, accel_biases, accelSens[2], FALSE); #endif if (result) return result; result = inv_device_power_mgmt(mlsl_handle, FALSE); if (result) return result; ptr = 0; dataStore[ptr++] = 0; /* total len of factory cal */ dataStore[ptr++] = 0; dataStore[ptr++] = 0; dataStore[ptr++] = ML_INIT_CAL_LEN; dataStore[ptr++] = 0; dataStore[ptr++] = 5; /* record type 5 - initial calibration */ tmp = temp_avg; /* temperature */ if (tmp < 0) tmp += 2 << 16; USHORT_TO_CHARS(&dataStore[ptr], tmp); ptr += 2; /* NOTE : 2 * test_setup.gyro_fs == 65536 / (32768 / test_setup.gyro_fs) */ lltmp = (long)gyro_biases[0] * 2 * test_setup.gyro_fs; /* x gyro avg */ if (lltmp < 0) lltmp += 1LL << 32; UINT_TO_CHARS(&dataStore[ptr], (uint32_t)lltmp); ptr += 4; lltmp = (long)gyro_biases[1] * 2 * test_setup.gyro_fs; /* y gyro avg */ if (lltmp < 0) lltmp += 1LL << 32; UINT_TO_CHARS(&dataStore[ptr], (uint32_t)lltmp); ptr += 4; lltmp = (long)gyro_biases[2] * 2 * test_setup.gyro_fs; /* z gyro avg */ if (lltmp < 0) lltmp += 1LL << 32; UINT_TO_CHARS(&dataStore[ptr], (uint32_t)lltmp); ptr += 4; lltmp = (long)accel_biases[0] * 65536L / accelSens[0]; /* x accel avg */ if (lltmp < 0) lltmp += 1LL << 32; UINT_TO_CHARS(&dataStore[ptr], (uint32_t)lltmp); ptr += 4; lltmp = (long)accel_biases[1] * 65536L / accelSens[1]; /* y accel avg */ if (lltmp < 0) lltmp += 1LL << 32; UINT_TO_CHARS(&dataStore[ptr], (uint32_t)lltmp); ptr += 4; lltmp = (long)accel_biases[2] * 65536L / accelSens[2]; /* z accel avg */ if (lltmp < 0) lltmp += 1LL << 32; UINT_TO_CHARS(&dataStore[ptr], (uint32_t)lltmp); ptr += 4; /* add a checksum for data */ chk = inv_checksum( dataStore + INV_CAL_HDR_LEN, ML_INIT_CAL_LEN - INV_CAL_HDR_LEN - INV_CAL_CHK_LEN); UINT_TO_CHARS(&dataStore[ptr], chk); ptr += 4; if (ptr != ML_INIT_CAL_LEN) { MPL_LOGI("Invalid calibration data length: exp %d, got %d\n", ML_INIT_CAL_LEN, ptr); return -1; } return result; } /** * @brief The main test API. Runs the MPU Self Test and, if successful, * stores the encoded initial calibration data on the final storage * medium of choice (cfr. inv_serial_write_cal() and the MLCAL_FILE * define in your mlsl implementation). * * @param mlsl_handle * serial interface handle to allow serial communication with the * device, both gyro and accelerometer. * @param provide_result * If 1: * perform and analyze the offset, drive frequency, and noise * calculation and compare it against set threshouds. Report * also the final result using a bit-mask like error code as * described in the inv_test_gyro() function. * When 0: * skip the noise and drive frequency calculation and pass/fail * assessment. It simply calculates the gyro and accel biases. * * @return 0 on success or a non-zero error code from the callees on error. */ inv_error_t inv_factory_calibrate(void *mlsl_handle, uint_fast8_t provide_result) { int result; result = inv_mpu_test(mlsl_handle, provide_result); if (provide_result) { MPL_LOGI("\n"); if (result == 0) { MPL_LOGI("Test : PASSED\n"); } else { MPL_LOGI("Test : FAILED %d/%04X - Biases NOT stored\n", result, result); return result; /* abort writing the calibration if the test is not successful */ } MPL_LOGI("\n"); } else { MPL_LOGI("\n"); if (result) { LOG_RESULT_LOCATION(result); return result; } } result = inv_serial_write_cal(dataStore, ML_INIT_CAL_LEN); if (result) { MPL_LOGI("Error : cannot write calibration on file - error %d\n", result); return result; } return INV_SUCCESS; } /* ----------------------------------------------------------------------- accel interface functions -----------------------------------------------------------------------*/ /** * @internal * @brief Reads data for X, Y, and Z axis from the accelerometer device. * Used only if an accelerometer has been setup using the * inv_test_setup_accel() API. * Takes care of the accelerometer endianess according to how the * device has been described in the corresponding accelerometer driver * file. * @param mlsl_handle * serial interface handle to allow serial communication with the * device, both gyro and accelerometer. * @param slave * a pointer to the descriptor of the slave accelerometer device * in use. Contains the necessary information to operate, read, * and communicate with the accelerometer device of choice. * See the declaration of struct ext_slave_descr in mpu.h. * @param pdata * a pointer to the platform info of the slave accelerometer * device in use. Describes how the device is oriented and * mounted on host platform's PCB. * @param vals * output pointer to return the accelerometer's X, Y, and Z axis * sensor data collected. * @return 0 on success or a non-zero error code on error. */ static inv_error_t test_get_data( void *mlsl_handle, struct mldl_cfg *mputestCfgPtr, short *vals) { inv_error_t result; unsigned char data[20]; struct ext_slave_descr *slave = mputestCfgPtr->accel; #ifndef CONFIG_MPU_SENSORS_MPU3050 struct ext_slave_platform_data *pdata = &mputestCfgPtr->pdata->accel; #endif #ifdef CONFIG_MPU_SENSORS_MPU3050 result = inv_serial_read(mlsl_handle, mputestCfgPtr->addr, 0x23, 6, data); #else result = inv_serial_read(mlsl_handle, pdata->address, slave->read_reg, slave->read_len, data); #endif if (result) { LOG_RESULT_LOCATION(result); return result; } if (VERBOSE_OUT) { MPL_LOGI("Accel : 0x%02X%02X 0x%02X%02X 0x%02X%02X (raw)\n", ACCEL_UNPACK(data)); } if (CHECK_NACKS(data)) { MPL_LOGI("Error fetching data from the accelerometer : " "all bytes read 0xff\n"); return INV_ERROR_SERIAL_READ; } if (slave->endian == EXT_SLAVE_BIG_ENDIAN) { vals[0] = CHARS_TO_SHORT(&data[0]); vals[1] = CHARS_TO_SHORT(&data[2]); vals[2] = CHARS_TO_SHORT(&data[4]); } else { vals[0] = CHARS_TO_SHORT_SWAPPED(&data[0]); vals[1] = CHARS_TO_SHORT_SWAPPED(&data[2]); vals[2] = CHARS_TO_SHORT_SWAPPED(&data[4]); } if (VERBOSE_OUT) { MPL_LOGI("Accel : %+13d %+13d %+13d (LSB)\n", vals[0], vals[1], vals[2]); } return INV_SUCCESS; } #ifdef __cplusplus } #endif /** * @} */