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"
26 #include "build/debug.h"
28 #include "common/axis.h"
29 #include "common/filter.h"
30 #include "common/maths.h"
32 #include "config/feature.h"
33 #include "config/parameter_group.h"
34 #include "config/parameter_group_ids.h"
36 #include "drivers/pwm_output.h"
37 #include "drivers/pwm_esc_detect.h"
38 #include "drivers/time.h"
40 #include "io/motors.h"
42 #include "fc/config.h"
43 #include "fc/rc_controls.h"
44 #include "fc/rc_modes.h"
45 #include "fc/runtime_config.h"
46 #include "fc/fc_core.h"
49 #include "flight/failsafe.h"
50 #include "flight/imu.h"
51 #include "flight/mixer.h"
52 #include "flight/pid.h"
56 #include "sensors/battery.h"
58 PG_REGISTER_WITH_RESET_TEMPLATE(mixerConfig_t
, mixerConfig
, PG_MIXER_CONFIG
, 0);
60 #ifndef TARGET_DEFAULT_MIXER
61 #define TARGET_DEFAULT_MIXER MIXER_QUADX
63 PG_RESET_TEMPLATE(mixerConfig_t
, mixerConfig
,
64 .mixerMode
= TARGET_DEFAULT_MIXER
,
65 .yaw_motors_reversed
= false,
68 PG_REGISTER_WITH_RESET_FN(motorConfig_t
, motorConfig
, PG_MOTOR_CONFIG
, 0);
70 void pgResetFn_motorConfig(motorConfig_t
*motorConfig
)
73 motorConfig
->minthrottle
= 1000;
74 motorConfig
->dev
.motorPwmRate
= BRUSHED_MOTORS_PWM_RATE
;
75 motorConfig
->dev
.motorPwmProtocol
= PWM_TYPE_BRUSHED
;
76 motorConfig
->dev
.useUnsyncedPwm
= true;
78 #ifdef BRUSHED_ESC_AUTODETECT
79 if (hardwareMotorType
== MOTOR_BRUSHED
) {
80 motorConfig
->minthrottle
= 1000;
81 motorConfig
->dev
.motorPwmRate
= BRUSHED_MOTORS_PWM_RATE
;
82 motorConfig
->dev
.motorPwmProtocol
= PWM_TYPE_BRUSHED
;
83 motorConfig
->dev
.useUnsyncedPwm
= true;
87 motorConfig
->minthrottle
= 1070;
88 motorConfig
->dev
.motorPwmRate
= BRUSHLESS_MOTORS_PWM_RATE
;
89 motorConfig
->dev
.motorPwmProtocol
= PWM_TYPE_ONESHOT125
;
92 motorConfig
->maxthrottle
= 2000;
93 motorConfig
->mincommand
= 1000;
94 motorConfig
->digitalIdleOffsetValue
= 450;
97 for (int i
= 0; i
< USABLE_TIMER_CHANNEL_COUNT
&& motorIndex
< MAX_SUPPORTED_MOTORS
; i
++) {
98 if (timerHardware
[i
].usageFlags
& TIM_USE_MOTOR
) {
99 motorConfig
->dev
.ioTags
[motorIndex
] = timerHardware
[i
].tag
;
105 PG_REGISTER_ARRAY(motorMixer_t
, MAX_SUPPORTED_MOTORS
, customMotorMixer
, PG_MOTOR_MIXER
, 0);
107 #define EXTERNAL_DSHOT_CONVERSION_FACTOR 2
108 // (minimum output value(1001) - (minimum input value(48) / conversion factor(2))
109 #define EXTERNAL_DSHOT_CONVERSION_OFFSET 977
110 #define EXTERNAL_CONVERSION_MIN_VALUE 1000
111 #define EXTERNAL_CONVERSION_MAX_VALUE 2000
112 #define EXTERNAL_CONVERSION_3D_MID_VALUE 1500
114 #define TRICOPTER_ERROR_RATE_YAW_SATURATED 75 // rate at which tricopter yaw axis becomes saturated, determined experimentally by TriFlight
116 static uint8_t motorCount
;
117 static float motorMixRange
;
119 float motor
[MAX_SUPPORTED_MOTORS
];
120 float motor_disarmed
[MAX_SUPPORTED_MOTORS
];
122 mixerMode_e currentMixerMode
;
123 static motorMixer_t currentMixer
[MAX_SUPPORTED_MOTORS
];
126 float pidSumLimitYaw
;
129 static const motorMixer_t mixerQuadX
[] = {
130 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
131 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
132 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
133 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
135 #ifndef USE_QUAD_MIXER_ONLY
136 static const motorMixer_t mixerTricopter
[] = {
137 { 1.0f
, 0.0f
, 1.333333f
, 0.0f
}, // REAR
138 { 1.0f
, -1.0f
, -0.666667f
, 0.0f
}, // RIGHT
139 { 1.0f
, 1.0f
, -0.666667f
, 0.0f
}, // LEFT
142 static const motorMixer_t mixerQuadP
[] = {
143 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
144 { 1.0f
, -1.0f
, 0.0f
, 1.0f
}, // RIGHT
145 { 1.0f
, 1.0f
, 0.0f
, 1.0f
}, // LEFT
146 { 1.0f
, 0.0f
, -1.0f
, -1.0f
}, // FRONT
149 #if defined(USE_UNCOMMON_MIXERS)
150 static const motorMixer_t mixerBicopter
[] = {
151 { 1.0f
, 1.0f
, 0.0f
, 0.0f
}, // LEFT
152 { 1.0f
, -1.0f
, 0.0f
, 0.0f
}, // RIGHT
155 #define mixerBicopter NULL
158 static const motorMixer_t mixerY4
[] = {
159 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR_TOP CW
160 { 1.0f
, -1.0f
, -1.0f
, 0.0f
}, // FRONT_R CCW
161 { 1.0f
, 0.0f
, 1.0f
, 1.0f
}, // REAR_BOTTOM CCW
162 { 1.0f
, 1.0f
, -1.0f
, 0.0f
}, // FRONT_L CW
166 #if (MAX_SUPPORTED_MOTORS >= 6)
167 static const motorMixer_t mixerHex6X
[] = {
168 { 1.0f
, -0.5f
, 0.866025f
, 1.0f
}, // REAR_R
169 { 1.0f
, -0.5f
, -0.866025f
, 1.0f
}, // FRONT_R
170 { 1.0f
, 0.5f
, 0.866025f
, -1.0f
}, // REAR_L
171 { 1.0f
, 0.5f
, -0.866025f
, -1.0f
}, // FRONT_L
172 { 1.0f
, -1.0f
, 0.0f
, -1.0f
}, // RIGHT
173 { 1.0f
, 1.0f
, 0.0f
, 1.0f
}, // LEFT
176 #if defined(USE_UNCOMMON_MIXERS)
177 static const motorMixer_t mixerHex6H
[] = {
178 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
179 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
180 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
181 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
182 { 1.0f
, 0.0f
, 0.0f
, 0.0f
}, // RIGHT
183 { 1.0f
, 0.0f
, 0.0f
, 0.0f
}, // LEFT
186 static const motorMixer_t mixerHex6P
[] = {
187 { 1.0f
, -0.866025f
, 0.5f
, 1.0f
}, // REAR_R
188 { 1.0f
, -0.866025f
, -0.5f
, -1.0f
}, // FRONT_R
189 { 1.0f
, 0.866025f
, 0.5f
, 1.0f
}, // REAR_L
190 { 1.0f
, 0.866025f
, -0.5f
, -1.0f
}, // FRONT_L
191 { 1.0f
, 0.0f
, -1.0f
, 1.0f
}, // FRONT
192 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
194 static const motorMixer_t mixerY6
[] = {
195 { 1.0f
, 0.0f
, 1.333333f
, 1.0f
}, // REAR
196 { 1.0f
, -1.0f
, -0.666667f
, -1.0f
}, // RIGHT
197 { 1.0f
, 1.0f
, -0.666667f
, -1.0f
}, // LEFT
198 { 1.0f
, 0.0f
, 1.333333f
, -1.0f
}, // UNDER_REAR
199 { 1.0f
, -1.0f
, -0.666667f
, 1.0f
}, // UNDER_RIGHT
200 { 1.0f
, 1.0f
, -0.666667f
, 1.0f
}, // UNDER_LEFT
203 #define mixerHex6H NULL
204 #define mixerHex6P NULL
206 #endif // USE_UNCOMMON_MIXERS
208 #define mixerHex6X NULL
209 #endif // MAX_SUPPORTED_MOTORS >= 6
211 #if defined(USE_UNCOMMON_MIXERS) && (MAX_SUPPORTED_MOTORS >= 8)
212 static const motorMixer_t mixerOctoX8
[] = {
213 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
214 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
215 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
216 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
217 { 1.0f
, -1.0f
, 1.0f
, 1.0f
}, // UNDER_REAR_R
218 { 1.0f
, -1.0f
, -1.0f
, -1.0f
}, // UNDER_FRONT_R
219 { 1.0f
, 1.0f
, 1.0f
, -1.0f
}, // UNDER_REAR_L
220 { 1.0f
, 1.0f
, -1.0f
, 1.0f
}, // UNDER_FRONT_L
223 static const motorMixer_t mixerOctoFlatP
[] = {
224 { 1.0f
, 0.707107f
, -0.707107f
, 1.0f
}, // FRONT_L
225 { 1.0f
, -0.707107f
, -0.707107f
, 1.0f
}, // FRONT_R
226 { 1.0f
, -0.707107f
, 0.707107f
, 1.0f
}, // REAR_R
227 { 1.0f
, 0.707107f
, 0.707107f
, 1.0f
}, // REAR_L
228 { 1.0f
, 0.0f
, -1.0f
, -1.0f
}, // FRONT
229 { 1.0f
, -1.0f
, 0.0f
, -1.0f
}, // RIGHT
230 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
231 { 1.0f
, 1.0f
, 0.0f
, -1.0f
}, // LEFT
234 static const motorMixer_t mixerOctoFlatX
[] = {
235 { 1.0f
, 1.0f
, -0.414178f
, 1.0f
}, // MIDFRONT_L
236 { 1.0f
, -0.414178f
, -1.0f
, 1.0f
}, // FRONT_R
237 { 1.0f
, -1.0f
, 0.414178f
, 1.0f
}, // MIDREAR_R
238 { 1.0f
, 0.414178f
, 1.0f
, 1.0f
}, // REAR_L
239 { 1.0f
, 0.414178f
, -1.0f
, -1.0f
}, // FRONT_L
240 { 1.0f
, -1.0f
, -0.414178f
, -1.0f
}, // MIDFRONT_R
241 { 1.0f
, -0.414178f
, 1.0f
, -1.0f
}, // REAR_R
242 { 1.0f
, 1.0f
, 0.414178f
, -1.0f
}, // MIDREAR_L
245 #define mixerOctoX8 NULL
246 #define mixerOctoFlatP NULL
247 #define mixerOctoFlatX NULL
250 static const motorMixer_t mixerVtail4
[] = {
251 { 1.0f
, -0.58f
, 0.58f
, 1.0f
}, // REAR_R
252 { 1.0f
, -0.46f
, -0.39f
, -0.5f
}, // FRONT_R
253 { 1.0f
, 0.58f
, 0.58f
, -1.0f
}, // REAR_L
254 { 1.0f
, 0.46f
, -0.39f
, 0.5f
}, // FRONT_L
257 static const motorMixer_t mixerAtail4
[] = {
258 { 1.0f
, 0.0f
, 1.0f
, 1.0f
}, // REAR_R
259 { 1.0f
, -1.0f
, -1.0f
, 0.0f
}, // FRONT_R
260 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR_L
261 { 1.0f
, 1.0f
, -1.0f
, -0.0f
}, // FRONT_L
264 #if defined(USE_UNCOMMON_MIXERS)
265 static const motorMixer_t mixerDualcopter
[] = {
266 { 1.0f
, 0.0f
, 0.0f
, -1.0f
}, // LEFT
267 { 1.0f
, 0.0f
, 0.0f
, 1.0f
}, // RIGHT
270 #define mixerDualcopter NULL
273 static const motorMixer_t mixerSingleProp
[] = {
274 { 1.0f
, 0.0f
, 0.0f
, 0.0f
},
277 static const motorMixer_t mixerQuadX1234
[] = {
278 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
279 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
280 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
281 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
284 // Keep synced with mixerMode_e
285 const mixer_t mixers
[] = {
286 // motors, use servo, motor mixer
287 { 0, false, NULL
}, // entry 0
288 { 3, true, mixerTricopter
}, // MIXER_TRI
289 { 4, false, mixerQuadP
}, // MIXER_QUADP
290 { 4, false, mixerQuadX
}, // MIXER_QUADX
291 { 2, true, mixerBicopter
}, // MIXER_BICOPTER
292 { 0, true, NULL
}, // * MIXER_GIMBAL
293 { 6, false, mixerY6
}, // MIXER_Y6
294 { 6, false, mixerHex6P
}, // MIXER_HEX6
295 { 1, true, mixerSingleProp
}, // * MIXER_FLYING_WING
296 { 4, false, mixerY4
}, // MIXER_Y4
297 { 6, false, mixerHex6X
}, // MIXER_HEX6X
298 { 8, false, mixerOctoX8
}, // MIXER_OCTOX8
299 { 8, false, mixerOctoFlatP
}, // MIXER_OCTOFLATP
300 { 8, false, mixerOctoFlatX
}, // 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 { 6, false, mixerHex6H
}, // MIXER_HEX6H
306 { 0, true, NULL
}, // * MIXER_PPM_TO_SERVO
307 { 2, true, mixerDualcopter
}, // MIXER_DUALCOPTER
308 { 1, true, NULL
}, // MIXER_SINGLECOPTER
309 { 4, false, mixerAtail4
}, // MIXER_ATAIL4
310 { 0, false, NULL
}, // MIXER_CUSTOM
311 { 2, true, NULL
}, // MIXER_CUSTOM_AIRPLANE
312 { 3, true, NULL
}, // MIXER_CUSTOM_TRI
313 { 4, false, mixerQuadX1234
},
315 #endif // !USE_QUAD_MIXER_ONLY
317 float motorOutputHigh
, motorOutputLow
;
319 static float disarmMotorOutput
, deadbandMotor3dHigh
, deadbandMotor3dLow
;
320 static uint16_t rcCommand3dDeadBandLow
;
321 static uint16_t rcCommand3dDeadBandHigh
;
322 static float rcCommandThrottleRange
, rcCommandThrottleRange3dLow
, rcCommandThrottleRange3dHigh
;
324 uint8_t getMotorCount(void)
329 float getMotorMixRange(void)
331 return motorMixRange
;
334 bool areMotorsRunning(void)
336 bool motorsRunning
= false;
337 if (ARMING_FLAG(ARMED
)) {
338 motorsRunning
= true;
340 for (int i
= 0; i
< motorCount
; i
++) {
341 if (motor_disarmed
[i
] != disarmMotorOutput
) {
342 motorsRunning
= true;
349 return motorsRunning
;
352 bool mixerIsOutputSaturated(int axis
, float errorRate
)
354 if (axis
== FD_YAW
&& (currentMixerMode
== MIXER_TRI
|| currentMixerMode
== MIXER_CUSTOM_TRI
)) {
355 return errorRate
> TRICOPTER_ERROR_RATE_YAW_SATURATED
;
357 return motorMixRange
>= 1.0f
;
362 // All PWM motor scaling is done to standard PWM range of 1000-2000 for easier tick conversion with legacy code / configurator
363 // DSHOT scaling is done to the actual dshot range
364 void initEscEndpoints(void)
366 // Can't use 'isMotorProtocolDshot()' here since motors haven't been initialised yet
367 switch (motorConfig()->dev
.motorPwmProtocol
) {
369 case PWM_TYPE_PROSHOT1000
:
370 case PWM_TYPE_DSHOT1200
:
371 case PWM_TYPE_DSHOT600
:
372 case PWM_TYPE_DSHOT300
:
373 case PWM_TYPE_DSHOT150
:
374 disarmMotorOutput
= DSHOT_DISARM_COMMAND
;
375 if (feature(FEATURE_3D
)) {
376 motorOutputLow
= DSHOT_MIN_THROTTLE
+ ((DSHOT_3D_DEADBAND_LOW
- DSHOT_MIN_THROTTLE
) / 100.0f
) * CONVERT_PARAMETER_TO_PERCENT(motorConfig()->digitalIdleOffsetValue
);
378 motorOutputLow
= DSHOT_MIN_THROTTLE
+ ((DSHOT_MAX_THROTTLE
- DSHOT_MIN_THROTTLE
) / 100.0f
) * CONVERT_PARAMETER_TO_PERCENT(motorConfig()->digitalIdleOffsetValue
);
380 motorOutputHigh
= DSHOT_MAX_THROTTLE
;
381 deadbandMotor3dHigh
= DSHOT_3D_DEADBAND_HIGH
+ ((DSHOT_MAX_THROTTLE
- DSHOT_3D_DEADBAND_HIGH
) / 100.0f
) * CONVERT_PARAMETER_TO_PERCENT(motorConfig()->digitalIdleOffsetValue
);
382 deadbandMotor3dLow
= DSHOT_3D_DEADBAND_LOW
;
387 if (feature(FEATURE_3D
)) {
388 disarmMotorOutput
= flight3DConfig()->neutral3d
;
389 motorOutputLow
= PWM_RANGE_MIN
;
391 disarmMotorOutput
= motorConfig()->mincommand
;
392 motorOutputLow
= motorConfig()->minthrottle
;
394 motorOutputHigh
= motorConfig()->maxthrottle
;
395 deadbandMotor3dHigh
= flight3DConfig()->deadband3d_high
;
396 deadbandMotor3dLow
= flight3DConfig()->deadband3d_low
;
401 rcCommandThrottleRange
= PWM_RANGE_MAX
- rxConfig()->mincheck
;
403 rcCommand3dDeadBandLow
= rxConfig()->midrc
- flight3DConfig()->deadband3d_throttle
;
404 rcCommand3dDeadBandHigh
= rxConfig()->midrc
+ flight3DConfig()->deadband3d_throttle
;
406 rcCommandThrottleRange3dLow
= rcCommand3dDeadBandLow
- PWM_RANGE_MIN
;
407 rcCommandThrottleRange3dHigh
= PWM_RANGE_MAX
- rcCommand3dDeadBandHigh
;
410 void mixerInit(mixerMode_e mixerMode
)
412 currentMixerMode
= mixerMode
;
417 void pidInitMixer(const struct pidProfile_s
*pidProfile
)
419 pidSumLimit
= CONVERT_PARAMETER_TO_FLOAT(pidProfile
->pidSumLimit
);
420 pidSumLimitYaw
= CONVERT_PARAMETER_TO_FLOAT(pidProfile
->pidSumLimitYaw
);
423 #ifndef USE_QUAD_MIXER_ONLY
425 void mixerConfigureOutput(void)
429 if (currentMixerMode
== MIXER_CUSTOM
|| currentMixerMode
== MIXER_CUSTOM_TRI
|| currentMixerMode
== MIXER_CUSTOM_AIRPLANE
) {
430 // load custom mixer into currentMixer
431 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
433 if (customMotorMixer(i
)->throttle
== 0.0f
)
435 currentMixer
[i
] = *customMotorMixer(i
);
439 motorCount
= mixers
[currentMixerMode
].motorCount
;
440 if (motorCount
> MAX_SUPPORTED_MOTORS
) {
441 motorCount
= MAX_SUPPORTED_MOTORS
;
443 // copy motor-based mixers
444 if (mixers
[currentMixerMode
].motor
) {
445 for (int i
= 0; i
< motorCount
; i
++)
446 currentMixer
[i
] = mixers
[currentMixerMode
].motor
[i
];
450 // in 3D mode, mixer gain has to be halved
451 if (feature(FEATURE_3D
)) {
452 if (motorCount
> 1) {
453 for (int i
= 0; i
< motorCount
; i
++) {
454 currentMixer
[i
].pitch
*= 0.5f
;
455 currentMixer
[i
].roll
*= 0.5f
;
456 currentMixer
[i
].yaw
*= 0.5f
;
461 mixerResetDisarmedMotors();
464 void mixerLoadMix(int index
, motorMixer_t
*customMixers
)
469 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
470 customMixers
[i
].throttle
= 0.0f
;
473 // do we have anything here to begin with?
474 if (mixers
[index
].motor
!= NULL
) {
475 for (int i
= 0; i
< mixers
[index
].motorCount
; i
++) {
476 customMixers
[i
] = mixers
[index
].motor
[i
];
481 void mixerConfigureOutput(void)
483 motorCount
= QUAD_MOTOR_COUNT
;
485 for (int i
= 0; i
< motorCount
; i
++) {
486 currentMixer
[i
] = mixerQuadX
[i
];
489 mixerResetDisarmedMotors();
493 void mixerResetDisarmedMotors(void)
495 // set disarmed motor values
496 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
497 motor_disarmed
[i
] = disarmMotorOutput
;
501 void writeMotors(void)
503 if (pwmAreMotorsEnabled()) {
504 for (int i
= 0; i
< motorCount
; i
++) {
505 pwmWriteMotor(i
, motor
[i
]);
507 pwmCompleteMotorUpdate(motorCount
);
511 static void writeAllMotors(int16_t mc
)
513 // Sends commands to all motors
514 for (int i
= 0; i
< motorCount
; i
++) {
521 void stopMotors(void)
523 writeAllMotors(disarmMotorOutput
);
525 delay(50); // give the timers and ESCs a chance to react.
528 void stopPwmAllMotors(void)
530 pwmShutdownPulsesForAllMotors(motorCount
);
531 delayMicroseconds(1500);
534 static float throttle
= 0;
535 static float motorOutputMin
;
536 static float motorRangeMin
;
537 static float motorRangeMax
;
538 static float motorOutputRange
;
539 static int8_t motorOutputMixSign
;
541 void calculateThrottleAndCurrentMotorEndpoints(void)
543 static uint16_t rcThrottlePrevious
= 0; // Store the last throttle direction for deadband transitions
544 float currentThrottleInputRange
= 0;
546 if(feature(FEATURE_3D
)) {
547 if (!ARMING_FLAG(ARMED
)) {
548 rcThrottlePrevious
= rxConfig()->midrc
; // When disarmed set to mid_rc. It always results in positive direction after arming.
551 if(rcCommand
[THROTTLE
] <= rcCommand3dDeadBandLow
) {
553 motorRangeMin
= motorOutputLow
;
554 motorRangeMax
= deadbandMotor3dLow
;
555 if(isMotorProtocolDshot()) {
556 motorOutputMin
= motorOutputLow
;
557 motorOutputRange
= deadbandMotor3dLow
- motorOutputLow
;
559 motorOutputMin
= deadbandMotor3dLow
;
560 motorOutputRange
= motorOutputLow
- deadbandMotor3dLow
;
562 motorOutputMixSign
= -1;
563 rcThrottlePrevious
= rcCommand
[THROTTLE
];
564 throttle
= rcCommand3dDeadBandLow
- rcCommand
[THROTTLE
];
565 currentThrottleInputRange
= rcCommandThrottleRange3dLow
;
566 } else if(rcCommand
[THROTTLE
] >= rcCommand3dDeadBandHigh
) {
568 motorRangeMin
= deadbandMotor3dHigh
;
569 motorRangeMax
= motorOutputHigh
;
570 motorOutputMin
= deadbandMotor3dHigh
;
571 motorOutputRange
= motorOutputHigh
- deadbandMotor3dHigh
;
572 motorOutputMixSign
= 1;
573 rcThrottlePrevious
= rcCommand
[THROTTLE
];
574 throttle
= rcCommand
[THROTTLE
] - rcCommand3dDeadBandHigh
;
575 currentThrottleInputRange
= rcCommandThrottleRange3dHigh
;
576 } else if((rcThrottlePrevious
<= rcCommand3dDeadBandLow
)) {
577 // INVERTED_TO_DEADBAND
578 motorRangeMin
= motorOutputLow
;
579 motorRangeMax
= deadbandMotor3dLow
;
580 if(isMotorProtocolDshot()) {
581 motorOutputMin
= motorOutputLow
;
582 motorOutputRange
= deadbandMotor3dLow
- motorOutputLow
;
584 motorOutputMin
= deadbandMotor3dLow
;
585 motorOutputRange
= motorOutputLow
- deadbandMotor3dLow
;
587 motorOutputMixSign
= -1;
589 currentThrottleInputRange
= rcCommandThrottleRange3dLow
;
591 // NORMAL_TO_DEADBAND
592 motorRangeMin
= deadbandMotor3dHigh
;
593 motorRangeMax
= motorOutputHigh
;
594 motorOutputMin
= deadbandMotor3dHigh
;
595 motorOutputRange
= motorOutputHigh
- deadbandMotor3dHigh
;
596 motorOutputMixSign
= 1;
598 currentThrottleInputRange
= rcCommandThrottleRange3dHigh
;
601 throttle
= rcCommand
[THROTTLE
] - rxConfig()->mincheck
;
602 currentThrottleInputRange
= rcCommandThrottleRange
;
603 motorRangeMin
= motorOutputLow
;
604 motorRangeMax
= motorOutputHigh
;
605 motorOutputMin
= motorOutputLow
;
606 motorOutputRange
= motorOutputHigh
- motorOutputLow
;
607 motorOutputMixSign
= 1;
610 throttle
= constrainf(throttle
/ currentThrottleInputRange
, 0.0f
, 1.0f
);
613 static void applyFlipOverAfterCrashModeToMotors(void)
615 float motorMix
[MAX_SUPPORTED_MOTORS
];
617 for (int i
= 0; i
< motorCount
; i
++) {
618 if (getRcDeflectionAbs(FD_ROLL
) > getRcDeflectionAbs(FD_PITCH
)) {
619 motorMix
[i
] = getRcDeflection(FD_ROLL
) * currentMixer
[i
].roll
* (-1);
621 motorMix
[i
] = getRcDeflection(FD_PITCH
) * currentMixer
[i
].pitch
* (-1);
624 // Apply the mix to motor endpoints
625 for (uint32_t i
= 0; i
< motorCount
; i
++) {
626 float motorOutput
= motorOutputMin
+ motorOutputRange
* (motorMix
[i
]);
627 //Add a little bit to the motorOutputMin so props aren't spinning when sticks are centered
628 motorOutput
= (motorOutput
< motorOutputMin
+ 20 ) ? disarmMotorOutput
: motorOutput
;
630 motor
[i
] = motorOutput
;
634 if (!ARMING_FLAG(ARMED
)) {
635 for (int i
= 0; i
< motorCount
; i
++) {
636 motor
[i
] = motor_disarmed
[i
];
641 static void applyMixToMotors(float motorMix
[MAX_SUPPORTED_MOTORS
])
643 // Now add in the desired throttle, but keep in a range that doesn't clip adjusted
644 // roll/pitch/yaw. This could move throttle down, but also up for those low throttle flips.
645 for (uint32_t i
= 0; i
< motorCount
; i
++) {
646 float motorOutput
= motorOutputMin
+ (motorOutputRange
* (motorOutputMixSign
* motorMix
[i
] + throttle
* currentMixer
[i
].throttle
));
648 if (failsafeIsActive()) {
649 if (isMotorProtocolDshot()) {
650 motorOutput
= (motorOutput
< motorRangeMin
) ? disarmMotorOutput
: motorOutput
; // Prevent getting into special reserved range
653 motorOutput
= constrain(motorOutput
, disarmMotorOutput
, motorRangeMax
);
655 motorOutput
= constrain(motorOutput
, motorRangeMin
, motorRangeMax
);
658 // Motor stop handling
659 if (feature(FEATURE_MOTOR_STOP
) && ARMING_FLAG(ARMED
) && !feature(FEATURE_3D
) && !isAirmodeActive()) {
660 if (((rcData
[THROTTLE
]) < rxConfig()->mincheck
)) {
661 motorOutput
= disarmMotorOutput
;
664 motor
[i
] = motorOutput
;
668 if (!ARMING_FLAG(ARMED
)) {
669 for (int i
= 0; i
< motorCount
; i
++) {
670 motor
[i
] = motor_disarmed
[i
];
675 void mixTable(uint8_t vbatPidCompensation
)
677 if (isFlipOverAfterCrashMode()) {
678 applyFlipOverAfterCrashModeToMotors();
682 // Find min and max throttle based on conditions. Throttle has to be known before mixing
683 calculateThrottleAndCurrentMotorEndpoints();
685 // Calculate and Limit the PIDsum
686 float scaledAxisPidRoll
=
687 constrainf((axisPID_P
[FD_ROLL
] + axisPID_I
[FD_ROLL
] + axisPID_D
[FD_ROLL
]) / PID_MIXER_SCALING
, -pidSumLimit
, pidSumLimit
);
688 float scaledAxisPidPitch
=
689 constrainf((axisPID_P
[FD_PITCH
] + axisPID_I
[FD_PITCH
] + axisPID_D
[FD_PITCH
]) / PID_MIXER_SCALING
, -pidSumLimit
, pidSumLimit
);
690 float scaledAxisPidYaw
=
691 -constrainf((axisPID_P
[FD_YAW
] + axisPID_I
[FD_YAW
]) / PID_MIXER_SCALING
, -pidSumLimitYaw
, pidSumLimitYaw
);
692 if (isMotorsReversed()) {
693 scaledAxisPidRoll
= -scaledAxisPidRoll
;
694 scaledAxisPidPitch
= -scaledAxisPidPitch
;
695 scaledAxisPidYaw
= -scaledAxisPidYaw
;
697 if (mixerConfig()->yaw_motors_reversed
) {
698 scaledAxisPidYaw
= -scaledAxisPidYaw
;
701 // Calculate voltage compensation
702 const float vbatCompensationFactor
= (vbatPidCompensation
) ? calculateVbatPidCompensation() : 1.0f
;
704 // Find roll/pitch/yaw desired output
705 float motorMix
[MAX_SUPPORTED_MOTORS
];
706 float motorMixMax
= 0, motorMixMin
= 0;
707 for (int i
= 0; i
< motorCount
; i
++) {
709 scaledAxisPidRoll
* currentMixer
[i
].roll
+
710 scaledAxisPidPitch
* currentMixer
[i
].pitch
+
711 scaledAxisPidYaw
* currentMixer
[i
].yaw
;
713 mix
*= vbatCompensationFactor
; // Add voltage compensation
715 if (mix
> motorMixMax
) {
717 } else if (mix
< motorMixMin
) {
723 motorMixRange
= motorMixMax
- motorMixMin
;
725 if (motorMixRange
> 1.0f
) {
726 for (int i
= 0; i
< motorCount
; i
++) {
727 motorMix
[i
] /= motorMixRange
;
729 // Get the maximum correction by setting offset to center when airmode enabled
730 if (isAirmodeActive()) {
734 if (isAirmodeActive() || throttle
> 0.5f
) { // Only automatically adjust throttle when airmode enabled. Airmode logic is always active on high throttle
735 const float throttleLimitOffset
= motorMixRange
/ 2.0f
;
736 throttle
= constrainf(throttle
, 0.0f
+ throttleLimitOffset
, 1.0f
- throttleLimitOffset
);
740 // Apply the mix to motor endpoints
741 applyMixToMotors(motorMix
);
744 float convertExternalToMotor(uint16_t externalValue
)
746 uint16_t motorValue
= externalValue
;
748 if (isMotorProtocolDshot()) {
749 // Add 1 to the value, otherwise throttle tops out at 2046
750 motorValue
= externalValue
<= EXTERNAL_CONVERSION_MIN_VALUE
? DSHOT_DISARM_COMMAND
: constrain((externalValue
- EXTERNAL_DSHOT_CONVERSION_OFFSET
) * EXTERNAL_DSHOT_CONVERSION_FACTOR
+ 1, DSHOT_MIN_THROTTLE
, DSHOT_MAX_THROTTLE
);
752 if (feature(FEATURE_3D
)) {
753 if (externalValue
== EXTERNAL_CONVERSION_3D_MID_VALUE
) {
754 motorValue
= DSHOT_DISARM_COMMAND
;
755 } else if (motorValue
>= DSHOT_MIN_THROTTLE
&& motorValue
<= DSHOT_3D_DEADBAND_LOW
) {
756 // Add 1 to the value, otherwise throttle tops out at 2046
757 motorValue
= DSHOT_MIN_THROTTLE
+ (DSHOT_3D_DEADBAND_LOW
- motorValue
) + 1;
763 return (float)motorValue
;
766 uint16_t convertMotorToExternal(float motorValue
)
768 uint16_t externalValue
= lrintf(motorValue
);
770 if (isMotorProtocolDshot()) {
771 if (feature(FEATURE_3D
) && motorValue
>= DSHOT_MIN_THROTTLE
&& motorValue
<= DSHOT_3D_DEADBAND_LOW
) {
772 // Subtract 1 to compensate for imbalance introduced in convertExternalToMotor()
773 motorValue
= DSHOT_MIN_THROTTLE
+ (DSHOT_3D_DEADBAND_LOW
- motorValue
) - 1;
776 // Subtract 1 to compensate for imbalance introduced in convertExternalToMotor()
777 externalValue
= motorValue
< DSHOT_MIN_THROTTLE
? EXTERNAL_CONVERSION_MIN_VALUE
: constrain(((motorValue
- 1)/ EXTERNAL_DSHOT_CONVERSION_FACTOR
) + EXTERNAL_DSHOT_CONVERSION_OFFSET
, EXTERNAL_CONVERSION_MIN_VALUE
+ 1, EXTERNAL_CONVERSION_MAX_VALUE
);
779 if (feature(FEATURE_3D
) && motorValue
== DSHOT_DISARM_COMMAND
) {
780 externalValue
= EXTERNAL_CONVERSION_3D_MID_VALUE
;
785 return externalValue
;