I recently build a multi-rate EKF for attitude estimation for my drone, I use a bmm150 magnetometer and bmi088 imu, and no matter how much I tune my filter my heading drift over time, roll, and pitch are precise, I put compass RMS noise in measurement noise but still not work, one question can we put RMS noise directly put on the R matrix? what should I do so my heading not drift? not that heading is so important for me as I build a GPS drone.
#include <Arduino.h>
#include <ArduinoEigenDense.h>
using namespace Eigen;
class EKF
{
private:
/* data */
public:
EKF(/* args */);
float mMagDataAvailable;
float mAccDataAvailable;
Vector4f xhat = Vector4f::Zero();
Vector4f xhatPrev = Vector4f::Zero();
Vector4f xhatNew = Vector4f::Zero();
VectorXf mErrorMag = VectorXf::Zero(3);
VectorXf mErrorAcc = VectorXf::Zero(3);
VectorXf mAccPredicted = VectorXf::Zero(3);
VectorXf mMagPredicted = VectorXf::Zero(3);
VectorXf mAccRef = VectorXf::Zero(3);
VectorXf mMagRef = VectorXf::Zero(3);
Vector3f eularAngle = Vector3f::Zero();
MatrixXf pHat = MatrixXf::Identity(4, 4);
MatrixXf pHatPrev = MatrixXf::Identity(4, 4);
MatrixXf pHatNew = MatrixXf::Identity(4, 4);
MatrixXf Q = MatrixXf::Zero(4, 4);
MatrixXf Wt = MatrixXf::Zero(4, 3);
MatrixXf gyro_noise = MatrixXf::Identity(3, 3);
MatrixXf F_t = MatrixXf::Zero(4, 4);
MatrixXf Ka = MatrixXf::Zero(4, 3);
MatrixXf Km = MatrixXf::Zero(4, 3);
MatrixXf Ra = MatrixXf::Identity(3, 3);
MatrixXf Rm = MatrixXf::Identity(3, 3);
MatrixXf Sa = MatrixXf::Zero(3, 3);
MatrixXf Sm = MatrixXf::Zero(3, 3);
MatrixXf Ha = MatrixXf::Zero(3, 4);
MatrixXf Hm = MatrixXf::Zero(3, 4);
MatrixXf I = MatrixXf::Identity(4, 4);
MatrixXf rotMatEuler = MatrixXf::Zero(3, 3);
void initialize(float ax, float ay, Vector3f mMag);
void fuseAngularValocity(float dt, Vector3f mGyro);
void fuseAcceleration(Vector3f mAcc);
void fuseCompass(Vector3f mMag);
Matrix3f q2R(Vector4f q);
void calculateJacobianOfAccel(VectorXf xhat, VectorXf ref);
void calculateJacobianOfCompass(VectorXf xhat, VectorXf ref);
void getRotationMatrix(VectorXf q);
void getEularAngles(VectorXf q);
float rmse;
int i = 0;
float error;
float meanSquaredErrors, sumSquaredErrors;
};
#include <EKF.h>
EKF::EKF(/* args */)
{
}
void EKF::fuseAngularValocity(float dt, Vector3f mGyro)
{
float qw = xhatPrev(0);
float qx = xhatPrev(1);
float qy = xhatPrev(2);
float qz = xhatPrev(3);
float wx = mGyro(0);
float wy = mGyro(1);
float wz = mGyro(2);
float DT = 0.5 * dt;
xhat(0) = qw - DT * wx * qx - DT * wy * qy - DT * wz * qz;
xhat(1) = qx + DT * wx * qw - DT * wy * qz + DT * wz * qy;
xhat(2) = qy + DT * wx * qz + DT * wy * qw - DT * wz * qx;
xhat(3) = qz - DT * wx * qy + DT * wy * qx + DT * wz * qw;
F_t(0, 0) = 1.0f;
F_t(0, 1) = -DT * wx;
F_t(0, 2) = -DT * wy;
F_t(0, 3) = -DT * wz;
F_t(1, 0) = DT * wx;
F_t(1, 1) = 1.0f;
F_t(1, 2) = DT * wz;
F_t(1, 3) = -DT * wy;
F_t(2, 0) = DT * wy;
F_t(2, 1) = -DT * wz;
F_t(2, 2) = 1.0f;
F_t(2, 3) = DT * wx;
F_t(3, 0) = DT * wz;
F_t(3, 1) = DT * wy;
F_t(3, 2) = -DT * wx;
F_t(3, 3) = 1.0f;
Wt(0, 0) = -qx * DT;
Wt(0, 1) = -qy * DT;
Wt(0, 2) = -qz * DT;
Wt(1, 0) = qw * DT;
Wt(1, 1) = -qz * DT;
Wt(1, 2) = qy * DT;
Wt(2, 0) = qz * DT;
Wt(2, 1) = qw * DT;
Wt(2, 2) = -qx * DT;
Wt(3, 0) = -qy * DT;
Wt(3, 1) = qx * DT;
Wt(3, 2) = qw * DT;
Q = Wt * gyro_noise * Wt.transpose();
pHat = F_t * pHatPrev * F_t.transpose() + Q;
}
void EKF::fuseAcceleration(Vector3f mAcc)
{
if (mAccDataAvailable)
{
mAcc.normalize();
mAccPredicted = q2R(xhat).transpose() * mAccRef;
mErrorAcc = mAcc - mAccPredicted;
calculateJacobianOfAccel(xhat, mAccRef);
Sa = Ha * pHat * Ha.transpose() + Ra;
Ka = pHat * Ha.transpose() * Sa.inverse();
xhatNew = xhat + Ka * mErrorAcc;
pHatNew = (I - Ka * Ha) * pHat;
xhatNew.normalize();
getEularAngles(xhatNew);
xhatPrev = xhatNew;
pHatPrev = pHatNew;
mAccDataAvailable = false;
}
}
void EKF::fuseCompass(Vector3f mMag)
{
if (mMagDataAvailable)
{
mMag.normalize();
mMagPredicted = q2R(xhat).transpose() * mMagRef;
mErrorMag = mMag - mMagPredicted;
calculateJacobianOfCompass(xhat, mMagRef);
Sm = Hm * pHat * Hm.transpose() + Rm;
Km = pHat * Hm.transpose() * Sm.inverse();
xhatNew = xhat + Km * mErrorMag;
pHatNew = (I - Km * Hm) * pHat;
xhatNew.normalize();
xhatPrev = xhatNew;
pHatPrev = pHatNew;
mMagDataAvailable = false;
}
}
Matrix3f EKF::q2R(Vector4f q)
{
Matrix3f rotMat;
rotMat << 1.0 - 2.0 * (q[2] * q[2] + q[3] * q[3]),
2.0 * (q[1] * q[2] - q[0] * q[3]),
2.0 * (q[1] * q[3] + q[0] * q[2]),
2.0 * (q[1] * q[2] + q[0] * q[3]),
1.0 - 2.0 * (q[1] * q[1] + q[3] * q[3]),
2.0 * (q[2] * q[3] - q[0] * q[1]),
2.0 * (q[1] * q[3] - q[0] * q[2]),
2.0 * (q[0] * q[1] + q[2] * q[3]),
1.0 - 2.0 * (q[1] * q[1] + q[2] * q[2]);
return rotMat;
}
void EKF::calculateJacobianOfAccel(VectorXf xhat, VectorXf ref)
{
float qw = xhat(0);
float qx = xhat(1);
float qy = xhat(2);
float qz = xhat(3);
float gx = ref(0);
float gy = ref(1);
float gz = ref(2);
Ha(0, 0) = gy * qz - gz * qy;
Ha(0, 1) = gy * qy + gz * qz;
Ha(0, 2) = -2.0f * gx * qy + gy * qx - gz * qw;
Ha(0, 3) = -2.0f * gx * qz + gy * qw + gz * qx;
Ha(1, 0) = -gx * qz + gz * qx;
Ha(1, 1) = gx * qy - 2.0f * gy * qx + gz * qw;
Ha(1, 2) = gx * qx + gz * qz;
Ha(1, 3) = -gx * qw - 2.0f * gy * qz + gz * qy;
Ha(2, 0) = gx * qy - gy * qx;
Ha(2, 1) = gx * qz - gy * qw - 2.0f * gz * qx;
Ha(2, 2) = gx * qw + gy * qz - 2.0f * gz * qy;
Ha(2, 3) = gx * qx + gy * qy;
Ha *= 2.0f;
}
void EKF::calculateJacobianOfCompass(VectorXf xhat, VectorXf ref)
{
float qw = xhat(0);
float qx = xhat(1);
float qy = xhat(2);
float qz = xhat(3);
float rx = ref(0);
float ry = ref(1);
float rz = ref(2);
Hm(0, 0) = ry * qz - rz * qy;
Hm(0, 1) = ry * qy + rz * qz;
Hm(0, 2) = -2.0f * rx * qy + ry * qx - rz * qw;
Hm(0, 3) = -2.0f * rx * qz + ry * qw + rz * qx;
Hm(1, 0) = -rx * qz + rz * qx;
Hm(1, 1) = rx * qy - 2.0f * ry * qx + rz * qw;
Hm(1, 2) = rx * qx + rz * qz;
Hm(1, 3) = -rx * qw - 2.0f * ry * qz + rz * qy;
Hm(2, 0) = rx * qy - ry * qx;
Hm(2, 1) = rx * qz - ry * qw - 2.0f * rz * qx;
Hm(2, 2) = rx * qw + ry * qz - 2.0f * rz * qy;
Hm(2, 3) = rx * qx + ry * qy;
Hm *= 2.0f;
}
void EKF::initialize(float ax, float ay, Vector3f mMag)
{
mMagRef << 0.80457377, 0.0084067, 0.59379321;
mAccRef << 0.0f, 0.0f, 1.0f;
// Optimal Values
// gyro_noise(0, 0) = (5.08244208f * 5.08244208f) / 3.5;
// gyro_noise(1, 1) = (9.12140635f * 9.12140635f) / 7.5;
// gyro_noise(2, 2) = (5.81727448f * 5.81727448f) / 3.5;
// Ra(0, 0) = 1.56341423f * 1.56341423f;
// Ra(1, 1) = 1.54743626f * 1.54743626f;
// Ra(2, 2) = 1.63774555f * 1.63774555f;
// Rm(0, 0) = 0.50485281f * 0.50485281f;
// Rm(1, 1) = 0.59562616f * 0.59562616f;
// Rm(2, 2) = 0.62478246f * 0.62478246f;
float theta = asinf(ax / 9.807);
float phi = asinf(ay / (9.807 * cos(theta)));
// magnetic heading correction due to roll and pitch angle
float Bxc = mMag(0) * cos(theta) + (mMag(1) * sin(phi) + mMag(2) * cos(phi)) * sin(theta);
float Byc = mMag(1) * cos(phi) - mMag(2) * sin(phi);
float psi = atan2(Byc, Bxc);
float cy = cos(psi * 0.5);
float sy = sin(psi * 0.5);
float cp = cos(theta * 0.5);
float sp = sin(theta * 0.5);
float cr = cos(phi * 0.5);
float sr = sin(phi * 0.5);
xhatPrev(0) = cr * cp * cy + sr * sp * sy;
xhatPrev(1) = sr * cp * cy - cr * sp * sy;
xhatPrev(2) = cr * sp * cy + sr * cp * sy;
xhatPrev(3) = cr * cp * sy - sr * sp * cy;
// gyro_noise(0, 0) = 5.08244208f * 5.08244208f;
// gyro_noise(1, 1) = 9.12140635f * 9.12140635f;
// gyro_noise(2, 2) = 5.81727448f * 5.81727448f;
gyro_noise(0, 0) = (5.08244208f * 5.08244208f) / 3.5f;
gyro_noise(1, 1) = (9.12140635f * 9.12140635f) / 7.5f;
gyro_noise(2, 2) = (2.81727448f * 2.81727448f) / 3.5f ;
Ra(0, 0) = 1.56341423f * 1.56341423f;
Ra(1, 1) = 1.54743626f * 1.54743626f;
Ra(2, 2) = 1.63774555f * 1.63774555f;
Rm(1, 1) = 1.000562616f * 1.000562616f;
Rm(0, 0) = 1.000485281f * 1.000485281f;
Rm(2, 2) = 1.400478246f * 1.400478246f;
}
void EKF::getRotationMatrix(VectorXf q)
{
// Matrix3f rotMat = Matrix3f::Zero();
rotMatEuler << q(0) * q(0) + q(1) * q(1) - q(2) * q(2) - q(3) * q(3),
2 * (q(1) * q(2) - q(0) * q(3)),
2 * (q(1) * q(3) + q(0) * q(2)),
2 * (q(1) * q(2) + q(0) * q(3)),
q(0) * q(0) - q(1) * q(1) + q(2) * q(2) - q(3) * q(3),
2 * (q(2) * q(3) - q(0) * q(1)),
2 * (q(1) * q(3) - q(0) * q(2)),
2 * (q(2) * q(3) + q(0) * q(1)),
q(0) * q(0) - q(1) * q(1) - q(2) * q(2) + q(3) * q(3);
}
void EKF::getEularAngles(VectorXf q)
{
// float magnitude = q.norm();
// float epsilon = 1e-6;
// bool isUnitQuaternion = abs(magnitude - 1.0) < epsilon;
// if (!isUnitQuaternion)
// {
// q.normalize();
// }
rotMatEuler = q2R(q);
float test = -rotMatEuler(2, 0);
if (test > 0.99999)
{
eularAngle(2) = 0.0f;
eularAngle(1) = PI / 2;
eularAngle(0) = atan2(rotMatEuler(0, 1), rotMatEuler(0, 2));
}
else if (test < -0.99999)
{
eularAngle(2) = 0.0f;
eularAngle(1) = -PI / 2;
eularAngle(0) = atan2(-rotMatEuler(0, 1), -rotMatEuler(0, 2));
}
else
{
eularAngle(2) = atan2(rotMatEuler(1, 0), rotMatEuler(0, 0));
eularAngle(1) = asin(-rotMatEuler(2, 0));
eularAngle(0) = atan2(rotMatEuler(2, 1), rotMatEuler(2, 2));
}
}