239 lines
6.6 KiB
C++
239 lines
6.6 KiB
C++
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// This file is part of RTIMULib-Teensy
|
|
//
|
|
// Copyright (c) 2014-2015, richards-tech
|
|
//
|
|
// 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 "RTFusionKalman4.h"
|
|
#include "RTIMUSettings.h"
|
|
|
|
// The QVALUE affects the gyro response.
|
|
|
|
#define KALMAN_QVALUE 0.001f
|
|
|
|
// The RVALUE controls the influence of the accels and compass.
|
|
// The bigger the value, the more sluggish the response.
|
|
|
|
#define KALMAN_RVALUE 0.0005f
|
|
|
|
#define KALMAN_QUATERNION_LENGTH 4
|
|
|
|
#define KALMAN_STATE_LENGTH 4 // just the quaternion for the moment
|
|
|
|
|
|
RTFusionKalman4::RTFusionKalman4()
|
|
{
|
|
reset();
|
|
}
|
|
|
|
RTFusionKalman4::~RTFusionKalman4()
|
|
{
|
|
}
|
|
|
|
void RTFusionKalman4::reset()
|
|
{
|
|
m_firstTime = true;
|
|
m_fusionPose = RTVector3();
|
|
m_fusionQPose.fromEuler(m_fusionPose);
|
|
m_gyro = RTVector3();
|
|
m_accel = RTVector3();
|
|
m_compass = RTVector3();
|
|
m_measuredPose = RTVector3();
|
|
m_measuredQPose.fromEuler(m_measuredPose);
|
|
m_Rk.fill(0);
|
|
m_Q.fill(0);
|
|
|
|
// initialize process noise covariance matrix
|
|
|
|
for (int i = 0; i < KALMAN_STATE_LENGTH; i++)
|
|
for (int j = 0; j < KALMAN_STATE_LENGTH; j++)
|
|
m_Q.setVal(i, i, KALMAN_QVALUE);
|
|
|
|
// initialize observation noise covariance matrix
|
|
|
|
|
|
for (int i = 0; i < KALMAN_STATE_LENGTH; i++)
|
|
for (int j = 0; j < KALMAN_STATE_LENGTH; j++)
|
|
m_Rk.setVal(i, i, KALMAN_RVALUE);
|
|
}
|
|
|
|
void RTFusionKalman4::predict()
|
|
{
|
|
RTMatrix4x4 mat;
|
|
RTQuaternion tQuat;
|
|
RTFLOAT x2, y2, z2;
|
|
|
|
// compute the state transition matrix
|
|
|
|
x2 = m_gyro.x() / (RTFLOAT)2.0;
|
|
y2 = m_gyro.y() / (RTFLOAT)2.0;
|
|
z2 = m_gyro.z() / (RTFLOAT)2.0;
|
|
|
|
m_Fk.setVal(0, 1, -x2);
|
|
m_Fk.setVal(0, 2, -y2);
|
|
m_Fk.setVal(0, 3, -z2);
|
|
|
|
m_Fk.setVal(1, 0, x2);
|
|
m_Fk.setVal(1, 2, z2);
|
|
m_Fk.setVal(1, 3, -y2);
|
|
|
|
m_Fk.setVal(2, 0, y2);
|
|
m_Fk.setVal(2, 1, -z2);
|
|
m_Fk.setVal(2, 3, x2);
|
|
|
|
m_Fk.setVal(3, 0, z2);
|
|
m_Fk.setVal(3, 1, y2);
|
|
m_Fk.setVal(3, 2, -x2);
|
|
|
|
m_FkTranspose = m_Fk.transposed();
|
|
|
|
// Predict new state estimate Xkk_1 = Fk * Xk_1k_1
|
|
|
|
tQuat = m_Fk * m_stateQ;
|
|
tQuat *= m_timeDelta;
|
|
m_stateQ += tQuat;
|
|
|
|
// m_stateQ.normalize();
|
|
|
|
// Compute PDot = Fk * Pk_1k_1 + Pk_1k_1 * FkTranspose (note Pkk == Pk_1k_1 at this stage)
|
|
|
|
m_PDot = m_Fk * m_Pkk;
|
|
mat = m_Pkk * m_FkTranspose;
|
|
m_PDot += mat;
|
|
|
|
// add in Q to get the new prediction
|
|
|
|
m_Pkk_1 = m_PDot + m_Q;
|
|
|
|
// multiply by deltaTime (variable name is now misleading though)
|
|
|
|
m_Pkk_1 *= m_timeDelta;
|
|
}
|
|
|
|
|
|
void RTFusionKalman4::update()
|
|
{
|
|
RTQuaternion delta;
|
|
RTMatrix4x4 Sk, SkInverse;
|
|
|
|
if (m_enableCompass || m_enableAccel) {
|
|
m_stateQError = m_measuredQPose - m_stateQ;
|
|
} else {
|
|
m_stateQError = RTQuaternion();
|
|
}
|
|
|
|
// Compute residual covariance Sk = Hk * Pkk_1 * HkTranspose + Rk
|
|
// Note: since Hk is the identity matrix, this has been simplified
|
|
|
|
Sk = m_Pkk_1 + m_Rk;
|
|
|
|
// Compute Kalman gain Kk = Pkk_1 * HkTranspose * SkInverse
|
|
// Note: again, the HkTranspose part is omitted
|
|
|
|
SkInverse = Sk.inverted();
|
|
|
|
m_Kk = m_Pkk_1 * SkInverse;
|
|
|
|
if (m_debug)
|
|
HAL_INFO(RTMath::display("Gain", m_Kk));
|
|
|
|
// make new state estimate
|
|
|
|
delta = m_Kk * m_stateQError;
|
|
|
|
m_stateQ += delta;
|
|
|
|
m_stateQ.normalize();
|
|
|
|
// produce new estimate covariance Pkk = (I - Kk * Hk) * Pkk_1
|
|
// Note: since Hk is the identity matrix, it is omitted
|
|
|
|
m_Pkk.setToIdentity();
|
|
m_Pkk -= m_Kk;
|
|
m_Pkk = m_Pkk * m_Pkk_1;
|
|
|
|
if (m_debug)
|
|
HAL_INFO(RTMath::display("Cov", m_Pkk));
|
|
}
|
|
|
|
void RTFusionKalman4::newIMUData(RTIMU_DATA& data, const RTIMUSettings *settings)
|
|
{
|
|
if (m_enableGyro)
|
|
m_gyro = data.gyro;
|
|
else
|
|
m_gyro = RTVector3();
|
|
m_accel = data.accel;
|
|
m_compass = data.compass;
|
|
m_compassValid = data.compassValid;
|
|
|
|
if (m_firstTime) {
|
|
m_lastFusionTime = data.timestamp;
|
|
calculatePose(m_accel, m_compass, settings->m_compassAdjDeclination);
|
|
m_Fk.fill(0);
|
|
|
|
// init covariance matrix to something
|
|
|
|
m_Pkk.fill(0);
|
|
for (int i = 0; i < 4; i++)
|
|
for (int j = 0; j < 4; j++)
|
|
m_Pkk.setVal(i,j, 0.5);
|
|
|
|
// initialize the observation model Hk
|
|
// Note: since the model is the state vector, this is an identity matrix so it won't be used
|
|
|
|
// initialize the poses
|
|
|
|
m_stateQ.fromEuler(m_measuredPose);
|
|
m_fusionQPose = m_stateQ;
|
|
m_fusionPose = m_measuredPose;
|
|
m_firstTime = false;
|
|
} else {
|
|
m_timeDelta = (RTFLOAT)(data.timestamp - m_lastFusionTime) / (RTFLOAT)1000000;
|
|
m_lastFusionTime = data.timestamp;
|
|
if (m_timeDelta <= 0)
|
|
return;
|
|
|
|
if (m_debug) {
|
|
HAL_INFO("\n------\n");
|
|
HAL_INFO1("IMU update delta time: %f\n", m_timeDelta);
|
|
}
|
|
|
|
calculatePose(data.accel, data.compass, settings->m_compassAdjDeclination);
|
|
|
|
predict();
|
|
update();
|
|
m_stateQ.toEuler(m_fusionPose);
|
|
m_fusionQPose = m_stateQ;
|
|
|
|
if (m_debug) {
|
|
HAL_INFO(RTMath::displayRadians("Measured pose", m_measuredPose));
|
|
HAL_INFO(RTMath::displayRadians("Kalman pose", m_fusionPose));
|
|
HAL_INFO(RTMath::displayRadians("Measured quat", m_measuredPose));
|
|
HAL_INFO(RTMath::display("Kalman quat", m_stateQ));
|
|
HAL_INFO(RTMath::display("Error quat", m_stateQError));
|
|
}
|
|
}
|
|
data.fusionPoseValid = true;
|
|
data.fusionQPoseValid = true;
|
|
data.fusionPose = m_fusionPose;
|
|
data.fusionQPose = m_fusionQPose;
|
|
}
|