Skip to content

Commit 4d5207e

Browse files
authored
Merge pull request #9905 from iNavFlight/dzikuvx-fixed-wing-pid-dampener
I-Term Lock for Fixed Wing
2 parents 2f2330e + fc9457f commit 4d5207e

File tree

6 files changed

+127
-37
lines changed

6 files changed

+127
-37
lines changed

docs/Settings.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,13 +1302,33 @@ Fixed-wing rate stabilisation I-gain for YAW
13021302

13031303
---
13041304

1305-
### fw_iterm_limit_stick_position
1305+
### fw_iterm_lock_engage_threshold
13061306

1307-
Iterm is not allowed to grow when stick position is above threshold. This solves the problem of bounceback or followthrough when full stick deflection is applied on poorely tuned fixed wings. In other words, stabilization is partialy disabled when pilot is actively controlling the aircraft and active when sticks are not touched. `0` mean stick is in center position, `1` means it is fully deflected to either side
1307+
Defines error rate (in percents of max rate) when Iterm Lock is engaged when sticks are release. Iterm Lock will stay active until error drops below this number
13081308

13091309
| Default | Min | Max |
13101310
| --- | --- | --- |
1311-
| 0.5 | 0 | 1 |
1311+
| 10 | 5 | 25 |
1312+
1313+
---
1314+
1315+
### fw_iterm_lock_rate_threshold
1316+
1317+
Defines rate percentage when full P I and D attenuation should happen. 100 disables Iterm Lock for P and D term
1318+
1319+
| Default | Min | Max |
1320+
| --- | --- | --- |
1321+
| 40 | 10 | 100 |
1322+
1323+
---
1324+
1325+
### fw_iterm_lock_time_max_ms
1326+
1327+
Defines max time in milliseconds for how long ITerm Lock will shut down Iterm after sticks are release
1328+
1329+
| Default | Min | Max |
1330+
| --- | --- | --- |
1331+
| 500 | 100 | 1000 |
13121332

13131333
---
13141334

src/main/common/maths.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -516,9 +516,25 @@ bool sensorCalibrationSolveForScale(sensorCalibrationState_t * state, float resu
516516
return sensorCalibrationValidateResult(result);
517517
}
518518

519+
float gaussian(const float x, const float mu, const float sigma) {
520+
return exp(-pow((double)(x - mu), 2) / (2 * pow((double)sigma, 2)));
521+
}
522+
519523
float bellCurve(const float x, const float curveWidth)
520524
{
521-
return powf(M_Ef, -sq(x) / (2.0f * sq(curveWidth)));
525+
return gaussian(x, 0.0f, curveWidth);
526+
}
527+
528+
/**
529+
* @brief Calculate the attenuation of a value using a Gaussian function.
530+
* Retuns 1 for input 0 and ~0 for input width.
531+
* @param input The input value.
532+
* @param width The width of the Gaussian function.
533+
* @return The attenuation of the input value.
534+
*/
535+
float attenuation(const float input, const float width) {
536+
const float sigma = width / 2.35482f; // Approximately width / sqrt(2 * ln(2))
537+
return gaussian(input, 0.0f, sigma);
522538
}
523539

