456 lines
15 KiB
C++
456 lines
15 KiB
C++
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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);
|
|
}
|
|
|