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/>.
24 #include "build/debug.h"
26 #include "common/axis.h"
27 #include "common/filter.h"
28 #include "common/maths.h"
29 #include "common/utils.h"
31 #include "config/config_reset.h"
32 #include "config/feature.h"
33 #include "config/parameter_group.h"
34 #include "config/parameter_group_ids.h"
35 #include "config/config_reset.h"
37 #include "drivers/pwm_output.h"
38 #include "drivers/pwm_mapping.h"
39 #include "drivers/time.h"
41 #include "fc/config.h"
42 #include "fc/rc_controls.h"
43 #include "fc/rc_modes.h"
44 #include "fc/runtime_config.h"
45 #include "fc/controlrate_profile.h"
46 #include "fc/settings.h"
48 #include "flight/failsafe.h"
49 #include "flight/imu.h"
50 #include "flight/mixer.h"
51 #include "flight/pid.h"
52 #include "flight/servos.h"
54 #include "navigation/navigation.h"
58 #include "sensors/battery.h"
60 FASTRAM
int16_t motor
[MAX_SUPPORTED_MOTORS
];
61 FASTRAM
int16_t motor_disarmed
[MAX_SUPPORTED_MOTORS
];
62 static float motorMixRange
;
63 static float mixerScale
= 1.0f
;
64 static EXTENDED_FASTRAM motorMixer_t currentMixer
[MAX_SUPPORTED_MOTORS
];
65 static EXTENDED_FASTRAM
uint8_t motorCount
= 0;
66 EXTENDED_FASTRAM
int mixerThrottleCommand
;
67 static EXTENDED_FASTRAM
int throttleIdleValue
= 0;
68 static EXTENDED_FASTRAM
int motorValueWhenStopped
= 0;
69 static reversibleMotorsThrottleState_e reversibleMotorsThrottleState
= MOTOR_DIRECTION_FORWARD
;
70 static EXTENDED_FASTRAM
int throttleDeadbandLow
= 0;
71 static EXTENDED_FASTRAM
int throttleDeadbandHigh
= 0;
72 static EXTENDED_FASTRAM
int throttleRangeMin
= 0;
73 static EXTENDED_FASTRAM
int throttleRangeMax
= 0;
74 static EXTENDED_FASTRAM
int8_t motorYawMultiplier
= 1;
76 int motorZeroCommand
= 0;
78 PG_REGISTER_WITH_RESET_TEMPLATE(reversibleMotorsConfig_t
, reversibleMotorsConfig
, PG_REVERSIBLE_MOTORS_CONFIG
, 0);
80 PG_RESET_TEMPLATE(reversibleMotorsConfig_t
, reversibleMotorsConfig
,
81 .deadband_low
= SETTING_3D_DEADBAND_LOW_DEFAULT
,
82 .deadband_high
= SETTING_3D_DEADBAND_HIGH_DEFAULT
,
83 .neutral
= SETTING_3D_NEUTRAL_DEFAULT
86 PG_REGISTER_WITH_RESET_TEMPLATE(motorConfig_t
, motorConfig
, PG_MOTOR_CONFIG
, 10);
88 PG_RESET_TEMPLATE(motorConfig_t
, motorConfig
,
89 .motorPwmProtocol
= SETTING_MOTOR_PWM_PROTOCOL_DEFAULT
,
90 .motorPwmRate
= SETTING_MOTOR_PWM_RATE_DEFAULT
,
91 .maxthrottle
= SETTING_MAX_THROTTLE_DEFAULT
,
92 .mincommand
= SETTING_MIN_COMMAND_DEFAULT
,
93 .motorPoleCount
= SETTING_MOTOR_POLES_DEFAULT
, // Most brushless motors that we use are 14 poles
95 PG_REGISTER_ARRAY_WITH_RESET_FN(timerOverride_t
, HARDWARE_TIMER_DEFINITION_COUNT
, timerOverrides
, PG_TIMER_OVERRIDE_CONFIG
, 0);
97 #define CRASH_OVER_AFTER_CRASH_FLIP_STICK_MIN 0.15f
99 void pgResetFn_timerOverrides(timerOverride_t
*instance
)
101 for (int i
= 0; i
< HARDWARE_TIMER_DEFINITION_COUNT
; ++i
) {
102 RESET_CONFIG(timerOverride_t
, &instance
[i
], .outputMode
= OUTPUT_MODE_AUTO
);
106 int getThrottleIdleValue(void)
108 if (!throttleIdleValue
) {
109 throttleIdleValue
= motorConfig()->mincommand
+ (((motorConfig()->maxthrottle
- motorConfig()->mincommand
) / 100.0f
) * currentBatteryProfile
->motor
.throttleIdle
);
112 return throttleIdleValue
;
115 static void computeMotorCount(void)
117 static bool firstRun
= true;
122 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
123 bool isMotorUsed
= false;
124 for(int j
= 0; j
< MAX_MIXER_PROFILE_COUNT
; j
++){
125 if (mixerMotorMixersByIndex(j
)[i
].throttle
!= 0.0f
) {
138 bool ifMotorstopFeatureEnabled(void){
139 return currentMixerConfig
.motorstopOnLow
;
142 uint8_t getMotorCount(void) {
146 float getMotorMixRange(void)
148 return motorMixRange
;
151 bool mixerIsOutputSaturated(void)
153 return motorMixRange
>= 1.0f
;
156 void mixerUpdateStateFlags(void)
158 DISABLE_STATE(FIXED_WING_LEGACY
);
159 DISABLE_STATE(MULTIROTOR
);
160 DISABLE_STATE(ROVER
);
162 DISABLE_STATE(AIRPLANE
);
163 DISABLE_STATE(MOVE_FORWARD_ONLY
);
165 if (currentMixerConfig
.platformType
== PLATFORM_AIRPLANE
) {
166 ENABLE_STATE(FIXED_WING_LEGACY
);
167 ENABLE_STATE(AIRPLANE
);
168 ENABLE_STATE(ALTITUDE_CONTROL
);
169 ENABLE_STATE(MOVE_FORWARD_ONLY
);
170 } if (currentMixerConfig
.platformType
== PLATFORM_ROVER
) {
172 ENABLE_STATE(FIXED_WING_LEGACY
);
173 ENABLE_STATE(MOVE_FORWARD_ONLY
);
174 } if (currentMixerConfig
.platformType
== PLATFORM_BOAT
) {
176 ENABLE_STATE(FIXED_WING_LEGACY
);
177 ENABLE_STATE(MOVE_FORWARD_ONLY
);
178 } else if (currentMixerConfig
.platformType
== PLATFORM_MULTIROTOR
) {
179 ENABLE_STATE(MULTIROTOR
);
180 ENABLE_STATE(ALTITUDE_CONTROL
);
181 } else if (currentMixerConfig
.platformType
== PLATFORM_TRICOPTER
) {
182 ENABLE_STATE(MULTIROTOR
);
183 ENABLE_STATE(ALTITUDE_CONTROL
);
184 } else if (currentMixerConfig
.platformType
== PLATFORM_HELICOPTER
) {
185 ENABLE_STATE(MULTIROTOR
);
186 ENABLE_STATE(ALTITUDE_CONTROL
);
189 if (currentMixerConfig
.hasFlaps
) {
190 ENABLE_STATE(FLAPERON_AVAILABLE
);
192 DISABLE_STATE(FLAPERON_AVAILABLE
);
195 currentMixerConfig
.platformType
== PLATFORM_BOAT
||
196 currentMixerConfig
.platformType
== PLATFORM_ROVER
||
197 navConfig()->fw
.useFwNavYawControl
199 ENABLE_STATE(FW_HEADING_USE_YAW
);
201 DISABLE_STATE(FW_HEADING_USE_YAW
);
205 void nullMotorRateLimiting(const float dT
)
213 loadPrimaryMotorMixer();
214 // in 3D mode, mixer gain has to be halved
215 if (feature(FEATURE_REVERSIBLE_MOTORS
)) {
219 throttleDeadbandLow
= PWM_RANGE_MIDDLE
- rcControlsConfig()->mid_throttle_deadband
;
220 throttleDeadbandHigh
= PWM_RANGE_MIDDLE
+ rcControlsConfig()->mid_throttle_deadband
;
222 mixerResetDisarmedMotors();
224 if (currentMixerConfig
.motorDirectionInverted
) {
225 motorYawMultiplier
= -1;
227 motorYawMultiplier
= 1;
231 void mixerResetDisarmedMotors(void)
233 getThrottleIdleValue();
235 if (feature(FEATURE_REVERSIBLE_MOTORS
)) {
236 motorZeroCommand
= reversibleMotorsConfig()->neutral
;
237 throttleRangeMin
= throttleDeadbandHigh
;
238 throttleRangeMax
= motorConfig()->maxthrottle
;
240 motorZeroCommand
= motorConfig()->mincommand
;
241 throttleRangeMin
= throttleIdleValue
;
242 throttleRangeMax
= motorConfig()->maxthrottle
;
245 reversibleMotorsThrottleState
= MOTOR_DIRECTION_FORWARD
;
247 if (ifMotorstopFeatureEnabled()) {
248 motorValueWhenStopped
= motorZeroCommand
;
250 motorValueWhenStopped
= throttleIdleValue
;
253 // set disarmed motor values
254 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
255 motor_disarmed
[i
] = motorZeroCommand
;
260 static uint16_t handleOutputScaling(
261 int16_t input
, // Input value from the mixer
262 int16_t stopThreshold
, // Threshold value to check if motor should be rotating or not
263 int16_t onStopValue
, // Value sent to the ESC when min rotation is required - on motor_stop it is STOP command, without motor_stop it's a value that keeps rotation
264 int16_t inputScaleMin
, // Input range - min value
265 int16_t inputScaleMax
, // Input range - max value
266 int16_t outputScaleMin
, // Output range - min value
267 int16_t outputScaleMax
, // Output range - max value
268 bool moveForward
// If motor should be rotating FORWARD or BACKWARD
272 if (moveForward
&& input
< stopThreshold
) {
273 //Send motor stop command
276 else if (!moveForward
&& input
> stopThreshold
) {
277 //Send motor stop command
281 //Scale input to protocol output values
282 value
= scaleRangef(input
, inputScaleMin
, inputScaleMax
, outputScaleMin
, outputScaleMax
);
283 value
= constrain(value
, outputScaleMin
, outputScaleMax
);
287 static void applyTurtleModeToMotors(void) {
289 if (ARMING_FLAG(ARMED
)) {
290 const float flipPowerFactor
= ((float)currentBatteryProfile
->motor
.turtleModePowerFactor
)/100.0f
;
291 const float stickDeflectionPitchAbs
= ABS(((float) rcCommand
[PITCH
]) / 500.0f
);
292 const float stickDeflectionRollAbs
= ABS(((float) rcCommand
[ROLL
]) / 500.0f
);
293 const float stickDeflectionYawAbs
= ABS(((float) rcCommand
[YAW
]) / 500.0f
);
294 //deflection stick position
296 const float stickDeflectionPitchExpo
=
297 flipPowerFactor
* stickDeflectionPitchAbs
+ power3(stickDeflectionPitchAbs
) * (1 - flipPowerFactor
);
298 const float stickDeflectionRollExpo
=
299 flipPowerFactor
* stickDeflectionRollAbs
+ power3(stickDeflectionRollAbs
) * (1 - flipPowerFactor
);
300 const float stickDeflectionYawExpo
=
301 flipPowerFactor
* stickDeflectionYawAbs
+ power3(stickDeflectionYawAbs
) * (1 - flipPowerFactor
);
303 float signPitch
= rcCommand
[PITCH
] < 0 ? 1 : -1;
304 float signRoll
= rcCommand
[ROLL
] < 0 ? 1 : -1;
305 float signYaw
= (float)((rcCommand
[YAW
] < 0 ? 1 : -1) * (currentMixerConfig
.motorDirectionInverted
? 1 : -1));
307 float stickDeflectionLength
= calc_length_pythagorean_2D(stickDeflectionPitchAbs
, stickDeflectionRollAbs
);
308 float stickDeflectionExpoLength
= calc_length_pythagorean_2D(stickDeflectionPitchExpo
, stickDeflectionRollExpo
);
310 if (stickDeflectionYawAbs
> MAX(stickDeflectionPitchAbs
, stickDeflectionRollAbs
)) {
311 // If yaw is the dominant, disable pitch and roll
312 stickDeflectionLength
= stickDeflectionYawAbs
;
313 stickDeflectionExpoLength
= stickDeflectionYawExpo
;
317 // If pitch/roll dominant, disable yaw
321 const float cosPhi
= (stickDeflectionLength
> 0) ? (stickDeflectionPitchAbs
+ stickDeflectionRollAbs
) /
322 (fast_fsqrtf(2.0f
) * stickDeflectionLength
) : 0;
323 const float cosThreshold
= fast_fsqrtf(3.0f
) / 2.0f
; // cos(PI/6.0f)
325 if (cosPhi
< cosThreshold
) {
326 // Enforce either roll or pitch exclusively, if not on diagonal
327 if (stickDeflectionRollAbs
> stickDeflectionPitchAbs
) {
334 // Apply a reasonable amount of stick deadband
335 const float crashFlipStickMinExpo
=
336 flipPowerFactor
* CRASH_OVER_AFTER_CRASH_FLIP_STICK_MIN
+ power3(CRASH_OVER_AFTER_CRASH_FLIP_STICK_MIN
) * (1 - flipPowerFactor
);
337 const float flipStickRange
= 1.0f
- crashFlipStickMinExpo
;
338 const float flipPower
= MAX(0.0f
, stickDeflectionExpoLength
- crashFlipStickMinExpo
) / flipStickRange
;
340 for (int i
= 0; i
< motorCount
; ++i
) {
342 float motorOutputNormalised
=
343 signPitch
* currentMixer
[i
].pitch
+
344 signRoll
* currentMixer
[i
].roll
+
345 signYaw
* currentMixer
[i
].yaw
;
347 if (motorOutputNormalised
< 0) {
348 motorOutputNormalised
= 0;
351 motorOutputNormalised
= MIN(1.0f
, flipPower
* motorOutputNormalised
);
353 motor
[i
] = (int16_t)scaleRangef(motorOutputNormalised
, 0, 1, motorConfig()->mincommand
, motorConfig()->maxthrottle
);
362 void FAST_CODE
writeMotors(void)
364 #if !defined(SITL_BUILD)
365 for (int i
= 0; i
< motorCount
; i
++) {
369 // If we use DSHOT we need to convert motorValue to DSHOT ranges
370 if (isMotorProtocolDigital()) {
372 if (feature(FEATURE_REVERSIBLE_MOTORS
)) {
373 if (reversibleMotorsThrottleState
== MOTOR_DIRECTION_FORWARD
) {
374 motorValue
= handleOutputScaling(
377 DSHOT_DISARM_COMMAND
,
380 DSHOT_3D_DEADBAND_HIGH
,
385 motorValue
= handleOutputScaling(
388 DSHOT_DISARM_COMMAND
,
392 DSHOT_3D_DEADBAND_LOW
,
398 motorValue
= handleOutputScaling(
401 DSHOT_DISARM_COMMAND
,
402 motorConfig()->mincommand
,
403 motorConfig()->maxthrottle
,
411 if (feature(FEATURE_REVERSIBLE_MOTORS
)) {
412 if (reversibleMotorsThrottleState
== MOTOR_DIRECTION_FORWARD
) {
413 motorValue
= handleOutputScaling(
419 reversibleMotorsConfig()->deadband_high
,
420 motorConfig()->maxthrottle
,
424 motorValue
= handleOutputScaling(
430 motorConfig()->mincommand
,
431 reversibleMotorsConfig()->deadband_low
,
436 motorValue
= motor
[i
];
441 // We don't define USE_DSHOT
442 motorValue
= motor
[i
];
445 pwmWriteMotor(i
, motorValue
);
450 void writeAllMotors(int16_t mc
)
452 // Sends commands to all motors
453 for (int i
= 0; i
< motorCount
; i
++) {
459 void stopMotors(void)
461 writeAllMotors(feature(FEATURE_REVERSIBLE_MOTORS
) ? reversibleMotorsConfig()->neutral
: motorConfig()->mincommand
);
463 delay(50); // give the timers and ESCs a chance to react.
466 void stopPwmAllMotors(void)
468 #if !defined(SITL_BUILD)
469 pwmShutdownPulsesForAllMotors(motorCount
);
474 static int getReversibleMotorsThrottleDeadband(void)
478 if (reversibleMotorsThrottleState
== MOTOR_DIRECTION_BACKWARD
) {
479 directionValue
= reversibleMotorsConfig()->deadband_low
;
481 directionValue
= reversibleMotorsConfig()->deadband_high
;
484 return ifMotorstopFeatureEnabled() ? reversibleMotorsConfig()->neutral
: directionValue
;
487 void FAST_CODE
mixTable(void)
490 if (FLIGHT_MODE(TURTLE_MODE
)) {
491 applyTurtleModeToMotors();
496 bool isDisarmed
= !ARMING_FLAG(ARMED
) || systemConfig()->groundTestMode
;
498 bool isDisarmed
= !ARMING_FLAG(ARMED
);
500 bool motorStopIsActive
= getMotorStatus() != MOTOR_RUNNING
&& !isDisarmed
;
501 if (isDisarmed
|| motorStopIsActive
) {
502 for (int i
= 0; i
< motorCount
; i
++) {
503 motor
[i
] = isDisarmed
? motor_disarmed
[i
] : motorValueWhenStopped
;
505 mixerThrottleCommand
= motor
[0];
509 int16_t input
[3]; // RPY, range [-500:+500]
510 // Allow direct stick input to motors in passthrough mode on airplanes
511 if (STATE(FIXED_WING_LEGACY
) && FLIGHT_MODE(MANUAL_MODE
)) {
512 // Direct passthru from RX
513 input
[ROLL
] = rcCommand
[ROLL
];
514 input
[PITCH
] = rcCommand
[PITCH
];
515 input
[YAW
] = rcCommand
[YAW
];
518 input
[ROLL
] = axisPID
[ROLL
];
519 input
[PITCH
] = axisPID
[PITCH
];
520 input
[YAW
] = axisPID
[YAW
];
523 // Initial mixer concept by bdoiron74 reused and optimized for Air Mode
524 int16_t rpyMix
[MAX_SUPPORTED_MOTORS
];
525 int16_t rpyMixMax
= 0; // assumption: symetrical about zero.
526 int16_t rpyMixMin
= 0;
528 // motors for non-servo mixes
529 for (int i
= 0; i
< motorCount
; i
++) {
531 (input
[PITCH
] * currentMixer
[i
].pitch
+
532 input
[ROLL
] * currentMixer
[i
].roll
+
533 -motorYawMultiplier
* input
[YAW
] * currentMixer
[i
].yaw
) * mixerScale
;
535 if (rpyMix
[i
] > rpyMixMax
) rpyMixMax
= rpyMix
[i
];
536 if (rpyMix
[i
] < rpyMixMin
) rpyMixMin
= rpyMix
[i
];
539 int16_t rpyMixRange
= rpyMixMax
- rpyMixMin
;
540 int16_t throttleRange
;
541 int16_t throttleMin
, throttleMax
;
543 // Find min and max throttle based on condition.
544 #ifdef USE_PROGRAMMING_FRAMEWORK
545 if (LOGIC_CONDITION_GLOBAL_FLAG(LOGIC_CONDITION_GLOBAL_FLAG_OVERRIDE_THROTTLE
)) {
546 throttleRangeMin
= throttleIdleValue
;
547 throttleRangeMax
= motorConfig()->maxthrottle
;
548 mixerThrottleCommand
= constrain(logicConditionValuesByType
[LOGIC_CONDITION_OVERRIDE_THROTTLE
], throttleRangeMin
, throttleRangeMax
);
551 if (feature(FEATURE_REVERSIBLE_MOTORS
)) {
553 if (rcCommand
[THROTTLE
] >= (throttleDeadbandHigh
) || STATE(SET_REVERSIBLE_MOTORS_FORWARD
)) {
555 * Throttle is above deadband, FORWARD direction
557 reversibleMotorsThrottleState
= MOTOR_DIRECTION_FORWARD
;
558 throttleRangeMax
= motorConfig()->maxthrottle
;
559 throttleRangeMin
= throttleDeadbandHigh
;
560 DISABLE_STATE(SET_REVERSIBLE_MOTORS_FORWARD
);
561 } else if (rcCommand
[THROTTLE
] <= throttleDeadbandLow
) {
563 * Throttle is below deadband, BACKWARD direction
565 reversibleMotorsThrottleState
= MOTOR_DIRECTION_BACKWARD
;
566 throttleRangeMax
= throttleDeadbandLow
;
567 throttleRangeMin
= motorConfig()->mincommand
;
571 motorValueWhenStopped
= getReversibleMotorsThrottleDeadband();
572 mixerThrottleCommand
= constrain(rcCommand
[THROTTLE
], throttleRangeMin
, throttleRangeMax
);
575 if(isMotorProtocolDigital() && feature(FEATURE_REVERSIBLE_MOTORS
) && reversibleMotorsThrottleState
== MOTOR_DIRECTION_BACKWARD
) {
577 * We need to start the throttle output from stick input to start in the middle of the stick at the low and.
578 * Without this, it's starting at the high side.
580 int throttleDistanceToMax
= throttleRangeMax
- rcCommand
[THROTTLE
];
581 mixerThrottleCommand
= throttleRangeMin
+ throttleDistanceToMax
;
585 mixerThrottleCommand
= rcCommand
[THROTTLE
];
586 throttleRangeMin
= throttleIdleValue
;
587 throttleRangeMax
= motorConfig()->maxthrottle
;
589 // Throttle scaling to limit max throttle when battery is full
590 #ifdef USE_PROGRAMMING_FRAMEWORK
591 mixerThrottleCommand
= ((mixerThrottleCommand
- throttleRangeMin
) * getThrottleScale(currentBatteryProfile
->motor
.throttleScale
)) + throttleRangeMin
;
593 mixerThrottleCommand
= ((mixerThrottleCommand
- throttleRangeMin
) * currentBatteryProfile
->motor
.throttleScale
) + throttleRangeMin
;
595 // Throttle compensation based on battery voltage
596 if (feature(FEATURE_THR_VBAT_COMP
) && isAmperageConfigured() && feature(FEATURE_VBAT
)) {
597 mixerThrottleCommand
= MIN(throttleRangeMin
+ (mixerThrottleCommand
- throttleRangeMin
) * calculateThrottleCompensationFactor(), throttleRangeMax
);
601 throttleMin
= throttleRangeMin
;
602 throttleMax
= throttleRangeMax
;
603 throttleRange
= throttleMax
- throttleMin
;
605 #define THROTTLE_CLIPPING_FACTOR 0.33f
606 motorMixRange
= (float)rpyMixRange
/ (float)throttleRange
;
607 if (motorMixRange
> 1.0f
) {
608 for (int i
= 0; i
< motorCount
; i
++) {
609 rpyMix
[i
] /= motorMixRange
;
612 // Allow some clipping on edges to soften correction response
613 throttleMin
= throttleMin
+ (throttleRange
/ 2) - (throttleRange
* THROTTLE_CLIPPING_FACTOR
/ 2);
614 throttleMax
= throttleMin
+ (throttleRange
/ 2) + (throttleRange
* THROTTLE_CLIPPING_FACTOR
/ 2);
616 throttleMin
= MIN(throttleMin
+ (rpyMixRange
/ 2), throttleMin
+ (throttleRange
/ 2) - (throttleRange
* THROTTLE_CLIPPING_FACTOR
/ 2));
617 throttleMax
= MAX(throttleMax
- (rpyMixRange
/ 2), throttleMin
+ (throttleRange
/ 2) + (throttleRange
* THROTTLE_CLIPPING_FACTOR
/ 2));
620 // Now add in the desired throttle, but keep in a range that doesn't clip adjusted
621 // roll/pitch/yaw. This could move throttle down, but also up for those low throttle flips.
622 for (int i
= 0; i
< motorCount
; i
++) {
623 motor
[i
] = rpyMix
[i
] + constrain(mixerThrottleCommand
* currentMixer
[i
].throttle
, throttleMin
, throttleMax
);
625 if (failsafeIsActive()) {
626 motor
[i
] = constrain(motor
[i
], motorConfig()->mincommand
, motorConfig()->maxthrottle
);
628 motor
[i
] = constrain(motor
[i
], throttleRangeMin
, throttleRangeMax
);
632 if (currentMixer
[i
].throttle
<= 0.0f
) {
633 motor
[i
] = motorZeroCommand
;
635 //spin stopped motors only in mixer transition mode
636 if (isMixerTransitionMixing
&& currentMixer
[i
].throttle
<= -1.05f
&& currentMixer
[i
].throttle
>= -2.0f
&& (!feature(FEATURE_REVERSIBLE_MOTORS
))) {
637 motor
[i
] = -currentMixer
[i
].throttle
* 1000;
638 motor
[i
] = constrain(motor
[i
], throttleRangeMin
, throttleRangeMax
);
643 int16_t getThrottlePercent(bool useScaled
)
645 int16_t thr
= constrain(mixerThrottleCommand
, PWM_RANGE_MIN
, PWM_RANGE_MAX
);
648 thr
= (thr
- throttleIdleValue
) * 100 / (motorConfig()->maxthrottle
- throttleIdleValue
);
650 thr
= (rxGetChannelValue(THROTTLE
) - PWM_RANGE_MIN
) * 100 / (PWM_RANGE_MAX
- PWM_RANGE_MIN
);
655 uint16_t setDesiredThrottle(uint16_t throttle
, bool allowMotorStop
)
657 const uint16_t throttleIdleValue
= getThrottleIdleValue();
659 if (allowMotorStop
&& throttle
< throttleIdleValue
) {
660 ENABLE_STATE(NAV_MOTOR_STOP_OR_IDLE
);
663 return constrain(throttle
, throttleIdleValue
, motorConfig()->maxthrottle
);
666 motorStatus_e
getMotorStatus(void)
668 if (STATE(NAV_MOTOR_STOP_OR_IDLE
)) {
669 return MOTOR_STOPPED_AUTO
;
672 const bool fixedWingOrAirmodeNotActive
= STATE(FIXED_WING_LEGACY
) || !STATE(AIRMODE_ACTIVE
);
674 if (throttleStickIsLow() && fixedWingOrAirmodeNotActive
) {
675 if ((navConfig()->general
.flags
.nav_overrides_motor_stop
== NOMS_OFF_ALWAYS
) && failsafeIsActive()) {
676 // If we are in failsafe and user was holding stick low before it was triggered and nav_overrides_motor_stop is set to OFF_ALWAYS
677 // and either on a plane or on a quad with inactive airmode - stop motor
678 return MOTOR_STOPPED_USER
;
680 } else if (!failsafeIsActive()) {
681 // If user is holding stick low, we are not in failsafe and either on a plane or on a quad with inactive
682 // airmode - we need to check if we are allowing navigation to override MOTOR_STOP
684 switch (navConfig()->general
.flags
.nav_overrides_motor_stop
) {
686 return navigationInAutomaticThrottleMode() ? MOTOR_RUNNING
: MOTOR_STOPPED_USER
;
689 return navigationIsFlyingAutonomousMode() ? MOTOR_RUNNING
: MOTOR_STOPPED_USER
;
693 return MOTOR_STOPPED_USER
;
698 return MOTOR_RUNNING
;
701 void loadPrimaryMotorMixer(void) {
702 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
703 currentMixer
[i
] = *primaryMotorMixer(i
);
707 bool areMotorsRunning(void)
709 if (ARMING_FLAG(ARMED
)) {
712 for (int i
= 0; i
< motorCount
; i
++) {
713 if (motor_disarmed
[i
] != motorZeroCommand
) {