Skip to content

Commit 3992b55

Browse files
feat: add madgwick and mahony AHRS algorithm (#206)
* add madgwick and mahony AHRS algorithm * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix sonar findings --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 09c8786 commit 3992b55

11 files changed

Lines changed: 657 additions & 190 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Refer to the documentation to quickly integrate and utilize the library's signal
2121
| [Controllers](doc/controllers/README.md) | Bang-Bang/Hysteresis, PID, LQR, LQI (Integral/Servo State Feedback), MPC, Saturation, Rate Limiter, Slew-Limited Saturation, Feedforward/2-DOF, Gain-Scheduled Controller, Lead-Lag Compensator, Luenberger Observer |
2222
| [Dynamics](doc/dynamics/README.md) | Euler-Lagrange, Newton-Euler, Recursive Newton-Euler, ABA |
2323
| [Estimators](doc/estimators/README.md) | Linear Regression, Polynomial Fitting, Yule-Walker (offline), Recursive Least Squares, LMS / NLMS Adaptive Filter (online), Consistency Metrics / NEES / NIS |
24-
| [Filters](doc/filters/README.md) | Kalman, Extended Kalman, Unscented Kalman, Alpha-Beta/Alpha-Beta-Gamma, FIR, IIR, Exponential Moving Average, Moving Average, Complementary, Median Filter, CIC (Cascaded Integrator-Comb), Notch/Comb Filter, Savitzky-Golay Filter, Biquad/Second-Order-Section Cascade |
24+
| [Filters](doc/filters/README.md) | Kalman, Extended Kalman, Unscented Kalman, Alpha-Beta/Alpha-Beta-Gamma, FIR, IIR, Exponential Moving Average, Moving Average, Complementary, Median Filter, CIC (Cascaded Integrator-Comb), Notch/Comb Filter, Savitzky-Golay Filter, Biquad/Second-Order-Section Cascade, Madgwick/Mahony AHRS |
2525
| [Kinematics](doc/kinematics/README.md) | Forward Kinematics |
2626
| [Neural Network](doc/neural_network/README.md) | Layers, activations, losses, model |
2727
| [Optimization](doc/optimization/README.md) | Gradient Descent |

ROADMAP.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ Difficulty legend:
2828
| # | Component | Target module | Difficulty |
2929
|----|------------------------------------------------------|---------------------------|------------|
3030
| 30 | Continuous → discrete conversion (`c2d`) | `math` | ★★★★☆ |
31-
| 33 | Madgwick / Mahony AHRS | `filters/active` | ★★★★☆ |
3231
| 34 | Sliding Mode Control (SMC) | `robust_control` (new) | ★★★★☆ |
3332
| 35 | Disturbance Observer (DOB) | `robust_control` (new) | ★★★★☆ |
3433
| 36 | Active Disturbance Rejection Control (ADRC + ESO) | `robust_control` (new) | ★★★★☆ |
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Madgwick / Mahony AHRS Filter
2+
3+
## Overview & Motivation
4+
5+
Any system that needs to know its 3-D orientation in space — a drone, a robot arm, an AR headset, a wearable device — must fuse data from multiple sensors. A **gyroscope** measures angular rate with high bandwidth and low short-term noise, but its integral drifts over time due to bias. An **accelerometer** measures the gravity vector, which gives a long-term absolute reference for pitch and roll, but it is contaminated by vibration. A **magnetometer** provides a heading reference for yaw, but it is affected by magnetic interference.
6+
7+
The Attitude and Heading Reference System (AHRS) filter solves the drift-correction problem in O(1) time per step with a fixed, small memory footprint. Two closely related algorithms — Madgwick's gradient-descent filter and Mahony's passive complementary filter — achieve this by continuously nudging the gyro-integrated quaternion so that the predicted sensor directions match the measured ones. Both are far cheaper to compute than a full quaternion Extended Kalman Filter, making them the standard choice for microcontroller-class attitude estimation.
8+
9+
## Mathematical Theory
10+
11+
### Quaternion State Representation
12+
13+
Orientation is maintained as a unit quaternion $q = [q_w, q_x, q_y, q_z]^T \in \mathbb{H}$, $\|q\| = 1$, mapping from the body frame to the Earth frame. Quaternions avoid the gimbal lock inherent in Euler angles and require fewer trigonometric operations than rotation matrices per integration step.
14+
15+
### Gyro Integration
16+
17+
The pure gyro propagation step integrates the body angular rate $\boldsymbol{\omega} = [\omega_x, \omega_y, \omega_z]^T$ in rad/s:
18+
19+
$$\dot{q} = \frac{1}{2} q \otimes \begin{bmatrix} 0 \\ \boldsymbol{\omega} \end{bmatrix}$$
20+
21+
$$q_{k+1} = q_k + \dot{q} \, T_s$$
22+
23+
followed by renormalization. This is the **predict** step; without a correction it drifts.
24+
25+
### Madgwick: Gradient-Descent Correction
26+
27+
Define the objective function as the alignment error between the predicted sensor directions and the measurements. For the gravity observation:
28+
29+
$$\mathbf{f}(q, \hat{\mathbf{a}}) = R(q)^T \mathbf{g}_{\text{ref}} - \hat{\mathbf{a}}$$
30+
31+
where $\mathbf{g}_{\text{ref}} = [0, 0, 1]^T$ and $\hat{\mathbf{a}}$ is the normalized accelerometer vector. The steepest-descent direction in quaternion space is:
32+
33+
$$\nabla F = J^T \mathbf{f}$$
34+
35+
where $J = \partial \mathbf{f}/\partial q$ is the $3 \times 4$ Jacobian of $\mathbf{f}$ with respect to $q$. This gradient is normalized and subtracted from the gyro-driven rate:
36+
37+
$$\dot{q} = \frac{1}{2} q \otimes \begin{bmatrix} 0 \\ \boldsymbol{\omega} \end{bmatrix} - \beta \, \frac{\nabla F}{\|\nabla F\|}$$
38+
39+
The parameter $\beta$ is the gradient-descent step size; it is set proportional to the expected gyro measurement error in rad/s.
40+
41+
For the magnetometer (MARG mode), the earth frame reference is $\mathbf{b} = [b_x, 0, b_z]^T$, where $b_x$ and $b_z$ are computed by rotating the normalized magnetometer measurement into the Earth frame and zeroing its $y$-component, making the heading reference dip-angle-agnostic. A second objective function $\mathbf{f}_\text{mag}$ and its Jacobian are added to the gradient.
42+
43+
### Mahony: Proportional-Integral Feedback on SO(3)
44+
45+
Rather than gradient descent, Mahony's filter uses a cross-product error:
46+
47+
$$\mathbf{e} = \hat{\mathbf{a}} \times \mathbf{v}$$
48+
49+
where $\mathbf{v}$ is the third column of $R(q)$ (the predicted gravity direction in the body frame). The angular rate is corrected before integration:
50+
51+
$$\boldsymbol{\omega}_c = \boldsymbol{\omega} + K_p \mathbf{e} + \mathbf{b}_\text{est}$$
52+
53+
$$\dot{\mathbf{b}}_\text{est} = K_i \mathbf{e}$$
54+
55+
The integral term $\mathbf{b}_\text{est}$ is a running estimate of the gyro bias; once it converges, the steady-state attitude error is driven to zero even under sustained gyro drift. The proportional gain $K_p$ sets the bandwidth of the correction loop; $K_i$ sets the bias-learning rate.
56+
57+
For MARG mode a magnetometer cross-product error is added to $\mathbf{e}$:
58+
59+
$$\mathbf{e} = \hat{\mathbf{a}} \times \mathbf{v} + \hat{\mathbf{m}} \times \mathbf{w}$$
60+
61+
where $\mathbf{w}$ is the predicted earth-field direction in the body frame from the current tilt.
62+
63+
### Renormalization
64+
65+
Both algorithms renormalize $q$ after every integration step to enforce the unit-norm constraint, compensating for the first-order Euler integration error that would otherwise slowly push $q$ off the unit sphere.
66+
67+
## Complexity Analysis
68+
69+
| Case | Time | Space | Notes |
70+
|------------|------|-------|-----------------------------------------------------------|
71+
| UpdateImu | O(1) | O(1) | Fixed multiply-add count; one inverse-sqrt normalization |
72+
| UpdateMarg | O(1) | O(1) | Two objective/gradient evaluations; same asymptotic cost |
73+
| Memory || 7 T | 4 quaternion + 3 integral bias floats; no buffers or heap |
74+
75+
The fixed cost makes both algorithms suitable for any loop rate the MCU can sustain, from 100 Hz audio-rate IMUs to 8 kHz flight-controller IMUs.
76+
77+
## Step-by-Step Walkthrough
78+
79+
**Scenario:** Quadrotor is hovering level. Gyro measures a small constant bias of 0.05 rad/s on the x-axis. Accelerometer reads $[0, 0, 9.81]$ m/s².
80+
81+
**Madgwick step (simplified):**
82+
83+
1. Normalize accelerometer: $\hat{\mathbf{a}} = [0, 0, 1]$.
84+
2. Predicted gravity from $q \approx [1, 0, 0, 0]$: $\mathbf{v} = [0, 0, 1]$.
85+
3. Objective: $\mathbf{f} = \mathbf{v} - \hat{\mathbf{a}} = [0, 0, 0]$ — no error, gradient is zero.
86+
4. Rate: $\dot{q} = \frac{1}{2} q \otimes [0, \text{bias}, 0, 0]$ — small drift from bias.
87+
5. Integrate: $q$ drifts slightly.
88+
89+
Over time without correction this drift accumulates; with the gradient term driving $\mathbf{f} \to 0$, Madgwick continuously nudges $q$ back to level.
90+
91+
**Mahony step (simplified):**
92+
93+
1. Cross-product error: $\mathbf{e} = [0,0,1] \times [0,0,1] = [0,0,0]$.
94+
2. Integral accumulates: $\mathbf{b}_\text{est} \mathrel{+}= K_i \mathbf{e} \cdot T_s = 0$.
95+
3. Corrected rate: $\boldsymbol{\omega}_c = [0.05, 0, 0] + 0 + 0 = [0.05, 0, 0]$ — still biased.
96+
4. After the cross-product error becomes non-zero (when $q$ drifts from level), the integral term ramps up to cancel the bias, driving attitude error back to zero.
97+
98+
## Pitfalls & Edge Cases
99+
100+
- **Free-fall detection.** When $\|\mathbf{a}\| \approx 0$ (no gravity signal), the accelerometer provides no valid reference. Skipping the correction step preserves attitude at the cost of gyro drift; attempting normalization would divide by near-zero.
101+
- **Magnetic disturbance.** Indoor environments contain ferromagnetic structures and electrical cables. When $\|\mathbf{m}\| \approx 0$ or the magnetometer reading is anomalous, falling back to 6-DOF (IMU-only) mode prevents heading corruption.
102+
- **Beta / Kp tuning.** Too large a $\beta$ or $K_p$ leads to excessive gyro attenuation and overshoot; too small and convergence to a tilt reference is slow. The Madgwick paper recommends $\beta \approx \sqrt{3/4} \cdot \dot{\sigma}_\beta$ where $\dot{\sigma}_\beta$ is the expected gyro measurement error.
103+
- **Quaternion sign ambiguity.** $q$ and $-q$ represent the same rotation. Algorithms that compare orientations must account for this; use the dot product $q_1 \cdot q_2 > 0$ before computing angular error.
104+
- **Large $T_s$.** The first-order Euler integration introduces $O(T_s^2)$ error per step. At slow update rates (below ~50 Hz) higher-order integrators or additional renormalization may be needed.
105+
- **Gimbal lock.** The quaternion representation is singularity-free; however, the Euler angle conversion $R \to (\phi, \theta, \psi)$ loses a degree of freedom at $\theta = \pm 90°$. Use the quaternion directly for any feedback control.
106+
107+
## Variants & Generalizations
108+
109+
| Variant | Key Difference |
110+
|------------------------------------------|-------------------------------------------------------------------------------------------------|
111+
| **6-DOF (IMU-only)** | Accelerometer alone; roll and pitch converge, yaw is unobservable |
112+
| **9-DOF (MARG)** | Adds magnetometer; all three angles converge given a non-disturbed field |
113+
| **Extended Kalman AHRS** | Treats noise covariances explicitly; heavier but allows systematic tuning via $Q$/$R$ matrices |
114+
| **Multiplicative EKF (MEKF)** | Kalman update on the error quaternion to preserve unit-norm; best-in-class accuracy, high cost |
115+
| **Gradient-descent with adaptive β** | Adjusts $\beta$ based on the magnitude of the gradient, reducing transient overshoot at startup |
116+
| **Second-order Runge-Kutta integration** | Reduces integration error at low update rates at the cost of one extra function evaluation |
117+
118+
## Applications
119+
120+
- **Unmanned aerial vehicles (UAVs):** Attitude stabilization loop runs at 400–8000 Hz; the O(1) cost is critical.
121+
- **Prosthetic limbs and rehabilitation robotics:** Accurate joint angle estimation from a wrist-worn IMU.
122+
- **Augmented and virtual reality headsets:** Sub-millisecond latency attitude updates for display lag minimization.
123+
- **Inertial navigation:** Dead-reckoning orientation prior to GPS fix.
124+
- **Industrial motion capture:** Body segment tracking with arrays of MEMS IMUs.
125+
- **Sports science wearables:** Running gait, golf swing, and rowing stroke angle analysis.
126+
127+
## Connections to Other Algorithms
128+
129+
| Algorithm | Relationship |
130+
|-------------------------------------------------------------|-------------------------------------------------------------------------------|
131+
| [Complementary Filter](../ComplementaryFilter.md) | The scalar 1-D ancestor; Madgwick/Mahony extend the idea to quaternion SO(3) |
132+
| [Extended Kalman Filter](../active/ExtendedKalmanFilter.md) | The probabilistic alternative; heavier but allows noise covariance estimation |
133+
| [Quaternion](../../math/Quaternion.md) | The state representation shared by all three-axis attitude estimators |
134+
135+
## References & Further Reading
136+
137+
- Madgwick, S., "An Efficient Orientation Filter for Inertial and Inertial/Magnetic Sensor Arrays," University of Bristol, 2010.
138+
- Mahony, R., Hamel, T., Pflimlin, J.-M., "Nonlinear Complementary Filters on the Special Orthogonal Group," *IEEE Transactions on Automatic Control*, 53(5), 1203–1218, 2008.
139+
- Diebel, J., "Representing Attitude: Euler Angles, Unit Quaternions, and Rotation Vectors," Stanford University, 2006.
140+
- Solin, A., Kannala, J., Rahtu, E., "Inertial Odometry on Handheld Smartphones," *FUSION 2018*.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#include "numerical/filters/active/AhrsMadgwickMahony.hpp"
2+
3+
namespace filters
4+
{
5+
template class AhrsFilter<float, AhrsMode::Madgwick>;
6+
template class AhrsFilter<float, AhrsMode::Mahony>;
7+
}

0 commit comments

Comments
 (0)