Skip to content

Commit e4809cd

Browse files
tridgezalice
andauthored
rangefinder complementary filter for 4.0 (#176)
* Filter: added reset with value to LowPassFilter2p * Filter: added a ComplementaryFilter library * Copter: use a complementary filter for rangefinder data this should reduce the impact of noise in the rangefinder data. Filter cutoff frequency is low in loiter and higher otherwise Co-authored-by: Alice Zielman <[email protected]>
1 parent da325df commit e4809cd

File tree

9 files changed

+205
-12
lines changed

9 files changed

+205
-12
lines changed

ArduCopter/APM_Config.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55

66
#define ARMING_DELAY_SEC 3.0f
77

8-
#define RANGEFINDER_WPNAV_FILT_HZ 3.0f
8+
#define RANGEFINDER_WPNAV_FILT_MIN_HZ 0.05
9+
#define RANGEFINDER_WPNAV_FILT_MAX_HZ 0.5
910

1011
#define LAND_DETECTOR_TRIGGER_SEC 0.5f
1112

ArduCopter/Copter.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
#include <AP_Motors/AP_Motors.h> // AP Motors library
5050
#include <AP_Stats/AP_Stats.h> // statistics library
5151
#include <Filter/Filter.h> // Filter library
52+
#include <Filter/ComplementaryFilter.h>
5253
#include <AP_Airspeed/AP_Airspeed.h> // needed for AHRS build
5354
#include <AP_Vehicle/AP_Vehicle.h> // needed for AHRS build
5455
#include <AP_InertialNav/AP_InertialNav.h> // ArduPilot Mega inertial navigation library
@@ -281,7 +282,8 @@ class Copter : public AP_Vehicle {
281282
int16_t alt_cm; // tilt compensated altitude (in cm) from rangefinder
282283
float inertial_alt_cm; // inertial alt at time of last rangefinder sample
283284
uint32_t last_healthy_ms;
284-
LowPassFilterFloat alt_cm_filt; // altitude filter
285+
ComplementaryFilter alt_cm_filt; // altitude filter
286+
float filter_hz;
285287
int16_t alt_cm_glitch_protected; // last glitch protected altitude
286288
int8_t glitch_count; // non-zero number indicates rangefinder is glitching
287289
uint32_t glitch_cleared_ms; // system time glitch cleared

ArduCopter/mode.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,9 @@ class ModeAuto : public Mode {
366366
// for GCS_MAVLink to call:
367367
bool do_guided(const AP_Mission::Mission_Command& cmd);
368368

369+
// are we in a loiter?
370+
bool in_loiter(void) const { return loiter_time != 0; }
371+
369372
AP_Mission mission{
370373
FUNCTOR_BIND_MEMBER(&ModeAuto::start_command, bool, const AP_Mission::Mission_Command &),
371374
FUNCTOR_BIND_MEMBER(&ModeAuto::verify_command, bool, const AP_Mission::Mission_Command &),
@@ -1470,4 +1473,4 @@ class ModeAutorotate : public Mode {
14701473
void warning_message(uint8_t message_n); //Handles output messages to the terminal
14711474

14721475
};
1473-
#endif
1476+
#endif

ArduCopter/sensors.cpp

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,44 @@ void Copter::read_rangefinder(void)
8383
if (rf_state.alt_healthy) {
8484
if (timed_out) {
8585
// reset filter if we haven't used it within the last second
86-
rf_state.alt_cm_filt.reset(rf_state.alt_cm);
86+
rf_state.alt_cm_filt.reset();
8787
} else {
88-
rf_state.alt_cm_filt.apply(rf_state.alt_cm, 0.05f);
88+
/*
89+
Apply a complementary filter to the rangefinder data, using the
90+
inertial altitude for the high frequency component and the
91+
rangefinder data for the low frequency component.
92+
93+
When we are loitering shift the time constant of the
94+
complementary filter towards a much lower frequency to filter
95+
out even quite long period noise from the rangefinder. When not
96+
loitering shift back to a shorter time period to handle changes
97+
in terrain
98+
*/
99+
float target_filter_hz = RANGEFINDER_WPNAV_FILT_MAX_HZ;
100+
if (control_mode == Mode::Number::AUTO &&
101+
mode_auto.mission.get_current_nav_cmd().id == MAV_CMD_NAV_WAYPOINT &&
102+
mode_auto.in_loiter()) {
103+
// we are holding position at a waypoint, slew the time constant
104+
target_filter_hz = RANGEFINDER_WPNAV_FILT_MIN_HZ;
105+
}
106+
107+
if (!is_equal(rangefinder_state.filter_hz, target_filter_hz)) {
108+
rf_state.alt_cm_filt.set_cutoff_frequency(target_filter_hz);
109+
rf_state.alt_cm_filt.reset();
110+
rangefinder_state.filter_hz = target_filter_hz;
111+
}
112+
113+
rf_state.alt_cm_filt.apply(rf_state.alt_cm, inertial_nav.get_altitude(), AP_HAL::micros());
114+
115+
// temporary debug log message to look at the
116+
// performance of the complementary filter
117+
AP::logger().Write("RF2", "TimeUS,LF,HF,Out,FHz", "Qffff",
118+
AP_HAL::micros64(),
119+
rf_state.alt_cm*0.01,
120+
inertial_nav.get_altitude()*0.01,
121+
rf_state.alt_cm_filt.get()*0.01,
122+
rf_state.filter_hz);
123+
89124
}
90125
rf_state.last_healthy_ms = now;
91126
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
This program is free software: you can redistribute it and/or modify
3+
it under the terms of the GNU General Public License as published by
4+
the Free Software Foundation, either version 3 of the License, or
5+
(at your option) any later version.
6+
7+
This program is distributed in the hope that it will be useful,
8+
but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
GNU General Public License for more details.
11+
12+
You should have received a copy of the GNU General Public License
13+
along with this program. If not, see <http://www.gnu.org/licenses/>.
14+
*/
15+
16+
#include "ComplementaryFilter.h"
17+
18+
/*
19+
complementary filter. Used to combine a reading that is long term
20+
stable but has high freq noise with another reading that has less
21+
high frequency noise and poor long term stability.
22+
*/
23+
24+
/*
25+
set crossover frequency between low frequency and high frequency
26+
components
27+
*/
28+
void ComplementaryFilter::set_cutoff_frequency(float freq_hz)
29+
{
30+
cutoff_freq = freq_hz;
31+
}
32+
33+
/*
34+
reset the filter to initial values
35+
*/
36+
void ComplementaryFilter::reset()
37+
{
38+
lp.reset();
39+
hp.reset();
40+
}
41+
42+
/*
43+
apply a low freqency and high freqency input to give a complementary
44+
filter result where signal below the cutoff comes from the low_freq
45+
input and above the cutoff from high freqency input. We take a
46+
timestamp on each input as the input data may vary somewhat in
47+
frequency
48+
*/
49+
float ComplementaryFilter::apply(float low_freq, float high_freq, uint32_t time_us)
50+
{
51+
if (!is_positive(cutoff_freq)) {
52+
reset();
53+
return low_freq;
54+
}
55+
const uint32_t dt_us = time_us - last_sample_us;
56+
57+
if (dt_us > 1e6) {
58+
// if we have not updated for 1s then assume we've had a
59+
// sensor outage and reset
60+
reset();
61+
}
62+
last_sample_us = time_us;
63+
64+
const float dt = MIN(dt_us * 1.0e-6, 1);
65+
66+
// keep a low pass filter of the sample rate. Rapidly changing
67+
// sample frequency in bi-quad filters leads to very nasty spikes
68+
if (!is_positive(sample_freq)) {
69+
sample_freq = 1.0/dt;
70+
} else {
71+
sample_freq = 0.99 * sample_freq + 0.01 * (1.0/dt);
72+
}
73+
74+
lp.compute_params(sample_freq, cutoff_freq, params);
75+
76+
float hp_out = hp.apply(high_freq, params);
77+
float lp_out = lp.apply(low_freq, params);
78+
79+
out = (high_freq - hp_out) + lp_out;
80+
81+
return out;
82+
}
83+
84+
/*
85+
return the current value
86+
*/
87+
float ComplementaryFilter::get(void)
88+
{
89+
return out;
90+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
This program is free software: you can redistribute it and/or modify
3+
it under the terms of the GNU General Public License as published by
4+
the Free Software Foundation, either version 3 of the License, or
5+
(at your option) any later version.
6+
7+
This program is distributed in the hope that it will be useful,
8+
but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
GNU General Public License for more details.
11+
12+
You should have received a copy of the GNU General Public License
13+
along with this program. If not, see <http://www.gnu.org/licenses/>.
14+
*/
15+
16+
#pragma once
17+
18+
#include <AP_Math/AP_Math.h>
19+
#include "LowPassFilter2p.h"
20+
21+
/*
22+
complementary filter. Used to combine a reading that is long term
23+
stable but has high freq noise with another reading that has less
24+
high frequency noise and poor long term stability.
25+
*/
26+
27+
class ComplementaryFilter
28+
{
29+
public:
30+
void set_cutoff_frequency(float freq_hz);
31+
void reset();
32+
float apply(float low_freq, float high_freq, uint32_t time_us);
33+
float get(void);
34+
35+
private:
36+
uint32_t last_sample_us;
37+
float cutoff_freq;
38+
float sample_freq;
39+
// use 2-pole low pass filters to get a reasonably sharp cutoff
40+
DigitalBiquadFilter<float>::biquad_params params;
41+
DigitalBiquadFilter<float> lp, hp;
42+
float out;
43+
};
44+
45+

libraries/Filter/LowPassFilter2p.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ T DigitalBiquadFilter<T>::apply(const T &sample, const struct biquad_params &par
1717
return sample;
1818
}
1919

20+
if (!initialised) {
21+
reset(sample, params);
22+
initialised = true;
23+
}
24+
2025
T delay_element_0 = sample - _delay_element_1 * params.a1 - _delay_element_2 * params.a2;
2126
T output = delay_element_0 * params.b0 + _delay_element_1 * params.b1 + _delay_element_2 * params.b2;
2227

@@ -29,6 +34,13 @@ T DigitalBiquadFilter<T>::apply(const T &sample, const struct biquad_params &par
2934
template <class T>
3035
void DigitalBiquadFilter<T>::reset() {
3136
_delay_element_1 = _delay_element_2 = T();
37+
initialised = false;
38+
}
39+
40+
template <class T>
41+
void DigitalBiquadFilter<T>::reset(const T &value, const struct biquad_params &params) {
42+
_delay_element_1 = _delay_element_2 = value * (1.0 / (1 + params.a1 + params.a2));
43+
initialised = true;
3244
}
3345

3446
template <class T>
@@ -99,6 +111,11 @@ void LowPassFilter2p<T>::reset(void) {
99111
return _filter.reset();
100112
}
101113

114+
template <class T>
115+
void LowPassFilter2p<T>::reset(const T &value) {
116+
return _filter.reset(value, _params);
117+
}
118+
102119
/*
103120
* Make an instances
104121
* Otherwise we have to move the constructor implementations to the header file :P
@@ -108,3 +125,4 @@ template class LowPassFilter2p<long>;
108125
template class LowPassFilter2p<float>;
109126
template class LowPassFilter2p<Vector2f>;
110127
template class LowPassFilter2p<Vector3f>;
128+
template class DigitalBiquadFilter<float>;

libraries/Filter/LowPassFilter2p.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ class DigitalBiquadFilter {
3939

4040
T apply(const T &sample, const struct biquad_params &params);
4141
void reset();
42+
void reset(const T &value, const struct biquad_params &params);
4243
static void compute_params(float sample_freq, float cutoff_freq, biquad_params &ret);
4344

4445
private:
4546
T _delay_element_1;
4647
T _delay_element_2;
48+
bool initialised;
4749
};
4850

4951
template <class T>
@@ -59,6 +61,7 @@ class LowPassFilter2p {
5961
float get_sample_freq(void) const;
6062
T apply(const T &sample);
6163
void reset(void);
64+
void reset(const T &value);
6265

6366
protected:
6467
struct DigitalBiquadFilter<T>::biquad_params _params;

libraries/Filter/examples/LowPassFilter2p/LowPassFilter2p.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,12 @@ void loop()
2626
for(int16_t i = 0; i < 300; i++ ) {
2727

2828
// new data value
29-
const float new_value = sinf((float)i * 2 * M_PI * 5 / 50.0f); // 5hz
30-
31-
// output to user
32-
hal.console->printf("applying: %6.4f", (double)new_value);
33-
29+
const float new_value = 17 + sinf((float)i * 2 * M_PI * 5 / 50.0f); // 5hz
3430
// apply new value and retrieved filtered result
3531
const float filtered_value = low_pass_filter.apply(new_value);
3632

37-
// display results
38-
hal.console->printf("\toutput: %6.4f\n", (double)filtered_value);
33+
// output to user
34+
hal.console->printf("applying: %6.4f -> %6.4f\n", (double)new_value, filtered_value);
3935

4036
hal.scheduler->delay(10);
4137
}

0 commit comments

Comments
 (0)