2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
24 #include "build/build_config.h"
25 #include "build/debug.h"
27 #include "common/axis.h"
28 #include "common/filter.h"
29 #include "common/maths.h"
30 #include "common/utils.h"
31 #include "common/fp_pid.h"
33 #include "config/parameter_group.h"
34 #include "config/parameter_group_ids.h"
36 #include "fc/config.h"
37 #include "fc/controlrate_profile.h"
38 #include "fc/rc_controls.h"
39 #include "fc/rc_modes.h"
40 #include "fc/runtime_config.h"
41 #include "fc/settings.h"
43 #include "flight/pid.h"
44 #include "flight/imu.h"
45 #include "flight/mixer.h"
46 #include "flight/rpm_filter.h"
47 #include "flight/kalman.h"
48 #include "flight/smith_predictor.h"
52 #include "navigation/navigation.h"
56 #include "sensors/battery.h"
57 #include "sensors/sensors.h"
58 #include "sensors/gyro.h"
59 #include "sensors/acceleration.h"
60 #include "sensors/compass.h"
61 #include "sensors/pitotmeter.h"
63 #include "scheduler/scheduler.h"
65 #include "programming/logic_condition.h"
69 float kP
; // Proportional gain
70 float kI
; // Integral gain
71 float kD
; // Derivative gain
72 float kFF
; // Feed-forward gain
73 float kCD
; // Control Derivative
74 float kT
; // Back-calculation tracking gain
81 float errorGyroIfLimit
;
83 // Used for ANGLE filtering (PT1, we don't need super-sharpness here)
84 pt1Filter_t angleFilterState
;
87 rateLimitFilter_t axisAccelFilter
;
88 pt1Filter_t ptermLpfState
;
89 filter_t dtermLpfState
;
93 float previousRateTarget
;
94 float previousRateGyro
;
97 pt1Filter_t dBoostLpf
;
98 biquadFilter_t dBoostGyroLpf
;
99 float dBoostTargetAcceleration
;
101 uint16_t pidSumLimit
;
102 filterApply4FnPtr ptermFilterApplyFn
;
103 bool itermLimitActive
;
104 bool itermFreezeActive
;
106 pt3Filter_t rateTargetFilter
;
108 smithPredictor_t smithPredictor
;
111 STATIC_FASTRAM
bool pidFiltersConfigured
= false;
112 static EXTENDED_FASTRAM
float headingHoldCosZLimit
;
113 static EXTENDED_FASTRAM
int16_t headingHoldTarget
;
114 static EXTENDED_FASTRAM pt1Filter_t headingHoldRateFilter
;
115 static EXTENDED_FASTRAM pt1Filter_t fixedWingTpaFilter
;
117 // Thrust PID Attenuation factor. 0.0f means fully attenuated, 1.0f no attenuation is applied
118 STATIC_FASTRAM
bool pidGainsUpdateRequired
;
119 FASTRAM
int16_t axisPID
[FLIGHT_DYNAMICS_INDEX_COUNT
];
122 int32_t axisPID_P
[FLIGHT_DYNAMICS_INDEX_COUNT
];
123 int32_t axisPID_I
[FLIGHT_DYNAMICS_INDEX_COUNT
];
124 int32_t axisPID_D
[FLIGHT_DYNAMICS_INDEX_COUNT
];
125 int32_t axisPID_F
[FLIGHT_DYNAMICS_INDEX_COUNT
];
126 int32_t axisPID_Setpoint
[FLIGHT_DYNAMICS_INDEX_COUNT
];
129 static EXTENDED_FASTRAM pidState_t pidState
[FLIGHT_DYNAMICS_INDEX_COUNT
];
130 static EXTENDED_FASTRAM pt1Filter_t windupLpf
[XYZ_AXIS_COUNT
];
131 static EXTENDED_FASTRAM
uint8_t itermRelax
;
133 #ifdef USE_ANTIGRAVITY
134 static EXTENDED_FASTRAM pt1Filter_t antigravityThrottleLpf
;
135 static EXTENDED_FASTRAM
float antigravityThrottleHpf
;
136 static EXTENDED_FASTRAM
float antigravityGain
;
137 static EXTENDED_FASTRAM
float antigravityAccelerator
;
140 #define D_BOOST_GYRO_LPF_HZ 80 // Biquad lowpass input cutoff to peak D around propwash frequencies
141 #define D_BOOST_LPF_HZ 7 // PT1 lowpass cutoff to smooth the boost effect
144 static EXTENDED_FASTRAM
float dBoostMin
;
145 static EXTENDED_FASTRAM
float dBoostMax
;
146 static EXTENDED_FASTRAM
float dBoostMaxAtAlleceleration
;
149 static EXTENDED_FASTRAM
uint8_t yawLpfHz
;
150 static EXTENDED_FASTRAM
float motorItermWindupPoint
;
151 static EXTENDED_FASTRAM
float antiWindupScaler
;
152 #ifdef USE_ANTIGRAVITY
153 static EXTENDED_FASTRAM
float iTermAntigravityGain
;
155 static EXTENDED_FASTRAM
uint8_t usedPidControllerType
;
157 typedef void (*pidControllerFnPtr
)(pidState_t
*pidState
, flight_dynamics_index_t axis
, float dT
);
158 static EXTENDED_FASTRAM pidControllerFnPtr pidControllerApplyFn
;
159 static EXTENDED_FASTRAM filterApplyFnPtr dTermLpfFilterApplyFn
;
160 static EXTENDED_FASTRAM
bool levelingEnabled
= false;
162 #define FIXED_WING_LEVEL_TRIM_MAX_ANGLE 10.0f // Max angle auto trimming can demand
163 #define FIXED_WING_LEVEL_TRIM_DIVIDER 500.0f
164 #define FIXED_WING_LEVEL_TRIM_MULTIPLIER 1.0f / FIXED_WING_LEVEL_TRIM_DIVIDER
165 #define FIXED_WING_LEVEL_TRIM_CONTROLLER_LIMIT FIXED_WING_LEVEL_TRIM_DIVIDER * FIXED_WING_LEVEL_TRIM_MAX_ANGLE
167 static EXTENDED_FASTRAM
float fixedWingLevelTrim
;
168 static EXTENDED_FASTRAM pidController_t fixedWingLevelTrimController
;
170 PG_REGISTER_PROFILE_WITH_RESET_TEMPLATE(pidProfile_t
, pidProfile
, PG_PID_PROFILE
, 6);
172 PG_RESET_TEMPLATE(pidProfile_t
, pidProfile
,
175 [PID_ROLL
] = { SETTING_MC_P_ROLL_DEFAULT
, SETTING_MC_I_ROLL_DEFAULT
, SETTING_MC_D_ROLL_DEFAULT
, SETTING_MC_CD_ROLL_DEFAULT
},
176 [PID_PITCH
] = { SETTING_MC_P_PITCH_DEFAULT
, SETTING_MC_I_PITCH_DEFAULT
, SETTING_MC_D_PITCH_DEFAULT
, SETTING_MC_CD_PITCH_DEFAULT
},
177 [PID_YAW
] = { SETTING_MC_P_YAW_DEFAULT
, SETTING_MC_I_YAW_DEFAULT
, SETTING_MC_D_YAW_DEFAULT
, SETTING_MC_CD_YAW_DEFAULT
},
179 .P
= SETTING_MC_P_LEVEL_DEFAULT
, // Self-level strength
180 .I
= SETTING_MC_I_LEVEL_DEFAULT
, // Self-leveing low-pass frequency (0 - disabled)
181 .D
= SETTING_MC_D_LEVEL_DEFAULT
, // 75% horizon strength
184 [PID_HEADING
] = { SETTING_NAV_MC_HEADING_P_DEFAULT
, 0, 0, 0 },
186 .P
= SETTING_NAV_MC_POS_XY_P_DEFAULT
, // NAV_POS_XY_P * 100
192 .P
= SETTING_NAV_MC_VEL_XY_P_DEFAULT
, // NAV_VEL_XY_P * 20
193 .I
= SETTING_NAV_MC_VEL_XY_I_DEFAULT
, // NAV_VEL_XY_I * 100
194 .D
= SETTING_NAV_MC_VEL_XY_D_DEFAULT
, // NAV_VEL_XY_D * 100
195 .FF
= SETTING_NAV_MC_VEL_XY_FF_DEFAULT
, // NAV_VEL_XY_D * 100
198 .P
= SETTING_NAV_MC_POS_Z_P_DEFAULT
, // NAV_POS_Z_P * 100
204 .P
= SETTING_NAV_MC_VEL_Z_P_DEFAULT
, // NAV_VEL_Z_P * 66.7
205 .I
= SETTING_NAV_MC_VEL_Z_I_DEFAULT
, // NAV_VEL_Z_I * 20
206 .D
= SETTING_NAV_MC_VEL_Z_D_DEFAULT
, // NAV_VEL_Z_D * 100
209 [PID_POS_HEADING
] = {
220 [PID_ROLL
] = { SETTING_FW_P_ROLL_DEFAULT
, SETTING_FW_I_ROLL_DEFAULT
, 0, SETTING_FW_FF_ROLL_DEFAULT
},
221 [PID_PITCH
] = { SETTING_FW_P_PITCH_DEFAULT
, SETTING_FW_I_PITCH_DEFAULT
, 0, SETTING_FW_FF_PITCH_DEFAULT
},
222 [PID_YAW
] = { SETTING_FW_P_YAW_DEFAULT
, SETTING_FW_I_YAW_DEFAULT
, 0, SETTING_FW_FF_YAW_DEFAULT
},
224 .P
= SETTING_FW_P_LEVEL_DEFAULT
, // Self-level strength
225 .I
= SETTING_FW_I_LEVEL_DEFAULT
, // Self-leveing low-pass frequency (0 - disabled)
226 .D
= SETTING_FW_D_LEVEL_DEFAULT
, // 75% horizon strength
229 [PID_HEADING
] = { SETTING_NAV_FW_HEADING_P_DEFAULT
, 0, 0, 0 },
231 .P
= SETTING_NAV_FW_POS_Z_P_DEFAULT
, // FW_POS_Z_P * 10
232 .I
= SETTING_NAV_FW_POS_Z_I_DEFAULT
, // FW_POS_Z_I * 10
233 .D
= SETTING_NAV_FW_POS_Z_D_DEFAULT
, // FW_POS_Z_D * 10
237 .P
= SETTING_NAV_FW_POS_XY_P_DEFAULT
, // FW_POS_XY_P * 100
238 .I
= SETTING_NAV_FW_POS_XY_I_DEFAULT
, // FW_POS_XY_I * 100
239 .D
= SETTING_NAV_FW_POS_XY_D_DEFAULT
, // FW_POS_XY_D * 100
242 [PID_POS_HEADING
] = {
243 .P
= SETTING_NAV_FW_POS_HDG_P_DEFAULT
,
244 .I
= SETTING_NAV_FW_POS_HDG_I_DEFAULT
,
245 .D
= SETTING_NAV_FW_POS_HDG_D_DEFAULT
,
251 .dterm_lpf_type
= SETTING_DTERM_LPF_TYPE_DEFAULT
,
252 .dterm_lpf_hz
= SETTING_DTERM_LPF_HZ_DEFAULT
,
253 .yaw_lpf_hz
= SETTING_YAW_LPF_HZ_DEFAULT
,
255 .itermWindupPointPercent
= SETTING_ITERM_WINDUP_DEFAULT
,
257 .axisAccelerationLimitYaw
= SETTING_RATE_ACCEL_LIMIT_YAW_DEFAULT
,
258 .axisAccelerationLimitRollPitch
= SETTING_RATE_ACCEL_LIMIT_ROLL_PITCH_DEFAULT
,
260 .heading_hold_rate_limit
= SETTING_HEADING_HOLD_RATE_LIMIT_DEFAULT
,
262 .max_angle_inclination
[FD_ROLL
] = SETTING_MAX_ANGLE_INCLINATION_RLL_DEFAULT
,
263 .max_angle_inclination
[FD_PITCH
] = SETTING_MAX_ANGLE_INCLINATION_PIT_DEFAULT
,
264 .pidSumLimit
= SETTING_PIDSUM_LIMIT_DEFAULT
,
265 .pidSumLimitYaw
= SETTING_PIDSUM_LIMIT_YAW_DEFAULT
,
267 .fixedWingItermThrowLimit
= SETTING_FW_ITERM_THROW_LIMIT_DEFAULT
,
268 .fixedWingReferenceAirspeed
= SETTING_FW_REFERENCE_AIRSPEED_DEFAULT
,
269 .fixedWingCoordinatedYawGain
= SETTING_FW_TURN_ASSIST_YAW_GAIN_DEFAULT
,
270 .fixedWingCoordinatedPitchGain
= SETTING_FW_TURN_ASSIST_PITCH_GAIN_DEFAULT
,
271 .fixedWingItermLimitOnStickPosition
= SETTING_FW_ITERM_LIMIT_STICK_POSITION_DEFAULT
,
272 .fixedWingYawItermBankFreeze
= SETTING_FW_YAW_ITERM_FREEZE_BANK_ANGLE_DEFAULT
,
274 .navVelXyDTermLpfHz
= SETTING_NAV_MC_VEL_XY_DTERM_LPF_HZ_DEFAULT
,
275 .navVelXyDtermAttenuation
= SETTING_NAV_MC_VEL_XY_DTERM_ATTENUATION_DEFAULT
,
276 .navVelXyDtermAttenuationStart
= SETTING_NAV_MC_VEL_XY_DTERM_ATTENUATION_START_DEFAULT
,
277 .navVelXyDtermAttenuationEnd
= SETTING_NAV_MC_VEL_XY_DTERM_ATTENUATION_END_DEFAULT
,
278 .iterm_relax_cutoff
= SETTING_MC_ITERM_RELAX_CUTOFF_DEFAULT
,
279 .iterm_relax
= SETTING_MC_ITERM_RELAX_DEFAULT
,
282 .dBoostMin
= SETTING_D_BOOST_MIN_DEFAULT
,
283 .dBoostMax
= SETTING_D_BOOST_MAX_DEFAULT
,
284 .dBoostMaxAtAlleceleration
= SETTING_D_BOOST_MAX_AT_ACCELERATION_DEFAULT
,
285 .dBoostGyroDeltaLpfHz
= SETTING_D_BOOST_GYRO_DELTA_LPF_HZ_DEFAULT
,
288 #ifdef USE_ANTIGRAVITY
289 .antigravityGain
= SETTING_ANTIGRAVITY_GAIN_DEFAULT
,
290 .antigravityAccelerator
= SETTING_ANTIGRAVITY_ACCELERATOR_DEFAULT
,
291 .antigravityCutoff
= SETTING_ANTIGRAVITY_CUTOFF_LPF_HZ_DEFAULT
,
294 .pidControllerType
= SETTING_PID_TYPE_DEFAULT
,
295 .navFwPosHdgPidsumLimit
= SETTING_NAV_FW_POS_HDG_PIDSUM_LIMIT_DEFAULT
,
296 .controlDerivativeLpfHz
= SETTING_MC_CD_LPF_HZ_DEFAULT
,
298 .fixedWingLevelTrim
= SETTING_FW_LEVEL_PITCH_TRIM_DEFAULT
,
299 .fixedWingLevelTrimGain
= SETTING_FW_LEVEL_PITCH_GAIN_DEFAULT
,
301 #ifdef USE_SMITH_PREDICTOR
302 .smithPredictorStrength
= SETTING_SMITH_PREDICTOR_STRENGTH_DEFAULT
,
303 .smithPredictorDelay
= SETTING_SMITH_PREDICTOR_DELAY_DEFAULT
,
304 .smithPredictorFilterHz
= SETTING_SMITH_PREDICTOR_LPF_HZ_DEFAULT
,
308 bool pidInitFilters(void)
310 const uint32_t refreshRate
= getLooptime();
312 if (refreshRate
== 0) {
316 for (int axis
= 0; axis
< 3; ++ axis
) {
317 initFilter(pidProfile()->dterm_lpf_type
, &pidState
[axis
].dtermLpfState
, pidProfile()->dterm_lpf_hz
, refreshRate
);
320 for (int i
= 0; i
< XYZ_AXIS_COUNT
; i
++) {
321 pt1FilterInit(&windupLpf
[i
], pidProfile()->iterm_relax_cutoff
, US2S(refreshRate
));
324 #ifdef USE_ANTIGRAVITY
325 pt1FilterInit(&antigravityThrottleLpf
, pidProfile()->antigravityCutoff
, US2S(TASK_PERIOD_HZ(TASK_AUX_RATE_HZ
)));
329 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
330 biquadFilterInitLPF(&pidState
[axis
].dBoostGyroLpf
, pidProfile()->dBoostGyroDeltaLpfHz
, getLooptime());
334 if (pidProfile()->controlDerivativeLpfHz
) {
335 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
336 pt3FilterInit(&pidState
[axis
].rateTargetFilter
, pt3FilterGain(pidProfile()->controlDerivativeLpfHz
, US2S(refreshRate
)));
340 #ifdef USE_SMITH_PREDICTOR
342 &pidState
[FD_ROLL
].smithPredictor
,
343 pidProfile()->smithPredictorDelay
,
344 pidProfile()->smithPredictorStrength
,
345 pidProfile()->smithPredictorFilterHz
,
349 &pidState
[FD_PITCH
].smithPredictor
,
350 pidProfile()->smithPredictorDelay
,
351 pidProfile()->smithPredictorStrength
,
352 pidProfile()->smithPredictorFilterHz
,
356 &pidState
[FD_YAW
].smithPredictor
,
357 pidProfile()->smithPredictorDelay
,
358 pidProfile()->smithPredictorStrength
,
359 pidProfile()->smithPredictorFilterHz
,
364 pidFiltersConfigured
= true;
369 void pidResetTPAFilter(void)
371 if (usedPidControllerType
== PID_TYPE_PIFF
&& currentControlRateProfile
->throttle
.fixedWingTauMs
> 0) {
372 pt1FilterInitRC(&fixedWingTpaFilter
, MS2S(currentControlRateProfile
->throttle
.fixedWingTauMs
), US2S(TASK_PERIOD_HZ(TASK_AUX_RATE_HZ
)));
373 pt1FilterReset(&fixedWingTpaFilter
, getThrottleIdleValue());
377 void pidResetErrorAccumulators(void)
379 // Reset R/P/Y integrator
380 for (int axis
= 0; axis
< 3; axis
++) {
381 pidState
[axis
].errorGyroIf
= 0.0f
;
382 pidState
[axis
].errorGyroIfLimit
= 0.0f
;
386 void pidReduceErrorAccumulators(int8_t delta
, uint8_t axis
)
388 pidState
[axis
].errorGyroIf
-= delta
;
389 pidState
[axis
].errorGyroIfLimit
-= delta
;
392 float getTotalRateTarget(void)
394 return calc_length_pythagorean_3D(pidState
[FD_ROLL
].rateTarget
, pidState
[FD_PITCH
].rateTarget
, pidState
[FD_YAW
].rateTarget
);
397 float getAxisIterm(uint8_t axis
)
399 return pidState
[axis
].errorGyroIf
;
402 static float pidRcCommandToAngle(int16_t stick
, int16_t maxInclination
)
404 stick
= constrain(stick
, -500, 500);
405 return scaleRangef((float) stick
, -500.0f
, 500.0f
, (float) -maxInclination
, (float) maxInclination
);
408 int16_t pidAngleToRcCommand(float angleDeciDegrees
, int16_t maxInclination
)
410 angleDeciDegrees
= constrainf(angleDeciDegrees
, (float) -maxInclination
, (float) maxInclination
);
411 return scaleRangef((float) angleDeciDegrees
, (float) -maxInclination
, (float) maxInclination
, -500.0f
, 500.0f
);
415 Map stick positions to desired rotatrion rate in given axis.
416 Rotation rate in dps at full stick deflection is defined by axis rate measured in dps/10
417 Rate 20 means 200dps at full stick deflection
419 float pidRateToRcCommand(float rateDPS
, uint8_t rate
)
421 const float maxRateDPS
= rate
* 10.0f
;
422 return scaleRangef(rateDPS
, -maxRateDPS
, maxRateDPS
, -500.0f
, 500.0f
);
425 float pidRcCommandToRate(int16_t stick
, uint8_t rate
)
427 const float maxRateDPS
= rate
* 10.0f
;
428 return scaleRangef((float) stick
, -500.0f
, 500.0f
, -maxRateDPS
, maxRateDPS
);
431 static float calculateFixedWingTPAFactor(uint16_t throttle
)
435 // tpa_rate is amount of curve TPA applied to PIDs
436 // tpa_breakpoint for fixed wing is cruise throttle value (value at which PIDs were tuned)
437 if (currentControlRateProfile
->throttle
.dynPID
!= 0 && currentControlRateProfile
->throttle
.pa_breakpoint
> getThrottleIdleValue() && !FLIGHT_MODE(AUTO_TUNE
) && ARMING_FLAG(ARMED
)) {
438 if (throttle
> getThrottleIdleValue()) {
439 // Calculate TPA according to throttle
440 tpaFactor
= 0.5f
+ ((float)(currentControlRateProfile
->throttle
.pa_breakpoint
- getThrottleIdleValue()) / (throttle
- getThrottleIdleValue()) / 2.0f
);
442 // Limit to [0.5; 2] range
443 tpaFactor
= constrainf(tpaFactor
, 0.5f
, 2.0f
);
449 // Attenuate TPA curve according to configured amount
450 tpaFactor
= 1.0f
+ (tpaFactor
- 1.0f
) * (currentControlRateProfile
->throttle
.dynPID
/ 100.0f
);
459 static float calculateMultirotorTPAFactor(void)
463 // TPA should be updated only when TPA is actually set
464 if (currentControlRateProfile
->throttle
.dynPID
== 0 || rcCommand
[THROTTLE
] < currentControlRateProfile
->throttle
.pa_breakpoint
) {
466 } else if (rcCommand
[THROTTLE
] < motorConfig()->maxthrottle
) {
467 tpaFactor
= (100 - (uint16_t)currentControlRateProfile
->throttle
.dynPID
* (rcCommand
[THROTTLE
] - currentControlRateProfile
->throttle
.pa_breakpoint
) / (float)(motorConfig()->maxthrottle
- currentControlRateProfile
->throttle
.pa_breakpoint
)) / 100.0f
;
469 tpaFactor
= (100 - currentControlRateProfile
->throttle
.dynPID
) / 100.0f
;
475 void schedulePidGainsUpdate(void)
477 pidGainsUpdateRequired
= true;
480 void updatePIDCoefficients(void)
482 STATIC_FASTRAM
uint16_t prevThrottle
= 0;
484 // Check if throttle changed. Different logic for fixed wing vs multirotor
485 if (usedPidControllerType
== PID_TYPE_PIFF
&& (currentControlRateProfile
->throttle
.fixedWingTauMs
> 0)) {
486 uint16_t filteredThrottle
= pt1FilterApply(&fixedWingTpaFilter
, rcCommand
[THROTTLE
]);
487 if (filteredThrottle
!= prevThrottle
) {
488 prevThrottle
= filteredThrottle
;
489 pidGainsUpdateRequired
= true;
493 if (rcCommand
[THROTTLE
] != prevThrottle
) {
494 prevThrottle
= rcCommand
[THROTTLE
];
495 pidGainsUpdateRequired
= true;
499 #ifdef USE_ANTIGRAVITY
500 if (usedPidControllerType
== PID_TYPE_PID
) {
501 antigravityThrottleHpf
= rcCommand
[THROTTLE
] - pt1FilterApply(&antigravityThrottleLpf
, rcCommand
[THROTTLE
]);
502 iTermAntigravityGain
= scaleRangef(fabsf(antigravityThrottleHpf
) * antigravityAccelerator
, 0.0f
, 1000.0f
, 1.0f
, antigravityGain
);
507 * Compute stick position in range of [-1.0f : 1.0f] without deadband and expo
509 for (int axis
= 0; axis
< 3; axis
++) {
510 pidState
[axis
].stickPosition
= constrain(rxGetChannelValue(axis
) - PWM_RANGE_MIDDLE
, -500, 500) / 500.0f
;
513 // If nothing changed - don't waste time recalculating coefficients
514 if (!pidGainsUpdateRequired
) {
518 const float tpaFactor
= usedPidControllerType
== PID_TYPE_PIFF
? calculateFixedWingTPAFactor(prevThrottle
) : calculateMultirotorTPAFactor();
520 // PID coefficients can be update only with THROTTLE and TPA or inflight PID adjustments
521 //TODO: Next step would be to update those only at THROTTLE or inflight adjustments change
522 for (int axis
= 0; axis
< 3; axis
++) {
523 if (usedPidControllerType
== PID_TYPE_PIFF
) {
524 // Airplanes - scale all PIDs according to TPA
525 pidState
[axis
].kP
= pidBank()->pid
[axis
].P
/ FP_PID_RATE_P_MULTIPLIER
* tpaFactor
;
526 pidState
[axis
].kI
= pidBank()->pid
[axis
].I
/ FP_PID_RATE_I_MULTIPLIER
* tpaFactor
;
527 pidState
[axis
].kD
= pidBank()->pid
[axis
].D
/ FP_PID_RATE_D_MULTIPLIER
* tpaFactor
;
528 pidState
[axis
].kFF
= pidBank()->pid
[axis
].FF
/ FP_PID_RATE_FF_MULTIPLIER
* tpaFactor
;
529 pidState
[axis
].kCD
= 0.0f
;
530 pidState
[axis
].kT
= 0.0f
;
533 const float axisTPA
= (axis
== FD_YAW
) ? 1.0f
: tpaFactor
;
534 pidState
[axis
].kP
= pidBank()->pid
[axis
].P
/ FP_PID_RATE_P_MULTIPLIER
* axisTPA
;
535 pidState
[axis
].kI
= pidBank()->pid
[axis
].I
/ FP_PID_RATE_I_MULTIPLIER
;
536 pidState
[axis
].kD
= pidBank()->pid
[axis
].D
/ FP_PID_RATE_D_MULTIPLIER
* axisTPA
;
537 pidState
[axis
].kCD
= (pidBank()->pid
[axis
].FF
/ FP_PID_RATE_D_FF_MULTIPLIER
* axisTPA
) / (getLooptime() * 0.000001f
);
538 pidState
[axis
].kFF
= 0.0f
;
540 // Tracking anti-windup requires P/I/D to be all defined which is only true for MC
541 if ((pidBank()->pid
[axis
].P
!= 0) && (pidBank()->pid
[axis
].I
!= 0) && (usedPidControllerType
== PID_TYPE_PID
)) {
542 pidState
[axis
].kT
= 2.0f
/ ((pidState
[axis
].kP
/ pidState
[axis
].kI
) + (pidState
[axis
].kD
/ pidState
[axis
].kP
));
544 pidState
[axis
].kT
= 0;
549 pidGainsUpdateRequired
= false;
552 static float calcHorizonRateMagnitude(void)
554 // Figure out the raw stick positions
555 const int32_t stickPosAil
= ABS(getRcStickDeflection(FD_ROLL
));
556 const int32_t stickPosEle
= ABS(getRcStickDeflection(FD_PITCH
));
557 const float mostDeflectedStickPos
= constrain(MAX(stickPosAil
, stickPosEle
), 0, 500) / 500.0f
;
558 const float modeTransitionStickPos
= constrain(pidBank()->pid
[PID_LEVEL
].D
, 0, 100) / 100.0f
;
560 float horizonRateMagnitude
;
562 // Calculate transition point according to stick deflection
563 if (mostDeflectedStickPos
<= modeTransitionStickPos
) {
564 horizonRateMagnitude
= mostDeflectedStickPos
/ modeTransitionStickPos
;
567 horizonRateMagnitude
= 1.0f
;
570 return horizonRateMagnitude
;
573 /* ANGLE freefloat deadband (degs).Angle error only starts to increase if atttiude outside deadband. */
574 int16_t angleFreefloatDeadband(int16_t deadband
, flight_dynamics_index_t axis
)
576 int16_t levelDatum
= axis
== FD_PITCH
? attitude
.raw
[axis
] + DEGREES_TO_DECIDEGREES(fixedWingLevelTrim
) : attitude
.raw
[axis
];
577 if (ABS(levelDatum
) > deadband
) {
578 return levelDatum
> 0 ? deadband
- levelDatum
: -(levelDatum
+ deadband
);
584 static float computePidLevelTarget(flight_dynamics_index_t axis
) {
585 // This is ROLL/PITCH, run ANGLE/HORIZON controllers
586 float angleTarget
= pidRcCommandToAngle(rcCommand
[axis
], pidProfile()->max_angle_inclination
[axis
]);
588 // Automatically pitch down if the throttle is manually controlled and reduced bellow cruise throttle
589 if ((axis
== FD_PITCH
) && STATE(AIRPLANE
) && FLIGHT_MODE(ANGLE_MODE
) && !navigationIsControllingThrottle()) {
590 angleTarget
+= scaleRange(MAX(0, currentBatteryProfile
->nav
.fw
.cruise_throttle
- rcCommand
[THROTTLE
]), 0, currentBatteryProfile
->nav
.fw
.cruise_throttle
- PWM_RANGE_MIN
, 0, navConfig()->fw
.minThrottleDownPitchAngle
);
593 //PITCH trim applied by a AutoLevel flight mode and manual pitch trimming
594 if (axis
== FD_PITCH
&& STATE(AIRPLANE
)) {
595 DEBUG_SET(DEBUG_AUTOLEVEL
, 0, angleTarget
* 10);
596 DEBUG_SET(DEBUG_AUTOLEVEL
, 1, fixedWingLevelTrim
* 10);
597 DEBUG_SET(DEBUG_AUTOLEVEL
, 2, getEstimatedActualVelocity(Z
));
600 * fixedWingLevelTrim has opposite sign to rcCommand.
601 * Positive rcCommand means nose should point downwards
602 * Negative rcCommand mean nose should point upwards
603 * This is counter intuitive and a natural way suggests that + should mean UP
604 * This is why fixedWingLevelTrim has opposite sign to rcCommand
605 * Positive fixedWingLevelTrim means nose should point upwards
606 * Negative fixedWingLevelTrim means nose should point downwards
608 angleTarget
-= DEGREES_TO_DECIDEGREES(fixedWingLevelTrim
);
609 DEBUG_SET(DEBUG_AUTOLEVEL
, 3, angleTarget
* 10);
615 static void pidLevel(const float angleTarget
, pidState_t
*pidState
, flight_dynamics_index_t axis
, float horizonRateMagnitude
, float dT
)
617 float angleErrorDeg
= DECIDEGREES_TO_DEGREES(angleTarget
- attitude
.raw
[axis
]);
619 // Soaring mode deadband inactive if pitch/roll stick not centered to allow RC stick adjustment
620 if (FLIGHT_MODE(SOARING_MODE
) && axis
== FD_PITCH
&& calculateRollPitchCenterStatus() == CENTERED
) {
621 angleErrorDeg
= DECIDEGREES_TO_DEGREES((float)angleFreefloatDeadband(DEGREES_TO_DECIDEGREES(navConfig()->fw
.soaring_pitch_deadband
), FD_PITCH
));
622 if (!angleErrorDeg
) {
623 pidState
->errorGyroIf
= 0.0f
;
624 pidState
->errorGyroIfLimit
= 0.0f
;
628 float angleRateTarget
= constrainf(angleErrorDeg
* (pidBank()->pid
[PID_LEVEL
].P
/ FP_PID_LEVEL_P_MULTIPLIER
), -currentControlRateProfile
->stabilized
.rates
[axis
] * 10.0f
, currentControlRateProfile
->stabilized
.rates
[axis
] * 10.0f
);
630 // Apply simple LPF to angleRateTarget to make response less jerky
631 // Ideas behind this:
632 // 1) Attitude is updated at gyro rate, rateTarget for ANGLE mode is calculated from attitude
633 // 2) If this rateTarget is passed directly into gyro-base PID controller this effectively doubles the rateError.
634 // D-term that is calculated from error tend to amplify this even more. Moreover, this tend to respond to every
635 // slightest change in attitude making self-leveling jittery
636 // 3) Lowering LEVEL P can make the effects of (2) less visible, but this also slows down self-leveling.
637 // 4) Human pilot response to attitude change in RATE mode is fairly slow and smooth, human pilot doesn't
638 // compensate for each slightest change
639 // 5) (2) and (4) lead to a simple idea of adding a low-pass filter on rateTarget for ANGLE mode damping
640 // response to rapid attitude changes and smoothing out self-leveling reaction
641 if (pidBank()->pid
[PID_LEVEL
].I
) {
642 // I8[PIDLEVEL] is filter cutoff frequency (Hz). Practical values of filtering frequency is 5-10 Hz
643 angleRateTarget
= pt1FilterApply4(&pidState
->angleFilterState
, angleRateTarget
, pidBank()->pid
[PID_LEVEL
].I
, dT
);
646 // P[LEVEL] defines self-leveling strength (both for ANGLE and HORIZON modes)
647 if (FLIGHT_MODE(HORIZON_MODE
)) {
648 pidState
->rateTarget
= (1.0f
- horizonRateMagnitude
) * angleRateTarget
+ horizonRateMagnitude
* pidState
->rateTarget
;
650 pidState
->rateTarget
= angleRateTarget
;
654 /* Apply angular acceleration limit to rate target to limit extreme stick inputs to respect physical capabilities of the machine */
655 static void pidApplySetpointRateLimiting(pidState_t
*pidState
, flight_dynamics_index_t axis
, float dT
)
657 const uint32_t axisAccelLimit
= (axis
== FD_YAW
) ? pidProfile()->axisAccelerationLimitYaw
: pidProfile()->axisAccelerationLimitRollPitch
;
659 if (axisAccelLimit
> AXIS_ACCEL_MIN_LIMIT
) {
660 pidState
->rateTarget
= rateLimitFilterApply4(&pidState
->axisAccelFilter
, pidState
->rateTarget
, (float)axisAccelLimit
, dT
);
664 bool isFixedWingItermLimitActive(float stickPosition
)
667 * Iterm anti windup whould be active only when pilot controls the rotation
668 * velocity directly, not when ANGLE or HORIZON are used
670 if (levelingEnabled
) {
674 return fabsf(stickPosition
) > pidProfile()->fixedWingItermLimitOnStickPosition
;
677 static float pTermProcess(pidState_t
*pidState
, float rateError
, float dT
) {
678 float newPTerm
= rateError
* pidState
->kP
;
680 return pidState
->ptermFilterApplyFn(&pidState
->ptermLpfState
, newPTerm
, yawLpfHz
, dT
);
684 static float FAST_CODE
applyDBoost(pidState_t
*pidState
, float currentRateTarget
, float dT
) {
688 const float dBoostGyroDelta
= (pidState
->gyroRate
- pidState
->previousRateGyro
) / dT
;
689 const float dBoostGyroAcceleration
= fabsf(biquadFilterApply(&pidState
->dBoostGyroLpf
, dBoostGyroDelta
));
690 const float dBoostRateAcceleration
= fabsf((currentRateTarget
- pidState
->previousRateTarget
) / dT
);
692 if (dBoostGyroAcceleration
>= dBoostRateAcceleration
) {
693 //Gyro is accelerating faster than setpoint, we want to smooth out
694 dBoost
= scaleRangef(dBoostGyroAcceleration
, 0.0f
, dBoostMaxAtAlleceleration
, 1.0f
, dBoostMax
);
696 //Setpoint is accelerating, we want to boost response
697 dBoost
= scaleRangef(dBoostRateAcceleration
, 0.0f
, pidState
->dBoostTargetAcceleration
, 1.0f
, dBoostMin
);
700 dBoost
= pt1FilterApply4(&pidState
->dBoostLpf
, dBoost
, D_BOOST_LPF_HZ
, dT
);
701 dBoost
= constrainf(dBoost
, dBoostMin
, dBoostMax
);
706 static float applyDBoost(pidState_t
*pidState
, float dT
) {
713 static float dTermProcess(pidState_t
*pidState
, float currentRateTarget
, float dT
) {
714 // Calculate new D-term
716 if (pidState
->kD
== 0) {
717 // optimisation for when D is zero, often used by YAW axis
720 float delta
= pidState
->previousRateGyro
- pidState
->gyroRate
;
722 delta
= dTermLpfFilterApplyFn((filter_t
*) &pidState
->dtermLpfState
, delta
);
724 // Calculate derivative
725 newDTerm
= delta
* (pidState
->kD
/ dT
) * applyDBoost(pidState
, currentRateTarget
, dT
);
730 static void applyItermLimiting(pidState_t
*pidState
) {
731 if (pidState
->itermLimitActive
) {
732 pidState
->errorGyroIf
= constrainf(pidState
->errorGyroIf
, -pidState
->errorGyroIfLimit
, pidState
->errorGyroIfLimit
);
735 pidState
->errorGyroIfLimit
= fabsf(pidState
->errorGyroIf
);
739 static void nullRateController(pidState_t
*pidState
, flight_dynamics_index_t axis
, float dT
) {
745 static void NOINLINE
pidApplyFixedWingRateController(pidState_t
*pidState
, flight_dynamics_index_t axis
, float dT
)
747 const float rateTarget
= getFlightAxisRateOverride(axis
, pidState
->rateTarget
);
749 const float rateError
= rateTarget
- pidState
->gyroRate
;
750 const float newPTerm
= pTermProcess(pidState
, rateError
, dT
);
751 const float newDTerm
= dTermProcess(pidState
, rateTarget
, dT
);
752 const float newFFTerm
= rateTarget
* pidState
->kFF
;
755 * Integral should be updated only if axis Iterm is not frozen
757 if (!pidState
->itermFreezeActive
) {
758 pidState
->errorGyroIf
+= rateError
* pidState
->kI
* dT
;
761 applyItermLimiting(pidState
);
763 if (pidProfile()->fixedWingItermThrowLimit
!= 0) {
764 pidState
->errorGyroIf
= constrainf(pidState
->errorGyroIf
, -pidProfile()->fixedWingItermThrowLimit
, pidProfile()->fixedWingItermThrowLimit
);
767 axisPID
[axis
] = constrainf(newPTerm
+ newFFTerm
+ pidState
->errorGyroIf
+ newDTerm
, -pidState
->pidSumLimit
, +pidState
->pidSumLimit
);
769 if (FLIGHT_MODE(SOARING_MODE
) && axis
== FD_PITCH
&& calculateRollPitchCenterStatus() == CENTERED
) {
770 if (!angleFreefloatDeadband(DEGREES_TO_DECIDEGREES(navConfig()->fw
.soaring_pitch_deadband
), FD_PITCH
)) {
771 axisPID
[FD_PITCH
] = 0; // center pitch servo if pitch attitude within soaring mode deadband
775 #ifdef USE_AUTOTUNE_FIXED_WING
776 if (FLIGHT_MODE(AUTO_TUNE
) && !FLIGHT_MODE(MANUAL_MODE
)) {
777 autotuneFixedWingUpdate(axis
, rateTarget
, pidState
->gyroRate
, constrainf(newPTerm
+ newFFTerm
, -pidState
->pidSumLimit
, +pidState
->pidSumLimit
));
782 axisPID_P
[axis
] = newPTerm
;
783 axisPID_I
[axis
] = pidState
->errorGyroIf
;
784 axisPID_D
[axis
] = newDTerm
;
785 axisPID_F
[axis
] = newFFTerm
;
786 axisPID_Setpoint
[axis
] = rateTarget
;
789 pidState
->previousRateGyro
= pidState
->gyroRate
;
793 static float FAST_CODE
applyItermRelax(const int axis
, float currentPidSetpoint
, float itermErrorRate
)
796 if (axis
< FD_YAW
|| itermRelax
== ITERM_RELAX_RPY
) {
798 const float setpointLpf
= pt1FilterApply(&windupLpf
[axis
], currentPidSetpoint
);
799 const float setpointHpf
= fabsf(currentPidSetpoint
- setpointLpf
);
801 const float itermRelaxFactor
= MAX(0, 1 - setpointHpf
/ MC_ITERM_RELAX_SETPOINT_THRESHOLD
);
802 return itermErrorRate
* itermRelaxFactor
;
806 return itermErrorRate
;
809 static void FAST_CODE NOINLINE
pidApplyMulticopterRateController(pidState_t
*pidState
, flight_dynamics_index_t axis
, float dT
)
812 const float rateTarget
= getFlightAxisRateOverride(axis
, pidState
->rateTarget
);
814 const float rateError
= rateTarget
- pidState
->gyroRate
;
815 const float newPTerm
= pTermProcess(pidState
, rateError
, dT
);
816 const float newDTerm
= dTermProcess(pidState
, rateTarget
, dT
);
818 const float rateTargetDelta
= rateTarget
- pidState
->previousRateTarget
;
819 const float rateTargetDeltaFiltered
= pt3FilterApply(&pidState
->rateTargetFilter
, rateTargetDelta
);
822 * Compute Control Derivative
824 const float newCDTerm
= rateTargetDeltaFiltered
* pidState
->kCD
;
826 // TODO: Get feedback from mixer on available correction range for each axis
827 const float newOutput
= newPTerm
+ newDTerm
+ pidState
->errorGyroIf
+ newCDTerm
;
828 const float newOutputLimited
= constrainf(newOutput
, -pidState
->pidSumLimit
, +pidState
->pidSumLimit
);
830 float itermErrorRate
= applyItermRelax(axis
, rateTarget
, rateError
);
832 #ifdef USE_ANTIGRAVITY
833 itermErrorRate
*= iTermAntigravityGain
;
836 pidState
->errorGyroIf
+= (itermErrorRate
* pidState
->kI
* antiWindupScaler
* dT
)
837 + ((newOutputLimited
- newOutput
) * pidState
->kT
* antiWindupScaler
* dT
);
839 // Don't grow I-term if motors are at their limit
840 applyItermLimiting(pidState
);
842 axisPID
[axis
] = newOutputLimited
;
845 axisPID_P
[axis
] = newPTerm
;
846 axisPID_I
[axis
] = pidState
->errorGyroIf
;
847 axisPID_D
[axis
] = newDTerm
;
848 axisPID_F
[axis
] = newCDTerm
;
849 axisPID_Setpoint
[axis
] = rateTarget
;
852 pidState
->previousRateTarget
= rateTarget
;
853 pidState
->previousRateGyro
= pidState
->gyroRate
;
856 void updateHeadingHoldTarget(int16_t heading
)
858 headingHoldTarget
= heading
;
861 void resetHeadingHoldTarget(int16_t heading
)
863 updateHeadingHoldTarget(heading
);
864 pt1FilterReset(&headingHoldRateFilter
, 0.0f
);
867 int16_t getHeadingHoldTarget(void) {
868 return headingHoldTarget
;
871 static uint8_t getHeadingHoldState(void)
873 // Don't apply heading hold if overall tilt is greater than maximum angle inclination
874 if (calculateCosTiltAngle() < headingHoldCosZLimit
) {
875 return HEADING_HOLD_DISABLED
;
878 int navHeadingState
= navigationGetHeadingControlState();
879 // NAV will prevent MAG_MODE from activating, but require heading control
880 if (navHeadingState
!= NAV_HEADING_CONTROL_NONE
) {
881 // Apply maghold only if heading control is in auto mode
882 if (navHeadingState
== NAV_HEADING_CONTROL_AUTO
) {
883 return HEADING_HOLD_ENABLED
;
886 else if (ABS(rcCommand
[YAW
]) == 0 && FLIGHT_MODE(HEADING_MODE
)) {
887 return HEADING_HOLD_ENABLED
;
889 return HEADING_HOLD_UPDATE_HEADING
;
892 return HEADING_HOLD_UPDATE_HEADING
;
896 * HEADING_HOLD P Controller returns desired rotation rate in dps to be fed to Rate controller
898 float pidHeadingHold(float dT
)
900 float headingHoldRate
;
902 int16_t error
= DECIDEGREES_TO_DEGREES(attitude
.values
.yaw
) - headingHoldTarget
;
905 * Convert absolute error into relative to current heading
916 New MAG_HOLD controller work slightly different that previous one.
917 Old one mapped error to rotation speed in following way:
918 - on rate 0 it gave about 0.5dps for each degree of error
919 - error 0 = rotation speed of 0dps
920 - error 180 = rotation speed of 96 degrees per second
922 - that gives about 2 seconds to correct any error, no matter how big. Of course, usually more because of inertia.
923 That was making him quite "soft" for small changes and rapid for big ones that started to appear
924 when INAV introduced real RTH and WAYPOINT that might require rapid heading changes.
926 New approach uses modified principle:
927 - manual yaw rate is not used. MAG_HOLD is decoupled from manual input settings
928 - instead, mag_hold_rate_limit is used. It defines max rotation speed in dps that MAG_HOLD controller can require from RateController
929 - computed rotation speed is capped at -mag_hold_rate_limit and mag_hold_rate_limit
930 - Default mag_hold_rate_limit = 40dps and default MAG_HOLD P-gain is 40
931 - With those values, maximum rotation speed will be required from Rate Controller when error is greater that 30 degrees
932 - For smaller error, required rate will be proportional.
933 - It uses LPF filter set at 2Hz to additionally smoothen out any rapid changes
934 - That makes correction of smaller errors stronger, and those of big errors softer
936 This make looks as very slow rotation rate, but please remember this is automatic mode.
937 Manual override with YAW input when MAG_HOLD is enabled will still use "manual" rates, not MAG_HOLD rates.
938 Highest possible correction is 180 degrees and it will take more less 4.5 seconds. It is even more than sufficient
939 to run RTH or WAYPOINT missions. My favourite rate range here is 20dps - 30dps that gives nice and smooth turns.
941 Correction for small errors is much faster now. For example, old contrioller for 2deg errors required 1dps (correction in 2 seconds).
942 New controller for 2deg error requires 2,6dps. 4dps for 3deg and so on up until mag_hold_rate_limit is reached.
945 headingHoldRate
= error
* pidBank()->pid
[PID_HEADING
].P
/ 30.0f
;
946 headingHoldRate
= constrainf(headingHoldRate
, -pidProfile()->heading_hold_rate_limit
, pidProfile()->heading_hold_rate_limit
);
947 headingHoldRate
= pt1FilterApply4(&headingHoldRateFilter
, headingHoldRate
, HEADING_HOLD_ERROR_LPF_FREQ
, dT
);
949 return headingHoldRate
;
953 * TURN ASSISTANT mode is an assisted mode to do a Yaw rotation on a ground plane, allowing one-stick turn in RATE more
954 * and keeping ROLL and PITCH attitude though the turn.
956 static void NOINLINE
pidTurnAssistant(pidState_t
*pidState
, float bankAngleTarget
, float pitchAngleTarget
)
958 fpVector3_t targetRates
;
959 targetRates
.x
= 0.0f
;
960 targetRates
.y
= 0.0f
;
962 if (STATE(AIRPLANE
)) {
963 if (calculateCosTiltAngle() >= 0.173648f
) {
964 // Ideal banked turn follow the equations:
965 // forward_vel^2 / radius = Gravity * tan(roll_angle)
966 // yaw_rate = forward_vel / radius
967 // If we solve for roll angle we get:
968 // tan(roll_angle) = forward_vel * yaw_rate / Gravity
969 // If we solve for yaw rate we get:
970 // yaw_rate = tan(roll_angle) * Gravity / forward_vel
972 #if defined(USE_PITOT)
973 float airspeedForCoordinatedTurn
= sensors(SENSOR_PITOT
) && pitotIsHealthy()? getAirspeedEstimate() : pidProfile()->fixedWingReferenceAirspeed
;
975 float airspeedForCoordinatedTurn
= pidProfile()->fixedWingReferenceAirspeed
;
978 // Constrain to somewhat sane limits - 10km/h - 216km/h
979 airspeedForCoordinatedTurn
= constrainf(airspeedForCoordinatedTurn
, 300.0f
, 6000.0f
);
981 // Calculate rate of turn in Earth frame according to FAA's Pilot's Handbook of Aeronautical Knowledge
982 bankAngleTarget
= constrainf(bankAngleTarget
, -DEGREES_TO_RADIANS(60), DEGREES_TO_RADIANS(60));
983 float turnRatePitchAdjustmentFactor
= cos_approx(fabsf(pitchAngleTarget
));
984 float coordinatedTurnRateEarthFrame
= GRAVITY_CMSS
* tan_approx(-bankAngleTarget
) / airspeedForCoordinatedTurn
* turnRatePitchAdjustmentFactor
;
986 targetRates
.z
= RADIANS_TO_DEGREES(coordinatedTurnRateEarthFrame
);
989 // Don't allow coordinated turn calculation if airplane is in hard bank or steep climb/dive
994 targetRates
.z
= pidState
[YAW
].rateTarget
;
997 // Transform calculated rate offsets into body frame and apply
998 imuTransformVectorEarthToBody(&targetRates
);
1000 // Add in roll and pitch
1001 pidState
[ROLL
].rateTarget
= constrainf(pidState
[ROLL
].rateTarget
+ targetRates
.x
, -currentControlRateProfile
->stabilized
.rates
[ROLL
] * 10.0f
, currentControlRateProfile
->stabilized
.rates
[ROLL
] * 10.0f
);
1002 pidState
[PITCH
].rateTarget
= constrainf(pidState
[PITCH
].rateTarget
+ targetRates
.y
* pidProfile()->fixedWingCoordinatedPitchGain
, -currentControlRateProfile
->stabilized
.rates
[PITCH
] * 10.0f
, currentControlRateProfile
->stabilized
.rates
[PITCH
] * 10.0f
);
1004 // Replace YAW on quads - add it in on airplanes
1005 if (STATE(AIRPLANE
)) {
1006 pidState
[YAW
].rateTarget
= constrainf(pidState
[YAW
].rateTarget
+ targetRates
.z
* pidProfile()->fixedWingCoordinatedYawGain
, -currentControlRateProfile
->stabilized
.rates
[YAW
] * 10.0f
, currentControlRateProfile
->stabilized
.rates
[YAW
] * 10.0f
);
1009 pidState
[YAW
].rateTarget
= constrainf(targetRates
.z
, -currentControlRateProfile
->stabilized
.rates
[YAW
] * 10.0f
, currentControlRateProfile
->stabilized
.rates
[YAW
] * 10.0f
);
1013 static void pidApplyFpvCameraAngleMix(pidState_t
*pidState
, uint8_t fpvCameraAngle
)
1015 static uint8_t lastFpvCamAngleDegrees
= 0;
1016 static float cosCameraAngle
= 1.0f
;
1017 static float sinCameraAngle
= 0.0f
;
1019 if (lastFpvCamAngleDegrees
!= fpvCameraAngle
) {
1020 lastFpvCamAngleDegrees
= fpvCameraAngle
;
1021 cosCameraAngle
= cos_approx(DEGREES_TO_RADIANS(fpvCameraAngle
));
1022 sinCameraAngle
= sin_approx(DEGREES_TO_RADIANS(fpvCameraAngle
));
1025 // Rotate roll/yaw command from camera-frame coordinate system to body-frame coordinate system
1026 const float rollRate
= pidState
[ROLL
].rateTarget
;
1027 const float yawRate
= pidState
[YAW
].rateTarget
;
1028 pidState
[ROLL
].rateTarget
= constrainf(rollRate
* cosCameraAngle
- yawRate
* sinCameraAngle
, -GYRO_SATURATION_LIMIT
, GYRO_SATURATION_LIMIT
);
1029 pidState
[YAW
].rateTarget
= constrainf(yawRate
* cosCameraAngle
+ rollRate
* sinCameraAngle
, -GYRO_SATURATION_LIMIT
, GYRO_SATURATION_LIMIT
);
1032 void checkItermLimitingActive(pidState_t
*pidState
)
1034 bool shouldActivate
;
1035 if (usedPidControllerType
== PID_TYPE_PIFF
) {
1036 shouldActivate
= isFixedWingItermLimitActive(pidState
->stickPosition
);
1039 shouldActivate
= mixerIsOutputSaturated();
1042 pidState
->itermLimitActive
= STATE(ANTI_WINDUP
) || shouldActivate
;
1045 void checkItermFreezingActive(pidState_t
*pidState
, flight_dynamics_index_t axis
)
1047 if (usedPidControllerType
== PID_TYPE_PIFF
&& pidProfile()->fixedWingYawItermBankFreeze
!= 0 && axis
== FD_YAW
) {
1048 // Do not allow yaw I-term to grow when bank angle is too large
1049 float bankAngle
= DECIDEGREES_TO_DEGREES(attitude
.values
.roll
);
1050 if (fabsf(bankAngle
) > pidProfile()->fixedWingYawItermBankFreeze
&& !(FLIGHT_MODE(AUTO_TUNE
) || FLIGHT_MODE(TURN_ASSISTANT
) || navigationRequiresTurnAssistance())){
1051 pidState
->itermFreezeActive
= true;
1054 pidState
->itermFreezeActive
= false;
1058 pidState
->itermFreezeActive
= false;
1063 void FAST_CODE
pidController(float dT
)
1065 if (!pidFiltersConfigured
) {
1069 bool canUseFpvCameraMix
= true;
1070 uint8_t headingHoldState
= getHeadingHoldState();
1072 // In case Yaw override is active, we engage the Heading Hold state
1073 if (isFlightAxisAngleOverrideActive(FD_YAW
)) {
1074 headingHoldState
= HEADING_HOLD_ENABLED
;
1075 headingHoldTarget
= getFlightAxisAngleOverride(FD_YAW
, 0);
1078 if (headingHoldState
== HEADING_HOLD_UPDATE_HEADING
) {
1079 updateHeadingHoldTarget(DECIDEGREES_TO_DEGREES(attitude
.values
.yaw
));
1082 for (int axis
= 0; axis
< 3; axis
++) {
1083 // Step 1: Calculate gyro rates
1084 pidState
[axis
].gyroRate
= gyro
.gyroADCf
[axis
];
1086 // Step 2: Read target
1089 if (axis
== FD_YAW
&& headingHoldState
== HEADING_HOLD_ENABLED
) {
1090 rateTarget
= pidHeadingHold(dT
);
1092 #ifdef USE_PROGRAMMING_FRAMEWORK
1093 rateTarget
= pidRcCommandToRate(getRcCommandOverride(rcCommand
, axis
), currentControlRateProfile
->stabilized
.rates
[axis
]);
1095 rateTarget
= pidRcCommandToRate(rcCommand
[axis
], currentControlRateProfile
->stabilized
.rates
[axis
]);
1099 // Limit desired rate to something gyro can measure reliably
1100 pidState
[axis
].rateTarget
= constrainf(rateTarget
, -GYRO_SATURATION_LIMIT
, +GYRO_SATURATION_LIMIT
);
1102 #ifdef USE_GYRO_KALMAN
1103 gyroKalmanUpdateSetpoint(axis
, pidState
[axis
].rateTarget
);
1106 #ifdef USE_SMITH_PREDICTOR
1107 pidState
[axis
].gyroRate
= applySmithPredictor(axis
, &pidState
[axis
].smithPredictor
, pidState
[axis
].gyroRate
);
1111 // Step 3: Run control for ANGLE_MODE, HORIZON_MODE, and HEADING_LOCK
1112 const float horizonRateMagnitude
= calcHorizonRateMagnitude();
1113 levelingEnabled
= false;
1114 for (uint8_t axis
= FD_ROLL
; axis
<= FD_PITCH
; axis
++) {
1115 if (FLIGHT_MODE(ANGLE_MODE
) || FLIGHT_MODE(HORIZON_MODE
) || isFlightAxisAngleOverrideActive(axis
)) {
1116 //If axis angle override, get the correct angle from Logic Conditions
1117 float angleTarget
= getFlightAxisAngleOverride(axis
, computePidLevelTarget(axis
));
1119 //Apply the Level PID controller
1120 pidLevel(angleTarget
, &pidState
[axis
], axis
, horizonRateMagnitude
, dT
);
1121 canUseFpvCameraMix
= false; // FPVANGLEMIX is incompatible with ANGLE/HORIZON
1122 levelingEnabled
= true;
1126 if ((FLIGHT_MODE(TURN_ASSISTANT
) || navigationRequiresTurnAssistance()) && (FLIGHT_MODE(ANGLE_MODE
) || FLIGHT_MODE(HORIZON_MODE
) || navigationRequiresTurnAssistance())) {
1127 float bankAngleTarget
= DECIDEGREES_TO_RADIANS(pidRcCommandToAngle(rcCommand
[FD_ROLL
], pidProfile()->max_angle_inclination
[FD_ROLL
]));
1128 float pitchAngleTarget
= DECIDEGREES_TO_RADIANS(pidRcCommandToAngle(rcCommand
[FD_PITCH
], pidProfile()->max_angle_inclination
[FD_PITCH
]));
1129 pidTurnAssistant(pidState
, bankAngleTarget
, pitchAngleTarget
);
1130 canUseFpvCameraMix
= false; // FPVANGLEMIX is incompatible with TURN_ASSISTANT
1133 if (canUseFpvCameraMix
&& IS_RC_MODE_ACTIVE(BOXFPVANGLEMIX
) && currentControlRateProfile
->misc
.fpvCamAngleDegrees
) {
1134 pidApplyFpvCameraAngleMix(pidState
, currentControlRateProfile
->misc
.fpvCamAngleDegrees
);
1137 // Prevent strong Iterm accumulation during stick inputs
1138 antiWindupScaler
= constrainf((1.0f
- getMotorMixRange()) / motorItermWindupPoint
, 0.0f
, 1.0f
);
1140 for (int axis
= 0; axis
< 3; axis
++) {
1141 // Apply setpoint rate of change limits
1142 pidApplySetpointRateLimiting(&pidState
[axis
], axis
, dT
);
1144 // Step 4: Run gyro-driven control
1145 checkItermLimitingActive(&pidState
[axis
]);
1146 checkItermFreezingActive(&pidState
[axis
], axis
);
1148 pidControllerApplyFn(&pidState
[axis
], axis
, dT
);
1152 pidType_e
pidIndexGetType(pidIndex_e pidIndex
)
1154 if (pidIndex
== PID_ROLL
|| pidIndex
== PID_PITCH
|| pidIndex
== PID_YAW
) {
1155 return usedPidControllerType
;
1157 if (STATE(AIRPLANE
) || STATE(ROVER
) || STATE(BOAT
)) {
1158 if (pidIndex
== PID_VEL_XY
|| pidIndex
== PID_VEL_Z
) {
1159 return PID_TYPE_NONE
;
1163 if (pidIndex
== PID_SURFACE
) {
1164 return PID_TYPE_NONE
;
1166 return PID_TYPE_PID
;
1171 // Calculate max overall tilt (max pitch + max roll combined) as a limit to heading hold
1172 headingHoldCosZLimit
= cos_approx(DECIDEGREES_TO_RADIANS(pidProfile()->max_angle_inclination
[FD_ROLL
])) *
1173 cos_approx(DECIDEGREES_TO_RADIANS(pidProfile()->max_angle_inclination
[FD_PITCH
]));
1175 pidGainsUpdateRequired
= false;
1177 itermRelax
= pidProfile()->iterm_relax
;
1179 yawLpfHz
= pidProfile()->yaw_lpf_hz
;
1180 motorItermWindupPoint
= 1.0f
- (pidProfile()->itermWindupPointPercent
/ 100.0f
);
1183 dBoostMin
= pidProfile()->dBoostMin
;
1184 dBoostMax
= pidProfile()->dBoostMax
;
1185 dBoostMaxAtAlleceleration
= pidProfile()->dBoostMaxAtAlleceleration
;
1188 #ifdef USE_ANTIGRAVITY
1189 antigravityGain
= pidProfile()->antigravityGain
;
1190 antigravityAccelerator
= pidProfile()->antigravityAccelerator
;
1193 for (uint8_t axis
= FD_ROLL
; axis
<= FD_YAW
; axis
++) {
1196 // Rate * 10 * 10. First 10 is to convert stick to DPS. Second 10 is to convert target to acceleration.
1197 // We assume, max acceleration is when pilot deflects the stick fully in 100ms
1198 pidState
[axis
].dBoostTargetAcceleration
= currentControlRateProfile
->stabilized
.rates
[axis
] * 10 * 10;
1201 pidState
[axis
].axis
= axis
;
1202 if (axis
== FD_YAW
) {
1203 pidState
[axis
].pidSumLimit
= pidProfile()->pidSumLimitYaw
;
1205 pidState
[axis
].ptermFilterApplyFn
= (filterApply4FnPtr
) pt1FilterApply4
;
1207 pidState
[axis
].ptermFilterApplyFn
= (filterApply4FnPtr
) nullFilterApply4
;
1210 pidState
[axis
].pidSumLimit
= pidProfile()->pidSumLimit
;
1211 pidState
[axis
].ptermFilterApplyFn
= (filterApply4FnPtr
) nullFilterApply4
;
1215 if (pidProfile()->pidControllerType
== PID_TYPE_AUTO
) {
1217 mixerConfig()->platformType
== PLATFORM_AIRPLANE
||
1218 mixerConfig()->platformType
== PLATFORM_BOAT
||
1219 mixerConfig()->platformType
== PLATFORM_ROVER
1221 usedPidControllerType
= PID_TYPE_PIFF
;
1223 usedPidControllerType
= PID_TYPE_PID
;
1226 usedPidControllerType
= pidProfile()->pidControllerType
;
1229 assignFilterApplyFn(pidProfile()->dterm_lpf_type
, pidProfile()->dterm_lpf_hz
, &dTermLpfFilterApplyFn
);
1231 if (usedPidControllerType
== PID_TYPE_PIFF
) {
1232 pidControllerApplyFn
= pidApplyFixedWingRateController
;
1233 } else if (usedPidControllerType
== PID_TYPE_PID
) {
1234 pidControllerApplyFn
= pidApplyMulticopterRateController
;
1236 pidControllerApplyFn
= nullRateController
;
1239 pidResetTPAFilter();
1241 fixedWingLevelTrim
= pidProfile()->fixedWingLevelTrim
;
1244 &fixedWingLevelTrimController
,
1246 (float)pidProfile()->fixedWingLevelTrimGain
/ 100000.0f
,
1255 const pidBank_t
* pidBank(void) {
1256 return usedPidControllerType
== PID_TYPE_PIFF
? &pidProfile()->bank_fw
: &pidProfile()->bank_mc
;
1258 pidBank_t
* pidBankMutable(void) {
1259 return usedPidControllerType
== PID_TYPE_PIFF
? &pidProfileMutable()->bank_fw
: &pidProfileMutable()->bank_mc
;
1262 void updateFixedWingLevelTrim(timeUs_t currentTimeUs
)
1264 if (!STATE(AIRPLANE
)) {
1268 static timeUs_t previousUpdateTimeUs
;
1269 static bool previousArmingState
;
1270 const float dT
= US2S(currentTimeUs
- previousUpdateTimeUs
);
1273 * On every ARM reset the controller
1275 if (ARMING_FLAG(ARMED
) && !previousArmingState
) {
1276 navPidReset(&fixedWingLevelTrimController
);
1280 * On disarm update the default value
1282 if (!ARMING_FLAG(ARMED
) && previousArmingState
) {
1283 pidProfileMutable()->fixedWingLevelTrim
= constrainf(fixedWingLevelTrim
, -FIXED_WING_LEVEL_TRIM_MAX_ANGLE
, FIXED_WING_LEVEL_TRIM_MAX_ANGLE
);
1287 * Prepare flags for the PID controller
1289 pidControllerFlags_e flags
= PID_LIMIT_INTEGRATOR
;
1291 //Iterm should freeze when sticks are deflected
1293 !IS_RC_MODE_ACTIVE(BOXAUTOLEVEL
) ||
1294 areSticksDeflected() ||
1295 (!FLIGHT_MODE(ANGLE_MODE
) && !FLIGHT_MODE(HORIZON_MODE
) && !FLIGHT_MODE(NAV_COURSE_HOLD_MODE
)) ||
1296 FLIGHT_MODE(SOARING_MODE
) ||
1297 navigationIsControllingAltitude()
1299 flags
|= PID_FREEZE_INTEGRATOR
;
1302 const float output
= navPidApply3(
1303 &fixedWingLevelTrimController
,
1304 0, //Setpoint is always 0 as we try to keep level flight
1305 getEstimatedActualVelocity(Z
),
1307 -FIXED_WING_LEVEL_TRIM_CONTROLLER_LIMIT
,
1308 FIXED_WING_LEVEL_TRIM_CONTROLLER_LIMIT
,
1314 DEBUG_SET(DEBUG_AUTOLEVEL
, 4, output
);
1315 fixedWingLevelTrim
= pidProfile()->fixedWingLevelTrim
+ (output
* FIXED_WING_LEVEL_TRIM_MULTIPLIER
);
1317 previousArmingState
= !!ARMING_FLAG(ARMED
);
1320 float getFixedWingLevelTrim(void)
1322 return STATE(AIRPLANE
) ? fixedWingLevelTrim
: 0;