//////////////////////////////////////////////////////////////////////////// // // This file is part of MPU9150Lib // // Copyright (c) 2013 Pansenti, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the // Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "MPU9150Lib.h" #include "inv_mpu.h" #include "inv_mpu_dmp_motion_driver.h" #include "MPUQuaternion.h" //////////////////////////////////////////////////////////////////////////// // // The functions below are from the InvenSense SDK example code. // // Original copyright notice below: /* $License: Copyright (C) 2011-2012 InvenSense Corporation, All Rights Reserved. See included License.txt for License information. $ */ /* These next two functions converts the orientation matrix (see * gyro_orientation) to a scalar representation for use by the DMP. * NOTE: These functions are borrowed from InvenSense's MPL. */ static inline unsigned short inv_row_2_scale(const signed char *row) { unsigned short b; if (row[0] > 0) b = 0; else if (row[0] < 0) b = 4; else if (row[1] > 0) b = 1; else if (row[1] < 0) b = 5; else if (row[2] > 0) b = 2; else if (row[2] < 0) b = 6; else b = 7; // error return b; } /* The sensors can be mounted onto the board in any orientation. The mounting * matrix seen below tells the MPL how to rotate the raw data from thei * driver(s). * TODO: The following matrices refer to the configuration on an internal test * board at Invensense. If needed, please modify the matrices to match the * chip-to-body matrix for your particular set up. */ static signed char gyro_orientation[9] = { 1, 0, 0, 0, 1, 0, 0, 0, 1}; static inline unsigned short inv_orientation_matrix_to_scalar(const signed char *mtx) { unsigned short scalar; /* XYZ 010_001_000 Identity Matrix XZY 001_010_000 YXZ 010_000_001 YZX 000_010_001 ZXY 001_000_010 ZYX 000_001_010 */ scalar = inv_row_2_scale(mtx); scalar |= inv_row_2_scale(mtx + 3) << 3; scalar |= inv_row_2_scale(mtx + 6) << 6; return scalar; } // //////////////////////////////////////////////////////////////////////////// MPU9150Lib::MPU9150Lib() { // use calibration if available m_useAccelCalibration = true; m_useMagCalibration = true; m_device = 0; } void MPU9150Lib::selectDevice(int device) { m_device = device; } void MPU9150Lib::useAccelCal(boolean useCal) { m_useAccelCalibration = useCal; } void MPU9150Lib::disableAccelCal() { if (!m_useAccelCalibration) return; m_useAccelCalibration = false; m_accelOffset[0] = 0; m_accelOffset[1] = 0; m_accelOffset[2] = 0; mpu_set_accel_bias(m_accelOffset); } void MPU9150Lib::useMagCal(boolean useCal) { m_useMagCalibration = useCal; } boolean MPU9150Lib::init(int mpuRate, int magMix, int magRate, int lpf) { struct int_param_s int_param; int result; mpu_select_device(m_device); dmp_select_device(m_device); if (magRate > 100) return false; // rate must be less than or equal to 100Hz if (magRate < 1) return false; m_magInterval = (unsigned long)(1000 / magRate); // record mag interval m_lastMagSample = millis(); if (mpuRate > 1000) return false; if (mpuRate < 1) return false; m_magMix = magMix; m_lastDMPYaw = 0; m_lastYaw = 0; // get calibration data if it's there if (calLibRead(m_device, &m_calData)) { // use calibration data if it's there and wanted m_useMagCalibration &= m_calData.magValid == 1; m_useAccelCalibration &= m_calData.accelValid == 1; // Process calibration data for runtime if (m_useMagCalibration) { m_magXOffset = (short)(((long)m_calData.magMaxX + (long)m_calData.magMinX) / 2); m_magXRange = m_calData.magMaxX - m_magXOffset; m_magYOffset = (short)(((long)m_calData.magMaxY + (long)m_calData.magMinY) / 2); m_magYRange = m_calData.magMaxY - m_magYOffset; m_magZOffset = (short)(((long)m_calData.magMaxZ + (long)m_calData.magMinZ) / 2); m_magZRange = m_calData.magMaxZ - m_magZOffset; } if (m_useAccelCalibration) { m_accelOffset[0] = -((long)m_calData.accelMaxX + (long)m_calData.accelMinX) / 2; m_accelOffset[1] = -((long)m_calData.accelMaxY + (long)m_calData.accelMinY) / 2; m_accelOffset[2] = -((long)m_calData.accelMaxZ + (long)m_calData.accelMinZ) / 2; mpu_set_accel_bias(m_accelOffset); m_accelXRange = m_calData.accelMaxX + (short)m_accelOffset[0]; m_accelYRange = m_calData.accelMaxY + (short)m_accelOffset[1]; m_accelZRange = m_calData.accelMaxZ + (short)m_accelOffset[2]; } } else { m_useMagCalibration = false; m_useAccelCalibration = false; } #ifdef MPULIB_DEBUG if (m_useMagCalibration) Serial.println("Using mag cal"); if (m_useAccelCalibration) Serial.println("Using accel cal"); #endif mpu_init_structures(); // Not using interrupts so set up this structure to keep the driver happy int_param.cb = NULL; int_param.pin = 0; int_param.lp_exit = 0; int_param.active_low = 1; result = mpu_init(&int_param); if (result != 0) { #ifdef MPULIB_DEBUG Serial.print("mpu_init failed with code: "); Serial.println(result); #endif return false; } mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL | INV_XYZ_COMPASS); // enable all of the sensors mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL); // get accel and gyro data in the FIFO also #ifdef MPULIB_DEBUG Serial.println("Loading firmware"); #endif if ((result = dmp_load_motion_driver_firmware()) != 0) { // try to load the DMP firmware #ifdef MPULIB_DEBUG Serial.print("Failed to load dmp firmware: "); Serial.println(result); #endif return false; } dmp_set_orientation(inv_orientation_matrix_to_scalar(gyro_orientation)); // set up the correct orientation dmp_enable_feature(DMP_FEATURE_6X_LP_QUAT | DMP_FEATURE_SEND_RAW_ACCEL | DMP_FEATURE_SEND_CAL_GYRO | DMP_FEATURE_GYRO_CAL); dmp_set_fifo_rate(mpuRate); if (mpu_set_dmp_state(1) != 0) { #ifdef MPULIB_DEBUG Serial.println("mpu_set_dmp_state failed"); #endif return false; } mpu_set_sample_rate(mpuRate); // set the update rate mpu_set_compass_sample_rate(magRate); // set the compass update rate to match if (lpf != 0) mpu_set_lpf(lpf); // set the low pass filter return true; } boolean MPU9150Lib::read() { short intStatus; int result; short sensors; unsigned char more; unsigned long timestamp; mpu_select_device(m_device); dmp_select_device(m_device); mpu_get_int_status(&intStatus); // get the current MPU state if ((intStatus & (MPU_INT_STATUS_DMP | MPU_INT_STATUS_DMP_0)) != (MPU_INT_STATUS_DMP | MPU_INT_STATUS_DMP_0)) return false; // get the data from the fifo if ((result = dmp_read_fifo(m_rawGyro, m_rawAccel, m_rawQuaternion, ×tamp, &sensors, &more)) != 0) { return false; } // got the fifo data so now get the mag data if it's time if ((millis() - m_lastMagSample) >= m_magInterval) { if ((result = mpu_get_compass_reg(m_rawMag, ×tamp)) != 0) { #ifdef MPULIB_DEBUG Serial.print("Failed to read compass: "); Serial.println(result); #endif return false; } // *** Note mag axes are changed here to align with gyros: Y = -X, X = Y m_lastMagSample = millis(); if (m_useMagCalibration) { m_calMag[VEC3_Y] = -(short)(((long)(m_rawMag[VEC3_X] - m_magXOffset) * (long)SENSOR_RANGE) / (long)m_magXRange); m_calMag[VEC3_X] = (short)(((long)(m_rawMag[VEC3_Y] - m_magYOffset) * (long)SENSOR_RANGE) / (long)m_magYRange); m_calMag[VEC3_Z] = (short)(((long)(m_rawMag[VEC3_Z] - m_magZOffset) * (long)SENSOR_RANGE) / (long)m_magZRange); } else { m_calMag[VEC3_Y] = -m_rawMag[VEC3_X]; m_calMag[VEC3_X] = m_rawMag[VEC3_Y]; m_calMag[VEC3_Z] = m_rawMag[VEC3_Z]; } } // got the raw data - now process m_dmpQuaternion[QUAT_W] = (float)m_rawQuaternion[QUAT_W]; // get float version of quaternion m_dmpQuaternion[QUAT_X] = (float)m_rawQuaternion[QUAT_X]; m_dmpQuaternion[QUAT_Y] = (float)m_rawQuaternion[QUAT_Y]; m_dmpQuaternion[QUAT_Z] = (float)m_rawQuaternion[QUAT_Z]; MPUQuaternionNormalize(m_dmpQuaternion); // and normalize MPUQuaternionQuaternionToEuler(m_dmpQuaternion, m_dmpEulerPose); // Scale accel data if (m_useAccelCalibration) { /* m_calAccel[VEC3_X] = -(short)((((long)m_rawAccel[VEC3_X] + m_accelOffset[0]) * (long)SENSOR_RANGE) / (long)m_accelXRange); m_calAccel[VEC3_Y] = (short)((((long)m_rawAccel[VEC3_Y] + m_accelOffset[1]) * (long)SENSOR_RANGE) / (long)m_accelYRange); m_calAccel[VEC3_Z] = (short)((((long)m_rawAccel[VEC3_Z] + m_accelOffset[2]) * (long)SENSOR_RANGE) / (long)m_accelZRange); */ if (m_rawAccel[VEC3_X] >= 0) m_calAccel[VEC3_X] = -(short)((((long)m_rawAccel[VEC3_X]) * (long)SENSOR_RANGE) / (long)m_calData.accelMaxX); else m_calAccel[VEC3_X] = -(short)((((long)m_rawAccel[VEC3_X]) * (long)SENSOR_RANGE) / -(long)m_calData.accelMinX); if (m_rawAccel[VEC3_Y] >= 0) m_calAccel[VEC3_Y] = (short)((((long)m_rawAccel[VEC3_Y]) * (long)SENSOR_RANGE) / (long)m_calData.accelMaxY); else m_calAccel[VEC3_Y] = (short)((((long)m_rawAccel[VEC3_Y]) * (long)SENSOR_RANGE) / -(long)m_calData.accelMinY); if (m_rawAccel[VEC3_Z] >= 0) m_calAccel[VEC3_Z] = (short)((((long)m_rawAccel[VEC3_Z]) * (long)SENSOR_RANGE) / (long)m_calData.accelMaxZ); else m_calAccel[VEC3_Z] = (short)((((long)m_rawAccel[VEC3_Z]) * (long)SENSOR_RANGE) / -(long)m_calData.accelMinZ); } else { m_calAccel[VEC3_X] = -m_rawAccel[VEC3_X]; m_calAccel[VEC3_Y] = m_rawAccel[VEC3_Y]; m_calAccel[VEC3_Z] = m_rawAccel[VEC3_Z]; } dataFusion(); return true; } void MPU9150Lib::dataFusion() { float qMag[4]; float deltaDMPYaw, deltaMagYaw; float newMagYaw, newYaw; float temp1[4], unFused[4]; float unFusedConjugate[4]; // *** NOTE *** pitch direction swapped here m_fusedEulerPose[VEC3_X] = m_dmpEulerPose[VEC3_X]; m_fusedEulerPose[VEC3_Y] = -m_dmpEulerPose[VEC3_Y]; m_fusedEulerPose[VEC3_Z] = 0; MPUQuaternionEulerToQuaternion(m_fusedEulerPose, unFused); // create a new quaternion deltaDMPYaw = -m_dmpEulerPose[VEC3_Z] + m_lastDMPYaw; // calculate change in yaw from dmp m_lastDMPYaw = m_dmpEulerPose[VEC3_Z]; // update that qMag[QUAT_W] = 0; qMag[QUAT_X] = m_calMag[VEC3_X]; qMag[QUAT_Y] = m_calMag[VEC3_Y]; qMag[QUAT_Z] = m_calMag[VEC3_Z]; // Tilt compensate mag with the unfused data (i.e. just roll and pitch with yaw 0) MPUQuaternionConjugate(unFused, unFusedConjugate); MPUQuaternionMultiply(qMag, unFusedConjugate, temp1); MPUQuaternionMultiply(unFused, temp1, qMag); // Now fuse this with the dmp yaw gyro information newMagYaw = -atan2(qMag[QUAT_Y], qMag[QUAT_X]); if (newMagYaw != newMagYaw) { // check for nAn #ifdef MPULIB_DEBUG Serial.println("***nAn\n"); #endif return; // just ignore in this case } if (newMagYaw < 0) newMagYaw = 2.0f * (float)M_PI + newMagYaw; // need 0 <= newMagYaw <= 2*PI newYaw = m_lastYaw + deltaDMPYaw; // compute new yaw from change if (newYaw > (2.0f * (float)M_PI)) // need 0 <= newYaw <= 2*PI newYaw -= 2.0f * (float)M_PI; if (newYaw < 0) newYaw += 2.0f * (float)M_PI; deltaMagYaw = newMagYaw - newYaw; // compute difference if (deltaMagYaw >= (float)M_PI) deltaMagYaw = deltaMagYaw - 2.0f * (float)M_PI; if (deltaMagYaw <= -(float)M_PI) deltaMagYaw = (2.0f * (float)M_PI + deltaMagYaw); if (m_magMix > 0) { newYaw += deltaMagYaw / m_magMix; // apply some of the correction if (newYaw > (2.0f * (float)M_PI)) // need 0 <= newYaw <= 2*PI newYaw -= 2.0f * (float)M_PI; if (newYaw < 0) newYaw += 2.0f * (float)M_PI; } m_lastYaw = newYaw; if (newYaw > (float)M_PI) newYaw -= 2.0f * (float)M_PI; m_fusedEulerPose[VEC3_Z] = newYaw; // fill in output yaw value MPUQuaternionEulerToQuaternion(m_fusedEulerPose, m_fusedQuaternion); } void MPU9150Lib::printQuaternion(long *quat) { Serial.print("w: "); Serial.print(quat[QUAT_W]); Serial.print(" x: "); Serial.print(quat[QUAT_X]); Serial.print(" y: "); Serial.print(quat[QUAT_Y]); Serial.print(" z: "); Serial.print(quat[QUAT_Z]); } void MPU9150Lib::printQuaternion(float *quat) { Serial.print("w: "); Serial.print(quat[QUAT_W]); Serial.print(" x: "); Serial.print(quat[QUAT_X]); Serial.print(" y: "); Serial.print(quat[QUAT_Y]); Serial.print(" z: "); Serial.print(quat[QUAT_Z]); } void MPU9150Lib::printVector(short *vec) { Serial.print("x: "); Serial.print(vec[VEC3_X]); Serial.print(" y: "); Serial.print(vec[VEC3_Y]); Serial.print(" z: "); Serial.print(vec[VEC3_Z]); } void MPU9150Lib::printVector(float *vec) { Serial.print("x: "); Serial.print(vec[VEC3_X]); Serial.print(" y: "); Serial.print(vec[VEC3_Y]); Serial.print(" z: "); Serial.print(vec[VEC3_Z]); } void MPU9150Lib::printAngles(float *vec) { Serial.print("x: "); Serial.print(vec[VEC3_X] * RAD_TO_DEGREE); Serial.print(" y: "); Serial.print(vec[VEC3_Y] * RAD_TO_DEGREE); Serial.print(" z: "); Serial.print(vec[VEC3_Z] * RAD_TO_DEGREE); }