Driver for CADDX camera GM3 gimbal (#13926)
[betaflight.git] / src / main / flight / mixer.c
blob925aee1ff8ef4e3e569e6446062cf7faff47b2e1
1 /*
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)
8 * any later version.
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/>.
21 #include <stdlib.h>
22 #include <math.h>
23 #include <float.h>
25 #include "platform.h"
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"
42 #include "fc/core.h"
43 #include "fc/rc.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"
58 #include "io/gps.h"
60 #include "pg/rx.h"
62 #include "rx/rx.h"
64 #include "sensors/battery.h"
65 #include "sensors/gyro.h"
66 #include "sensors/sensors.h"
68 #include "mixer.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)
85 return motorMixRange;
88 void writeMotors(void)
90 motorWriteAll(motor);
93 static void writeAllMotors(int16_t mc)
95 // Sends commands to all motors
96 for (int i = 0; i < mixerRuntime.motorCount; i++) {
97 motor[i] = mc;
99 writeMotors();
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;
140 } else {
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()) {
149 // INVERTED
150 motorRangeMin = mixerRuntime.motorOutputLow;
151 motorRangeMax = mixerRuntime.deadbandMotor3dLow;
152 #ifdef USE_DSHOT
153 if (isMotorProtocolDshot()) {
154 motorOutputMin = mixerRuntime.motorOutputLow;
155 motorOutputRange = mixerRuntime.deadbandMotor3dLow - mixerRuntime.motorOutputLow;
156 } else
157 #endif
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) {
172 // NORMAL
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;
191 #ifdef USE_DSHOT
192 if (isMotorProtocolDshot()) {
193 motorOutputMin = mixerRuntime.motorOutputLow;
194 motorOutputRange = mixerRuntime.deadbandMotor3dLow - mixerRuntime.motorOutputLow;
195 } else
196 #endif
198 motorOutputMin = mixerRuntime.deadbandMotor3dLow;
199 motorOutputRange = mixerRuntime.motorOutputLow - mixerRuntime.deadbandMotor3dLow;
202 if (motorOutputMixSign != -1) {
203 reversalTimeUs = currentTimeUs;
205 motorOutputMixSign = -1;
207 throttle = 0;
208 currentThrottleInputRange = rcCommandThrottleRange3dLow;
209 } else {
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;
219 throttle = 0;
220 currentThrottleInputRange = rcCommandThrottleRange3dHigh;
222 if (currentTimeUs - reversalTimeUs < 250000) {
223 // keep iterm zero for 250ms after motor reversal
224 pidResetIterm();
226 } else {
227 throttle = rcCommand[THROTTLE] - PWM_RANGE_MIN + throttleAngleCorrection;
228 currentThrottleInputRange = PWM_RANGE;
229 #ifdef USE_DYN_IDLE
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));
248 } else {
249 motorRangeMinIncrease = 0;
251 #endif
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);
265 #else
266 motorRangeMax = mixerRuntime.motorOutputHigh;
267 #endif
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)
280 #ifdef USE_ACC
281 static bool isTiltAngleAtStartSet = false;
282 static float tiltAngleAtStart = 1.0f;
283 #endif
285 if (!isCrashFlipModeActive()) {
286 #ifdef USE_ACC
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
290 #endif
291 // signal that crashflip mode is off
292 return false;
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;
308 signRoll = 0;
309 signPitch = 0;
310 } else {
311 // If pitch/roll dominant, disable yaw
312 signYaw = 0;
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) {
321 signPitch = 0;
322 } else {
323 signRoll = 0;
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) {
338 #ifdef USE_ACC
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;
359 #endif // USE_ACC
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;
381 } else {
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
396 return true;
399 #ifdef USE_RPM_LIMIT
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;
408 // PID
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);
426 // Output
427 pidOutput = MAX(0.0f, pidOutput);
428 throttle = constrainf(throttle - pidOutput, 0.0f, 1.0f);
429 prevError = error;
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
442 #ifdef USE_WING
443 static float motorOutputRms = 0.0f;
445 float getMotorOutputRms(void)
447 return motorOutputRms;
449 #endif // USE_WING
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);
459 #endif
460 motorOutput = motorOutputMin + motorOutputRange * motorOutput;
462 #ifdef USE_SERVOS
463 if (mixerIsTricopter()) {
464 motorOutput += mixerTricopterMotorCorrection(i);
466 #endif
467 if (failsafeIsActive()) {
468 #ifdef USE_DSHOT
469 if (isMotorProtocolDshot()) {
470 motorOutput = (motorOutput < motorRangeMin) ? mixerRuntime.disarmMotorOutput : motorOutput; // Prevent getting into special reserved range
472 #endif
473 motorOutput = constrainf(motorOutput, mixerRuntime.disarmMotorOutput, motorRangeMax);
474 } else {
475 motorOutput = constrainf(motorOutput, motorRangeMin, motorRangeMax);
477 motor[i] = motorOutput;
480 // Disarmed mode
481 if (!ARMING_FLAG(ARMED)) {
482 for (int i = 0; i < mixerRuntime.motorCount; i++) {
483 motor[i] = motor_disarmed[i];
487 #ifdef USE_WING
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);
498 #endif // USE_WING
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);
516 return throttle;
519 static void applyMotorStop(void)
521 for (int i = 0; i < mixerRuntime.motorCount; i++) {
522 motor[i] = mixerRuntime.disarmMotorOutput;
525 #ifdef USE_WING
526 motorOutputRms = 0.0f;
527 #endif // USE_WING
530 #ifdef USE_DYN_LPF
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;
548 #endif
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);
572 } else {
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;
609 #ifdef USE_GPS
610 const float speed = STATE(GPS_FIX) ? gpsSol.speed3d / 100.0f : 0.0f; // m/s
611 #else
612 const float speed = 0.0f;
613 #endif
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;
655 #endif
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);
677 #endif
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];
697 #endif
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);
729 // and for TPA
730 pidUpdateTpaFactor(throttle);
732 #ifdef USE_DYN_LPF
733 // keep the changes to dynamic lowpass clean, without unnecessary dynamic changes
734 updateDynLpfCutoffs(currentTimeUs, throttle);
735 #endif
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);
743 #endif
745 // send throttle value to blackbox, including scaling and throttle boost, but not TL compensation, dyn idle or airmode
746 mixerThrottle = throttle;
748 #ifdef USE_DYN_IDLE
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);
753 #endif
755 #ifdef USE_THRUST_LINEARIZATION
756 // reduce throttle to offset additional motor output
757 throttle = pidCompensateThrustLinearization(throttle);
758 #endif
760 #ifdef USE_RPM_LIMIT
761 if (RPM_LIMIT_ACTIVE && useDshotTelemetry && ARMING_FLAG(ARMED)) {
762 applyRpmLimiter(&mixerRuntime);
764 #endif
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++) {
771 float mix =
772 scaledAxisPidRoll * activeMixer[i].roll +
773 scaledAxisPidPitch * activeMixer[i].pitch +
774 scaledAxisPidYaw * activeMixer[i].yaw;
776 if (mix > motorMixMax) {
777 motorMixMax = mix;
778 } else if (mix < motorMixMin) {
779 motorMixMin = mix;
781 motorMix[i] = mix;
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) {
789 throttle = 0.5f;
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) {
797 throttle = 0.0f;
799 #endif
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();
806 #endif
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();
814 #endif
816 motorMixRange = motorMixMax - motorMixMin;
818 // note that here airmodeEnabled is true also when Launch Control is active
819 switch (mixerConfig()->mixer_type) {
820 case MIXER_LEGACY:
821 applyMixerAdjustment(motorMix, motorMixMin, motorMixMax, airmodeEnabled);
822 break;
823 case MIXER_LINEAR:
824 case MIXER_DYNAMIC:
825 applyMixerAdjustmentLinear(motorMix, airmodeEnabled);
826 break;
827 case MIXER_EZLANDING:
828 applyMixerAdjustmentEzLand(motorMix, motorMixMin, motorMixMax);
829 break;
830 default:
831 applyMixerAdjustment(motorMix, motorMixMin, motorMixMax, airmodeEnabled);
832 break;
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)) {
841 applyMotorStop();
842 } else {
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)
860 return rcThrottle;
863 bool crashFlipSuccessful(void)
865 return crashflipSuccess;