Skip to content

Commit 39a5198

Browse files
committed
Fix computational error in quaternion/Madgwick implementation
A few errors were found in the zscilib implementation the Madgwick filter which this pull request addresses. The python AHRS library was used as a reference for comparison, and the outputs of this implementation were compared with AHRS. The following discrepancies were found: 1) In quaternions.c, the order of operations in zsl_quat_from_ang_vel is reversed. The orientation of the Earth frame relative to the sensor frame must be computed as qw, not wq. One can refer to the AHRS documentation at https://ahrs.readthedocs.io/en/latest/filters/madgwick.html for more details: https://github.com/user-attachments/assets/9d2f1d98-33c3-4d3d-9729-f89e9d12206c 2) In the Magwick algorithm, we found that the objective function f is computed incorrectly due to an incorrect rotation to the earth frame: https://github.com/user-attachments/assets/920afc04-73ac-42f3-92b2-722396107127 We fix this and compute f explicitly as per equation 25 of the Madgwick paper. This pull request aligns the implementation with that of AHRS and includes the equation numbers from the original Madgwick paper so that it is easy to troubleshoot. Signed-off-by: Ismail Degani <deganii@gmail.com>
1 parent ee1b287 commit 39a5198

File tree

2 files changed

+40
-31
lines changed

2 files changed

+40
-31
lines changed

src/orientation/fusion/madgwick.c

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021 Marti Riba Pons
2+
* Copyright (c) 2021 Marti Riba Pons
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -38,31 +38,33 @@ static int zsl_fus_madgwick_imu(struct zsl_vec *g, struct zsl_vec *a,
3838
ZSL_VECTOR_DEF(grad, 4);
3939
zsl_vec_init(&grad);
4040

41+
42+
/* AHRS Convention Variables (https://github.com/Mayitzin/ahrs/) */
43+
struct zsl_quat q_dot;
44+
struct zsl_quat q_gyr;
45+
46+
q_gyr.r = 0.0;
47+
q_gyr.i = g->data[0];
48+
q_gyr.j = g->data[1];
49+
q_gyr.k = g->data[2];
50+
51+
/* AHRS: qDot = 0.5 * q_prod(q, [0, *gyr]) # (eq. 12) */
52+
zsl_quat_mult(q, &q_gyr, &q_dot);
53+
zsl_quat_scale_d(&q_dot, 0.5);
54+
4155
/* Continue with the calculations only if the data from the accelerometer
4256
* is valid (non zero). */
4357
if ((a != NULL) && ZSL_ABS(zsl_vec_norm(a)) > 1E-6) {
4458

4559
/* Normalize the acceleration vector. */
4660
zsl_vec_to_unit(a);
4761

48-
/* Define the normalized quaternion of acceleration on the earth's
49-
* reference frame, which only has a vertical component. */
50-
struct zsl_quat qa = {
51-
.r = 0.0,
52-
.i = 0.0,
53-
.j = 0.0,
54-
.k = 1.0
55-
};
56-
57-
/* Calculate the function f by using the input quaternion to rotate the
58-
* normalised acceleration vector in the earth's reference frame, and
59-
* then substracting the acceleration vector. */
62+
/* Calculate the objective function f by simplifying the sensor field s to
63+
* g=[0,0,0,1] and subtracting the acceleration vector. (Equation 25) */
6064
ZSL_MATRIX_DEF(f, 3, 1);
61-
struct zsl_quat qaq;
62-
zsl_quat_rot(q, &qa, &qaq);
63-
f.data[0] = qaq.i - a->data[0];
64-
f.data[1] = qaq.j - a->data[1];
65-
f.data[2] = qaq.k - a->data[2];
65+
f.data[0] = 2.0*(q->i*q->k - q->r *q->j) - a->data[0];
66+
f.data[1] = 2.0*(q->r*q->i + q->j *q->k) - a->data[1];
67+
f.data[2] = 2.0*(0.5 - q->i*q->i - q->j *q->j) - a->data[2];
6668

6769
/* Define and compute the transpose of the Jacobian matrix of the
6870
* function f. */
@@ -84,14 +86,21 @@ static int zsl_fus_madgwick_imu(struct zsl_vec *g, struct zsl_vec *a,
8486
/* Normalize the gradient vector. */
8587
zsl_vec_from_arr(&grad, jtf.data);
8688
zsl_vec_to_unit(&grad);
89+
90+
/* AHRS: qDot -= self.gain*gradient # (eq. 33) */
91+
q_dot.r -= *beta * grad.data[0];
92+
q_dot.i -= *beta * grad.data[1];
93+
q_dot.j -= *beta * grad.data[2];
94+
q_dot.k -= *beta * grad.data[3];
8795
}
8896

89-
/* Update the input quaternion with a modified quaternion integration. */
90-
zsl_quat_from_ang_vel(g, q, 1.0 / zsl_fus_madg_freq, q);
91-
q->r -= (1.0 / zsl_fus_madg_freq) * (*beta * grad.data[0]);
92-
q->i -= (1.0 / zsl_fus_madg_freq) * (*beta * grad.data[1]);
93-
q->j -= (1.0 / zsl_fus_madg_freq) * (*beta * grad.data[2]);
94-
q->k -= (1.0 / zsl_fus_madg_freq) * (*beta * grad.data[3]);
97+
/* Update the input quaternion
98+
* AHRS: q += qDot*self.Dt # (eq. 13) */
99+
zsl_real_t dt = 1.0 / zsl_fus_madg_freq;
100+
q -> r += q_dot.r * dt;
101+
q -> i += q_dot.i * dt;
102+
q -> j += q_dot.j * dt;
103+
q -> k += q_dot.k * dt;
95104

96105
/* Normalize the output quaternion. */
97106
zsl_quat_to_unit_d(q);

src/orientation/quaternions.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ int zsl_quat_from_ang_vel(struct zsl_vec *w, struct zsl_quat *qin,
460460

461461
struct zsl_quat qin2;
462462
struct zsl_quat qout2;
463-
struct zsl_quat wq;
463+
struct zsl_quat qw;
464464
struct zsl_quat wquat = {
465465
.r = 0.0,
466466
.i = w->data[0],
@@ -469,12 +469,12 @@ int zsl_quat_from_ang_vel(struct zsl_vec *w, struct zsl_quat *qin,
469469
};
470470

471471
zsl_quat_to_unit(qin, &qin2);
472-
zsl_quat_mult(&wquat, &qin2, &wq);
473-
zsl_quat_scale_d(&wq, 0.5 * t);
474-
qout2.r = qin2.r + wq.r;
475-
qout2.i = qin2.i + wq.i;
476-
qout2.j = qin2.j + wq.j;
477-
qout2.k = qin2.k + wq.k;
472+
zsl_quat_mult(&qin2, &wquat, &qw);
473+
zsl_quat_scale_d(&qw, 0.5 * t);
474+
qout2.r = qin2.r + qw.r;
475+
qout2.i = qin2.i + qw.i;
476+
qout2.j = qin2.j + qw.j;
477+
qout2.k = qin2.k + qw.k;
478478

479479
zsl_quat_to_unit(&qout2, qout);
480480

0 commit comments

Comments
 (0)