2 * This file is part of Cleanflight.
4 * Cleanflight is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Cleanflight is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Cleanflight. If not, see <http://www.gnu.org/licenses/>.
25 #include "build/debug.h"
27 #include "build/build_config.h"
29 #include "common/axis.h"
30 #include "common/maths.h"
31 #include "common/filter.h"
33 #include "config/parameter_group.h"
35 #include "drivers/system.h"
36 #include "drivers/pwm_output.h"
37 #include "drivers/pwm_mapping.h"
38 #include "drivers/sensor.h"
39 #include "drivers/accgyro.h"
40 #include "drivers/system.h"
44 #include "io/motors.h"
46 #include "fc/rc_controls.h"
48 #include "sensors/sensors.h"
49 #include "sensors/acceleration.h"
51 #include "flight/mixer.h"
52 #include "flight/failsafe.h"
53 #include "flight/pid.h"
54 #include "flight/imu.h"
56 #include "config/parameter_group_ids.h"
57 #include "config/feature.h"
58 #include "config/config_reset.h"
60 #include "fc/runtime_config.h"
61 #include "fc/config.h"
67 int16_t motor
[MAX_SUPPORTED_MOTORS
];
68 int16_t motor_disarmed
[MAX_SUPPORTED_MOTORS
];
70 bool motorLimitReached
;
72 motorMixer_t currentMixer
[MAX_SUPPORTED_MOTORS
];
74 PG_REGISTER_ARR(motorMixer_t
, MAX_SUPPORTED_MOTORS
, customMotorMixer
, PG_MOTOR_MIXER
, 0);
75 PG_REGISTER_WITH_RESET_TEMPLATE(mixerConfig_t
, mixerConfig
, PG_MIXER_CONFIG
, 0);
76 PG_REGISTER_WITH_RESET_TEMPLATE(motor3DConfig_t
, motor3DConfig
, PG_MOTOR_3D_CONFIG
, 0);
78 PG_RESET_TEMPLATE(motor3DConfig_t
, motor3DConfig
,
79 .deadband3d_low
= 1406,
80 .deadband3d_high
= 1514,
86 PG_RESET_TEMPLATE(mixerConfig_t
, mixerConfig
,
87 .mixerMode
= MIXER_QUADX
,
88 .pid_at_min_throttle
= 1,
89 .yaw_motor_direction
= 1,
90 .yaw_jump_prevention_limit
= 200,
92 .tri_unarmed_servo
= 1,
93 .servo_lowpass_freq
= 400.0f
,
96 PG_RESET_TEMPLATE(mixerConfig_t
, mixerConfig
,
97 .mixerMode
= MIXER_QUADX
,
98 .pid_at_min_throttle
= 1,
99 .yaw_motor_direction
= 1,
100 .yaw_jump_prevention_limit
= 200,
111 static const motorMixer_t mixerQuadX
[] = {
112 //throttle, roll, pitch, yaw
113 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R (M1)
114 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R (M2)
115 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L (M3)
116 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L (M4)
119 #ifndef USE_QUAD_MIXER_ONLY
127 static const motorMixer_t mixerQuadP
[] = {
128 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
129 { 1.0f
, -1.0f
, 0.0f
, 1.0f
}, // RIGHT
130 { 1.0f
, 1.0f
, 0.0f
, 1.0f
}, // LEFT
131 { 1.0f
, 0.0f
, -1.0f
, -1.0f
}, // FRONT
141 static const motorMixer_t mixerVtail4
[] = {
142 { 1.0f
, -0.58f
, 0.58f
, 1.0f
}, // REAR_R
143 { 1.0f
, -0.46f
, -0.39f
, -0.5f
}, // FRONT_R
144 { 1.0f
, 0.58f
, 0.58f
, -1.0f
}, // REAR_L
145 { 1.0f
, 0.46f
, -0.39f
, 0.5f
}, // FRONT_L
155 static const motorMixer_t mixerAtail4
[] = {
156 { 1.0f
, -0.58f
, 0.58f
, -1.0f
}, // REAR_R
157 { 1.0f
, -0.46f
, -0.39f
, 0.5f
}, // FRONT_R
158 { 1.0f
, 0.58f
, 0.58f
, 1.0f
}, // REAR_L
159 { 1.0f
, 0.46f
, -0.39f
, -0.5f
}, // FRONT_L
169 static const motorMixer_t mixerY4
[] = {
170 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR_TOP CW
171 { 1.0f
, -1.0f
, -1.0f
, 0.0f
}, // FRONT_R CCW
172 { 1.0f
, 0.0f
, 1.0f
, 1.0f
}, // REAR_BOTTOM CCW
173 { 1.0f
, 1.0f
, -1.0f
, 0.0f
}, // FRONT_L CW
176 #if (MAX_SUPPORTED_MOTORS >= 6)
177 static const motorMixer_t mixerY6
[] = {
178 { 1.0f
, 0.0f
, 1.333333f
, 1.0f
}, // REAR
179 { 1.0f
, -1.0f
, -0.666667f
, -1.0f
}, // RIGHT
180 { 1.0f
, 1.0f
, -0.666667f
, -1.0f
}, // LEFT
181 { 1.0f
, 0.0f
, 1.333333f
, -1.0f
}, // UNDER_REAR
182 { 1.0f
, -1.0f
, -0.666667f
, 1.0f
}, // UNDER_RIGHT
183 { 1.0f
, 1.0f
, -0.666667f
, 1.0f
}, // UNDER_LEFT
186 static const motorMixer_t mixerHex6H
[] = {
187 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
188 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
189 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
190 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
191 { 1.0f
, 0.0f
, 0.0f
, 0.0f
}, // RIGHT
192 { 1.0f
, 0.0f
, 0.0f
, 0.0f
}, // LEFT
195 static const motorMixer_t mixerHex6P
[] = {
196 { 1.0f
, -0.866025f
, 0.5f
, 1.0f
}, // REAR_R
197 { 1.0f
, -0.866025f
, -0.5f
, -1.0f
}, // FRONT_R
198 { 1.0f
, 0.866025f
, 0.5f
, 1.0f
}, // REAR_L
199 { 1.0f
, 0.866025f
, -0.5f
, -1.0f
}, // FRONT_L
200 { 1.0f
, 0.0f
, -1.0f
, 1.0f
}, // FRONT
201 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
204 static const motorMixer_t mixerHex6X
[] = {
205 { 1.0f
, -0.5f
, 0.866025f
, 1.0f
}, // REAR_R
206 { 1.0f
, -0.5f
, -0.866025f
, 1.0f
}, // FRONT_R
207 { 1.0f
, 0.5f
, 0.866025f
, -1.0f
}, // REAR_L
208 { 1.0f
, 0.5f
, -0.866025f
, -1.0f
}, // FRONT_L
209 { 1.0f
, -1.0f
, 0.0f
, -1.0f
}, // RIGHT
210 { 1.0f
, 1.0f
, 0.0f
, 1.0f
}, // LEFT
214 #if (MAX_SUPPORTED_MOTORS >= 8)
215 static const motorMixer_t mixerOctoX8
[] = {
216 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
217 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
218 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
219 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
220 { 1.0f
, -1.0f
, 1.0f
, 1.0f
}, // UNDER_REAR_R
221 { 1.0f
, -1.0f
, -1.0f
, -1.0f
}, // UNDER_FRONT_R
222 { 1.0f
, 1.0f
, 1.0f
, -1.0f
}, // UNDER_REAR_L
223 { 1.0f
, 1.0f
, -1.0f
, 1.0f
}, // UNDER_FRONT_L
226 static const motorMixer_t mixerOctoFlatP
[] = {
227 { 1.0f
, 0.707107f
, -0.707107f
, 1.0f
},// FRONT_L
228 { 1.0f
, -0.707107f
, -0.707107f
, 1.0f
},// FRONT_R
229 { 1.0f
, -0.707107f
, 0.707107f
, 1.0f
},// REAR_R
230 { 1.0f
, 0.707107f
, 0.707107f
, 1.0f
},// REAR_L
231 { 1.0f
, 0.0f
, -1.0f
, -1.0f
},// FRONT
232 { 1.0f
, -1.0f
, 0.0f
, -1.0f
},// RIGHT
233 { 1.0f
, 0.0f
, 1.0f
, -1.0f
},// REAR
234 { 1.0f
, 1.0f
, 0.0f
, -1.0f
},// LEFT
237 static const motorMixer_t mixerOctoFlatX
[] = {
238 { 1.0f
, 1.0f
, -0.414178f
, 1.0f
},// MIDFRONT_L
239 { 1.0f
, -0.414178f
, -1.0f
, 1.0f
},// FRONT_R
240 { 1.0f
, -1.0f
, 0.414178f
, 1.0f
},// MIDREAR_R
241 { 1.0f
, 0.414178f
, 1.0f
, 1.0f
},// REAR_L
242 { 1.0f
, 0.414178f
, -1.0f
, -1.0f
},// FRONT_L
243 { 1.0f
, -1.0f
, -0.414178f
, -1.0f
},// MIDFRONT_R
244 { 1.0f
, -0.414178f
, 1.0f
, -1.0f
},// REAR_R
245 { 1.0f
, 1.0f
, 0.414178f
, -1.0f
},// MIDREAR_L
249 static const motorMixer_t mixerSingleProp
[] = {
250 { 1.0f
, 0.0f
, 0.0f
, 0.0f
},
253 static const motorMixer_t mixerDualcopter
[] = {
254 { 1.0f
, 0.0f
, 0.0f
, -1.0f
}, // LEFT
255 { 1.0f
, 0.0f
, 0.0f
, 1.0f
}, // RIGHT
258 static const motorMixer_t mixerBicopter
[] = {
259 { 1.0f
, 1.0f
, 0.0f
, 0.0f
}, // LEFT
260 { 1.0f
, -1.0f
, 0.0f
, 0.0f
}, // RIGHT
263 static const motorMixer_t mixerTricopter
[] = {
264 { 1.0f
, 0.0f
, 1.333333f
, 0.0f
}, // REAR
265 { 1.0f
, -1.0f
, -0.666667f
, 0.0f
}, // RIGHT
266 { 1.0f
, 1.0f
, -0.666667f
, 0.0f
}, // LEFT
269 // Keep synced with mixerMode_e
270 const mixer_t mixers
[] = {
271 // motors, use servo, motor mixer
272 { 0, false, NULL
}, // entry 0
273 { 3, true, mixerTricopter
}, // MIXER_TRI
274 { 4, false, mixerQuadP
}, // MIXER_QUADP
275 { 4, false, mixerQuadX
}, // MIXER_QUADX
276 { 2, true, mixerBicopter
}, // MIXER_BICOPTER
277 { 0, true, NULL
}, // * MIXER_GIMBAL
278 #if (MAX_SUPPORTED_MOTORS >= 6)
279 { 6, false, mixerY6
}, // MIXER_Y6
280 { 6, false, mixerHex6P
}, // MIXER_HEX6
282 { 6, false, NULL
}, // MIXER_Y6
283 { 6, false, NULL
}, // MIXER_HEX6
285 { 1, true, mixerSingleProp
}, // * MIXER_FLYING_WING
286 { 4, false, mixerY4
}, // MIXER_Y4
287 #if (MAX_SUPPORTED_MOTORS >= 6)
288 { 6, false, mixerHex6X
}, // MIXER_HEX6X
290 { 6, false, NULL
}, // MIXER_HEX6X
292 #if (MAX_SUPPORTED_MOTORS >= 8)
293 { 8, false, mixerOctoX8
}, // MIXER_OCTOX8
294 { 8, false, mixerOctoFlatP
}, // MIXER_OCTOFLATP
295 { 8, false, mixerOctoFlatX
}, // MIXER_OCTOFLATX
297 { 8, false, NULL
}, // MIXER_OCTOX8
298 { 8, false, NULL
}, // MIXER_OCTOFLATP
299 { 8, false, NULL
}, // MIXER_OCTOFLATX
301 { 1, true, mixerSingleProp
}, // * MIXER_AIRPLANE
302 { 0, true, NULL
}, // * MIXER_HELI_120_CCPM
303 { 0, true, NULL
}, // * MIXER_HELI_90_DEG
304 { 4, false, mixerVtail4
}, // MIXER_VTAIL4
305 #if (MAX_SUPPORTED_MOTORS >= 6)
306 { 6, false, mixerHex6H
}, // MIXER_HEX6H
308 { 6, false, NULL
}, // MIXER_HEX6H
310 { 0, true, NULL
}, // * MIXER_PPM_TO_SERVO
311 { 2, true, mixerDualcopter
}, // MIXER_DUALCOPTER
312 { 1, true, NULL
}, // MIXER_SINGLECOPTER
313 { 4, false, mixerAtail4
}, // MIXER_ATAIL4
314 { 0, false, NULL
}, // MIXER_CUSTOM
315 { 2, true, NULL
}, // MIXER_CUSTOM_AIRPLANE
316 { 3, true, NULL
}, // MIXER_CUSTOM_TRI
320 motorMixer_t
*customMixers
;
322 void mixerInit(motorMixer_t
*initialCustomMixers
)
324 customMixers
= initialCustomMixers
;
327 #if !defined(USE_SERVOS) || defined(USE_QUAD_MIXER_ONLY)
328 void mixerUsePWMIOConfiguration(pwmIOConfiguration_t
*pwmIOConfiguration
)
330 UNUSED(pwmIOConfiguration
);
333 for (i
= 0; i
< motorCount
; i
++) {
334 currentMixer
[i
] = mixerQuadX
[i
];
336 mixerResetDisarmedMotors();
341 #ifndef USE_QUAD_MIXER_ONLY
342 void mixerLoadMix(int index
, motorMixer_t
*customMixers
)
349 for (i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++)
350 customMixers
[i
].throttle
= 0.0f
;
352 // do we have anything here to begin with?
353 if (mixers
[index
].motor
!= NULL
) {
354 for (i
= 0; i
< mixers
[index
].motorCount
; i
++)
355 customMixers
[i
] = mixers
[index
].motor
[i
];
361 void mixerResetDisarmedMotors(void)
364 // set disarmed motor values
365 for (i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++)
366 motor_disarmed
[i
] = feature(FEATURE_3D
) ? motor3DConfig()->neutral3d
: motorConfig()->mincommand
;
369 void writeMotors(void)
373 for (i
= 0; i
< motorCount
; i
++)
374 pwmWriteMotor(i
, motor
[i
]);
377 if (feature(FEATURE_ONESHOT125
)) {
378 pwmCompleteOneshotMotorUpdate(motorCount
);
382 void writeAllMotors(int16_t mc
)
386 // Sends commands to all motors
387 for (i
= 0; i
< motorCount
; i
++)
392 void stopMotors(void)
394 writeAllMotors(feature(FEATURE_3D
) ? motor3DConfig()->neutral3d
: motorConfig()->mincommand
);
396 delay(50); // give the timers and ESCs a chance to react.
399 void StopPwmAllMotors()
401 pwmShutdownPulsesForAllMotors(motorCount
);
404 uint16_t mixConstrainMotorForFailsafeCondition(uint8_t motorIndex
)
406 return constrain(motor
[motorIndex
], motorConfig()->mincommand
, motorConfig()->maxthrottle
);
413 bool isFailsafeActive
= failsafeIsActive();
415 if (motorCount
>= 4 && mixerConfig()->yaw_jump_prevention_limit
< YAW_JUMP_PREVENTION_LIMIT_HIGH
) {
416 // prevent "yaw jump" during yaw correction
417 axisPID
[FD_YAW
] = constrain(axisPID
[FD_YAW
], -mixerConfig()->yaw_jump_prevention_limit
- ABS(rcCommand
[YAW
]), mixerConfig()->yaw_jump_prevention_limit
+ ABS(rcCommand
[YAW
]));
420 if (rcModeIsActive(BOXAIRMODE
)) {
421 // Initial mixer concept by bdoiron74 reused and optimized for Air Mode
422 int16_t rollPitchYawMix
[MAX_SUPPORTED_MOTORS
];
423 int16_t rollPitchYawMixMax
= 0; // assumption: symetrical about zero.
424 int16_t rollPitchYawMixMin
= 0;
426 // Find roll/pitch/yaw desired output
427 for (i
= 0; i
< motorCount
; i
++) {
429 axisPID
[FD_PITCH
] * currentMixer
[i
].pitch
+
430 axisPID
[FD_ROLL
] * currentMixer
[i
].roll
+
431 -mixerConfig()->yaw_motor_direction
* axisPID
[FD_YAW
] * currentMixer
[i
].yaw
;
433 if (rollPitchYawMix
[i
] > rollPitchYawMixMax
) rollPitchYawMixMax
= rollPitchYawMix
[i
];
434 if (rollPitchYawMix
[i
] < rollPitchYawMixMin
) rollPitchYawMixMin
= rollPitchYawMix
[i
];
437 // Scale roll/pitch/yaw uniformly to fit within throttle range
438 int16_t rollPitchYawMixRange
= rollPitchYawMixMax
- rollPitchYawMixMin
;
439 int16_t throttleRange
, throttle
;
440 int16_t throttleMin
, throttleMax
;
441 static int16_t throttlePrevious
= 0; // Store the last throttle direction for deadband transitions in 3D.
443 // Find min and max throttle based on condition. Use rcData for 3D to prevent loss of power due to min_check
444 if (feature(FEATURE_3D
)) {
445 if (!ARMING_FLAG(ARMED
)) throttlePrevious
= rxConfig()->midrc
; // When disarmed set to mid_rc. It always results in positive direction after arming.
447 if ((rcData
[THROTTLE
] <= (rxConfig()->midrc
- rcControlsConfig()->deadband3d_throttle
))) { // Out of band handling
448 throttleMax
= motor3DConfig()->deadband3d_low
;
449 throttleMin
= motorConfig()->minthrottle
;
450 throttlePrevious
= throttle
= rcData
[THROTTLE
];
451 } else if (rcData
[THROTTLE
] >= (rxConfig()->midrc
+ rcControlsConfig()->deadband3d_throttle
)) { // Positive handling
452 throttleMax
= motorConfig()->maxthrottle
;
453 throttleMin
= motor3DConfig()->deadband3d_high
;
454 throttlePrevious
= throttle
= rcData
[THROTTLE
];
455 } else if ((throttlePrevious
<= (rxConfig()->midrc
- rcControlsConfig()->deadband3d_throttle
))) { // Deadband handling from negative to positive
456 throttle
= throttleMax
= motor3DConfig()->deadband3d_low
;
457 throttleMin
= motorConfig()->minthrottle
;
458 } else { // Deadband handling from positive to negative
459 throttleMax
= motorConfig()->maxthrottle
;
460 throttle
= throttleMin
= motor3DConfig()->deadband3d_high
;
463 throttle
= rcCommand
[THROTTLE
];
464 throttleMin
= motorConfig()->minthrottle
;
465 throttleMax
= motorConfig()->maxthrottle
;
468 throttleRange
= throttleMax
- throttleMin
;
470 if (rollPitchYawMixRange
> throttleRange
) {
471 motorLimitReached
= true;
472 float mixReduction
= (float) throttleRange
/ rollPitchYawMixRange
;
473 for (i
= 0; i
< motorCount
; i
++) {
474 rollPitchYawMix
[i
] = lrintf((float) rollPitchYawMix
[i
] * mixReduction
);
476 // Get the maximum correction by setting throttle offset to center.
477 throttleMin
= throttleMax
= throttleMin
+ (throttleRange
/ 2);
479 motorLimitReached
= false;
480 throttleMin
= throttleMin
+ (rollPitchYawMixRange
/ 2);
481 throttleMax
= throttleMax
- (rollPitchYawMixRange
/ 2);
484 // Now add in the desired throttle, but keep in a range that doesn't clip adjusted
485 // roll/pitch/yaw. This could move throttle down, but also up for those low throttle flips.
486 for (i
= 0; i
< motorCount
; i
++) {
487 motor
[i
] = rollPitchYawMix
[i
] + constrain(throttle
* currentMixer
[i
].throttle
, throttleMin
, throttleMax
);
489 if (isFailsafeActive
) {
490 motor
[i
] = mixConstrainMotorForFailsafeCondition(i
);
491 } else if (feature(FEATURE_3D
)) {
492 if (throttlePrevious
<= (rxConfig()->midrc
- rcControlsConfig()->deadband3d_throttle
)) {
493 motor
[i
] = constrain(motor
[i
], motorConfig()->minthrottle
, motor3DConfig()->deadband3d_low
);
495 motor
[i
] = constrain(motor
[i
], motor3DConfig()->deadband3d_high
, motorConfig()->maxthrottle
);
498 motor
[i
] = constrain(motor
[i
], motorConfig()->minthrottle
, motorConfig()->maxthrottle
);
502 // motors for non-servo mixes
503 for (i
= 0; i
< motorCount
; i
++) {
505 rcCommand
[THROTTLE
] * currentMixer
[i
].throttle
+
506 axisPID
[FD_PITCH
] * currentMixer
[i
].pitch
+
507 axisPID
[FD_ROLL
] * currentMixer
[i
].roll
+
508 -mixerConfig()->yaw_motor_direction
* axisPID
[FD_YAW
] * currentMixer
[i
].yaw
;
511 // Find the maximum motor output.
512 int16_t maxMotor
= motor
[0];
513 for (i
= 1; i
< motorCount
; i
++) {
514 // If one motor is above the maxthrottle threshold, we reduce the value
515 // of all motors by the amount of overshoot. That way, only one motor
516 // is at max and the relative power of each motor is preserved.
517 if (motor
[i
] > maxMotor
) {
522 int16_t maxThrottleDifference
= 0;
523 if (maxMotor
> motorConfig()->maxthrottle
) {
524 maxThrottleDifference
= maxMotor
- motorConfig()->maxthrottle
;
527 for (i
= 0; i
< motorCount
; i
++) {
528 // this is a way to still have good gyro corrections if at least one motor reaches its max.
529 motor
[i
] -= maxThrottleDifference
;
531 if (feature(FEATURE_3D
)) {
532 if (mixerConfig()->pid_at_min_throttle
533 || rcData
[THROTTLE
] <= rxConfig()->midrc
- rcControlsConfig()->deadband3d_throttle
534 || rcData
[THROTTLE
] >= rxConfig()->midrc
+ rcControlsConfig()->deadband3d_throttle
) {
535 if (rcData
[THROTTLE
] > rxConfig()->midrc
) {
536 motor
[i
] = constrain(motor
[i
], motor3DConfig()->deadband3d_high
, motorConfig()->maxthrottle
);
538 motor
[i
] = constrain(motor
[i
], motorConfig()->mincommand
, motor3DConfig()->deadband3d_low
);
541 if (rcData
[THROTTLE
] > rxConfig()->midrc
) {
542 motor
[i
] = motor3DConfig()->deadband3d_high
;
544 motor
[i
] = motor3DConfig()->deadband3d_low
;
548 if (isFailsafeActive
) {
549 motor
[i
] = mixConstrainMotorForFailsafeCondition(i
);
551 // If we're at minimum throttle and FEATURE_MOTOR_STOP enabled,
552 // do not spin the motors.
553 motor
[i
] = constrain(motor
[i
], motorConfig()->minthrottle
, motorConfig()->maxthrottle
);
554 if ((rcData
[THROTTLE
]) < rxConfig()->mincheck
) {
555 if (feature(FEATURE_MOTOR_STOP
)) {
556 motor
[i
] = motorConfig()->mincommand
;
557 } else if (mixerConfig()->pid_at_min_throttle
== 0) {
558 motor
[i
] = motorConfig()->minthrottle
;
567 /* Disarmed for all mixers */
568 if (!ARMING_FLAG(ARMED
)) {
569 for (i
= 0; i
< motorCount
; i
++) {
570 motor
[i
] = motor_disarmed
[i
];
574 // motor outputs are used as sources for servo mixing, so motors must be calculated before servos.
576 #if !defined(USE_QUAD_MIXER_ONLY) && defined(USE_SERVOS)