AHRS Extended Kalman filter
The extended Kalman filter(EKF) is the nonlinear version of the Kalman filter. EKF has 'Predict' and 'Update' stages and recursivley estimates target values.
enum { EKF_X = 0, EKF_Y, EKF_Z, EKF_W };
static const float EKF_Q_ROT = 0.1f;
static const float EKF_R_ACC = 200.0f;
static const float EKF_R_MAG = 2000.0f;
static float quaternion[4] = {0.0, 0.0, 0.0, 1.0};
static float P[4][4] = {{1.0, 0.0, 0.0, 0.0},
{0.0, 1.0, 0.0, 0.0},
{0.0, 0.0, 1.0, 0.0},
{0.0, 0.0, 0.0, 1.0}};
static float F[4][4];
static float y[6];
static float H[6][4];
static float S[6][6];
static float K[4][6];
void ahrs_get_unit_vector(float *vector, uint16_t vector_size) {
double sq_sum = 0;
for(uint16_t i = 0; i < vector_size; ++i) {
sq_sum += (vector[i] * vector[i]);
}
float norm = sqrt(sq_sum);
for(uint16_t i = 0; i < vector_size; ++i) { vector[i] /= norm; }
}
Predict
void ahrs_predict(float *half_delta_angle) {
/*
* x_(k|k-1) = f(x_(k-1|k-1))
*/
float delta_quaternion[4];
delta_quaternion[EKF_X] = half_delta_angle[EKF_Z] * quaternion[EKF_Y]
- half_delta_angle[EKF_Y] * quaternion[EKF_Z]
+ half_delta_angle[EKF_X] * quaternion[EKF_W];
delta_quaternion[EKF_Y] = -half_delta_angle[EKF_Z] * quaternion[EKF_X]
+ half_delta_angle[EKF_X] * quaternion[EKF_Z]
+ half_delta_angle[EKF_Y] * quaternion[EKF_W];
delta_quaternion[EKF_Z] = half_delta_angle[EKF_Y] * quaternion[EKF_X]
- half_delta_angle[EKF_X] * quaternion[EKF_Y]
+ half_delta_angle[EKF_Z] * quaternion[EKF_W];
delta_quaternion[EKF_W] = -half_delta_angle[EKF_X] * quaternion[EKF_X]
- half_delta_angle[EKF_Y] * quaternion[EKF_Y]
- half_delta_angle[EKF_Z] * quaternion[EKF_Z];
quaternion[EKF_X] += delta_quaternion[EKF_X];
quaternion[EKF_Y] += delta_quaternion[EKF_Y];
quaternion[EKF_Z] += delta_quaternion[EKF_Z];
quaternion[EKF_W] += delta_quaternion[EKF_W];
/*
* F = df/dx
*/
F[0][0] = 1.0;
F[0][1] = half_delta_angle[EKF_Z];
F[0][2] = -half_delta_angle[EKF_Y];
F[0][3] = half_delta_angle[EKF_X];
F[1][0] = -half_delta_angle[EKF_Z];
F[1][1] = 1.0;
F[1][2] = half_delta_angle[EKF_X];
F[1][3] = half_delta_angle[EKF_Y];
F[2][0] = half_delta_angle[EKF_Y];
F[2][1] = -half_delta_angle[EKF_X];
F[2][2] = 1.0;
F[2][3] = half_delta_angle[EKF_Z];
F[3][0] = -half_delta_angle[EKF_X];
F[3][1] = -half_delta_angle[EKF_Y];
F[3][2] = -half_delta_angle[EKF_Z];
F[3][3] = 1.0;
/*
* P_(k|k-1) = F*P_(k-1|k-1)*F^T + Q
* K is used instead of F*P_(k-1|k-1)
*/
for(uint16_t i = 0; i < 4; ++i) {
for(uint16_t j = 0; j < 4; ++j) {
K[i][j] = F[i][0] * P[0][j]
+ F[i][1] * P[1][j]
+ F[i][2] * P[2][j]
+ F[i][3] * P[3][j];
}
}
for(uint16_t i = 0; i < 4; ++i) {
for(uint16_t j = 0; j < 4; ++j) {
P[i][j] = K[i][0] * F[j][0]
+ K[i][1] * F[j][1]
+ K[i][2] * F[j][2]
+ K[i][3] * F[j][3];
}
P[i][i] += EKF_Q_ROT;
}
ahrs_get_unit_vector(quaternion, 4);
}