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/>.
29 #include "build/debug.h"
31 #include "common/axis.h"
32 #include "common/filter.h"
33 #include "common/maths.h"
35 #include "config/config.h"
36 #include "config/feature.h"
38 #include "drivers/dshot.h"
39 #include "drivers/io.h"
40 #include "drivers/motor.h"
41 #include "drivers/time.h"
43 #include "fc/controlrate_profile.h"
46 #include "fc/rc_controls.h"
47 #include "fc/rc_modes.h"
48 #include "fc/runtime_config.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"
62 #include "sensors/battery.h"
63 #include "sensors/gyro.h"
67 #define DYN_LPF_THROTTLE_STEPS 100
68 #define DYN_LPF_THROTTLE_UPDATE_DELAY_US 5000 // minimum of 5ms between updates
70 static FAST_DATA_ZERO_INIT
float motorMixRange
;
72 float FAST_DATA_ZERO_INIT motor
[MAX_SUPPORTED_MOTORS
];
73 float motor_disarmed
[MAX_SUPPORTED_MOTORS
];
75 static FAST_DATA_ZERO_INIT
int throttleAngleCorrection
;
77 float getMotorMixRange(void)
82 void writeMotors(void)
87 static void writeAllMotors(int16_t mc
)
89 // Sends commands to all motors
90 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
98 writeAllMotors(mixerRuntime
.disarmMotorOutput
);
99 delay(50); // give the timers and ESCs a chance to react.
102 static FAST_DATA_ZERO_INIT
float throttle
= 0;
103 static FAST_DATA_ZERO_INIT
float mixerThrottle
= 0;
104 static FAST_DATA_ZERO_INIT
float motorOutputMin
;
105 static FAST_DATA_ZERO_INIT
float motorRangeMin
;
106 static FAST_DATA_ZERO_INIT
float motorRangeMax
;
107 static FAST_DATA_ZERO_INIT
float motorOutputRange
;
108 static FAST_DATA_ZERO_INIT
int8_t motorOutputMixSign
;
110 static void calculateThrottleAndCurrentMotorEndpoints(timeUs_t currentTimeUs
)
112 static uint16_t rcThrottlePrevious
= 0; // Store the last throttle direction for deadband transitions
113 static timeUs_t reversalTimeUs
= 0; // time when motors last reversed in 3D mode
114 static float motorRangeMinIncrease
= 0;
116 float currentThrottleInputRange
= 0;
117 if (mixerRuntime
.feature3dEnabled
) {
118 uint16_t rcCommand3dDeadBandLow
;
119 uint16_t rcCommand3dDeadBandHigh
;
121 if (!ARMING_FLAG(ARMED
)) {
122 rcThrottlePrevious
= rxConfig()->midrc
; // When disarmed set to mid_rc. It always results in positive direction after arming.
125 if (IS_RC_MODE_ACTIVE(BOX3D
) || flight3DConfig()->switched_mode3d
) {
126 // The min_check range is halved because the output throttle is scaled to 500us.
127 // So by using half of min_check we maintain the same low-throttle deadband
128 // stick travel as normal non-3D mode.
129 const int mincheckOffset
= (rxConfig()->mincheck
- PWM_RANGE_MIN
) / 2;
130 rcCommand3dDeadBandLow
= rxConfig()->midrc
- mincheckOffset
;
131 rcCommand3dDeadBandHigh
= rxConfig()->midrc
+ mincheckOffset
;
133 rcCommand3dDeadBandLow
= rxConfig()->midrc
- flight3DConfig()->deadband3d_throttle
;
134 rcCommand3dDeadBandHigh
= rxConfig()->midrc
+ flight3DConfig()->deadband3d_throttle
;
137 const float rcCommandThrottleRange3dLow
= rcCommand3dDeadBandLow
- PWM_RANGE_MIN
;
138 const float rcCommandThrottleRange3dHigh
= PWM_RANGE_MAX
- rcCommand3dDeadBandHigh
;
140 if (rcCommand
[THROTTLE
] <= rcCommand3dDeadBandLow
|| isFlipOverAfterCrashActive()) {
142 motorRangeMin
= mixerRuntime
.motorOutputLow
;
143 motorRangeMax
= mixerRuntime
.deadbandMotor3dLow
;
145 if (isMotorProtocolDshot()) {
146 motorOutputMin
= mixerRuntime
.motorOutputLow
;
147 motorOutputRange
= mixerRuntime
.deadbandMotor3dLow
- mixerRuntime
.motorOutputLow
;
151 motorOutputMin
= mixerRuntime
.deadbandMotor3dLow
;
152 motorOutputRange
= mixerRuntime
.motorOutputLow
- mixerRuntime
.deadbandMotor3dLow
;
155 if (motorOutputMixSign
!= -1) {
156 reversalTimeUs
= currentTimeUs
;
158 motorOutputMixSign
= -1;
160 rcThrottlePrevious
= rcCommand
[THROTTLE
];
161 throttle
= rcCommand3dDeadBandLow
- rcCommand
[THROTTLE
];
162 currentThrottleInputRange
= rcCommandThrottleRange3dLow
;
163 } else if (rcCommand
[THROTTLE
] >= rcCommand3dDeadBandHigh
) {
165 motorRangeMin
= mixerRuntime
.deadbandMotor3dHigh
;
166 motorRangeMax
= mixerRuntime
.motorOutputHigh
;
167 motorOutputMin
= mixerRuntime
.deadbandMotor3dHigh
;
168 motorOutputRange
= mixerRuntime
.motorOutputHigh
- mixerRuntime
.deadbandMotor3dHigh
;
169 if (motorOutputMixSign
!= 1) {
170 reversalTimeUs
= currentTimeUs
;
172 motorOutputMixSign
= 1;
173 rcThrottlePrevious
= rcCommand
[THROTTLE
];
174 throttle
= rcCommand
[THROTTLE
] - rcCommand3dDeadBandHigh
;
175 currentThrottleInputRange
= rcCommandThrottleRange3dHigh
;
176 } else if ((rcThrottlePrevious
<= rcCommand3dDeadBandLow
&&
177 !flight3DConfigMutable()->switched_mode3d
) ||
178 isMotorsReversed()) {
179 // INVERTED_TO_DEADBAND
180 motorRangeMin
= mixerRuntime
.motorOutputLow
;
181 motorRangeMax
= mixerRuntime
.deadbandMotor3dLow
;
184 if (isMotorProtocolDshot()) {
185 motorOutputMin
= mixerRuntime
.motorOutputLow
;
186 motorOutputRange
= mixerRuntime
.deadbandMotor3dLow
- mixerRuntime
.motorOutputLow
;
190 motorOutputMin
= mixerRuntime
.deadbandMotor3dLow
;
191 motorOutputRange
= mixerRuntime
.motorOutputLow
- mixerRuntime
.deadbandMotor3dLow
;
194 if (motorOutputMixSign
!= -1) {
195 reversalTimeUs
= currentTimeUs
;
197 motorOutputMixSign
= -1;
200 currentThrottleInputRange
= rcCommandThrottleRange3dLow
;
202 // NORMAL_TO_DEADBAND
203 motorRangeMin
= mixerRuntime
.deadbandMotor3dHigh
;
204 motorRangeMax
= mixerRuntime
.motorOutputHigh
;
205 motorOutputMin
= mixerRuntime
.deadbandMotor3dHigh
;
206 motorOutputRange
= mixerRuntime
.motorOutputHigh
- mixerRuntime
.deadbandMotor3dHigh
;
207 if (motorOutputMixSign
!= 1) {
208 reversalTimeUs
= currentTimeUs
;
210 motorOutputMixSign
= 1;
212 currentThrottleInputRange
= rcCommandThrottleRange3dHigh
;
214 if (currentTimeUs
- reversalTimeUs
< 250000) {
215 // keep iterm zero for 250ms after motor reversal
219 throttle
= rcCommand
[THROTTLE
] - PWM_RANGE_MIN
+ throttleAngleCorrection
;
220 currentThrottleInputRange
= PWM_RANGE
;
223 if (mixerRuntime
.dynIdleMinRps
> 0.0f
) {
224 const float maxIncrease
= isAirmodeActivated()
225 ? mixerRuntime
.dynIdleMaxIncrease
: mixerRuntime
.dynIdleStartIncrease
;
226 float minRps
= getMinMotorFrequency();
227 DEBUG_SET(DEBUG_DYN_IDLE
, 3, lrintf(minRps
* 10.0f
));
228 float rpsError
= mixerRuntime
.dynIdleMinRps
- minRps
;
229 // PT1 type lowpass delay and smoothing for D
230 minRps
= mixerRuntime
.prevMinRps
+ mixerRuntime
.minRpsDelayK
* (minRps
- mixerRuntime
.prevMinRps
);
231 float dynIdleD
= (mixerRuntime
.prevMinRps
- minRps
) * mixerRuntime
.dynIdleDGain
;
232 mixerRuntime
.prevMinRps
= minRps
;
233 float dynIdleP
= rpsError
* mixerRuntime
.dynIdlePGain
;
234 rpsError
= MAX(-0.1f
, rpsError
); //I rises fast, falls slowly
235 mixerRuntime
.dynIdleI
+= rpsError
* mixerRuntime
.dynIdleIGain
;
236 mixerRuntime
.dynIdleI
= constrainf(mixerRuntime
.dynIdleI
, 0.0f
, maxIncrease
);
237 motorRangeMinIncrease
= constrainf((dynIdleP
+ mixerRuntime
.dynIdleI
+ dynIdleD
), 0.0f
, maxIncrease
);
238 DEBUG_SET(DEBUG_DYN_IDLE
, 0, MAX(-1000, lrintf(dynIdleP
* 10000)));
239 DEBUG_SET(DEBUG_DYN_IDLE
, 1, lrintf(mixerRuntime
.dynIdleI
* 10000));
240 DEBUG_SET(DEBUG_DYN_IDLE
, 2, lrintf(dynIdleD
* 10000));
242 motorRangeMinIncrease
= 0;
246 #if defined(USE_BATTERY_VOLTAGE_SAG_COMPENSATION)
247 float motorRangeAttenuationFactor
= 0;
248 // reduce motorRangeMax when battery is full
249 if (mixerRuntime
.vbatSagCompensationFactor
> 0.0f
) {
250 const uint16_t currentCellVoltage
= getBatterySagCellVoltage();
251 // batteryGoodness = 1 when voltage is above vbatFull, and 0 when voltage is below vbatLow
252 float batteryGoodness
= 1.0f
- constrainf((mixerRuntime
.vbatFull
- currentCellVoltage
) / mixerRuntime
.vbatRangeToCompensate
, 0.0f
, 1.0f
);
253 motorRangeAttenuationFactor
= (mixerRuntime
.vbatRangeToCompensate
/ mixerRuntime
.vbatFull
) * batteryGoodness
* mixerRuntime
.vbatSagCompensationFactor
;
254 DEBUG_SET(DEBUG_BATTERY
, 2, lrintf(batteryGoodness
* 100));
255 DEBUG_SET(DEBUG_BATTERY
, 3, lrintf(motorRangeAttenuationFactor
* 1000));
257 motorRangeMax
= isFlipOverAfterCrashActive() ? mixerRuntime
.motorOutputHigh
: mixerRuntime
.motorOutputHigh
- motorRangeAttenuationFactor
* (mixerRuntime
.motorOutputHigh
- mixerRuntime
.motorOutputLow
);
259 motorRangeMax
= mixerRuntime
.motorOutputHigh
;
262 motorRangeMin
= mixerRuntime
.motorOutputLow
+ motorRangeMinIncrease
* (mixerRuntime
.motorOutputHigh
- mixerRuntime
.motorOutputLow
);
263 motorOutputMin
= motorRangeMin
;
264 motorOutputRange
= motorRangeMax
- motorRangeMin
;
265 motorOutputMixSign
= 1;
268 throttle
= constrainf(throttle
/ currentThrottleInputRange
, 0.0f
, 1.0f
);
271 #define CRASH_FLIP_DEADBAND 20
272 #define CRASH_FLIP_STICK_MINF 0.15f
274 static void applyFlipOverAfterCrashModeToMotors(void)
276 if (ARMING_FLAG(ARMED
)) {
277 const float flipPowerFactor
= 1.0f
- mixerConfig()->crashflip_expo
/ 100.0f
;
278 const float stickDeflectionPitchAbs
= getRcDeflectionAbs(FD_PITCH
);
279 const float stickDeflectionRollAbs
= getRcDeflectionAbs(FD_ROLL
);
280 const float stickDeflectionYawAbs
= getRcDeflectionAbs(FD_YAW
);
282 const float stickDeflectionPitchExpo
= flipPowerFactor
* stickDeflectionPitchAbs
+ power3(stickDeflectionPitchAbs
) * (1 - flipPowerFactor
);
283 const float stickDeflectionRollExpo
= flipPowerFactor
* stickDeflectionRollAbs
+ power3(stickDeflectionRollAbs
) * (1 - flipPowerFactor
);
284 const float stickDeflectionYawExpo
= flipPowerFactor
* stickDeflectionYawAbs
+ power3(stickDeflectionYawAbs
) * (1 - flipPowerFactor
);
286 float signPitch
= getRcDeflection(FD_PITCH
) < 0 ? 1 : -1;
287 float signRoll
= getRcDeflection(FD_ROLL
) < 0 ? 1 : -1;
288 float signYaw
= (getRcDeflection(FD_YAW
) < 0 ? 1 : -1) * (mixerConfig()->yaw_motors_reversed
? 1 : -1);
290 float stickDeflectionLength
= sqrtf(sq(stickDeflectionPitchAbs
) + sq(stickDeflectionRollAbs
));
291 float stickDeflectionExpoLength
= sqrtf(sq(stickDeflectionPitchExpo
) + sq(stickDeflectionRollExpo
));
293 if (stickDeflectionYawAbs
> MAX(stickDeflectionPitchAbs
, stickDeflectionRollAbs
)) {
294 // If yaw is the dominant, disable pitch and roll
295 stickDeflectionLength
= stickDeflectionYawAbs
;
296 stickDeflectionExpoLength
= stickDeflectionYawExpo
;
300 // If pitch/roll dominant, disable yaw
304 const float cosPhi
= (stickDeflectionLength
> 0) ? (stickDeflectionPitchAbs
+ stickDeflectionRollAbs
) / (sqrtf(2.0f
) * stickDeflectionLength
) : 0;
305 const float cosThreshold
= sqrtf(3.0f
)/2.0f
; // cos(PI/6.0f)
307 if (cosPhi
< cosThreshold
) {
308 // Enforce either roll or pitch exclusively, if not on diagonal
309 if (stickDeflectionRollAbs
> stickDeflectionPitchAbs
) {
316 // Apply a reasonable amount of stick deadband
317 const float crashFlipStickMinExpo
= flipPowerFactor
* CRASH_FLIP_STICK_MINF
+ power3(CRASH_FLIP_STICK_MINF
) * (1 - flipPowerFactor
);
318 const float flipStickRange
= 1.0f
- crashFlipStickMinExpo
;
319 const float flipPower
= MAX(0.0f
, stickDeflectionExpoLength
- crashFlipStickMinExpo
) / flipStickRange
;
321 for (int i
= 0; i
< mixerRuntime
.motorCount
; ++i
) {
322 float motorOutputNormalised
=
323 signPitch
* mixerRuntime
.currentMixer
[i
].pitch
+
324 signRoll
* mixerRuntime
.currentMixer
[i
].roll
+
325 signYaw
* mixerRuntime
.currentMixer
[i
].yaw
;
327 if (motorOutputNormalised
< 0) {
328 if (mixerConfig()->crashflip_motor_percent
> 0) {
329 motorOutputNormalised
= -motorOutputNormalised
* (float)mixerConfig()->crashflip_motor_percent
/ 100.0f
;
331 motorOutputNormalised
= 0;
334 motorOutputNormalised
= MIN(1.0f
, flipPower
* motorOutputNormalised
);
335 float motorOutput
= motorOutputMin
+ motorOutputNormalised
* motorOutputRange
;
337 // Add a little bit to the motorOutputMin so props aren't spinning when sticks are centered
338 motorOutput
= (motorOutput
< motorOutputMin
+ CRASH_FLIP_DEADBAND
) ? mixerRuntime
.disarmMotorOutput
: (motorOutput
- CRASH_FLIP_DEADBAND
);
340 motor
[i
] = motorOutput
;
344 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
345 motor
[i
] = motor_disarmed
[i
];
350 static void applyMixToMotors(float motorMix
[MAX_SUPPORTED_MOTORS
], motorMixer_t
*activeMixer
)
352 // Now add in the desired throttle, but keep in a range that doesn't clip adjusted
353 // roll/pitch/yaw. This could move throttle down, but also up for those low throttle flips.
354 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
355 float motorOutput
= motorOutputMixSign
* motorMix
[i
] + throttle
* activeMixer
[i
].throttle
;
356 #ifdef USE_THRUST_LINEARIZATION
357 motorOutput
= pidApplyThrustLinearization(motorOutput
);
359 motorOutput
= motorOutputMin
+ motorOutputRange
* motorOutput
;
362 if (mixerIsTricopter()) {
363 motorOutput
+= mixerTricopterMotorCorrection(i
);
366 if (failsafeIsActive()) {
368 if (isMotorProtocolDshot()) {
369 motorOutput
= (motorOutput
< motorRangeMin
) ? mixerRuntime
.disarmMotorOutput
: motorOutput
; // Prevent getting into special reserved range
372 motorOutput
= constrainf(motorOutput
, mixerRuntime
.disarmMotorOutput
, motorRangeMax
);
374 motorOutput
= constrainf(motorOutput
, motorRangeMin
, motorRangeMax
);
376 motor
[i
] = motorOutput
;
380 if (!ARMING_FLAG(ARMED
)) {
381 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
382 motor
[i
] = motor_disarmed
[i
];
387 static float applyThrottleLimit(float throttle
)
389 if (currentControlRateProfile
->throttle_limit_percent
< 100) {
390 const float throttleLimitFactor
= currentControlRateProfile
->throttle_limit_percent
/ 100.0f
;
391 switch (currentControlRateProfile
->throttle_limit_type
) {
392 case THROTTLE_LIMIT_TYPE_SCALE
:
393 return throttle
* throttleLimitFactor
;
394 case THROTTLE_LIMIT_TYPE_CLIP
:
395 return MIN(throttle
, throttleLimitFactor
);
402 static void applyMotorStop(void)
404 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
405 motor
[i
] = mixerRuntime
.disarmMotorOutput
;
410 static void updateDynLpfCutoffs(timeUs_t currentTimeUs
, float throttle
)
412 static timeUs_t lastDynLpfUpdateUs
= 0;
413 static int dynLpfPreviousQuantizedThrottle
= -1; // to allow an initial zero throttle to set the filter cutoff
415 if (cmpTimeUs(currentTimeUs
, lastDynLpfUpdateUs
) >= DYN_LPF_THROTTLE_UPDATE_DELAY_US
) {
416 const int quantizedThrottle
= lrintf(throttle
* DYN_LPF_THROTTLE_STEPS
); // quantize the throttle reduce the number of filter updates
417 if (quantizedThrottle
!= dynLpfPreviousQuantizedThrottle
) {
418 // scale the quantized value back to the throttle range so the filter cutoff steps are repeatable
419 const float dynLpfThrottle
= (float)quantizedThrottle
/ DYN_LPF_THROTTLE_STEPS
;
420 dynLpfGyroUpdate(dynLpfThrottle
);
421 dynLpfDTermUpdate(dynLpfThrottle
);
422 dynLpfPreviousQuantizedThrottle
= quantizedThrottle
;
423 lastDynLpfUpdateUs
= currentTimeUs
;
429 static void applyMixerAdjustmentLinear(float *motorMix
, const bool airmodeEnabled
)
431 float airmodeTransitionPercent
= 1.0f
;
432 float motorDeltaScale
= 0.5f
;
434 if (!airmodeEnabled
&& throttle
< 0.5f
) {
435 // 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.
436 // also lays the groundwork for how an airmode percent would work.
437 airmodeTransitionPercent
= scaleRangef(throttle
, 0.0f
, 0.5f
, 0.5f
, 1.0f
); // 0.5 throttle is full transition, and 0.0 throttle is 50% airmodeTransitionPercent
438 motorDeltaScale
*= airmodeTransitionPercent
; // this should be half of the motor authority allowed
441 const float motorMixNormalizationFactor
= motorMixRange
> 1.0f
? airmodeTransitionPercent
/ motorMixRange
: airmodeTransitionPercent
;
443 const float motorMixDelta
= motorDeltaScale
* motorMixRange
;
445 float minMotor
= FLT_MAX
;
446 float maxMotor
= FLT_MIN
;
448 for (int i
= 0; i
< mixerRuntime
.motorCount
; ++i
) {
449 if (mixerConfig()->mixer_type
== MIXER_LINEAR
) {
450 motorMix
[i
] = scaleRangef(throttle
, 0.0f
, 1.0f
, motorMix
[i
] + motorMixDelta
, motorMix
[i
] - motorMixDelta
);
452 motorMix
[i
] = scaleRangef(throttle
, 0.0f
, 1.0f
, motorMix
[i
] + fabsf(motorMix
[i
]), motorMix
[i
] - fabsf(motorMix
[i
]));
454 motorMix
[i
] *= motorMixNormalizationFactor
;
456 maxMotor
= MAX(motorMix
[i
], maxMotor
);
457 minMotor
= MIN(motorMix
[i
], minMotor
);
460 // constrain throttle so it won't clip any outputs
461 throttle
= constrainf(throttle
, -minMotor
, 1.0f
- maxMotor
);
464 static void applyMixerAdjustment(float *motorMix
, const float motorMixMin
, const float motorMixMax
, const bool airmodeEnabled
)
466 #ifdef USE_AIRMODE_LPF
467 const float unadjustedThrottle
= throttle
;
468 throttle
+= pidGetAirmodeThrottleOffset();
469 float airmodeThrottleChange
= 0.0f
;
471 float airmodeTransitionPercent
= 1.0f
;
473 if (!airmodeEnabled
&& throttle
< 0.5f
) {
474 // 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.
475 // also lays the groundwork for how an airmode percent would work.
476 airmodeTransitionPercent
= scaleRangef(throttle
, 0.0f
, 0.5f
, 0.5f
, 1.0f
); // 0.5 throttle is full transition, and 0.0 throttle is 50% airmodeTransitionPercent
479 const float motorMixNormalizationFactor
= motorMixRange
> 1.0f
? airmodeTransitionPercent
/ motorMixRange
: airmodeTransitionPercent
;
481 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
482 motorMix
[i
] *= motorMixNormalizationFactor
;
485 const float normalizedMotorMixMin
= motorMixMin
* motorMixNormalizationFactor
;
486 const float normalizedMotorMixMax
= motorMixMax
* motorMixNormalizationFactor
;
487 throttle
= constrainf(throttle
, -normalizedMotorMixMin
, 1.0f
- normalizedMotorMixMax
);
489 #ifdef USE_AIRMODE_LPF
490 airmodeThrottleChange
= constrainf(unadjustedThrottle
, -normalizedMotorMixMin
, 1.0f
- normalizedMotorMixMax
) - unadjustedThrottle
;
491 pidUpdateAirmodeLpf(airmodeThrottleChange
);
495 FAST_CODE_NOINLINE
void mixTable(timeUs_t currentTimeUs
)
497 // Find min and max throttle based on conditions. Throttle has to be known before mixing
498 calculateThrottleAndCurrentMotorEndpoints(currentTimeUs
);
500 if (isFlipOverAfterCrashActive()) {
501 applyFlipOverAfterCrashModeToMotors();
506 const bool launchControlActive
= isLaunchControlActive();
508 motorMixer_t
* activeMixer
= &mixerRuntime
.currentMixer
[0];
509 #ifdef USE_LAUNCH_CONTROL
510 if (launchControlActive
&& (currentPidProfile
->launchControlMode
== LAUNCH_CONTROL_MODE_PITCHONLY
)) {
511 activeMixer
= &mixerRuntime
.launchControlMixer
[0];
515 // Calculate and Limit the PID sum
516 const float scaledAxisPidRoll
=
517 constrainf(pidData
[FD_ROLL
].Sum
, -currentPidProfile
->pidSumLimit
, currentPidProfile
->pidSumLimit
) / PID_MIXER_SCALING
;
518 const float scaledAxisPidPitch
=
519 constrainf(pidData
[FD_PITCH
].Sum
, -currentPidProfile
->pidSumLimit
, currentPidProfile
->pidSumLimit
) / PID_MIXER_SCALING
;
521 uint16_t yawPidSumLimit
= currentPidProfile
->pidSumLimitYaw
;
523 #ifdef USE_YAW_SPIN_RECOVERY
524 const bool yawSpinDetected
= gyroYawSpinDetected();
525 if (yawSpinDetected
) {
526 yawPidSumLimit
= PIDSUM_LIMIT_MAX
; // Set to the maximum limit during yaw spin recovery to prevent limiting motor authority
528 #endif // USE_YAW_SPIN_RECOVERY
530 float scaledAxisPidYaw
=
531 constrainf(pidData
[FD_YAW
].Sum
, -yawPidSumLimit
, yawPidSumLimit
) / PID_MIXER_SCALING
;
533 if (!mixerConfig()->yaw_motors_reversed
) {
534 scaledAxisPidYaw
= -scaledAxisPidYaw
;
537 // Apply the throttle_limit_percent to scale or limit the throttle based on throttle_limit_type
538 if (currentControlRateProfile
->throttle_limit_type
!= THROTTLE_LIMIT_TYPE_OFF
) {
539 throttle
= applyThrottleLimit(throttle
);
542 // use scaled throttle, without dynamic idle throttle offset, as the input to antigravity
543 pidUpdateAntiGravityThrottleFilter(throttle
);
546 pidUpdateTpaFactor(throttle
);
549 // keep the changes to dynamic lowpass clean, without unnecessary dynamic changes
550 updateDynLpfCutoffs(currentTimeUs
, throttle
);
553 // apply throttle boost when throttle moves quickly
554 #if defined(USE_THROTTLE_BOOST)
555 if (throttleBoost
> 0.0f
) {
556 const float throttleHpf
= throttle
- pt1FilterApply(&throttleLpf
, throttle
);
557 throttle
= constrainf(throttle
+ throttleBoost
* throttleHpf
, 0.0f
, 1.0f
);
561 // send throttle value to blackbox, including scaling and throttle boost, but not TL compensation, dyn idle or airmode
562 mixerThrottle
= throttle
;
565 // Set min throttle offset of 1% when stick is at zero and dynamic idle is active
566 if (mixerRuntime
.dynIdleMinRps
> 0.0f
) {
567 throttle
= MAX(throttle
, 0.01f
);
571 #ifdef USE_THRUST_LINEARIZATION
572 // reduce throttle to offset additional motor output
573 throttle
= pidCompensateThrustLinearization(throttle
);
576 // Find roll/pitch/yaw desired output
577 // ??? Where is the optimal location for this code?
578 float motorMix
[MAX_SUPPORTED_MOTORS
];
579 float motorMixMax
= 0, motorMixMin
= 0;
580 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
583 scaledAxisPidRoll
* activeMixer
[i
].roll
+
584 scaledAxisPidPitch
* activeMixer
[i
].pitch
+
585 scaledAxisPidYaw
* activeMixer
[i
].yaw
;
587 if (mix
> motorMixMax
) {
589 } else if (mix
< motorMixMin
) {
595 // The following fixed throttle values will not be shown in the blackbox log
596 // ?? Should they be influenced by airmode? If not, should go after the apply airmode code.
597 const bool airmodeEnabled
= airmodeIsEnabled() || launchControlActive
;
598 #ifdef USE_YAW_SPIN_RECOVERY
599 // 50% throttle provides the maximum authority for yaw recovery when airmode is not active.
600 // When airmode is active the throttle setting doesn't impact recovery authority.
601 if (yawSpinDetected
&& !airmodeEnabled
) {
604 #endif // USE_YAW_SPIN_RECOVERY
606 #ifdef USE_LAUNCH_CONTROL
607 // While launch control is active keep the throttle at minimum.
608 // Once the pilot triggers the launch throttle control will be reactivated.
609 if (launchControlActive
) {
614 #ifdef USE_GPS_RESCUE
615 // If gps rescue is active then override the throttle. This prevents things
616 // like throttle boost or throttle limit from negatively affecting the throttle.
617 if (FLIGHT_MODE(GPS_RESCUE_MODE
)) {
618 throttle
= gpsRescueGetThrottle();
622 motorMixRange
= motorMixMax
- motorMixMin
;
623 if (mixerConfig()->mixer_type
> MIXER_LEGACY
) {
624 applyMixerAdjustmentLinear(motorMix
, airmodeEnabled
);
626 applyMixerAdjustment(motorMix
, motorMixMin
, motorMixMax
, airmodeEnabled
);
629 if (featureIsEnabled(FEATURE_MOTOR_STOP
)
630 && ARMING_FLAG(ARMED
)
631 && !mixerRuntime
.feature3dEnabled
633 && !FLIGHT_MODE(GPS_RESCUE_MODE
) // disable motor_stop while GPS Rescue is active
634 && (rcData
[THROTTLE
] < rxConfig()->mincheck
)) {
635 // motor_stop handling
638 // Apply the mix to motor endpoints
639 applyMixToMotors(motorMix
, activeMixer
);
643 void mixerSetThrottleAngleCorrection(int correctionValue
)
645 throttleAngleCorrection
= correctionValue
;
648 float mixerGetThrottle(void)
650 return mixerThrottle
;