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/build_config.h"
27 #include "common/axis.h"
28 #include "common/maths.h"
29 #include "common/filter.h"
31 #include "drivers/system.h"
32 #include "drivers/pwm_output.h"
34 #include "io/motors.h"
38 #include "sensors/battery.h"
40 #include "flight/mixer.h"
41 #include "flight/failsafe.h"
42 #include "flight/pid.h"
43 #include "flight/imu.h"
45 #include "fc/config.h"
46 #include "fc/rc_controls.h"
47 #include "fc/runtime_config.h"
49 #include "config/feature.h"
50 #include "config/config_master.h"
52 #define EXTERNAL_DSHOT_CONVERSION_FACTOR 2
53 // (minimum output value(1001) - (minimum input value(48) / conversion factor(2))
54 #define EXTERNAL_DSHOT_CONVERSION_OFFSET 977
55 #define EXTERNAL_CONVERSION_MIN_VALUE 1000
56 #define EXTERNAL_CONVERSION_MAX_VALUE 2000
58 static uint8_t motorCount
;
59 static float motorMixRange
;
61 int16_t motor
[MAX_SUPPORTED_MOTORS
];
62 int16_t motor_disarmed
[MAX_SUPPORTED_MOTORS
];
64 static mixerConfig_t
*mixerConfig
;
65 static flight3DConfig_t
*flight3DConfig
;
66 static motorConfig_t
*motorConfig
;
67 static airplaneConfig_t
*airplaneConfig
;
70 mixerMode_e currentMixerMode
;
71 static motorMixer_t currentMixer
[MAX_SUPPORTED_MOTORS
];
74 static const motorMixer_t mixerQuadX
[] = {
75 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
76 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
77 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
78 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
80 #ifndef USE_QUAD_MIXER_ONLY
81 static const motorMixer_t mixerTricopter
[] = {
82 { 1.0f
, 0.0f
, 1.333333f
, 0.0f
}, // REAR
83 { 1.0f
, -1.0f
, -0.666667f
, 0.0f
}, // RIGHT
84 { 1.0f
, 1.0f
, -0.666667f
, 0.0f
}, // LEFT
87 static const motorMixer_t mixerQuadP
[] = {
88 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
89 { 1.0f
, -1.0f
, 0.0f
, 1.0f
}, // RIGHT
90 { 1.0f
, 1.0f
, 0.0f
, 1.0f
}, // LEFT
91 { 1.0f
, 0.0f
, -1.0f
, -1.0f
}, // FRONT
94 static const motorMixer_t mixerBicopter
[] = {
95 { 1.0f
, 1.0f
, 0.0f
, 0.0f
}, // LEFT
96 { 1.0f
, -1.0f
, 0.0f
, 0.0f
}, // RIGHT
99 static const motorMixer_t mixerY6
[] = {
100 { 1.0f
, 0.0f
, 1.333333f
, 1.0f
}, // REAR
101 { 1.0f
, -1.0f
, -0.666667f
, -1.0f
}, // RIGHT
102 { 1.0f
, 1.0f
, -0.666667f
, -1.0f
}, // LEFT
103 { 1.0f
, 0.0f
, 1.333333f
, -1.0f
}, // UNDER_REAR
104 { 1.0f
, -1.0f
, -0.666667f
, 1.0f
}, // UNDER_RIGHT
105 { 1.0f
, 1.0f
, -0.666667f
, 1.0f
}, // UNDER_LEFT
108 static const motorMixer_t mixerHex6P
[] = {
109 { 1.0f
, -0.866025f
, 0.5f
, 1.0f
}, // REAR_R
110 { 1.0f
, -0.866025f
, -0.5f
, -1.0f
}, // FRONT_R
111 { 1.0f
, 0.866025f
, 0.5f
, 1.0f
}, // REAR_L
112 { 1.0f
, 0.866025f
, -0.5f
, -1.0f
}, // FRONT_L
113 { 1.0f
, 0.0f
, -1.0f
, 1.0f
}, // FRONT
114 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
117 static const motorMixer_t mixerY4
[] = {
118 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR_TOP CW
119 { 1.0f
, -1.0f
, -1.0f
, 0.0f
}, // FRONT_R CCW
120 { 1.0f
, 0.0f
, 1.0f
, 1.0f
}, // REAR_BOTTOM CCW
121 { 1.0f
, 1.0f
, -1.0f
, 0.0f
}, // FRONT_L CW
124 static const motorMixer_t mixerHex6X
[] = {
125 { 1.0f
, -0.5f
, 0.866025f
, 1.0f
}, // REAR_R
126 { 1.0f
, -0.5f
, -0.866025f
, 1.0f
}, // FRONT_R
127 { 1.0f
, 0.5f
, 0.866025f
, -1.0f
}, // REAR_L
128 { 1.0f
, 0.5f
, -0.866025f
, -1.0f
}, // FRONT_L
129 { 1.0f
, -1.0f
, 0.0f
, -1.0f
}, // RIGHT
130 { 1.0f
, 1.0f
, 0.0f
, 1.0f
}, // LEFT
133 static const motorMixer_t mixerOctoX8
[] = {
134 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
135 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
136 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
137 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
138 { 1.0f
, -1.0f
, 1.0f
, 1.0f
}, // UNDER_REAR_R
139 { 1.0f
, -1.0f
, -1.0f
, -1.0f
}, // UNDER_FRONT_R
140 { 1.0f
, 1.0f
, 1.0f
, -1.0f
}, // UNDER_REAR_L
141 { 1.0f
, 1.0f
, -1.0f
, 1.0f
}, // UNDER_FRONT_L
144 static const motorMixer_t mixerOctoFlatP
[] = {
145 { 1.0f
, 0.707107f
, -0.707107f
, 1.0f
}, // FRONT_L
146 { 1.0f
, -0.707107f
, -0.707107f
, 1.0f
}, // FRONT_R
147 { 1.0f
, -0.707107f
, 0.707107f
, 1.0f
}, // REAR_R
148 { 1.0f
, 0.707107f
, 0.707107f
, 1.0f
}, // REAR_L
149 { 1.0f
, 0.0f
, -1.0f
, -1.0f
}, // FRONT
150 { 1.0f
, -1.0f
, 0.0f
, -1.0f
}, // RIGHT
151 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
152 { 1.0f
, 1.0f
, 0.0f
, -1.0f
}, // LEFT
155 static const motorMixer_t mixerOctoFlatX
[] = {
156 { 1.0f
, 1.0f
, -0.414178f
, 1.0f
}, // MIDFRONT_L
157 { 1.0f
, -0.414178f
, -1.0f
, 1.0f
}, // FRONT_R
158 { 1.0f
, -1.0f
, 0.414178f
, 1.0f
}, // MIDREAR_R
159 { 1.0f
, 0.414178f
, 1.0f
, 1.0f
}, // REAR_L
160 { 1.0f
, 0.414178f
, -1.0f
, -1.0f
}, // FRONT_L
161 { 1.0f
, -1.0f
, -0.414178f
, -1.0f
}, // MIDFRONT_R
162 { 1.0f
, -0.414178f
, 1.0f
, -1.0f
}, // REAR_R
163 { 1.0f
, 1.0f
, 0.414178f
, -1.0f
}, // MIDREAR_L
166 static const motorMixer_t mixerVtail4
[] = {
167 { 1.0f
, -0.58f
, 0.58f
, 1.0f
}, // REAR_R
168 { 1.0f
, -0.46f
, -0.39f
, -0.5f
}, // FRONT_R
169 { 1.0f
, 0.58f
, 0.58f
, -1.0f
}, // REAR_L
170 { 1.0f
, 0.46f
, -0.39f
, 0.5f
}, // FRONT_L
173 static const motorMixer_t mixerAtail4
[] = {
174 { 1.0f
, 0.0f
, 1.0f
, 1.0f
}, // REAR_R
175 { 1.0f
, -1.0f
, -1.0f
, 0.0f
}, // FRONT_R
176 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR_L
177 { 1.0f
, 1.0f
, -1.0f
, -0.0f
}, // FRONT_L
180 static const motorMixer_t mixerHex6H
[] = {
181 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
182 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
183 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
184 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
185 { 1.0f
, 0.0f
, 0.0f
, 0.0f
}, // RIGHT
186 { 1.0f
, 0.0f
, 0.0f
, 0.0f
}, // LEFT
189 static const motorMixer_t mixerDualcopter
[] = {
190 { 1.0f
, 0.0f
, 0.0f
, -1.0f
}, // LEFT
191 { 1.0f
, 0.0f
, 0.0f
, 1.0f
}, // RIGHT
194 static const motorMixer_t mixerSingleProp
[] = {
195 { 1.0f
, 0.0f
, 0.0f
, 0.0f
},
198 static const motorMixer_t mixerQuadX1234
[] = {
199 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
200 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
201 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
202 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
205 // Keep synced with mixerMode_e
206 const mixer_t mixers
[] = {
207 // motors, use servo, motor mixer
208 { 0, false, NULL
}, // entry 0
209 { 3, true, mixerTricopter
}, // MIXER_TRI
210 { 4, false, mixerQuadP
}, // MIXER_QUADP
211 { 4, false, mixerQuadX
}, // MIXER_QUADX
212 { 2, true, mixerBicopter
}, // MIXER_BICOPTER
213 { 0, true, NULL
}, // * MIXER_GIMBAL
214 { 6, false, mixerY6
}, // MIXER_Y6
215 { 6, false, mixerHex6P
}, // MIXER_HEX6
216 { 1, true, mixerSingleProp
}, // * MIXER_FLYING_WING
217 { 4, false, mixerY4
}, // MIXER_Y4
218 { 6, false, mixerHex6X
}, // MIXER_HEX6X
219 { 8, false, mixerOctoX8
}, // MIXER_OCTOX8
220 { 8, false, mixerOctoFlatP
}, // MIXER_OCTOFLATP
221 { 8, false, mixerOctoFlatX
}, // MIXER_OCTOFLATX
222 { 1, true, mixerSingleProp
}, // * MIXER_AIRPLANE
223 { 0, true, NULL
}, // * MIXER_HELI_120_CCPM
224 { 0, true, NULL
}, // * MIXER_HELI_90_DEG
225 { 4, false, mixerVtail4
}, // MIXER_VTAIL4
226 { 6, false, mixerHex6H
}, // MIXER_HEX6H
227 { 0, true, NULL
}, // * MIXER_PPM_TO_SERVO
228 { 2, true, mixerDualcopter
}, // MIXER_DUALCOPTER
229 { 1, true, NULL
}, // MIXER_SINGLECOPTER
230 { 4, false, mixerAtail4
}, // MIXER_ATAIL4
231 { 0, false, NULL
}, // MIXER_CUSTOM
232 { 2, true, NULL
}, // MIXER_CUSTOM_AIRPLANE
233 { 3, true, NULL
}, // MIXER_CUSTOM_TRI
234 { 4, false, mixerQuadX1234
},
238 static motorMixer_t
*customMixers
;
240 static uint16_t disarmMotorOutput
, deadbandMotor3dHigh
, deadbandMotor3dLow
;
241 uint16_t motorOutputHigh
, motorOutputLow
;
242 static float rcCommandThrottleRange
, rcCommandThrottleRange3dLow
, rcCommandThrottleRange3dHigh
;
244 uint8_t getMotorCount()
249 float getMotorMixRange()
251 return motorMixRange
;
254 bool isMotorProtocolDshot(void) {
256 switch(motorConfig
->motorPwmProtocol
) {
257 case PWM_TYPE_DSHOT1200
:
258 case PWM_TYPE_DSHOT600
:
259 case PWM_TYPE_DSHOT300
:
260 case PWM_TYPE_DSHOT150
:
270 // Add here scaled ESC outputs for digital protol
271 void initEscEndpoints(void) {
273 if (isMotorProtocolDshot()) {
274 disarmMotorOutput
= DSHOT_DISARM_COMMAND
;
275 if (feature(FEATURE_3D
))
276 motorOutputLow
= DSHOT_MIN_THROTTLE
+ lrintf(((DSHOT_3D_DEADBAND_LOW
- DSHOT_MIN_THROTTLE
) / 100.0f
) * motorConfig
->digitalIdleOffsetPercent
);
278 motorOutputLow
= DSHOT_MIN_THROTTLE
+ lrintf(((DSHOT_MAX_THROTTLE
- DSHOT_MIN_THROTTLE
) / 100.0f
) * motorConfig
->digitalIdleOffsetPercent
);
279 motorOutputHigh
= DSHOT_MAX_THROTTLE
;
280 deadbandMotor3dHigh
= DSHOT_3D_DEADBAND_HIGH
+ lrintf(((DSHOT_MAX_THROTTLE
- DSHOT_3D_DEADBAND_HIGH
) / 100.0f
) * motorConfig
->digitalIdleOffsetPercent
); // TODO - Not working yet !! Mixer requires some throttle rescaling changes
281 deadbandMotor3dLow
= DSHOT_3D_DEADBAND_LOW
;
285 disarmMotorOutput
= (feature(FEATURE_3D
)) ? flight3DConfig
->neutral3d
: motorConfig
->mincommand
;
286 motorOutputLow
= motorConfig
->minthrottle
;
287 motorOutputHigh
= motorConfig
->maxthrottle
;
288 deadbandMotor3dHigh
= flight3DConfig
->deadband3d_high
;
289 deadbandMotor3dLow
= flight3DConfig
->deadband3d_low
;
292 rcCommandThrottleRange
= (PWM_RANGE_MAX
- rxConfig
->mincheck
);
293 rcCommandThrottleRange3dLow
= rxConfig
->midrc
- rxConfig
->mincheck
- flight3DConfig
->deadband3d_throttle
;
294 rcCommandThrottleRange3dHigh
= PWM_RANGE_MAX
- rxConfig
->midrc
- flight3DConfig
->deadband3d_throttle
;
297 void mixerUseConfigs(
298 flight3DConfig_t
*flight3DConfigToUse
,
299 motorConfig_t
*motorConfigToUse
,
300 mixerConfig_t
*mixerConfigToUse
,
301 airplaneConfig_t
*airplaneConfigToUse
,
302 rxConfig_t
*rxConfigToUse
)
304 flight3DConfig
= flight3DConfigToUse
;
305 motorConfig
= motorConfigToUse
;
306 mixerConfig
= mixerConfigToUse
;
307 airplaneConfig
= airplaneConfigToUse
;
308 rxConfig
= rxConfigToUse
;
311 void mixerInit(mixerMode_e mixerMode
, motorMixer_t
*initialCustomMixers
)
313 currentMixerMode
= mixerMode
;
315 customMixers
= initialCustomMixers
;
320 #ifndef USE_QUAD_MIXER_ONLY
322 void mixerConfigureOutput(void)
326 if (currentMixerMode
== MIXER_CUSTOM
|| currentMixerMode
== MIXER_CUSTOM_TRI
|| currentMixerMode
== MIXER_CUSTOM_AIRPLANE
) {
327 // load custom mixer into currentMixer
328 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
330 if (customMixers
[i
].throttle
== 0.0f
)
332 currentMixer
[i
] = customMixers
[i
];
336 motorCount
= mixers
[currentMixerMode
].motorCount
;
337 if (motorCount
> MAX_SUPPORTED_MOTORS
) {
338 motorCount
= MAX_SUPPORTED_MOTORS
;
340 // copy motor-based mixers
341 if (mixers
[currentMixerMode
].motor
) {
342 for (int i
= 0; i
< motorCount
; i
++)
343 currentMixer
[i
] = mixers
[currentMixerMode
].motor
[i
];
347 // in 3D mode, mixer gain has to be halved
348 if (feature(FEATURE_3D
)) {
349 if (motorCount
> 1) {
350 for (int i
= 0; i
< motorCount
; i
++) {
351 currentMixer
[i
].pitch
*= 0.5f
;
352 currentMixer
[i
].roll
*= 0.5f
;
353 currentMixer
[i
].yaw
*= 0.5f
;
358 mixerResetDisarmedMotors();
361 void mixerLoadMix(int index
, motorMixer_t
*customMixers
)
366 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
367 customMixers
[i
].throttle
= 0.0f
;
370 // do we have anything here to begin with?
371 if (mixers
[index
].motor
!= NULL
) {
372 for (int i
= 0; i
< mixers
[index
].motorCount
; i
++) {
373 customMixers
[i
] = mixers
[index
].motor
[i
];
378 void mixerConfigureOutput(void)
380 motorCount
= QUAD_MOTOR_COUNT
;
382 for (int i
= 0; i
< motorCount
; i
++) {
383 currentMixer
[i
] = mixerQuadX
[i
];
386 mixerResetDisarmedMotors();
390 void mixerResetDisarmedMotors(void)
392 // set disarmed motor values
393 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
394 motor_disarmed
[i
] = disarmMotorOutput
;
398 void writeMotors(void)
400 if (pwmAreMotorsEnabled()) {
401 for (int i
= 0; i
< motorCount
; i
++) {
402 pwmWriteMotor(i
, motor
[i
]);
406 pwmCompleteMotorUpdate(motorCount
);
409 static void writeAllMotors(int16_t mc
)
411 // Sends commands to all motors
412 for (int i
= 0; i
< motorCount
; i
++) {
419 void stopMotors(void)
421 writeAllMotors(disarmMotorOutput
);
423 delay(50); // give the timers and ESCs a chance to react.
426 void stopPwmAllMotors(void)
428 pwmShutdownPulsesForAllMotors(motorCount
);
429 delayMicroseconds(1500);
432 void mixTable(pidProfile_t
*pidProfile
)
434 // Scale roll/pitch/yaw uniformly to fit within throttle range
435 // Initial mixer concept by bdoiron74 reused and optimized for Air Mode
436 float throttle
= 0, currentThrottleInputRange
= 0;
437 uint16_t motorOutputMin
, motorOutputMax
;
438 static uint16_t throttlePrevious
= 0; // Store the last throttle direction for deadband transitions
439 bool mixerInversion
= false;
441 // Find min and max throttle based on condition.
442 if (feature(FEATURE_3D
)) {
443 if (!ARMING_FLAG(ARMED
)) throttlePrevious
= rxConfig
->midrc
; // When disarmed set to mid_rc. It always results in positive direction after arming.
445 if ((rcCommand
[THROTTLE
] <= (rxConfig
->midrc
- flight3DConfig
->deadband3d_throttle
))) { // Out of band handling
446 motorOutputMax
= deadbandMotor3dLow
;
447 motorOutputMin
= motorOutputLow
;
448 throttlePrevious
= rcCommand
[THROTTLE
];
449 throttle
= rcCommand
[THROTTLE
] - rxConfig
->mincheck
;
450 currentThrottleInputRange
= rcCommandThrottleRange3dLow
;
451 if(isMotorProtocolDshot()) mixerInversion
= true;
452 } else if (rcCommand
[THROTTLE
] >= (rxConfig
->midrc
+ flight3DConfig
->deadband3d_throttle
)) { // Positive handling
453 motorOutputMax
= motorOutputHigh
;
454 motorOutputMin
= deadbandMotor3dHigh
;
455 throttlePrevious
= rcCommand
[THROTTLE
];
456 throttle
= rcCommand
[THROTTLE
] - rxConfig
->midrc
- flight3DConfig
->deadband3d_throttle
;
457 currentThrottleInputRange
= rcCommandThrottleRange3dHigh
;
458 } else if ((throttlePrevious
<= (rxConfig
->midrc
- flight3DConfig
->deadband3d_throttle
))) { // Deadband handling from negative to positive
459 motorOutputMax
= deadbandMotor3dLow
;
460 motorOutputMin
= motorOutputLow
;
461 throttle
= rxConfig
->midrc
- flight3DConfig
->deadband3d_throttle
;
462 currentThrottleInputRange
= rcCommandThrottleRange3dLow
;
463 if(isMotorProtocolDshot()) mixerInversion
= true;
464 } else { // Deadband handling from positive to negative
465 motorOutputMax
= motorOutputHigh
;
466 motorOutputMin
= deadbandMotor3dHigh
;
468 currentThrottleInputRange
= rcCommandThrottleRange3dHigh
;
471 throttle
= rcCommand
[THROTTLE
] - rxConfig
->mincheck
;
472 currentThrottleInputRange
= rcCommandThrottleRange
;
473 motorOutputMin
= motorOutputLow
;
474 motorOutputMax
= motorOutputHigh
;
477 throttle
= constrainf(throttle
/ currentThrottleInputRange
, 0.0f
, 1.0f
);
478 const float motorOutputRange
= motorOutputMax
- motorOutputMin
;
480 float scaledAxisPIDf
[3];
482 for (int axis
= 0; axis
< 3; axis
++) {
483 scaledAxisPIDf
[axis
] = constrainf(axisPIDf
[axis
] / PID_MIXER_SCALING
, -pidProfile
->pidSumLimit
, pidProfile
->pidSumLimit
);
486 // Calculate voltage compensation
487 const float vbatCompensationFactor
= (batteryConfig
&& pidProfile
->vbatPidCompensation
) ? calculateVbatPidCompensation() : 1.0f
;
489 // Find roll/pitch/yaw desired output
490 float motorMix
[MAX_SUPPORTED_MOTORS
];
491 float motorMixMax
= 0, motorMixMin
= 0;
492 for (int i
= 0; i
< motorCount
; i
++) {
494 scaledAxisPIDf
[PITCH
] * currentMixer
[i
].pitch
+
495 scaledAxisPIDf
[ROLL
] * currentMixer
[i
].roll
+
496 scaledAxisPIDf
[YAW
] * currentMixer
[i
].yaw
* (-mixerConfig
->yaw_motor_direction
);
498 if (vbatCompensationFactor
> 1.0f
) {
499 motorMix
[i
] *= vbatCompensationFactor
; // Add voltage compensation
502 if (motorMix
[i
] > motorMixMax
) {
503 motorMixMax
= motorMix
[i
];
504 } else if (motorMix
[i
] < motorMixMin
) {
505 motorMixMin
= motorMix
[i
];
509 motorMixRange
= motorMixMax
- motorMixMin
;
511 if (motorMixRange
> 1.0f
) {
512 for (int i
= 0; i
< motorCount
; i
++) {
513 motorMix
[i
] /= motorMixRange
;
515 // Get the maximum correction by setting offset to center
518 float throttleLimitOffset
= motorMixRange
/ 2.0f
;
519 throttle
= constrainf(throttle
, 0.0f
+ throttleLimitOffset
, 1.0f
- throttleLimitOffset
);
522 // Now add in the desired throttle, but keep in a range that doesn't clip adjusted
523 // roll/pitch/yaw. This could move throttle down, but also up for those low throttle flips.
525 for (i
= 0; i
< motorCount
; i
++) {
526 motor
[i
] = motorOutputMin
+ lrintf(motorOutputRange
* (motorMix
[i
] + (throttle
* currentMixer
[i
].throttle
)));
528 // Dshot works exactly opposite in lower 3D section.
529 if (mixerInversion
) {
530 motor
[i
] = motorOutputMin
+ (motorOutputMax
- motor
[i
]);
533 if (failsafeIsActive()) {
534 if (isMotorProtocolDshot())
535 motor
[i
] = (motor
[i
] < motorOutputMin
) ? disarmMotorOutput
: motor
[i
]; // Prevent getting into special reserved range
537 motor
[i
] = constrain(motor
[i
], disarmMotorOutput
, motorOutputMax
);
539 motor
[i
] = constrain(motor
[i
], motorOutputMin
, motorOutputMax
);
542 // Motor stop handling
543 if (feature(FEATURE_MOTOR_STOP
) && ARMING_FLAG(ARMED
) && !feature(FEATURE_3D
) && !isAirmodeActive()) {
544 if (((rcData
[THROTTLE
]) < rxConfig
->mincheck
)) {
545 motor
[i
] = disarmMotorOutput
;
551 if (!ARMING_FLAG(ARMED
)) {
552 for (i
= 0; i
< motorCount
; i
++) {
553 motor
[i
] = motor_disarmed
[i
];
558 uint16_t convertExternalToMotor(uint16_t externalValue
)
560 uint16_t motorValue
= externalValue
;
562 if (isMotorProtocolDshot()) {
563 motorValue
= externalValue
<= EXTERNAL_CONVERSION_MIN_VALUE
? DSHOT_DISARM_COMMAND
: constrain((externalValue
- EXTERNAL_DSHOT_CONVERSION_OFFSET
) * EXTERNAL_DSHOT_CONVERSION_FACTOR
, DSHOT_MIN_THROTTLE
, DSHOT_MAX_THROTTLE
);
570 uint16_t convertMotorToExternal(uint16_t motorValue
)
572 uint16_t externalValue
= motorValue
;
574 if (isMotorProtocolDshot()) {
575 externalValue
= motorValue
< DSHOT_MIN_THROTTLE
? EXTERNAL_CONVERSION_MIN_VALUE
: constrain((motorValue
/ EXTERNAL_DSHOT_CONVERSION_FACTOR
) + EXTERNAL_DSHOT_CONVERSION_OFFSET
, EXTERNAL_CONVERSION_MIN_VALUE
+ 1, EXTERNAL_CONVERSION_MAX_VALUE
);
579 return externalValue
;