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/>.
23 #include "unittest_macros.h"
24 #include "gtest/gtest.h"
25 #include "build/debug.h"
27 bool simulatedAirmodeEnabled
= true;
28 float simulatedSetpointRate
[3] = { 0,0,0 };
29 float simulatedPrevSetpointRate
[3] = { 0,0,0 };
30 float simulatedRcDeflection
[3] = { 0,0,0 };
31 float simulatedThrottlePIDAttenuation
= 1.0f
;
32 float simulatedMotorMixRange
= 0.0f
;
34 int16_t debug
[DEBUG16_VALUE_COUNT
];
40 #include "build/debug.h"
42 #include "common/axis.h"
43 #include "common/maths.h"
44 #include "common/filter.h"
46 #include "config/config.h"
47 #include "config/config_reset.h"
49 #include "drivers/sound_beeper.h"
50 #include "drivers/time.h"
52 #include "fc/controlrate_profile.h"
56 #include "fc/rc_controls.h"
57 #include "fc/runtime_config.h"
59 #include "flight/imu.h"
60 #include "flight/mixer.h"
61 #include "flight/pid.h"
62 #include "flight/pid_init.h"
67 #include "pg/pg_ids.h"
69 #include "sensors/gyro.h"
70 #include "sensors/acceleration.h"
73 attitudeEulerAngles_t attitude
;
75 PG_REGISTER(accelerometerConfig_t
, accelerometerConfig
, PG_ACCELEROMETER_CONFIG
, 0);
76 PG_REGISTER(systemConfig_t
, systemConfig
, PG_SYSTEM_CONFIG
, 2);
78 bool unitLaunchControlActive
= false;
79 launchControlMode_e unitLaunchControlMode
= LAUNCH_CONTROL_MODE_NORMAL
;
81 float getThrottlePIDAttenuation(void) { return simulatedThrottlePIDAttenuation
; }
82 float getMotorMixRange(void) { return simulatedMotorMixRange
; }
83 float getSetpointRate(int axis
) { return simulatedSetpointRate
[axis
]; }
84 bool isAirmodeActivated() { return simulatedAirmodeEnabled
; }
85 float getRcDeflectionAbs(int axis
) { return fabsf(simulatedRcDeflection
[axis
]); }
86 void systemBeep(bool) { }
87 bool gyroOverflowDetected(void) { return false; }
88 float getRcDeflection(int axis
) { return simulatedRcDeflection
[axis
]; }
89 void beeperConfirmationBeeps(uint8_t) { }
90 bool isLaunchControlActive(void) {return unitLaunchControlActive
; }
91 void disarm(flightLogDisarmReason_e
) { }
92 float applyFeedforwardLimit(int axis
, float value
, float Kp
, float currentPidSetpoint
)
96 UNUSED(currentPidSetpoint
);
99 void feedforwardInit(const pidProfile_t
) { }
100 float feedforwardApply(int axis
, bool newRcFrame
, feedforwardAveraging_t feedforwardAveraging
)
103 UNUSED(feedforwardAveraging
);
104 const float feedforwardTransitionFactor
= pidGetFeedforwardTransitionFactor();
105 float setpointDelta
= simulatedSetpointRate
[axis
] - simulatedPrevSetpointRate
[axis
];
106 setpointDelta
*= feedforwardTransitionFactor
> 0 ? MIN(1.0f
, getRcDeflectionAbs(axis
) * feedforwardTransitionFactor
) : 1;
107 return setpointDelta
;
109 bool shouldApplyFeedforwardLimits(int axis
)
114 bool getShouldUpdateFeedforward() { return true; }
115 void initRcProcessing(void) { }
118 pidProfile_t
*pidProfile
;
122 // Always use same defaults for testing in future releases even when defaults change
123 void setDefaultTestSettings(void) {
125 pidProfile
= pidProfilesMutable(1);
126 pidProfile
->pid
[PID_ROLL
] = { 40, 40, 30, 65 };
127 pidProfile
->pid
[PID_PITCH
] = { 58, 50, 35, 60 };
128 pidProfile
->pid
[PID_YAW
] = { 70, 45, 20, 60 };
129 pidProfile
->pid
[PID_LEVEL
] = { 50, 50, 75, 0 };
131 // Compensate for the upscaling done without 'use_integrated_yaw'
132 pidProfile
->pid
[PID_YAW
].I
= pidProfile
->pid
[PID_YAW
].I
/ 2.5f
;
134 pidProfile
->pidSumLimit
= PIDSUM_LIMIT
;
135 pidProfile
->pidSumLimitYaw
= PIDSUM_LIMIT_YAW
;
136 pidProfile
->yaw_lowpass_hz
= 0;
137 pidProfile
->dterm_lpf1_static_hz
= 100;
138 pidProfile
->dterm_lpf2_static_hz
= 0;
139 pidProfile
->dterm_notch_hz
= 260;
140 pidProfile
->dterm_notch_cutoff
= 160;
141 pidProfile
->dterm_lpf1_type
= FILTER_BIQUAD
;
142 pidProfile
->itermWindupPointPercent
= 50;
143 pidProfile
->pidAtMinThrottle
= PID_STABILISATION_ON
;
144 pidProfile
->levelAngleLimit
= 55;
145 pidProfile
->feedforward_transition
= 100;
146 pidProfile
->yawRateAccelLimit
= 100;
147 pidProfile
->rateAccelLimit
= 0;
148 pidProfile
->antiGravityMode
= ANTI_GRAVITY_SMOOTH
;
149 pidProfile
->itermThrottleThreshold
= 250;
150 pidProfile
->itermAcceleratorGain
= 1000;
151 pidProfile
->crash_time
= 500;
152 pidProfile
->crash_delay
= 0;
153 pidProfile
->crash_recovery_angle
= 10;
154 pidProfile
->crash_recovery_rate
= 100;
155 pidProfile
->crash_dthreshold
= 50;
156 pidProfile
->crash_gthreshold
= 400;
157 pidProfile
->crash_setpoint_threshold
= 350;
158 pidProfile
->crash_recovery
= PID_CRASH_RECOVERY_OFF
;
159 pidProfile
->horizon_tilt_effect
= 75;
160 pidProfile
->horizon_tilt_expert_mode
= false;
161 pidProfile
->crash_limit_yaw
= 200;
162 pidProfile
->itermLimit
= 150;
163 pidProfile
->throttle_boost
= 0;
164 pidProfile
->throttle_boost_cutoff
= 15;
165 pidProfile
->iterm_rotation
= false;
166 pidProfile
->iterm_relax
= ITERM_RELAX_OFF
,
167 pidProfile
->iterm_relax_cutoff
= 11,
168 pidProfile
->iterm_relax_type
= ITERM_RELAX_SETPOINT
,
169 pidProfile
->abs_control_gain
= 0,
170 pidProfile
->launchControlMode
= LAUNCH_CONTROL_MODE_NORMAL
,
171 pidProfile
->launchControlGain
= 40,
172 pidProfile
->level_race_mode
= false,
174 gyro
.targetLooptime
= 8000;
177 timeUs_t
currentTestTime(void) {
178 return targetPidLooptime
* loopIter
++;
181 void resetTest(void) {
183 simulatedThrottlePIDAttenuation
= 1.0f
;
184 simulatedMotorMixRange
= 0.0f
;
186 pidStabilisationState(PID_STABILISATION_OFF
);
187 DISABLE_ARMING_FLAG(ARMED
);
189 setDefaultTestSettings();
190 for (int axis
= FD_ROLL
; axis
<= FD_YAW
; axis
++) {
195 pidData
[axis
].Sum
= 0;
196 simulatedSetpointRate
[axis
] = 0;
197 simulatedRcDeflection
[axis
] = 0;
198 gyro
.gyroADCf
[axis
] = 0;
200 attitude
.values
.roll
= 0;
201 attitude
.values
.pitch
= 0;
202 attitude
.values
.yaw
= 0;
205 unitLaunchControlActive
= false;
206 pidProfile
->launchControlMode
= unitLaunchControlMode
;
208 loadControlRateProfile();
210 currentControlRateProfile
->levelExpo
[FD_ROLL
] = 0;
211 currentControlRateProfile
->levelExpo
[FD_PITCH
] = 0;
213 // Run pidloop for a while after reset
214 for (int loop
= 0; loop
< 20; loop
++) {
215 pidController(pidProfile
, currentTestTime());
219 void setStickPosition(int axis
, float stickRatio
) {
220 simulatedPrevSetpointRate
[axis
] = simulatedSetpointRate
[axis
];
221 simulatedSetpointRate
[axis
] = 1998.0f
* stickRatio
;
222 simulatedRcDeflection
[axis
] = stickRatio
;
225 // All calculations will have 10% tolerance
226 float calculateTolerance(float input
) {
227 return fabsf(input
* 0.1f
);
230 TEST(pidControllerTest
, testInitialisation
)
234 // In initial state PIDsums should be 0
235 for (int axis
= 0; axis
<= FD_YAW
; axis
++) {
236 EXPECT_FLOAT_EQ(0, pidData
[axis
].P
);
237 EXPECT_FLOAT_EQ(0, pidData
[axis
].I
);
238 EXPECT_FLOAT_EQ(0, pidData
[axis
].D
);
242 TEST(pidControllerTest
, testStabilisationDisabled
) {
243 ENABLE_ARMING_FLAG(ARMED
);
244 // Run few loops to make sure there is no error building up when stabilisation disabled
246 for (int loop
= 0; loop
< 10; loop
++) {
247 pidController(pidProfile
, currentTestTime());
249 // PID controller should not do anything, while stabilisation disabled
250 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].P
);
251 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].P
);
252 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].P
);
253 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].I
);
254 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].I
);
255 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].I
);
256 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].D
);
257 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].D
);
258 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].D
);
262 TEST(pidControllerTest
, testPidLoop
) {
263 // Make sure to start with fresh values
265 ENABLE_ARMING_FLAG(ARMED
);
266 pidStabilisationState(PID_STABILISATION_ON
);
268 pidController(pidProfile
, currentTestTime());
270 // Loop 1 - Expecting zero since there is no error
271 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].P
);
272 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].P
);
273 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].P
);
274 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].I
);
275 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].I
);
276 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].I
);
277 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].D
);
278 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].D
);
279 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].D
);
281 // Add some rotation on ROLL to generate error
282 gyro
.gyroADCf
[FD_ROLL
] = 100;
283 pidController(pidProfile
, currentTestTime());
285 // Loop 2 - Expect PID loop reaction to ROLL error
286 EXPECT_NEAR(-128.1, pidData
[FD_ROLL
].P
, calculateTolerance(-128.1));
287 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].P
);
288 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].P
);
289 EXPECT_NEAR(-7.8, pidData
[FD_ROLL
].I
, calculateTolerance(-7.8));
290 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].I
);
291 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].I
);
292 EXPECT_NEAR(-198.4, pidData
[FD_ROLL
].D
, calculateTolerance(-198.4));
293 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].D
);
294 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].D
);
296 // Add some rotation on PITCH to generate error
297 gyro
.gyroADCf
[FD_PITCH
] = -100;
298 pidController(pidProfile
, currentTestTime());
300 // Loop 3 - Expect PID loop reaction to PITCH error, ROLL is still in error
301 EXPECT_NEAR(-128.1, pidData
[FD_ROLL
].P
, calculateTolerance(-128.1));
302 EXPECT_NEAR(185.8, pidData
[FD_PITCH
].P
, calculateTolerance(185.8));
303 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].P
);
304 EXPECT_NEAR(-15.6, pidData
[FD_ROLL
].I
, calculateTolerance(-15.6));
305 EXPECT_NEAR(9.8, pidData
[FD_PITCH
].I
, calculateTolerance(9.8));
306 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].I
);
307 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].D
);
308 EXPECT_NEAR(231.4, pidData
[FD_PITCH
].D
, calculateTolerance(231.4));
309 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].D
);
311 // Add some rotation on YAW to generate error
312 gyro
.gyroADCf
[FD_YAW
] = 100;
313 pidController(pidProfile
, currentTestTime());
315 // Loop 4 - Expect PID loop reaction to PITCH error, ROLL and PITCH are still in error
316 EXPECT_NEAR(-128.1, pidData
[FD_ROLL
].P
, calculateTolerance(-128.1));
317 EXPECT_NEAR(185.8, pidData
[FD_PITCH
].P
, calculateTolerance(185.8));
318 EXPECT_NEAR(-224.2, pidData
[FD_YAW
].P
, calculateTolerance(-224.2));
319 EXPECT_NEAR(-23.5, pidData
[FD_ROLL
].I
, calculateTolerance(-23.5));
320 EXPECT_NEAR(19.6, pidData
[FD_PITCH
].I
, calculateTolerance(19.6));
321 EXPECT_NEAR(-8.7, pidData
[FD_YAW
].I
, calculateTolerance(-8.7));
322 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].D
);
323 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].D
);
324 EXPECT_NEAR(-132.25, pidData
[FD_YAW
].D
, calculateTolerance(-132.25));
326 // Simulate Iterm behaviour during mixer saturation
327 simulatedMotorMixRange
= 1.2f
;
328 pidController(pidProfile
, currentTestTime());
329 EXPECT_NEAR(-31.3, pidData
[FD_ROLL
].I
, calculateTolerance(-31.3));
330 EXPECT_NEAR(29.3, pidData
[FD_PITCH
].I
, calculateTolerance(29.3));
331 EXPECT_NEAR(-8.8, pidData
[FD_YAW
].I
, calculateTolerance(-8.8));
332 simulatedMotorMixRange
= 0;
334 // Match the stick to gyro to stop error
335 simulatedSetpointRate
[FD_ROLL
] = 100;
336 simulatedSetpointRate
[FD_PITCH
] = -100;
337 simulatedSetpointRate
[FD_YAW
] = 100;
339 for(int loop
= 0; loop
< 5; loop
++) {
340 pidController(pidProfile
, currentTestTime());
342 // Iterm is stalled as it is not accumulating anymore
343 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].P
);
344 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].P
);
345 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].P
);
346 EXPECT_NEAR(-31.3, pidData
[FD_ROLL
].I
, calculateTolerance(-31.3));
347 EXPECT_NEAR(29.3, pidData
[FD_PITCH
].I
, calculateTolerance(29.3));
348 EXPECT_NEAR(-10.6, pidData
[FD_YAW
].I
, calculateTolerance(-10.6));
349 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].D
);
350 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].D
);
351 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].D
);
353 // Now disable Stabilisation
354 pidStabilisationState(PID_STABILISATION_OFF
);
355 pidController(pidProfile
, currentTestTime());
357 // Should all be zero again
358 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].P
);
359 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].P
);
360 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].P
);
361 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].I
);
362 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].I
);
363 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].I
);
364 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].D
);
365 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].D
);
366 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].D
);
369 TEST(pidControllerTest
, testPidLevel
) {
370 // Make sure to start with fresh values
372 ENABLE_ARMING_FLAG(ARMED
);
373 pidStabilisationState(PID_STABILISATION_ON
);
375 // Test Angle mode response
376 enableFlightMode(ANGLE_MODE
);
377 float currentPidSetpoint
= 30;
378 rollAndPitchTrims_t angleTrim
= { { 0, 0 } };
380 currentPidSetpoint
= pidLevel(FD_ROLL
, pidProfile
, &angleTrim
, currentPidSetpoint
);
381 EXPECT_FLOAT_EQ(0, currentPidSetpoint
);
382 currentPidSetpoint
= pidLevel(FD_PITCH
, pidProfile
, &angleTrim
, currentPidSetpoint
);
383 EXPECT_FLOAT_EQ(0, currentPidSetpoint
);
385 // Test attitude response
386 setStickPosition(FD_ROLL
, 1.0f
);
387 setStickPosition(FD_PITCH
, -1.0f
);
388 currentPidSetpoint
= pidLevel(FD_ROLL
, pidProfile
, &angleTrim
, currentPidSetpoint
);
389 EXPECT_FLOAT_EQ(275, currentPidSetpoint
);
390 currentPidSetpoint
= pidLevel(FD_PITCH
, pidProfile
, &angleTrim
, currentPidSetpoint
);
391 EXPECT_FLOAT_EQ(-275, currentPidSetpoint
);
393 setStickPosition(FD_ROLL
, -0.5f
);
394 setStickPosition(FD_PITCH
, 0.5f
);
395 currentPidSetpoint
= pidLevel(FD_ROLL
, pidProfile
, &angleTrim
, currentPidSetpoint
);
396 EXPECT_FLOAT_EQ(-137.5, currentPidSetpoint
);
397 currentPidSetpoint
= pidLevel(FD_PITCH
, pidProfile
, &angleTrim
, currentPidSetpoint
);
398 EXPECT_FLOAT_EQ(137.5, currentPidSetpoint
);
400 attitude
.values
.roll
= -275;
401 attitude
.values
.pitch
= 275;
402 currentPidSetpoint
= pidLevel(FD_ROLL
, pidProfile
, &angleTrim
, currentPidSetpoint
);
403 EXPECT_FLOAT_EQ(0, currentPidSetpoint
);
404 currentPidSetpoint
= pidLevel(FD_PITCH
, pidProfile
, &angleTrim
, currentPidSetpoint
);
405 EXPECT_FLOAT_EQ(0, currentPidSetpoint
);
407 // Disable ANGLE_MODE
408 disableFlightMode(ANGLE_MODE
);
409 currentPidSetpoint
= pidLevel(FD_ROLL
, pidProfile
, &angleTrim
, currentPidSetpoint
);
410 EXPECT_FLOAT_EQ(0, currentPidSetpoint
);
411 currentPidSetpoint
= pidLevel(FD_PITCH
, pidProfile
, &angleTrim
, currentPidSetpoint
);
412 EXPECT_FLOAT_EQ(0, currentPidSetpoint
);
414 // Test level mode expo
415 enableFlightMode(ANGLE_MODE
);
416 attitude
.values
.roll
= 0;
417 attitude
.values
.pitch
= 0;
418 setStickPosition(FD_ROLL
, 0.5f
);
419 setStickPosition(FD_PITCH
, -0.5f
);
420 currentControlRateProfile
->levelExpo
[FD_ROLL
] = 50;
421 currentControlRateProfile
->levelExpo
[FD_PITCH
] = 26;
422 currentPidSetpoint
= pidLevel(FD_ROLL
, pidProfile
, &angleTrim
, currentPidSetpoint
);
423 EXPECT_FLOAT_EQ(85.9375, currentPidSetpoint
);
424 currentPidSetpoint
= pidLevel(FD_PITCH
, pidProfile
, &angleTrim
, currentPidSetpoint
);
425 EXPECT_FLOAT_EQ(-110.6875, currentPidSetpoint
);
429 TEST(pidControllerTest
, testPidHorizon
) {
431 ENABLE_ARMING_FLAG(ARMED
);
432 pidStabilisationState(PID_STABILISATION_ON
);
433 enableFlightMode(HORIZON_MODE
);
435 // Test full stick response
436 setStickPosition(FD_ROLL
, 1.0f
);
437 setStickPosition(FD_PITCH
, -1.0f
);
438 EXPECT_FLOAT_EQ(0, calcHorizonLevelStrength());
440 // Expect full rate output on full stick
441 // Test zero stick response
442 setStickPosition(FD_ROLL
, 0);
443 setStickPosition(FD_PITCH
, 0);
444 EXPECT_FLOAT_EQ(1, calcHorizonLevelStrength());
446 // Test small stick response
447 setStickPosition(FD_ROLL
, 0.1f
);
448 setStickPosition(FD_PITCH
, -0.1f
);
449 EXPECT_NEAR(0.82, calcHorizonLevelStrength(), calculateTolerance(0.82));
452 TEST(pidControllerTest
, testMixerSaturation
) {
454 ENABLE_ARMING_FLAG(ARMED
);
455 pidStabilisationState(PID_STABILISATION_ON
);
457 // Test full stick response
458 setStickPosition(FD_ROLL
, 1.0f
);
459 setStickPosition(FD_PITCH
, -1.0f
);
460 setStickPosition(FD_YAW
, 1.0f
);
461 simulatedMotorMixRange
= 2.0f
;
462 pidController(pidProfile
, currentTestTime());
464 // Expect no iterm accumulation for yaw
465 EXPECT_FLOAT_EQ(150, pidData
[FD_ROLL
].I
);
466 EXPECT_FLOAT_EQ(-150, pidData
[FD_PITCH
].I
);
467 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].I
);
469 // Test itermWindup limit (note: windup limit now only affects yaw)
470 // First store values without exceeding iterm windup limit
472 ENABLE_ARMING_FLAG(ARMED
);
473 pidStabilisationState(PID_STABILISATION_ON
);
474 setStickPosition(FD_ROLL
, 0.1f
);
475 setStickPosition(FD_PITCH
, -0.1f
);
476 setStickPosition(FD_YAW
, 0.1f
);
477 simulatedMotorMixRange
= 0.0f
;
478 pidController(pidProfile
, currentTestTime());
479 float rollTestIterm
= pidData
[FD_ROLL
].I
;
480 float pitchTestIterm
= pidData
[FD_PITCH
].I
;
481 float yawTestIterm
= pidData
[FD_YAW
].I
;
483 // Now compare values when exceeding the limit
485 ENABLE_ARMING_FLAG(ARMED
);
486 pidStabilisationState(PID_STABILISATION_ON
);
487 setStickPosition(FD_ROLL
, 0.1f
);
488 setStickPosition(FD_PITCH
, -0.1f
);
489 setStickPosition(FD_YAW
, 0.1f
);
490 simulatedMotorMixRange
= (pidProfile
->itermWindupPointPercent
+ 1) / 100.0f
;
491 pidController(pidProfile
, currentTestTime());
492 EXPECT_FLOAT_EQ(pidData
[FD_ROLL
].I
, rollTestIterm
);
493 EXPECT_FLOAT_EQ(pidData
[FD_PITCH
].I
, pitchTestIterm
);
494 EXPECT_LT(pidData
[FD_YAW
].I
, yawTestIterm
);
497 // TODO - Add more scenarios
498 TEST(pidControllerTest
, testCrashRecoveryMode
) {
500 pidProfile
->crash_recovery
= PID_CRASH_RECOVERY_ON
;
502 ENABLE_ARMING_FLAG(ARMED
);
503 pidStabilisationState(PID_STABILISATION_ON
);
504 sensorsSet(SENSOR_ACC
);
506 EXPECT_FALSE(crashRecoveryModeActive());
508 int loopsToCrashTime
= (int)((pidProfile
->crash_time
* 1000) / targetPidLooptime
) + 1;
510 // generate crash detection for roll axis
511 gyro
.gyroADCf
[FD_ROLL
] = 800;
512 simulatedMotorMixRange
= 1.2f
;
513 for (int loop
=0; loop
<= loopsToCrashTime
; loop
++) {
514 gyro
.gyroADCf
[FD_ROLL
] += gyro
.gyroADCf
[FD_ROLL
];
515 // advance the time to avoid initialized state prevention of crash recovery
516 pidController(pidProfile
, currentTestTime() + 2000000);
519 EXPECT_TRUE(crashRecoveryModeActive());
520 // Add additional verifications
523 TEST(pidControllerTest
, testFeedForward
) {
525 ENABLE_ARMING_FLAG(ARMED
);
526 pidStabilisationState(PID_STABILISATION_ON
);
528 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].F
);
529 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].F
);
530 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].F
);
532 // Match the stick to gyro to stop error
533 setStickPosition(FD_ROLL
, 1.0f
);
534 setStickPosition(FD_PITCH
, -1.0f
);
535 setStickPosition(FD_YAW
, -1.0f
);
537 pidController(pidProfile
, currentTestTime());
539 EXPECT_NEAR(2232.78, pidData
[FD_ROLL
].F
, calculateTolerance(2232.78));
540 EXPECT_NEAR(-2061.03, pidData
[FD_PITCH
].F
, calculateTolerance(-2061.03));
541 EXPECT_NEAR(-2061.03, pidData
[FD_YAW
].F
, calculateTolerance(-2061.03));
543 // Match the stick to gyro to stop error
544 setStickPosition(FD_ROLL
, 0.5f
);
545 setStickPosition(FD_PITCH
, -0.5f
);
546 setStickPosition(FD_YAW
, -0.5f
);
548 pidController(pidProfile
, currentTestTime());
550 EXPECT_NEAR(-558.20, pidData
[FD_ROLL
].F
, calculateTolerance(-558.20));
551 EXPECT_NEAR(515.26, pidData
[FD_PITCH
].F
, calculateTolerance(515.26));
552 EXPECT_NEAR(515.26, pidData
[FD_YAW
].F
, calculateTolerance(515.26));
554 setStickPosition(FD_ROLL
, 0.0f
);
555 setStickPosition(FD_PITCH
, 0.0f
);
556 setStickPosition(FD_YAW
, 0.0f
);
558 for (int loop
= 0; loop
<= 15; loop
++) {
559 gyro
.gyroADCf
[FD_ROLL
] += gyro
.gyroADCf
[FD_ROLL
];
560 pidController(pidProfile
, currentTestTime());
563 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].F
);
564 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].F
);
565 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].F
);
568 TEST(pidControllerTest
, testItermRelax
) {
570 pidProfile
->iterm_relax
= ITERM_RELAX_RP
;
571 ENABLE_ARMING_FLAG(ARMED
);
572 pidStabilisationState(PID_STABILISATION_ON
);
574 pidProfile
->iterm_relax_type
= ITERM_RELAX_SETPOINT
;
577 float itermErrorRate
= 0;
578 float currentPidSetpoint
= 0;
581 applyItermRelax(FD_PITCH
, 0, gyroRate
, &itermErrorRate
, ¤tPidSetpoint
);
582 EXPECT_FLOAT_EQ(itermErrorRate
, 0);
583 itermErrorRate
= -10;
584 currentPidSetpoint
= 10;
585 pidData
[FD_PITCH
].I
= 10;
587 applyItermRelax(FD_PITCH
, pidData
[FD_PITCH
].I
, gyroRate
, &itermErrorRate
, ¤tPidSetpoint
);
589 EXPECT_NEAR(-8.16, itermErrorRate
, calculateTolerance(-8.16));
590 currentPidSetpoint
+= ITERM_RELAX_SETPOINT_THRESHOLD
;
591 applyItermRelax(FD_PITCH
, pidData
[FD_PITCH
].I
, gyroRate
, &itermErrorRate
, ¤tPidSetpoint
);
592 EXPECT_NEAR(0, itermErrorRate
, calculateTolerance(0));
593 applyItermRelax(FD_PITCH
, pidData
[FD_PITCH
].I
, gyroRate
, &itermErrorRate
, ¤tPidSetpoint
);
594 EXPECT_NEAR(0, itermErrorRate
, calculateTolerance(0));
596 pidProfile
->iterm_relax_type
= ITERM_RELAX_GYRO
;
599 currentPidSetpoint
= 100;
600 applyItermRelax(FD_PITCH
, pidData
[FD_PITCH
].I
, gyroRate
, &itermErrorRate
, ¤tPidSetpoint
);
601 EXPECT_FLOAT_EQ(itermErrorRate
, 0);
603 itermErrorRate
= -10;
604 applyItermRelax(FD_PITCH
, pidData
[FD_PITCH
].I
, gyroRate
, &itermErrorRate
, ¤tPidSetpoint
);
605 EXPECT_NEAR(7, itermErrorRate
, calculateTolerance(7));
607 applyItermRelax(FD_PITCH
, pidData
[FD_PITCH
].I
, gyroRate
, &itermErrorRate
, ¤tPidSetpoint
);
608 EXPECT_NEAR(-10, itermErrorRate
, calculateTolerance(-10));
610 pidProfile
->iterm_relax
= ITERM_RELAX_RP_INC
;
613 itermErrorRate
= -10;
614 pidData
[FD_PITCH
].I
= 10;
615 currentPidSetpoint
= 10;
616 applyItermRelax(FD_PITCH
, pidData
[FD_PITCH
].I
, gyroRate
, &itermErrorRate
, ¤tPidSetpoint
);
617 EXPECT_FLOAT_EQ(itermErrorRate
, -10);
619 pidData
[FD_PITCH
].I
= -10;
620 applyItermRelax(FD_PITCH
, pidData
[FD_PITCH
].I
, gyroRate
, &itermErrorRate
, ¤tPidSetpoint
);
621 EXPECT_FLOAT_EQ(itermErrorRate
, 10);
622 itermErrorRate
= -10;
623 currentPidSetpoint
= 10;
624 applyItermRelax(FD_PITCH
, pidData
[FD_PITCH
].I
, gyroRate
, &itermErrorRate
, ¤tPidSetpoint
);
625 EXPECT_FLOAT_EQ(itermErrorRate
, -100);
627 pidProfile
->iterm_relax_type
= ITERM_RELAX_SETPOINT
;
630 itermErrorRate
= -10;
631 currentPidSetpoint
= ITERM_RELAX_SETPOINT_THRESHOLD
;
632 applyItermRelax(FD_YAW
, pidData
[FD_YAW
].I
, gyroRate
, &itermErrorRate
, ¤tPidSetpoint
);
633 EXPECT_FLOAT_EQ(itermErrorRate
, -10);
635 pidProfile
->iterm_relax
= ITERM_RELAX_RPY
;
637 applyItermRelax(FD_YAW
, pidData
[FD_YAW
].I
, gyroRate
, &itermErrorRate
, ¤tPidSetpoint
);
638 EXPECT_NEAR(-3.6, itermErrorRate
, calculateTolerance(-3.6));
641 // TODO - Add more tests
642 TEST(pidControllerTest
, testAbsoluteControl
) {
644 pidProfile
->abs_control_gain
= 10;
646 ENABLE_ARMING_FLAG(ARMED
);
647 pidStabilisationState(PID_STABILISATION_ON
);
651 float itermErrorRate
= 10;
652 float currentPidSetpoint
= 10;
654 applyAbsoluteControl(FD_PITCH
, gyroRate
, ¤tPidSetpoint
, &itermErrorRate
);
656 EXPECT_NEAR(10.8, itermErrorRate
, calculateTolerance(10.8));
657 EXPECT_NEAR(10.8, currentPidSetpoint
, calculateTolerance(10.8));
659 applyAbsoluteControl(FD_PITCH
, gyroRate
, ¤tPidSetpoint
, &itermErrorRate
);
660 EXPECT_NEAR(10.8, itermErrorRate
, calculateTolerance(10.8));
661 EXPECT_NEAR(10.8, currentPidSetpoint
, calculateTolerance(10.8));
664 axisError
[FD_PITCH
] = -60;
665 applyAbsoluteControl(FD_PITCH
, gyroRate
, ¤tPidSetpoint
, &itermErrorRate
);
666 EXPECT_NEAR(-79.2, itermErrorRate
, calculateTolerance(-79.2));
667 EXPECT_NEAR(-79.2, currentPidSetpoint
, calculateTolerance(-79.2));
670 TEST(pidControllerTest
, testDtermFiltering
) {
674 TEST(pidControllerTest
, testItermRotationHandling
) {
678 rotateItermAndAxisError();
679 EXPECT_FLOAT_EQ(pidData
[FD_ROLL
].I
, 0);
680 EXPECT_FLOAT_EQ(pidData
[FD_PITCH
].I
, 0);
681 EXPECT_FLOAT_EQ(pidData
[FD_YAW
].I
, 0);
683 pidProfile
->iterm_rotation
= true;
686 rotateItermAndAxisError();
687 EXPECT_FLOAT_EQ(pidData
[FD_ROLL
].I
, 0);
688 EXPECT_FLOAT_EQ(pidData
[FD_PITCH
].I
, 0);
689 EXPECT_FLOAT_EQ(pidData
[FD_YAW
].I
, 0);
691 pidData
[FD_ROLL
].I
= 10;
692 pidData
[FD_PITCH
].I
= 1000;
693 pidData
[FD_YAW
].I
= 1000;
694 gyro
.gyroADCf
[FD_ROLL
] = -1000;
695 rotateItermAndAxisError();
696 EXPECT_FLOAT_EQ(pidData
[FD_ROLL
].I
, 10);
697 EXPECT_NEAR(860.37, pidData
[FD_PITCH
].I
, calculateTolerance(860.37));
698 EXPECT_NEAR(1139.6, pidData
[FD_YAW
].I
, calculateTolerance(1139.6));
700 pidProfile
->abs_control_gain
= 10;
702 pidData
[FD_ROLL
].I
= 10;
703 pidData
[FD_PITCH
].I
= 1000;
704 pidData
[FD_YAW
].I
= 1000;
706 gyro
.gyroADCf
[FD_ROLL
] = -1000;
707 // FIXME - axisError changes don't affect the system. This is a potential bug or intendend behaviour?
708 axisError
[FD_PITCH
] = 1000;
709 axisError
[FD_YAW
] = 1000;
710 rotateItermAndAxisError();
711 EXPECT_FLOAT_EQ(pidData
[FD_ROLL
].I
, 10);
712 EXPECT_NEAR(860.37, pidData
[FD_PITCH
].I
, calculateTolerance(860.37));
713 EXPECT_NEAR(1139.6, pidData
[FD_YAW
].I
, calculateTolerance(1139.6));
716 TEST(pidControllerTest
, testLaunchControl
) {
717 // The launchControlGain is indirectly tested since when launch control is active the
718 // the gain overrides the PID settings. If the logic to use launchControlGain wasn't
719 // working then any I calculations would be incorrect.
722 unitLaunchControlActive
= true;
723 ENABLE_ARMING_FLAG(ARMED
);
724 pidStabilisationState(PID_STABILISATION_ON
);
726 // test that feedforward and D are disabled (always zero) when launch control is active
728 pidController(pidProfile
, currentTestTime());
730 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].F
);
731 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].F
);
732 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].F
);
733 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].D
);
734 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].D
);
735 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].D
);
737 // Move the sticks to induce feedforward
738 setStickPosition(FD_ROLL
, 0.5f
);
739 setStickPosition(FD_PITCH
, -0.5f
);
740 setStickPosition(FD_YAW
, -0.5f
);
742 // add gyro activity to induce D
743 gyro
.gyroADCf
[FD_ROLL
] = -1000;
744 gyro
.gyroADCf
[FD_PITCH
] = 1000;
745 gyro
.gyroADCf
[FD_YAW
] = -1000;
747 pidController(pidProfile
, currentTestTime());
749 // validate that feedforwad is still 0
750 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].F
);
751 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].F
);
752 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].F
);
754 // validate that D is still 0
755 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].D
);
756 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].D
);
757 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].D
);
759 // test NORMAL mode - expect P/I on roll and pitch, P on yaw but I == 0
760 unitLaunchControlMode
= LAUNCH_CONTROL_MODE_NORMAL
;
762 unitLaunchControlActive
= true;
763 ENABLE_ARMING_FLAG(ARMED
);
764 pidStabilisationState(PID_STABILISATION_ON
);
766 pidController(pidProfile
, currentTestTime());
768 gyro
.gyroADCf
[FD_ROLL
] = -20;
769 gyro
.gyroADCf
[FD_PITCH
] = 20;
770 gyro
.gyroADCf
[FD_YAW
] = -20;
771 pidController(pidProfile
, currentTestTime());
773 EXPECT_NEAR(25.62, pidData
[FD_ROLL
].P
, calculateTolerance(25.62));
774 EXPECT_NEAR(1.56, pidData
[FD_ROLL
].I
, calculateTolerance(1.56));
775 EXPECT_NEAR(-37.15, pidData
[FD_PITCH
].P
, calculateTolerance(-37.15));
776 EXPECT_NEAR(-1.56, pidData
[FD_PITCH
].I
, calculateTolerance(-1.56));
777 EXPECT_NEAR(44.84, pidData
[FD_YAW
].P
, calculateTolerance(44.84));
778 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].I
);
780 // test PITCHONLY mode - expect P/I only on pitch; I cannot go negative
781 unitLaunchControlMode
= LAUNCH_CONTROL_MODE_PITCHONLY
;
783 unitLaunchControlActive
= true;
784 ENABLE_ARMING_FLAG(ARMED
);
785 pidStabilisationState(PID_STABILISATION_ON
);
787 pidController(pidProfile
, currentTestTime());
789 // first test that pitch I is prevented from going negative
790 gyro
.gyroADCf
[FD_ROLL
] = 0;
791 gyro
.gyroADCf
[FD_PITCH
] = 20;
792 gyro
.gyroADCf
[FD_YAW
] = 0;
793 pidController(pidProfile
, currentTestTime());
795 EXPECT_FLOAT_EQ(0, pidData
[FD_PITCH
].I
);
797 gyro
.gyroADCf
[FD_ROLL
] = 20;
798 gyro
.gyroADCf
[FD_PITCH
] = -20;
799 gyro
.gyroADCf
[FD_YAW
] = 20;
800 pidController(pidProfile
, currentTestTime());
802 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].P
);
803 EXPECT_FLOAT_EQ(0, pidData
[FD_ROLL
].I
);
804 EXPECT_NEAR(37.15, pidData
[FD_PITCH
].P
, calculateTolerance(37.15));
805 EXPECT_NEAR(1.56, pidData
[FD_PITCH
].I
, calculateTolerance(1.56));
806 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].P
);
807 EXPECT_FLOAT_EQ(0, pidData
[FD_YAW
].I
);
809 // test FULL mode - expect P/I on all axes
810 unitLaunchControlMode
= LAUNCH_CONTROL_MODE_FULL
;
812 unitLaunchControlActive
= true;
813 ENABLE_ARMING_FLAG(ARMED
);
814 pidStabilisationState(PID_STABILISATION_ON
);
816 pidController(pidProfile
, currentTestTime());
818 gyro
.gyroADCf
[FD_ROLL
] = -20;
819 gyro
.gyroADCf
[FD_PITCH
] = 20;
820 gyro
.gyroADCf
[FD_YAW
] = -20;
821 pidController(pidProfile
, currentTestTime());
823 EXPECT_NEAR(25.62, pidData
[FD_ROLL
].P
, calculateTolerance(25.62));
824 EXPECT_NEAR(1.56, pidData
[FD_ROLL
].I
, calculateTolerance(1.56));
825 EXPECT_NEAR(-37.15, pidData
[FD_PITCH
].P
, calculateTolerance(-37.15));
826 EXPECT_NEAR(-1.56, pidData
[FD_PITCH
].I
, calculateTolerance(-1.56));
827 EXPECT_NEAR(44.84, pidData
[FD_YAW
].P
, calculateTolerance(44.84));
828 EXPECT_NEAR(1.56, pidData
[FD_YAW
].I
, calculateTolerance(1.56));