Skip to main content

rust_gnc/filters/
complementary.rs

1//! # Complementary Filter
2//! 
3//! This module implements a first-order Complementary Filter, a computationally 
4//! efficient sensor fusion algorithm used to estimate vehicle attitude.
5//! 
6//! ### Theory of Operation
7//! The filter functions as a combination of a **High-Pass Filter** for the 
8//! gyroscope and a **Low-Pass Filter** for the accelerometer:
9//! 1. **Gyroscope integration** provides high-frequency tracking but suffers 
10//!    from low-frequency bias/drift.
11//! 2. **Accelerometer reference** provides stable low-frequency absolute 
12//!    orientation but is susceptible to high-frequency vibration/noise.
13use crate::filters::{Filter, InertialInput};
14use crate::units::Radians;
15
16/// A first-order Complementary Filter for attitude estimation.
17/// 
18/// The internal state represents the filtered angle, blending integrated 
19/// angular rates with an absolute reference.
20pub struct ComplementaryFilter {
21    alpha: f32, 
22    state: Radians,
23}
24
25impl ComplementaryFilter {
26    /// Alpha is the trust coefficient for the gyroscope measurement. A value of 1.0 means we trust the gyro completely, while 0.0 means we trust the accelerometer completely.
27    /// Typically set to 0.98 for flight controllers.
28    pub fn new(alpha: f32) -> Self {
29        Self {
30            alpha: alpha.clamp(0.0, 1.0),
31            state: Radians(0.0),
32        }
33    }
34
35    /// Dynamically updates the trust coefficient.
36    /// 
37    /// Useful for adaptive filtering where trust in the accelerometer 
38    /// might decrease during high-G maneuvers.
39    pub fn set_alpha(&mut self, alpha: f32) {
40        self.alpha = alpha.clamp(0.0, 1.0);
41    }
42}
43
44impl Filter for ComplementaryFilter {
45    type Input = InertialInput; // (gyro_rate, accel_angle)
46    type Output = Radians; // Estimated angle
47
48    fn new(alpha: f32) -> Self {
49        Self {
50            alpha: alpha.clamp(0.0, 1.0),
51            state: Radians(0.0),
52        }
53    }
54
55    /// Processes a new sensor sample to update the attitude estimate.
56    /// 
57    /// This method implements the discrete-time fusion equation:
58    /// `θ = α * (θ + ω * dt) + (1 - α) * a`
59    /// 
60    /// ### Mathematical Steps
61    /// 1. **Prediction**: Integrate the angular rate (gyro) into the current state.
62    /// 2. **Correction**: Apply the reference angle (accel) to cancel integration drift.
63    fn update(&mut self, input: Self::Input, dt: f32) -> Self::Output {
64        let integrated_rate = self.state.0 + input.rate.0 * dt; // Integrate gyro rate to get angle
65        let filtered_angle = self.alpha * integrated_rate + ((1.0 - self.alpha) * input.reference.0); // Blend with accelerometer angle
66
67        self.state = Radians(filtered_angle);
68        self.state
69    }
70
71    /// Resets the filter's internal state to zero.
72    /// 
73    /// Should be called during the 'Disarm' phase to ensure the next 
74    /// takeoff sequence starts from a clean level-reference.
75    fn reset(&mut self) {
76        self.state = Radians(0.0);
77    }
78}