524540
float fast_fsqrtf(const float value) {

src/main/common/maths.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ float acos_approx(float x);
195195
void arraySubInt32(int32_t *dest, int32_t *array1, int32_t *array2, int count);
196196

197197
float bellCurve(const float x, const float curveWidth);
198+
float attenuation(const float input, const float width);
199+
float gaussian(const float x, const float mu, const float sigma);
198200
float fast_fsqrtf(const float value);
199201
float calc_length_pythagorean_2D(const float firstElement, const float secondElement);
200202
float calc_length_pythagorean_3D(const float firstElement, const float secondElement, const float thirdElement);

src/main/fc/settings.yaml

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1934,12 +1934,6 @@ groups:
19341934
field: fixedWingCoordinatedPitchGain
19351935
min: 0
19361936
max: 2
1937-
- name: fw_iterm_limit_stick_position
1938-
description: "Iterm is not allowed to grow when stick position is above threshold. This solves the problem of bounceback or followthrough when full stick deflection is applied on poorely tuned fixed wings. In other words, stabilization is partialy disabled when pilot is actively controlling the aircraft and active when sticks are not touched. `0` mean stick is in center position, `1` means it is fully deflected to either side"
1939-
default_value: 0.5
1940-
field: fixedWingItermLimitOnStickPosition
1941-
min: 0
1942-
max: 1
19431937
- name: fw_yaw_iterm_freeze_bank_angle
19441938
description: "Yaw Iterm is frozen when bank angle is above this threshold [degrees]. This solves the problem of the rudder counteracting turns by partially disabling yaw stabilization when making banked turns. Setting to 0 (the default) disables this feature. Only applies when autopilot is not active and TURN ASSIST is disabled."
19451939
default_value: 0
@@ -2233,6 +2227,24 @@ groups:
22332227
field: fixedWingLevelTrimGain
22342228
min: 0
22352229
max: 20
2230+
- name: fw_iterm_lock_time_max_ms
2231+
description: Defines max time in milliseconds for how long ITerm Lock will shut down Iterm after sticks are release
2232+
default_value: 500
2233+
field: fwItermLockTimeMaxMs
2234+
min: 100
2235+
max: 1000
2236+
- name: fw_iterm_lock_rate_threshold
2237+
description: Defines rate percentage when full P I and D attenuation should happen. 100 disables Iterm Lock for P and D term
2238+
field: fwItermLockRateLimit
2239+
default_value: 40
2240+
min: 10
2241+
max: 100
2242+
- name: fw_iterm_lock_engage_threshold
2243+
description: Defines error rate (in percents of max rate) when Iterm Lock is engaged when sticks are release. Iterm Lock will stay active until error drops below this number
2244+
default_value: 10
2245+
min: 5
2246+
max: 25
2247+
field: fwItermLockEngageThreshold
22362248

22372249
- name: PG_PID_AUTOTUNE_CONFIG
22382250
type: pidAutotuneConfig_t

src/main/flight/pid.c

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@
6565

6666
#include "programming/logic_condition.h"
6767

68+
typedef struct {
69+
float aP;
70+
float aI;
71+
float aD;
72+
float aFF;
73+
timeMs_t targetOverThresholdTimeMs;
74+
} fwPidAttenuation_t;
75+
6876
typedef struct {
6977
uint8_t axis;
7078
float kP; // Proportional gain
@@ -106,6 +114,8 @@ typedef struct {
106114
pt3Filter_t rateTargetFilter;
107115

108116
smithPredictor_t smithPredictor;
117+
118+
fwPidAttenuation_t attenuation;
109119
} pidState_t;
110120

111121
STATIC_FASTRAM bool pidFiltersConfigured = false;
@@ -157,7 +167,6 @@ static EXTENDED_FASTRAM uint8_t usedPidControllerType;
157167
typedef void (*pidControllerFnPtr)(pidState_t *pidState, float dT, float dT_inv);
158168
static EXTENDED_FASTRAM pidControllerFnPtr pidControllerApplyFn;
159169
static EXTENDED_FASTRAM filterApplyFnPtr dTermLpfFilterApplyFn;
160-
static EXTENDED_FASTRAM bool levelingEnabled = false;
161170
static EXTENDED_FASTRAM bool restartAngleHoldMode = true;
162171
static EXTENDED_FASTRAM bool angleHoldIsLevel = false;
163172

@@ -169,7 +178,7 @@ static EXTENDED_FASTRAM bool angleHoldIsLevel = false;
169178
static EXTENDED_FASTRAM float fixedWingLevelTrim;
170179
static EXTENDED_FASTRAM pidController_t fixedWingLevelTrimController;
171180

172-
PG_REGISTER_PROFILE_WITH_RESET_TEMPLATE(pidProfile_t, pidProfile, PG_PID_PROFILE, 8);
181+
PG_REGISTER_PROFILE_WITH_RESET_TEMPLATE(pidProfile_t, pidProfile, PG_PID_PROFILE, 9);
173182

174183
PG_RESET_TEMPLATE(pidProfile_t, pidProfile,
175184
.bank_mc = {
@@ -268,7 +277,6 @@ PG_RESET_TEMPLATE(pidProfile_t, pidProfile,
268277
.fixedWingReferenceAirspeed = SETTING_FW_REFERENCE_AIRSPEED_DEFAULT,
269278
.fixedWingCoordinatedYawGain = SETTING_FW_TURN_ASSIST_YAW_GAIN_DEFAULT,
270279
.fixedWingCoordinatedPitchGain = SETTING_FW_TURN_ASSIST_PITCH_GAIN_DEFAULT,
271-
.fixedWingItermLimitOnStickPosition = SETTING_FW_ITERM_LIMIT_STICK_POSITION_DEFAULT,
272280
.fixedWingYawItermBankFreeze = SETTING_FW_YAW_ITERM_FREEZE_BANK_ANGLE_DEFAULT,
273281

274282
.navVelXyDTermLpfHz = SETTING_NAV_MC_VEL_XY_DTERM_LPF_HZ_DEFAULT,
@@ -304,6 +312,9 @@ PG_RESET_TEMPLATE(pidProfile_t, pidProfile,
304312
.smithPredictorDelay = SETTING_SMITH_PREDICTOR_DELAY_DEFAULT,
305313
.smithPredictorFilterHz = SETTING_SMITH_PREDICTOR_LPF_HZ_DEFAULT,
306314
#endif
315+
.fwItermLockTimeMaxMs = SETTING_FW_ITERM_LOCK_TIME_MAX_MS_DEFAULT,
316+
.fwItermLockRateLimit = SETTING_FW_ITERM_LOCK_RATE_THRESHOLD_DEFAULT,
317+
.fwItermLockEngageThreshold = SETTING_FW_ITERM_LOCK_ENGAGE_THRESHOLD_DEFAULT,
307318
);
308319

309320
bool pidInitFilters(void)
@@ -670,19 +681,6 @@ static void pidApplySetpointRateLimiting(pidState_t *pidState, flight_dynamics_i
670681
}
671682
}
672683

673-
bool isFixedWingItermLimitActive(float stickPosition)
674-
{
675-
/*
676-
* Iterm anti windup whould be active only when pilot controls the rotation
677-
* velocity directly, not when ANGLE or HORIZON are used
678-
*/
679-
if (levelingEnabled) {
680-
return false;
681-
}
682-
683-
return fabsf(stickPosition) > pidProfile()->fixedWingItermLimitOnStickPosition;
684-
}
685-
686684
static float pTermProcess(pidState_t *pidState, float rateError, float dT) {
687685
float newPTerm = rateError * pidState->kP;
688686

@@ -751,20 +749,61 @@ static void nullRateController(pidState_t *pidState, float dT, float dT_inv) {
751749
UNUSED(dT_inv);
752750
}
753751

752+
static void fwRateAttenuation(pidState_t *pidState, const float rateTarget, const float rateError) {
753+
const float maxRate = currentControlRateProfile->stabilized.rates[pidState->axis] * 10.0f;
754+
755+
const float dampingFactor = attenuation(rateTarget, maxRate * pidProfile()->fwItermLockRateLimit / 100.0f);
756+
757+
/*
758+
* Iterm damping is applied (down to 0) when:
759+
* abs(error) > 10% rate and sticks were moved in the last 500ms (hard stop at this mark)
760+
761+
* itermAttenuation = MIN(curve(setpoint), (abs(error) > 10%) && (sticks were deflected in 500ms) ? 0 : 1)
762+
*/
763+
764+
//If error is greater than 10% or max rate
765+
const bool errorThresholdReached = fabsf(rateError) > maxRate * pidProfile()->fwItermLockEngageThreshold / 100.0f;
766+
767+
//If stick (setpoint) was moved above threshold in the last 500ms
768+
if (fabsf(rateTarget) > maxRate * 0.2f) {
769+
pidState->attenuation.targetOverThresholdTimeMs = millis();
770+
}
771+
772+
//If error is below threshold, we no longer track time for lock mechanism
773+
if (!errorThresholdReached) {
774+
pidState->attenuation.targetOverThresholdTimeMs = 0;
775+
}
776+
777+
pidState->attenuation.aI = MIN(dampingFactor, (errorThresholdReached && (millis() - pidState->attenuation.targetOverThresholdTimeMs) < pidProfile()->fwItermLockTimeMaxMs) ? 0.0f : 1.0f);
778+
779+
//P & D damping factors are always the same and based on current damping factor
780+
pidState->attenuation.aP = dampingFactor;
781+
pidState->attenuation.aD = dampingFactor;
782+
783+
if (pidState->axis == FD_ROLL) {
784+
DEBUG_SET(DEBUG_ALWAYS, 0, pidState->attenuation.aP * 1000);
785+
DEBUG_SET(DEBUG_ALWAYS, 1, pidState->attenuation.aI * 1000);
786+
DEBUG_SET(DEBUG_ALWAYS, 2, pidState->attenuation.aD * 1000);
787+
}
788+
}
789+
754790
static void NOINLINE pidApplyFixedWingRateController(pidState_t *pidState, float dT, float dT_inv)
755791
{
756792
const float rateTarget = getFlightAxisRateOverride(pidState->axis, pidState->rateTarget);
757793

758794
const float rateError = rateTarget - pidState->gyroRate;
759-
const float newPTerm = pTermProcess(pidState, rateError, dT);
760-
const float newDTerm = dTermProcess(pidState, rateTarget, dT, dT_inv);
795+
796+
fwRateAttenuation(pidState, rateTarget, rateError);
797+
798+
const float newPTerm = pTermProcess(pidState, rateError, dT) * pidState->attenuation.aP;
799+
const float newDTerm = dTermProcess(pidState, rateTarget, dT, dT_inv) * pidState->attenuation.aD;
761800
const float newFFTerm = rateTarget * pidState->kFF;
762801

763802
/*
764803
* Integral should be updated only if axis Iterm is not frozen
765804
*/
766805
if (!pidState->itermFreezeActive) {
767-
pidState->errorGyroIf += rateError * pidState->kI * dT;
806+
pidState->errorGyroIf += rateError * pidState->kI * dT * pidState->attenuation.aI;
768807
}
769808

770809
applyItermLimiting(pidState);
@@ -1046,11 +1085,9 @@ static void pidApplyFpvCameraAngleMix(pidState_t *pidState, uint8_t fpvCameraAng
10461085

10471086
void checkItermLimitingActive(pidState_t *pidState)
10481087
{
1049-
bool shouldActivate;
1050-
if (usedPidControllerType == PID_TYPE_PIFF) {
1051-
shouldActivate = isFixedWingItermLimitActive(pidState->stickPosition);
1052-
} else
1053-
{
1088+
bool shouldActivate = false;
1089+
1090+
if (usedPidControllerType == PID_TYPE_PID) {
10541091
shouldActivate = mixerIsOutputSaturated(); //just in case, since it is already managed by itermWindupPointPercent
10551092
}
10561093

@@ -1180,7 +1217,6 @@ void FAST_CODE pidController(float dT)
11801217

11811218
// Step 3: Run control for ANGLE_MODE, HORIZON_MODE and ANGLEHOLD_MODE
11821219
const float horizonRateMagnitude = FLIGHT_MODE(HORIZON_MODE) ? calcHorizonRateMagnitude() : 0.0f;
1183-
levelingEnabled = false;
11841220
angleHoldIsLevel = false;
11851221

11861222
for (uint8_t axis = FD_ROLL; axis <= FD_PITCH; axis++) {
@@ -1200,7 +1236,6 @@ void FAST_CODE pidController(float dT)
12001236
// Apply the Level PID controller
12011237
pidLevel(angleTarget, &pidState[axis], axis, horizonRateMagnitude, dT);
12021238
canUseFpvCameraMix = false; // FPVANGLEMIX is incompatible with ANGLE/HORIZON
1203-
levelingEnabled = true;
12041239
} else {
12051240
restartAngleHoldMode = true;
12061241
}

src/main/flight/pid.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ typedef struct pidProfile_s {
121121
float fixedWingReferenceAirspeed; // Reference tuning airspeed for the airplane - the speed for which PID gains are tuned
122122
float fixedWingCoordinatedYawGain; // This is the gain of the yaw rate required to keep the yaw rate consistent with the turn rate for a coordinated turn.
123123
float fixedWingCoordinatedPitchGain; // This is the gain of the pitch rate to keep the pitch angle constant during coordinated turns.
124-
float fixedWingItermLimitOnStickPosition; //Do not allow Iterm to grow when stick position is above this point
125124
uint16_t fixedWingYawItermBankFreeze; // Freeze yaw Iterm when bank angle is more than this many degrees
126125

127126
float navVelXyDTermLpfHz;
@@ -156,6 +155,12 @@ typedef struct pidProfile_s {
156155
float smithPredictorDelay;
157156
uint16_t smithPredictorFilterHz;
158157
#endif
158+
159+
160+
uint16_t fwItermLockTimeMaxMs;
161+
uint8_t fwItermLockRateLimit;
162+
uint8_t fwItermLockEngageThreshold;
163+
159164
} pidProfile_t;
160165

161166
typedef struct pidAutotuneConfig_s {

0 commit comments

Comments
 (0)