2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
27 #include "build/debug.h"
29 #include "common/axis.h"
30 #include "common/filter.h"
31 #include "common/maths.h"
33 #include "config/config.h"
34 #include "config/feature.h"
36 #include "drivers/dshot.h"
37 #include "drivers/io.h"
38 #include "drivers/motor.h"
39 #include "drivers/time.h"
41 #include "fc/controlrate_profile.h"
44 #include "fc/rc_controls.h"
45 #include "fc/rc_modes.h"
46 #include "fc/runtime_config.h"
48 #include "flight/alt_hold.h"
49 #include "flight/autopilot.h"
50 #include "flight/failsafe.h"
51 #include "flight/gps_rescue.h"
52 #include "flight/imu.h"
53 #include "flight/mixer_init.h"
54 #include "flight/mixer_tricopter.h"
55 #include "flight/pid.h"
56 #include "flight/rpm_filter.h"
64 #include "sensors/battery.h"
65 #include "sensors/gyro.h"
66 #include "sensors/sensors.h"
70 #define DYN_LPF_THROTTLE_STEPS 100
71 #define DYN_LPF_THROTTLE_UPDATE_DELAY_US 5000 // minimum of 5ms between updates
73 #define CRASHFLIP_MOTOR_DEADBAND 0.02f // 2%; send 'disarm' value to motors below this drive value
74 #define CRASHFLIP_STICK_DEADBAND 0.15f // 15%
76 static FAST_DATA_ZERO_INIT
float motorMixRange
;
78 float FAST_DATA_ZERO_INIT motor
[MAX_SUPPORTED_MOTORS
];
79 float motor_disarmed
[MAX_SUPPORTED_MOTORS
];
81 static FAST_DATA_ZERO_INIT
int throttleAngleCorrection
;
83 float getMotorMixRange(void)
88 void writeMotors(void)
93 static void writeAllMotors(int16_t mc
)
95 // Sends commands to all motors
96 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
102 void stopMotors(void)
104 writeAllMotors(mixerRuntime
.disarmMotorOutput
);
105 delay(50); // give the timers and ESCs a chance to react.
108 static FAST_DATA_ZERO_INIT
float throttle
= 0;
109 static FAST_DATA_ZERO_INIT
float rcThrottle
= 0;
110 static FAST_DATA_ZERO_INIT
float mixerThrottle
= 0;
111 static FAST_DATA_ZERO_INIT
float motorOutputMin
;
112 static FAST_DATA_ZERO_INIT
float motorRangeMin
;
113 static FAST_DATA_ZERO_INIT
float motorRangeMax
;
114 static FAST_DATA_ZERO_INIT
float motorOutputRange
;
115 static FAST_DATA_ZERO_INIT
int8_t motorOutputMixSign
;
116 static FAST_DATA_ZERO_INIT
bool crashflipSuccess
= false;
118 static void calculateThrottleAndCurrentMotorEndpoints(timeUs_t currentTimeUs
)
120 static uint16_t rcThrottlePrevious
= 0; // Store the last throttle direction for deadband transitions
121 static timeUs_t reversalTimeUs
= 0; // time when motors last reversed in 3D mode
122 static float motorRangeMinIncrease
= 0;
124 float currentThrottleInputRange
= 0;
125 if (mixerRuntime
.feature3dEnabled
) {
126 uint16_t rcCommand3dDeadBandLow
;
127 uint16_t rcCommand3dDeadBandHigh
;
129 if (!ARMING_FLAG(ARMED
)) {
130 rcThrottlePrevious
= rxConfig()->midrc
; // When disarmed set to mid_rc. It always results in positive direction after arming.
133 if (IS_RC_MODE_ACTIVE(BOX3D
) || flight3DConfig()->switched_mode3d
) {
134 // The min_check range is halved because the output throttle is scaled to 500us.
135 // So by using half of min_check we maintain the same low-throttle deadband
136 // stick travel as normal non-3D mode.
137 const int mincheckOffset
= (rxConfig()->mincheck
- PWM_RANGE_MIN
) / 2;
138 rcCommand3dDeadBandLow
= rxConfig()->midrc
- mincheckOffset
;
139 rcCommand3dDeadBandHigh
= rxConfig()->midrc
+ mincheckOffset
;
141 rcCommand3dDeadBandLow
= rxConfig()->midrc
- flight3DConfig()->deadband3d_throttle
;
142 rcCommand3dDeadBandHigh
= rxConfig()->midrc
+ flight3DConfig()->deadband3d_throttle
;
145 const float rcCommandThrottleRange3dLow
= rcCommand3dDeadBandLow
- PWM_RANGE_MIN
;
146 const float rcCommandThrottleRange3dHigh
= PWM_RANGE_MAX
- rcCommand3dDeadBandHigh
;
148 if (rcCommand
[THROTTLE
] <= rcCommand3dDeadBandLow
|| isCrashFlipModeActive()) {
150 motorRangeMin
= mixerRuntime
.motorOutputLow
;
151 motorRangeMax
= mixerRuntime
.deadbandMotor3dLow
;
153 if (isMotorProtocolDshot()) {
154 motorOutputMin
= mixerRuntime
.motorOutputLow
;
155 motorOutputRange
= mixerRuntime
.deadbandMotor3dLow
- mixerRuntime
.motorOutputLow
;
159 motorOutputMin
= mixerRuntime
.deadbandMotor3dLow
;
160 motorOutputRange
= mixerRuntime
.motorOutputLow
- mixerRuntime
.deadbandMotor3dLow
;
163 if (motorOutputMixSign
!= -1) {
164 reversalTimeUs
= currentTimeUs
;
166 motorOutputMixSign
= -1;
168 rcThrottlePrevious
= rcCommand
[THROTTLE
];
169 throttle
= rcCommand3dDeadBandLow
- rcCommand
[THROTTLE
];
170 currentThrottleInputRange
= rcCommandThrottleRange3dLow
;
171 } else if (rcCommand
[THROTTLE
] >= rcCommand3dDeadBandHigh
) {
173 motorRangeMin
= mixerRuntime
.deadbandMotor3dHigh
;
174 motorRangeMax
= mixerRuntime
.motorOutputHigh
;
175 motorOutputMin
= mixerRuntime
.deadbandMotor3dHigh
;
176 motorOutputRange
= mixerRuntime
.motorOutputHigh
- mixerRuntime
.deadbandMotor3dHigh
;
177 if (motorOutputMixSign
!= 1) {
178 reversalTimeUs
= currentTimeUs
;
180 motorOutputMixSign
= 1;
181 rcThrottlePrevious
= rcCommand
[THROTTLE
];
182 throttle
= rcCommand
[THROTTLE
] - rcCommand3dDeadBandHigh
;
183 currentThrottleInputRange
= rcCommandThrottleRange3dHigh
;
184 } else if ((rcThrottlePrevious
<= rcCommand3dDeadBandLow
&&
185 !flight3DConfigMutable()->switched_mode3d
) ||
186 isMotorsReversed()) {
187 // INVERTED_TO_DEADBAND
188 motorRangeMin
= mixerRuntime
.motorOutputLow
;
189 motorRangeMax
= mixerRuntime
.deadbandMotor3dLow
;
192 if (isMotorProtocolDshot()) {
193 motorOutputMin
= mixerRuntime
.motorOutputLow
;
194 motorOutputRange
= mixerRuntime
.deadbandMotor3dLow
- mixerRuntime
.motorOutputLow
;
198 motorOutputMin
= mixerRuntime
.deadbandMotor3dLow
;
199 motorOutputRange
= mixerRuntime
.motorOutputLow
- mixerRuntime
.deadbandMotor3dLow
;
202 if (motorOutputMixSign
!= -1) {
203 reversalTimeUs
= currentTimeUs
;
205 motorOutputMixSign
= -1;
208 currentThrottleInputRange
= rcCommandThrottleRange3dLow
;
210 // NORMAL_TO_DEADBAND
211 motorRangeMin
= mixerRuntime
.deadbandMotor3dHigh
;
212 motorRangeMax
= mixerRuntime
.motorOutputHigh
;
213 motorOutputMin
= mixerRuntime
.deadbandMotor3dHigh
;
214 motorOutputRange
= mixerRuntime
.motorOutputHigh
- mixerRuntime
.deadbandMotor3dHigh
;
215 if (motorOutputMixSign
!= 1) {
216 reversalTimeUs
= currentTimeUs
;
218 motorOutputMixSign
= 1;
220 currentThrottleInputRange
= rcCommandThrottleRange3dHigh
;
222 if (currentTimeUs
- reversalTimeUs
< 250000) {
223 // keep iterm zero for 250ms after motor reversal
227 throttle
= rcCommand
[THROTTLE
] - PWM_RANGE_MIN
+ throttleAngleCorrection
;
228 currentThrottleInputRange
= PWM_RANGE
;
230 if (mixerRuntime
.dynIdleMinRps
> 0.0f
) {
231 const float maxIncrease
= wasThrottleRaised()
232 ? mixerRuntime
.dynIdleMaxIncrease
: mixerRuntime
.dynIdleStartIncrease
;
233 float minRps
= getMinMotorFrequencyHz();
234 DEBUG_SET(DEBUG_DYN_IDLE
, 3, lrintf(minRps
* 10.0f
));
235 float rpsError
= mixerRuntime
.dynIdleMinRps
- minRps
;
236 // PT1 type lowpass delay and smoothing for D
237 minRps
= mixerRuntime
.prevMinRps
+ mixerRuntime
.minRpsDelayK
* (minRps
- mixerRuntime
.prevMinRps
);
238 float dynIdleD
= (mixerRuntime
.prevMinRps
- minRps
) * mixerRuntime
.dynIdleDGain
;
239 mixerRuntime
.prevMinRps
= minRps
;
240 float dynIdleP
= rpsError
* mixerRuntime
.dynIdlePGain
;
241 rpsError
= MAX(-0.1f
, rpsError
); //I rises fast, falls slowly
242 mixerRuntime
.dynIdleI
+= rpsError
* mixerRuntime
.dynIdleIGain
;
243 mixerRuntime
.dynIdleI
= constrainf(mixerRuntime
.dynIdleI
, 0.0f
, maxIncrease
);
244 motorRangeMinIncrease
= constrainf((dynIdleP
+ mixerRuntime
.dynIdleI
+ dynIdleD
), 0.0f
, maxIncrease
);
245 DEBUG_SET(DEBUG_DYN_IDLE
, 0, MAX(-1000, lrintf(dynIdleP
* 10000)));
246 DEBUG_SET(DEBUG_DYN_IDLE
, 1, lrintf(mixerRuntime
.dynIdleI
* 10000));
247 DEBUG_SET(DEBUG_DYN_IDLE
, 2, lrintf(dynIdleD
* 10000));
249 motorRangeMinIncrease
= 0;
253 #if defined(USE_BATTERY_VOLTAGE_SAG_COMPENSATION)
254 float motorRangeAttenuationFactor
= 0;
255 // reduce motorRangeMax when battery is full
256 if (mixerRuntime
.vbatSagCompensationFactor
> 0.0f
) {
257 const uint16_t currentCellVoltage
= getBatterySagCellVoltage();
258 // batteryGoodness = 1 when voltage is above vbatFull, and 0 when voltage is below vbatLow
259 float batteryGoodness
= 1.0f
- constrainf((mixerRuntime
.vbatFull
- currentCellVoltage
) / mixerRuntime
.vbatRangeToCompensate
, 0.0f
, 1.0f
);
260 motorRangeAttenuationFactor
= (mixerRuntime
.vbatRangeToCompensate
/ mixerRuntime
.vbatFull
) * batteryGoodness
* mixerRuntime
.vbatSagCompensationFactor
;
261 DEBUG_SET(DEBUG_BATTERY
, 2, lrintf(batteryGoodness
* 100));
262 DEBUG_SET(DEBUG_BATTERY
, 3, lrintf(motorRangeAttenuationFactor
* 1000));
264 motorRangeMax
= isCrashFlipModeActive() ? mixerRuntime
.motorOutputHigh
: mixerRuntime
.motorOutputHigh
- motorRangeAttenuationFactor
* (mixerRuntime
.motorOutputHigh
- mixerRuntime
.motorOutputLow
);
266 motorRangeMax
= mixerRuntime
.motorOutputHigh
;
268 motorRangeMin
= mixerRuntime
.motorOutputLow
+ motorRangeMinIncrease
* (mixerRuntime
.motorOutputHigh
- mixerRuntime
.motorOutputLow
);
269 motorOutputMin
= motorRangeMin
;
270 motorOutputRange
= motorRangeMax
- motorRangeMin
;
271 motorOutputMixSign
= 1;
274 throttle
= constrainf(throttle
/ currentThrottleInputRange
, 0.0f
, 1.0f
);
275 rcThrottle
= throttle
;
278 static bool applyCrashFlipModeToMotors(void)
281 static bool isTiltAngleAtStartSet
= false;
282 static float tiltAngleAtStart
= 1.0f
;
285 if (!isCrashFlipModeActive()) {
287 // trigger the capture of initial tilt angle on next activation of crashflip mode
288 isTiltAngleAtStartSet
= false;
289 // default the success flag to false, to block quick re-arming unless successful
291 // signal that crashflip mode is off
295 const float stickDeflectionPitchAbs
= getRcDeflectionAbs(FD_PITCH
);
296 const float stickDeflectionRollAbs
= getRcDeflectionAbs(FD_ROLL
);
297 const float stickDeflectionYawAbs
= getRcDeflectionAbs(FD_YAW
);
299 float signPitch
= getRcDeflection(FD_PITCH
) < 0 ? 1 : -1;
300 float signRoll
= getRcDeflection(FD_ROLL
) < 0 ? 1 : -1;
301 float signYaw
= (getRcDeflection(FD_YAW
) < 0 ? 1 : -1) * (mixerConfig()->yaw_motors_reversed
? 1 : -1);
303 float stickDeflectionLength
= sqrtf(sq(stickDeflectionPitchAbs
) + sq(stickDeflectionRollAbs
));
305 if (stickDeflectionYawAbs
> MAX(stickDeflectionPitchAbs
, stickDeflectionRollAbs
)) {
306 // If yaw is the dominant, disable pitch and roll
307 stickDeflectionLength
= stickDeflectionYawAbs
;
311 // If pitch/roll dominant, disable yaw
315 const float cosPhi
= (stickDeflectionLength
> 0) ? (stickDeflectionPitchAbs
+ stickDeflectionRollAbs
) / (sqrtf(2.0f
) * stickDeflectionLength
) : 0;
316 const float cosThreshold
= sqrtf(3.0f
) / 2.0f
; // cos(30 deg)
318 if (cosPhi
< cosThreshold
) {
319 // Enforce either roll or pitch exclusively, if not on diagonal
320 if (stickDeflectionRollAbs
> stickDeflectionPitchAbs
) {
327 // Calculate crashflipPower from stick deflection with a reasonable amount of stick deadband
328 float crashflipPower
= stickDeflectionLength
> CRASHFLIP_STICK_DEADBAND
? stickDeflectionLength
: 0.0f
;
330 // calculate flipPower attenuators
331 float crashflipRateAttenuator
= 1.0f
;
332 float crashflipAttitudeAttenuator
= 1.0f
;
333 const float crashflipRateLimit
= mixerConfig()->crashflip_rate
* 10.0f
; // eg 35 = no power by 350 deg/s
334 const float halfComplete
= 0.5f
; // attitude or rate changes less that this will be ignored
336 // disable both attenuators if the user's crashflip_rate is zero
337 if (crashflipRateLimit
> 0) {
339 // Calculate an attenuator based on change of attitude (requires Acc)
340 // with Acc, crashflipAttitudeAttenuator will be zero after approx 90 degree rotation, and
341 // motors will stop / not spin while attitude remains more than ~90 degrees from initial attitude
342 // without Acc, the user must manually center the stick, or exit crashflip mode, or disarm, to stop the motors
343 // re-initialisation of crashFlip mode by arm/disarm is required to reset the initial tilt angle
344 if (sensors(SENSOR_ACC
)) {
345 const float tiltAngle
= getCosTiltAngle(); // -1 if flat inverted, 0 when 90° (on edge), +1 when flat and upright
346 if (!isTiltAngleAtStartSet
) {
347 tiltAngleAtStart
= tiltAngle
;
348 isTiltAngleAtStartSet
= true;
349 crashflipSuccess
= false;
351 // attitudeChangeNeeded is 1.0 at the start, decreasing to 0 when attitude change exceeds approx 90 degrees
352 const float attitudeChangeNeeded
= fmaxf(1.0f
- fabsf(tiltAngle
- tiltAngleAtStart
), 0.0f
);
353 // no attenuation unless a significant attitude change has occurred
354 crashflipAttitudeAttenuator
= attitudeChangeNeeded
> halfComplete
? 1.0f
: attitudeChangeNeeded
/ halfComplete
;
356 // signal success to enable quick restart, if attitude change implies success when reverting the switch
357 crashflipSuccess
= attitudeChangeNeeded
== 0.0f
;
360 // Calculate an attenuation factor based on rate of rotation... note:
361 // if driving roll or pitch, quad usually turns on that axis, but if one motor sticks, could be a diagonal rotation
362 // if driving diagonally, the turn could be either roll or pitch
363 // if driving yaw, typically one motor sticks, and the quad yaws a little then flips diagonally
364 const float gyroRate
= fmaxf(fabsf(gyro
.gyroADCf
[FD_ROLL
]), fabsf(gyro
.gyroADCf
[FD_PITCH
]));
365 const float gyroRateChange
= fminf(gyroRate
/ crashflipRateLimit
, 1.0f
);
366 // no attenuation unless a significant gyro rate change has occurred
367 crashflipRateAttenuator
= gyroRateChange
< halfComplete
? 1.0f
: (1.0f
- gyroRateChange
) / halfComplete
;
369 crashflipPower
*= crashflipAttitudeAttenuator
* crashflipRateAttenuator
;
372 for (int i
= 0; i
< mixerRuntime
.motorCount
; ++i
) {
373 float motorOutputNormalised
=
374 signPitch
* mixerRuntime
.currentMixer
[i
].pitch
+
375 signRoll
* mixerRuntime
.currentMixer
[i
].roll
+
376 signYaw
* mixerRuntime
.currentMixer
[i
].yaw
;
378 if (motorOutputNormalised
< 0) {
379 if (mixerConfig()->crashflip_motor_percent
> 0) {
380 motorOutputNormalised
= -motorOutputNormalised
* (float)mixerConfig()->crashflip_motor_percent
/ 100.0f
;
382 motorOutputNormalised
= 0;
386 motorOutputNormalised
= MIN(1.0f
, crashflipPower
* motorOutputNormalised
);
387 float motorOutput
= motorOutputMin
+ motorOutputNormalised
* motorOutputRange
;
389 // set motors to disarm value when intended increase is less than deadband value
390 motorOutput
= (motorOutputNormalised
< CRASHFLIP_MOTOR_DEADBAND
) ? mixerRuntime
.disarmMotorOutput
: motorOutput
;
392 motor
[i
] = motorOutput
;
395 // signal that crashflip mode has been applied to motors
400 #define STICK_HIGH_DEADBAND 5 // deadband to make sure throttle cap can raise, even with maxcheck set around 2000
401 static void applyRpmLimiter(mixerRuntime_t
*mixer
)
403 static float prevError
= 0.0f
;
404 const float unsmoothedAverageRpm
= getDshotRpmAverage();
405 const float averageRpm
= pt1FilterApply(&mixer
->rpmLimiterAverageRpmFilter
, unsmoothedAverageRpm
);
406 const float error
= averageRpm
- mixer
->rpmLimiterRpmLimit
;
409 const float p
= error
* mixer
->rpmLimiterPGain
;
410 const float d
= (error
- prevError
) * mixer
->rpmLimiterDGain
; // rpmLimiterDGain already adjusted for looprate (see mixer_init.c)
411 mixer
->rpmLimiterI
+= error
* mixer
->rpmLimiterIGain
; // rpmLimiterIGain already adjusted for looprate (see mixer_init.c)
412 mixer
->rpmLimiterI
= MAX(0.0f
, mixer
->rpmLimiterI
);
413 float pidOutput
= p
+ mixer
->rpmLimiterI
+ d
;
415 // Throttle limit learning
416 if (error
> 0.0f
&& rcCommand
[THROTTLE
] < rxConfig()->maxcheck
) {
417 mixer
->rpmLimiterThrottleScale
*= 1.0f
- 4.8f
* pidGetDT();
418 } else if (pidOutput
< -400.0f
* pidGetDT() && lrintf(rcCommand
[THROTTLE
]) >= rxConfig()->maxcheck
- STICK_HIGH_DEADBAND
&& !areMotorsSaturated()) { // Throttle accel corresponds with motor accel
419 mixer
->rpmLimiterThrottleScale
*= 1.0f
+ 3.2f
* pidGetDT();
421 mixer
->rpmLimiterThrottleScale
= constrainf(mixer
->rpmLimiterThrottleScale
, 0.01f
, 1.0f
);
423 float rpmLimiterThrottleScaleOffset
= pt1FilterApply(&mixer
->rpmLimiterThrottleScaleOffsetFilter
, constrainf(mixer
->rpmLimiterRpmLimit
/ motorEstimateMaxRpm(), 0.0f
, 1.0f
) - mixer
->rpmLimiterInitialThrottleScale
);
424 throttle
*= constrainf(mixer
->rpmLimiterThrottleScale
+ rpmLimiterThrottleScaleOffset
, 0.0f
, 1.0f
);
427 pidOutput
= MAX(0.0f
, pidOutput
);
428 throttle
= constrainf(throttle
- pidOutput
, 0.0f
, 1.0f
);
431 DEBUG_SET(DEBUG_RPM_LIMIT
, 0, lrintf(averageRpm
));
432 DEBUG_SET(DEBUG_RPM_LIMIT
, 1, lrintf(rpmLimiterThrottleScaleOffset
* 100.0f
));
433 DEBUG_SET(DEBUG_RPM_LIMIT
, 2, lrintf(mixer
->rpmLimiterThrottleScale
* 100.0f
));
434 DEBUG_SET(DEBUG_RPM_LIMIT
, 3, lrintf(throttle
* 100.0f
));
435 DEBUG_SET(DEBUG_RPM_LIMIT
, 4, lrintf(error
));
436 DEBUG_SET(DEBUG_RPM_LIMIT
, 5, lrintf(p
* 100.0f
));
437 DEBUG_SET(DEBUG_RPM_LIMIT
, 6, lrintf(mixer
->rpmLimiterI
* 100.0f
));
438 DEBUG_SET(DEBUG_RPM_LIMIT
, 7, lrintf(d
* 100.0f
));
440 #endif // USE_RPM_LIMIT
443 static float motorOutputRms
= 0.0f
;
445 float getMotorOutputRms(void)
447 return motorOutputRms
;
451 static void applyMixToMotors(const float motorMix
[MAX_SUPPORTED_MOTORS
], motorMixer_t
*activeMixer
)
453 // Now add in the desired throttle, but keep in a range that doesn't clip adjusted
454 // roll/pitch/yaw. This could move throttle down, but also up for those low throttle flips.
455 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
456 float motorOutput
= motorOutputMixSign
* motorMix
[i
] + throttle
* activeMixer
[i
].throttle
;
457 #ifdef USE_THRUST_LINEARIZATION
458 motorOutput
= pidApplyThrustLinearization(motorOutput
);
460 motorOutput
= motorOutputMin
+ motorOutputRange
* motorOutput
;
463 if (mixerIsTricopter()) {
464 motorOutput
+= mixerTricopterMotorCorrection(i
);
467 if (failsafeIsActive()) {
469 if (isMotorProtocolDshot()) {
470 motorOutput
= (motorOutput
< motorRangeMin
) ? mixerRuntime
.disarmMotorOutput
: motorOutput
; // Prevent getting into special reserved range
473 motorOutput
= constrainf(motorOutput
, mixerRuntime
.disarmMotorOutput
, motorRangeMax
);
475 motorOutput
= constrainf(motorOutput
, motorRangeMin
, motorRangeMax
);
477 motor
[i
] = motorOutput
;
481 if (!ARMING_FLAG(ARMED
)) {
482 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
483 motor
[i
] = motor_disarmed
[i
];
488 float motorSumSquares
= 0.0f
;
489 if (motorIsEnabled()) {
490 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
491 if (motorIsMotorEnabled(i
)) {
492 const float motorOutput
= scaleRangef(motorConvertToExternal(motor
[i
]), PWM_RANGE_MIN
, PWM_RANGE_MAX
, 0.0f
, 1.0f
);
493 motorSumSquares
+= motorOutput
* motorOutput
;
497 motorOutputRms
= sqrtf(motorSumSquares
/ mixerRuntime
.motorCount
);
500 DEBUG_SET(DEBUG_EZLANDING
, 1, throttle
* 10000U);
501 // DEBUG_EZLANDING 0 is the ezLanding factor 2 is the throttle limit
504 static float applyThrottleLimit(float throttle
)
506 if (currentControlRateProfile
->throttle_limit_percent
< 100 && !RPM_LIMIT_ACTIVE
) {
507 const float throttleLimitFactor
= currentControlRateProfile
->throttle_limit_percent
/ 100.0f
;
508 switch (currentControlRateProfile
->throttle_limit_type
) {
509 case THROTTLE_LIMIT_TYPE_SCALE
:
510 return throttle
* throttleLimitFactor
;
511 case THROTTLE_LIMIT_TYPE_CLIP
:
512 return MIN(throttle
, throttleLimitFactor
);
519 static void applyMotorStop(void)
521 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
522 motor
[i
] = mixerRuntime
.disarmMotorOutput
;
526 motorOutputRms
= 0.0f
;
531 static void updateDynLpfCutoffs(timeUs_t currentTimeUs
, float throttle
)
533 static timeUs_t lastDynLpfUpdateUs
= 0;
534 static int dynLpfPreviousQuantizedThrottle
= -1; // to allow an initial zero throttle to set the filter cutoff
536 if (cmpTimeUs(currentTimeUs
, lastDynLpfUpdateUs
) >= DYN_LPF_THROTTLE_UPDATE_DELAY_US
) {
537 const int quantizedThrottle
= lrintf(throttle
* DYN_LPF_THROTTLE_STEPS
); // quantize the throttle reduce the number of filter updates
538 if (quantizedThrottle
!= dynLpfPreviousQuantizedThrottle
) {
539 // scale the quantized value back to the throttle range so the filter cutoff steps are repeatable
540 const float dynLpfThrottle
= (float)quantizedThrottle
/ DYN_LPF_THROTTLE_STEPS
;
541 dynLpfGyroUpdate(dynLpfThrottle
);
542 dynLpfDTermUpdate(dynLpfThrottle
);
543 dynLpfPreviousQuantizedThrottle
= quantizedThrottle
;
544 lastDynLpfUpdateUs
= currentTimeUs
;
550 static void applyMixerAdjustmentLinear(float *motorMix
, const bool airmodeEnabled
)
552 float airmodeTransitionPercent
= 1.0f
;
553 float motorDeltaScale
= 0.5f
;
555 if (!airmodeEnabled
&& throttle
< 0.5f
) {
556 // this scales the motor mix authority to be 0.5 at 0 throttle, and 1.0 at 0.5 throttle as airmode off intended for things to work.
557 // also lays the groundwork for how an airmode percent would work.
558 airmodeTransitionPercent
= scaleRangef(throttle
, 0.0f
, 0.5f
, 0.5f
, 1.0f
); // 0.5 throttle is full transition, and 0.0 throttle is 50% airmodeTransitionPercent
559 motorDeltaScale
*= airmodeTransitionPercent
; // this should be half of the motor authority allowed
562 const float motorMixNormalizationFactor
= motorMixRange
> 1.0f
? airmodeTransitionPercent
/ motorMixRange
: airmodeTransitionPercent
;
564 const float motorMixDelta
= motorDeltaScale
* motorMixRange
;
566 float minMotor
= FLT_MAX
;
567 float maxMotor
= FLT_MIN
;
569 for (int i
= 0; i
< mixerRuntime
.motorCount
; ++i
) {
570 if (mixerConfig()->mixer_type
== MIXER_LINEAR
) {
571 motorMix
[i
] = scaleRangef(throttle
, 0.0f
, 1.0f
, motorMix
[i
] + motorMixDelta
, motorMix
[i
] - motorMixDelta
);
573 motorMix
[i
] = scaleRangef(throttle
, 0.0f
, 1.0f
, motorMix
[i
] + fabsf(motorMix
[i
]), motorMix
[i
] - fabsf(motorMix
[i
]));
575 motorMix
[i
] *= motorMixNormalizationFactor
;
577 maxMotor
= MAX(motorMix
[i
], maxMotor
);
578 minMotor
= MIN(motorMix
[i
], minMotor
);
581 // constrain throttle so it won't clip any outputs
582 throttle
= constrainf(throttle
, -minMotor
, 1.0f
- maxMotor
);
585 static float calcEzLandLimit(float maxDeflection
, float speed
)
587 // calculate limit to where the mixer can raise the throttle based on RPY stick deflection
588 // 0.0 = no increas allowed, 1.0 = 100% increase allowed
589 const float deflectionLimit
= mixerRuntime
.ezLandingThreshold
> 0.0f
? fminf(1.0f
, maxDeflection
/ mixerRuntime
.ezLandingThreshold
) : 0.0f
;
590 DEBUG_SET(DEBUG_EZLANDING
, 4, lrintf(deflectionLimit
* 10000.0f
));
592 // calculate limit to where the mixer can raise the throttle based on speed
593 // TODO sanity checks like number of sats, dop, accuracy?
594 const float speedLimit
= mixerRuntime
.ezLandingSpeed
> 0.0f
? fminf(1.0f
, speed
/ mixerRuntime
.ezLandingSpeed
) : 0.0f
;
595 DEBUG_SET(DEBUG_EZLANDING
, 5, lrintf(speedLimit
* 10000.0f
));
597 // get the highest of the limits from deflection, speed, and the base ez_landing_limit
598 const float deflectionAndSpeedLimit
= fmaxf(deflectionLimit
, speedLimit
);
599 return fmaxf(mixerRuntime
.ezLandingLimit
, deflectionAndSpeedLimit
);
602 static void applyMixerAdjustmentEzLand(float *motorMix
, const float motorMixMin
, const float motorMixMax
)
604 // Calculate factor for normalizing motor mix range to <= 1.0
605 const float baseNormalizationFactor
= motorMixRange
> 1.0f
? 1.0f
/ motorMixRange
: 1.0f
;
606 const float normalizedMotorMixMin
= motorMixMin
* baseNormalizationFactor
;
607 const float normalizedMotorMixMax
= motorMixMax
* baseNormalizationFactor
;
610 const float speed
= STATE(GPS_FIX
) ? gpsSol
.speed3d
/ 100.0f
: 0.0f
; // m/s
612 const float speed
= 0.0f
;
615 const float ezLandLimit
= calcEzLandLimit(getMaxRcDeflectionAbs(), speed
);
616 // use the largest of throttle and limit calculated from RPY stick positions
617 float upperLimit
= fmaxf(ezLandLimit
, throttle
);
618 // limit throttle to avoid clipping the highest motor output
619 upperLimit
= fminf(upperLimit
, 1.0f
- normalizedMotorMixMax
);
621 // Lower throttle Limit
622 const float epsilon
= 1.0e-6f
; // add small value to avoid divisions by zero
623 const float absMotorMixMin
= fabsf(normalizedMotorMixMin
) + epsilon
;
624 const float lowerLimit
= fminf(upperLimit
, absMotorMixMin
);
626 // represents how much motor values have to be scaled to avoid clipping
627 const float ezLandFactor
= upperLimit
/ absMotorMixMin
;
629 // scale motor values
630 const float normalizationFactor
= baseNormalizationFactor
* fminf(1.0f
, ezLandFactor
);
631 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
632 motorMix
[i
] *= normalizationFactor
;
634 motorMixRange
*= baseNormalizationFactor
;
635 // Make anti windup recognize reduced authority range
636 motorMixRange
= fmaxf(motorMixRange
, 1.0f
/ ezLandFactor
);
638 // Constrain throttle
639 throttle
= constrainf(throttle
, lowerLimit
, upperLimit
);
641 // Log ezLandFactor, upper throttle limit, and ezLandFactor if throttle was zero
642 DEBUG_SET(DEBUG_EZLANDING
, 0, fminf(1.0f
, ezLandFactor
) * 10000U);
643 // DEBUG_EZLANDING 1 is the adjusted throttle
644 DEBUG_SET(DEBUG_EZLANDING
, 2, upperLimit
* 10000U);
645 DEBUG_SET(DEBUG_EZLANDING
, 3, fminf(1.0f
, ezLandLimit
/ absMotorMixMin
) * 10000U);
646 // DEBUG_EZLANDING 4 and 5 is the upper limits based on stick input and speed respectively
649 static void applyMixerAdjustment(float *motorMix
, const float motorMixMin
, const float motorMixMax
, const bool airmodeEnabled
)
651 #ifdef USE_AIRMODE_LPF
652 const float unadjustedThrottle
= throttle
;
653 throttle
+= pidGetAirmodeThrottleOffset();
654 float airmodeThrottleChange
= 0.0f
;
656 float airmodeTransitionPercent
= 1.0f
;
658 if (!airmodeEnabled
&& throttle
< 0.5f
) {
659 // this scales the motor mix authority to be 0.5 at 0 throttle, and 1.0 at 0.5 throttle as airmode off intended for things to work.
660 // also lays the groundwork for how an airmode percent would work.
661 airmodeTransitionPercent
= scaleRangef(throttle
, 0.0f
, 0.5f
, 0.5f
, 1.0f
); // 0.5 throttle is full transition, and 0.0 throttle is 50% airmodeTransitionPercent
664 const float motorMixNormalizationFactor
= motorMixRange
> 1.0f
? airmodeTransitionPercent
/ motorMixRange
: airmodeTransitionPercent
;
666 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
667 motorMix
[i
] *= motorMixNormalizationFactor
;
670 const float normalizedMotorMixMin
= motorMixMin
* motorMixNormalizationFactor
;
671 const float normalizedMotorMixMax
= motorMixMax
* motorMixNormalizationFactor
;
672 throttle
= constrainf(throttle
, -normalizedMotorMixMin
, 1.0f
- normalizedMotorMixMax
);
674 #ifdef USE_AIRMODE_LPF
675 airmodeThrottleChange
= constrainf(unadjustedThrottle
, -normalizedMotorMixMin
, 1.0f
- normalizedMotorMixMax
) - unadjustedThrottle
;
676 pidUpdateAirmodeLpf(airmodeThrottleChange
);
680 FAST_CODE_NOINLINE
void mixTable(timeUs_t currentTimeUs
)
682 const bool launchControlActive
= isLaunchControlActive();
683 const bool airmodeEnabled
= isAirmodeEnabled() || launchControlActive
;
685 // Find min and max throttle based on conditions. Throttle has to be known before mixing
686 calculateThrottleAndCurrentMotorEndpoints(currentTimeUs
);
688 if (applyCrashFlipModeToMotors()) {
689 return; // if crash flip mode has been applied to the motors, mixing is done
692 motorMixer_t
* activeMixer
= &mixerRuntime
.currentMixer
[0];
693 #ifdef USE_LAUNCH_CONTROL
694 if (launchControlActive
&& (currentPidProfile
->launchControlMode
== LAUNCH_CONTROL_MODE_PITCHONLY
)) {
695 activeMixer
= &mixerRuntime
.launchControlMixer
[0];
699 // Calculate and Limit the PID sum
700 const float scaledAxisPidRoll
=
701 constrainf(pidData
[FD_ROLL
].Sum
, -currentPidProfile
->pidSumLimit
, currentPidProfile
->pidSumLimit
) / PID_MIXER_SCALING
;
702 const float scaledAxisPidPitch
=
703 constrainf(pidData
[FD_PITCH
].Sum
, -currentPidProfile
->pidSumLimit
, currentPidProfile
->pidSumLimit
) / PID_MIXER_SCALING
;
705 uint16_t yawPidSumLimit
= currentPidProfile
->pidSumLimitYaw
;
707 #ifdef USE_YAW_SPIN_RECOVERY
708 const bool yawSpinDetected
= gyroYawSpinDetected();
709 if (yawSpinDetected
) {
710 yawPidSumLimit
= PIDSUM_LIMIT_MAX
; // Set to the maximum limit during yaw spin recovery to prevent limiting motor authority
712 #endif // USE_YAW_SPIN_RECOVERY
714 float scaledAxisPidYaw
=
715 constrainf(pidData
[FD_YAW
].Sum
, -yawPidSumLimit
, yawPidSumLimit
) / PID_MIXER_SCALING
;
717 if (!mixerConfig()->yaw_motors_reversed
) {
718 scaledAxisPidYaw
= -scaledAxisPidYaw
;
721 // Apply the throttle_limit_percent to scale or limit the throttle based on throttle_limit_type
722 if (currentControlRateProfile
->throttle_limit_type
!= THROTTLE_LIMIT_TYPE_OFF
) {
723 throttle
= applyThrottleLimit(throttle
);
726 // use scaled throttle, without dynamic idle throttle offset, as the input to antigravity
727 pidUpdateAntiGravityThrottleFilter(throttle
);
730 pidUpdateTpaFactor(throttle
);
733 // keep the changes to dynamic lowpass clean, without unnecessary dynamic changes
734 updateDynLpfCutoffs(currentTimeUs
, throttle
);
737 // apply throttle boost when throttle moves quickly
738 #if defined(USE_THROTTLE_BOOST)
739 if (throttleBoost
> 0.0f
) {
740 const float throttleHpf
= throttle
- pt1FilterApply(&throttleLpf
, throttle
);
741 throttle
= constrainf(throttle
+ throttleBoost
* throttleHpf
, 0.0f
, 1.0f
);
745 // send throttle value to blackbox, including scaling and throttle boost, but not TL compensation, dyn idle or airmode
746 mixerThrottle
= throttle
;
749 // Set min throttle offset of 1% when stick is at zero and dynamic idle is active
750 if (mixerRuntime
.dynIdleMinRps
> 0.0f
) {
751 throttle
= MAX(throttle
, 0.01f
);
755 #ifdef USE_THRUST_LINEARIZATION
756 // reduce throttle to offset additional motor output
757 throttle
= pidCompensateThrustLinearization(throttle
);
761 if (RPM_LIMIT_ACTIVE
&& useDshotTelemetry
&& ARMING_FLAG(ARMED
)) {
762 applyRpmLimiter(&mixerRuntime
);
766 // Find roll/pitch/yaw desired output
767 // ??? Where is the optimal location for this code?
768 float motorMix
[MAX_SUPPORTED_MOTORS
];
769 float motorMixMax
= 0, motorMixMin
= 0;
770 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
772 scaledAxisPidRoll
* activeMixer
[i
].roll
+
773 scaledAxisPidPitch
* activeMixer
[i
].pitch
+
774 scaledAxisPidYaw
* activeMixer
[i
].yaw
;
776 if (mix
> motorMixMax
) {
778 } else if (mix
< motorMixMin
) {
784 // The following fixed throttle values will not be shown in the blackbox log
785 #ifdef USE_YAW_SPIN_RECOVERY
786 // 50% throttle provides the maximum authority for yaw recovery when airmode is not active.
787 // When airmode is active the throttle setting doesn't impact recovery authority.
788 if (yawSpinDetected
&& !airmodeEnabled
) {
791 #endif // USE_YAW_SPIN_RECOVERY
793 #ifdef USE_LAUNCH_CONTROL
794 // While launch control is active keep the throttle at minimum.
795 // Once the pilot triggers the launch throttle control will be reactivated.
796 if (launchControlActive
) {
801 #ifdef USE_ALT_HOLD_MODE
802 // Throttle value to be used during altitude hold mode (and failsafe landing mode)
803 if (FLIGHT_MODE(ALT_HOLD_MODE
)) {
804 throttle
= getAutopilotThrottle();
808 #ifdef USE_GPS_RESCUE
809 // If gps rescue is active then override the throttle. This prevents things
810 // like throttle boost or throttle limit from negatively affecting the throttle.
811 if (FLIGHT_MODE(GPS_RESCUE_MODE
)) {
812 throttle
= getAutopilotThrottle();
816 motorMixRange
= motorMixMax
- motorMixMin
;
818 // note that here airmodeEnabled is true also when Launch Control is active
819 switch (mixerConfig()->mixer_type
) {
821 applyMixerAdjustment(motorMix
, motorMixMin
, motorMixMax
, airmodeEnabled
);
825 applyMixerAdjustmentLinear(motorMix
, airmodeEnabled
);
827 case MIXER_EZLANDING
:
828 applyMixerAdjustmentEzLand(motorMix
, motorMixMin
, motorMixMax
);
831 applyMixerAdjustment(motorMix
, motorMixMin
, motorMixMax
, airmodeEnabled
);
835 if (featureIsEnabled(FEATURE_MOTOR_STOP
)
836 && ARMING_FLAG(ARMED
)
837 && !mixerRuntime
.feature3dEnabled
838 && !airmodeEnabled
// not with airmode or launch control
839 && !FLIGHT_MODE(GPS_RESCUE_MODE
| ALT_HOLD_MODE
) // not in autopilot modes
840 && (rcData
[THROTTLE
] < rxConfig()->mincheck
)) {
843 // Apply the mix to motor endpoints
844 applyMixToMotors(motorMix
, activeMixer
);
848 void mixerSetThrottleAngleCorrection(int correctionValue
)
850 throttleAngleCorrection
= correctionValue
;
853 float mixerGetThrottle(void)
855 return mixerThrottle
;
858 float mixerGetRcThrottle(void)
863 bool crashFlipSuccessful(void)
865 return crashflipSuccess
;