Skip to content

Commit 258aa6f

Browse files
艾多多艾多多
authored andcommitted
flight/pid: add D-term pre-differentiation LPF
Differentiation amplifies high-frequency noise by f_loop/f_cutoff (~9x at 1kHz loop). Add an optional PT1 pre-filter applied before differentiation to reduce this amplification to ~3.6x, at the cost of only +0.6ms latency at the default 250Hz cutoff. This is inspired by Betaflight's architecture where gyro data is filtered before the derivative is computed. New parameter: dterm_lpf2_hz (default 250, 0 = disabled) - 5-inch builds: 250Hz recommended - 7-inch builds: 200Hz recommended Benefits: - D gain headroom increases ~15-25% - Improved propwash rejection - Better altitude/position hold disturbance rejection The added latency is negligible for the position/altitude loop (bandwidth <5Hz). Backward compatible: set dterm_lpf2_hz=0 to restore stock behavior. Closes: #11463
1 parent 76809d7 commit 258aa6f

3 files changed

Lines changed: 31 additions & 5 deletions

File tree

src/main/fc/settings.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2034,6 +2034,12 @@ groups:
20342034
default_value: 110
20352035
min: 0
20362036
max: 500
2037+
- name: dterm_lpf2_hz
2038+
description: "Dterm pre-differentiation LPF cutoff (Hz). Filters gyro BEFORE differentiation to reduce noise amplification (Betaflight-style). Higher = less delay, more noise. 0 = disabled. Recommended: 250 for 5in, 200 for 7in."
2039+
default_value: 250
2040+
field: dterm_lpf2_hz
2041+
min: 0
2042+
max: 500
20372043
- name: dterm_lpf_type
20382044
description: "Defines the type of stage 1 D-term LPF filter. Possible values: `PT1`, `BIQUAD`, `PT2`, `PT3`."
20392045
default_value: "PT2"

src/main/flight/pid.c

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ typedef struct {
9797
rateLimitFilter_t axisAccelFilter;
9898
pt1Filter_t ptermLpfState;
9999
filter_t dtermLpfState;
100+
pt1Filter_t dtermLpf2State; // Pre-differentiation LPF (BF-style: filter before diff to reduce noise amplification)
101+
float previousFilteredGyroRate; // Stores filtered gyro for pre-diff architecture
100102

101103
float stickPosition;
102104

@@ -160,6 +162,7 @@ static EXTENDED_FASTRAM float dBoostMaxAtAlleceleration;
160162
static EXTENDED_FASTRAM uint8_t yawLpfHz;
161163
static EXTENDED_FASTRAM float motorItermWindupPoint;
162164
static EXTENDED_FASTRAM float antiWindupScaler;
165+
static EXTENDED_FASTRAM uint16_t dtermLpf2Hz;
163166
#ifdef USE_ANTIGRAVITY
164167
static EXTENDED_FASTRAM float iTermAntigravityGain;
165168
#endif
@@ -262,6 +265,7 @@ PG_RESET_TEMPLATE(pidProfile_t, pidProfile,
262265

263266
.dterm_lpf_type = SETTING_DTERM_LPF_TYPE_DEFAULT,
264267
.dterm_lpf_hz = SETTING_DTERM_LPF_HZ_DEFAULT,
268+
.dterm_lpf2_hz = 250,
265269
.yaw_lpf_hz = SETTING_YAW_LPF_HZ_DEFAULT,
266270

267271
.itermWindupPointPercent = SETTING_ITERM_WINDUP_DEFAULT,
@@ -331,6 +335,14 @@ bool pidInitFilters(void)
331335
initFilter(pidProfile()->dterm_lpf_type, &pidState[axis].dtermLpfState, pidProfile()->dterm_lpf_hz, refreshRate);
332336
}
333337

338+
dtermLpf2Hz = pidProfile()->dterm_lpf2_hz;
339+
if (dtermLpf2Hz > 0) {
340+
for (int axis = 0; axis < 3; axis++) {
341+
pt1FilterInit(&pidState[axis].dtermLpf2State, dtermLpf2Hz, US2S(refreshRate));
342+
pidState[axis].previousFilteredGyroRate = 0.0f;
343+
}
344+
}
345+
334346
for (int i = 0; i < XYZ_AXIS_COUNT; i++) {
335347
pt1FilterInit(&windupLpf[i], pidProfile()->iterm_relax_cutoff, US2S(refreshRate));
336348
}
@@ -771,18 +783,25 @@ static float applyDBoost(pidState_t *pidState, float dT) {
771783
#endif
772784

773785
static float dTermProcess(pidState_t *pidState, float currentRateTarget, float dT, float dT_inv) {
774-
// Calculate new D-term
775786
float newDTerm = 0;
776787
if (pidState->kD == 0) {
777-
// optimisation for when D is zero, often used by YAW axis
778788
newDTerm = 0;
779789
} else {
780-
float delta = pidState->previousRateGyro - pidState->gyroRate;
790+
float delta;
791+
if (dtermLpf2Hz > 0) {
792+
// Pre-filter gyro before differentiation (Betaflight-style).
793+
// Differentiation amplifies noise by f_loop/f_cutoff (~9x at 1kHz);
794+
// filtering first reduces this to ~3.6x with only +0.6ms latency at 250Hz.
795+
const float filteredGyro = pt1FilterApply(&pidState->dtermLpf2State, pidState->gyroRate);
796+
delta = pidState->previousFilteredGyroRate - filteredGyro;
797+
pidState->previousFilteredGyroRate = filteredGyro;
798+
} else {
799+
delta = pidState->previousRateGyro - pidState->gyroRate;
800+
}
781801

782802
delta = dTermLpfFilterApplyFn((filter_t *) &pidState->dtermLpfState, delta);
783803

784-
// Calculate derivative
785-
newDTerm = delta * (pidState->kD * dT_inv) * applyDBoost(pidState, currentRateTarget, dT, dT_inv);
804+
newDTerm = delta * (pidState->kD * dT_inv) * applyDBoost(pidState, currentRateTarget, dT, dT_inv);
786805
}
787806
return(newDTerm);
788807
}

src/main/flight/pid.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ typedef struct pidProfile_s {
103103

104104
uint8_t dterm_lpf_type; // Dterm LPF type: PT1, BIQUAD
105105
uint16_t dterm_lpf_hz;
106+
uint16_t dterm_lpf2_hz; // Dterm second stage LPF (pre-differentiation, like Betaflight)
106107

107108
uint8_t yaw_lpf_hz;
108109

0 commit comments

Comments
 (0)