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/>.
25 #include "blackbox/blackbox.h"
26 #include "blackbox/blackbox_fielddefs.h"
28 #include "common/axis.h"
29 #include "common/maths.h"
30 #include "common/utils.h"
32 #include "drivers/time.h"
34 #include "config/feature.h"
35 #include "config/parameter_group.h"
36 #include "config/parameter_group_ids.h"
38 #include "fc/config.h"
39 #include "fc/controlrate_profile.h"
40 #include "fc/rc_controls.h"
41 #include "fc/rc_adjustments.h"
42 #include "fc/runtime_config.h"
44 #include "flight/pid.h"
46 #define AUTOTUNE_FIXED_WING_OVERSHOOT_TIME 100
47 #define AUTOTUNE_FIXED_WING_UNDERSHOOT_TIME 200
48 #define AUTOTUNE_FIXED_WING_INTEGRATOR_TC 600
49 #define AUTOTUNE_FIXED_WING_DECREASE_STEP 8 // 8%
50 #define AUTOTUNE_FIXED_WING_INCREASE_STEP 5 // 5%
51 #define AUTOTUNE_FIXED_WING_MIN_FF 10
52 #define AUTOTUNE_FIXED_WING_MAX_FF 200
54 PG_REGISTER_WITH_RESET_TEMPLATE(pidAutotuneConfig_t
, pidAutotuneConfig
, PG_PID_AUTOTUNE_CONFIG
, 0);
56 PG_RESET_TEMPLATE(pidAutotuneConfig_t
, pidAutotuneConfig
,
57 .fw_overshoot_time
= AUTOTUNE_FIXED_WING_OVERSHOOT_TIME
,
58 .fw_undershoot_time
= AUTOTUNE_FIXED_WING_UNDERSHOOT_TIME
,
59 .fw_max_rate_threshold
= 50,
60 .fw_ff_to_p_gain
= 10,
61 .fw_ff_to_i_time_constant
= AUTOTUNE_FIXED_WING_INTEGRATOR_TC
,
71 pidAutotuneState_e state
;
72 timeMs_t stateEnterTime
;
80 #define AUTOTUNE_SAVE_PERIOD 5000 // Save interval is 5 seconds - when we turn off autotune we'll restore values from previous update at most 5 sec ago
82 #if defined(USE_AUTOTUNE_FIXED_WING) || defined(USE_AUTOTUNE_MULTIROTOR)
84 static pidAutotuneData_t tuneCurrent
[XYZ_AXIS_COUNT
];
85 static pidAutotuneData_t tuneSaved
[XYZ_AXIS_COUNT
];
86 static timeMs_t lastGainsUpdateTime
;
88 void autotuneUpdateGains(pidAutotuneData_t
* data
)
90 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
91 pidBankMutable()->pid
[axis
].P
= lrintf(data
[axis
].gainP
);
92 pidBankMutable()->pid
[axis
].I
= lrintf(data
[axis
].gainI
);
93 pidBankMutable()->pid
[axis
].D
= 0;
94 pidBankMutable()->pid
[axis
].FF
= lrintf(data
[axis
].gainFF
);
96 schedulePidGainsUpdate();
99 void autotuneCheckUpdateGains(void)
101 const timeMs_t currentTimeMs
= millis();
103 if ((currentTimeMs
- lastGainsUpdateTime
) < AUTOTUNE_SAVE_PERIOD
) {
107 // If pilot will exit autotune we'll restore values we are flying now
108 memcpy(tuneSaved
, tuneCurrent
, sizeof(pidAutotuneData_t
) * XYZ_AXIS_COUNT
);
109 autotuneUpdateGains(tuneSaved
);
110 lastGainsUpdateTime
= currentTimeMs
;
113 void autotuneStart(void)
115 for (int axis
= 0; axis
< XYZ_AXIS_COUNT
; axis
++) {
116 tuneCurrent
[axis
].gainP
= pidBank()->pid
[axis
].P
;
117 tuneCurrent
[axis
].gainI
= pidBank()->pid
[axis
].I
;
118 tuneCurrent
[axis
].gainFF
= pidBank()->pid
[axis
].FF
;
119 tuneCurrent
[axis
].pidSaturated
= false;
120 tuneCurrent
[axis
].stateEnterTime
= millis();
121 tuneCurrent
[axis
].state
= DEMAND_TOO_LOW
;
124 memcpy(tuneSaved
, tuneCurrent
, sizeof(pidAutotuneData_t
) * XYZ_AXIS_COUNT
);
125 lastGainsUpdateTime
= millis();
128 void autotuneUpdateState(void)
130 if (IS_RC_MODE_ACTIVE(BOXAUTOTUNE
) && ARMING_FLAG(ARMED
)) {
131 if (!FLIGHT_MODE(AUTO_TUNE
)) {
133 ENABLE_FLIGHT_MODE(AUTO_TUNE
);
136 autotuneCheckUpdateGains();
139 if (FLIGHT_MODE(AUTO_TUNE
)) {
140 autotuneUpdateGains(tuneSaved
);
143 DISABLE_FLIGHT_MODE(AUTO_TUNE
);
147 static void blackboxLogAutotuneEvent(adjustmentFunction_e adjustmentFunction
, int32_t newValue
)
150 UNUSED(adjustmentFunction
);
153 if (feature(FEATURE_BLACKBOX
)) {
154 flightLogEvent_inflightAdjustment_t eventData
;
155 eventData
.adjustmentFunction
= adjustmentFunction
;
156 eventData
.newValue
= newValue
;
157 eventData
.floatFlag
= false;
158 blackboxLogEvent(FLIGHT_LOG_EVENT_INFLIGHT_ADJUSTMENT
, (flightLogEventData_t
*)&eventData
);
163 #if defined(USE_AUTOTUNE_FIXED_WING)
165 void autotuneFixedWingUpdate(const flight_dynamics_index_t axis
, float desiredRateDps
, float reachedRateDps
, float pidOutput
)
167 const timeMs_t currentTimeMs
= millis();
168 const float absDesiredRateDps
= fabsf(desiredRateDps
);
169 float maxDesiredRate
= currentControlRateProfile
->stabilized
.rates
[axis
] * 10.0f
;
170 pidAutotuneState_e newState
;
172 // Use different max desired rate in ANGLE for pitch and roll
173 // Maximum reasonable error in ANGLE mode is 200% of angle inclination (control dublet), but we are conservative and tune for control singlet.
174 if (FLIGHT_MODE(ANGLE_MODE
) && (axis
== FD_PITCH
|| axis
== FD_ROLL
)) {
175 float maxDesiredRateInAngleMode
= DECIDEGREES_TO_DEGREES(pidProfile()->max_angle_inclination
[axis
] * 1.0f
) * pidBank()->pid
[PID_LEVEL
].P
/ FP_PID_LEVEL_P_MULTIPLIER
;
176 maxDesiredRate
= MIN(maxDesiredRate
, maxDesiredRateInAngleMode
);
179 if (fabsf(pidOutput
) >= pidProfile()->pidSumLimit
) {
180 // If we have saturated the pid output by P+FF don't increase the gain
181 tuneCurrent
[axis
].pidSaturated
= true;
184 if (absDesiredRateDps
< (pidAutotuneConfig()->fw_max_rate_threshold
/ 100.0f
) * maxDesiredRate
) {
185 // We can make decisions only when we are demanding at least 50% of max configured rate
186 newState
= DEMAND_TOO_LOW
;
188 else if (fabsf(reachedRateDps
) > absDesiredRateDps
) {
189 newState
= DEMAND_OVERSHOOT
;
192 newState
= DEMAND_UNDERSHOOT
;
195 if (newState
!= tuneCurrent
[axis
].state
) {
196 const timeDelta_t stateTimeMs
= currentTimeMs
- tuneCurrent
[axis
].stateEnterTime
;
197 bool gainsUpdated
= false;
199 switch (tuneCurrent
[axis
].state
) {
202 case DEMAND_OVERSHOOT
:
203 if (stateTimeMs
>= pidAutotuneConfig()->fw_overshoot_time
) {
204 tuneCurrent
[axis
].gainFF
= tuneCurrent
[axis
].gainFF
* (100 - AUTOTUNE_FIXED_WING_DECREASE_STEP
) / 100.0f
;
205 if (tuneCurrent
[axis
].gainFF
< AUTOTUNE_FIXED_WING_MIN_FF
) {
206 tuneCurrent
[axis
].gainFF
= AUTOTUNE_FIXED_WING_MIN_FF
;
211 case DEMAND_UNDERSHOOT
:
212 if (stateTimeMs
>= pidAutotuneConfig()->fw_undershoot_time
&& !tuneCurrent
[axis
].pidSaturated
) {
213 tuneCurrent
[axis
].gainFF
= tuneCurrent
[axis
].gainFF
* (100 + AUTOTUNE_FIXED_WING_INCREASE_STEP
) / 100.0f
;
214 if (tuneCurrent
[axis
].gainFF
> AUTOTUNE_FIXED_WING_MAX_FF
) {
215 tuneCurrent
[axis
].gainFF
= AUTOTUNE_FIXED_WING_MAX_FF
;
223 // Set P-gain to 10% of FF gain (quite agressive - FIXME)
224 tuneCurrent
[axis
].gainP
= tuneCurrent
[axis
].gainFF
* (pidAutotuneConfig()->fw_ff_to_p_gain
/ 100.0f
);
226 // Set integrator gain to reach the same response as FF gain in 0.667 second
227 tuneCurrent
[axis
].gainI
= (tuneCurrent
[axis
].gainFF
/ FP_PID_RATE_FF_MULTIPLIER
) * (1000.0f
/ pidAutotuneConfig()->fw_ff_to_i_time_constant
) * FP_PID_RATE_I_MULTIPLIER
;
228 tuneCurrent
[axis
].gainI
= constrainf(tuneCurrent
[axis
].gainI
, 2.0f
, 50.0f
);
229 autotuneUpdateGains(tuneCurrent
);
233 blackboxLogAutotuneEvent(ADJUSTMENT_ROLL_D
, tuneCurrent
[axis
].gainFF
);
237 blackboxLogAutotuneEvent(ADJUSTMENT_PITCH_D
, tuneCurrent
[axis
].gainFF
);
241 blackboxLogAutotuneEvent(ADJUSTMENT_YAW_D
, tuneCurrent
[axis
].gainFF
);
246 // Change state and reset saturation flag
247 tuneCurrent
[axis
].state
= newState
;
248 tuneCurrent
[axis
].stateEnterTime
= currentTimeMs
;
249 tuneCurrent
[axis
].pidSaturated
= false;