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"
27 #include "build/build_config.h"
30 #include "common/axis.h"
31 #include "common/maths.h"
32 #include "common/filter.h"
34 #include "drivers/system.h"
35 #include "drivers/pwm_output.h"
36 #include "drivers/pwm_mapping.h"
37 #include "drivers/sensor.h"
38 #include "drivers/accgyro.h"
39 #include "drivers/system.h"
43 #include "io/gimbal.h"
44 #include "io/escservo.h"
45 #include "fc/rc_controls.h"
48 #include "sensors/sensors.h"
49 #include "sensors/acceleration.h"
50 #include "sensors/gyro.h"
52 #include "flight/mixer.h"
53 #include "flight/failsafe.h"
54 #include "flight/pid.h"
55 #include "flight/imu.h"
56 #include "flight/navigation_rewrite.h"
58 #include "fc/runtime_config.h"
60 #include "config/config.h"
61 #include "config/config_profile.h"
67 int16_t motor
[MAX_SUPPORTED_MOTORS
];
68 int16_t motor_disarmed
[MAX_SUPPORTED_MOTORS
];
70 bool motorLimitReached
= false;
72 static mixerConfig_t
*mixerConfig
;
73 static flight3DConfig_t
*flight3DConfig
;
74 static escAndServoConfig_t
*escAndServoConfig
;
75 static rxConfig_t
*rxConfig
;
77 static mixerMode_e currentMixerMode
;
78 static motorMixer_t currentMixer
[MAX_SUPPORTED_MOTORS
];
82 static uint8_t servoRuleCount
= 0;
83 static servoMixer_t currentServoMixer
[MAX_SERVO_RULES
];
84 static gimbalConfig_t
*gimbalConfig
;
85 int16_t servo
[MAX_SUPPORTED_SERVOS
];
86 static int servoOutputEnabled
;
88 static uint8_t mixerUsesServos
;
89 static uint8_t minServoIndex
;
90 static uint8_t maxServoIndex
;
92 static servoParam_t
*servoConf
;
93 static biquadFilter_t servoFitlerState
[MAX_SUPPORTED_SERVOS
];
94 static bool servoFilterIsSet
;
97 static const motorMixer_t mixerQuadX
[] = {
98 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
99 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
100 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
101 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
103 #ifndef USE_QUAD_MIXER_ONLY
104 static const motorMixer_t mixerTricopter
[] = {
105 { 1.0f
, 0.0f
, 1.333333f
, 0.0f
}, // REAR
106 { 1.0f
, -1.0f
, -0.666667f
, 0.0f
}, // RIGHT
107 { 1.0f
, 1.0f
, -0.666667f
, 0.0f
}, // LEFT
110 static const motorMixer_t mixerQuadP
[] = {
111 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
112 { 1.0f
, -1.0f
, 0.0f
, 1.0f
}, // RIGHT
113 { 1.0f
, 1.0f
, 0.0f
, 1.0f
}, // LEFT
114 { 1.0f
, 0.0f
, -1.0f
, -1.0f
}, // FRONT
117 #ifndef DISABLE_UNCOMMON_MIXERS
118 static const motorMixer_t mixerVtail4
[] = {
119 { 1.0f
, -0.58f
, 0.58f
, 1.0f
}, // REAR_R
120 { 1.0f
, -0.46f
, -0.39f
, -0.5f
}, // FRONT_R
121 { 1.0f
, 0.58f
, 0.58f
, -1.0f
}, // REAR_L
122 { 1.0f
, 0.46f
, -0.39f
, 0.5f
}, // FRONT_L
125 static const motorMixer_t mixerAtail4
[] = {
126 { 1.0f
, 0.0f
, 1.0f
, 1.0f
}, // REAR_R
127 { 1.0f
, -1.0f
, -1.0f
, 0.0f
}, // FRONT_R
128 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR_L
129 { 1.0f
, 1.0f
, -1.0f
, -0.0f
}, // FRONT_L
132 static const motorMixer_t mixerY4
[] = {
133 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR_TOP CW
134 { 1.0f
, -1.0f
, -1.0f
, 0.0f
}, // FRONT_R CCW
135 { 1.0f
, 0.0f
, 1.0f
, 1.0f
}, // REAR_BOTTOM CCW
136 { 1.0f
, 1.0f
, -1.0f
, 0.0f
}, // FRONT_L CW
139 #if (MAX_SUPPORTED_MOTORS >= 6)
140 static const motorMixer_t mixerHex6H
[] = {
141 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
142 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
143 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
144 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
145 { 1.0f
, 0.0f
, 0.0f
, 0.0f
}, // RIGHT
146 { 1.0f
, 0.0f
, 0.0f
, 0.0f
}, // LEFT
149 static const motorMixer_t mixerY6
[] = {
150 { 1.0f
, 0.0f
, 1.333333f
, 1.0f
}, // REAR
151 { 1.0f
, -1.0f
, -0.666667f
, -1.0f
}, // RIGHT
152 { 1.0f
, 1.0f
, -0.666667f
, -1.0f
}, // LEFT
153 { 1.0f
, 0.0f
, 1.333333f
, -1.0f
}, // UNDER_REAR
154 { 1.0f
, -1.0f
, -0.666667f
, 1.0f
}, // UNDER_RIGHT
155 { 1.0f
, 1.0f
, -0.666667f
, 1.0f
}, // UNDER_LEFT
158 static const motorMixer_t mixerHex6P
[] = {
159 { 1.0f
, -0.866025f
, 0.5f
, 1.0f
}, // REAR_R
160 { 1.0f
, -0.866025f
, -0.5f
, -1.0f
}, // FRONT_R
161 { 1.0f
, 0.866025f
, 0.5f
, 1.0f
}, // REAR_L
162 { 1.0f
, 0.866025f
, -0.5f
, -1.0f
}, // FRONT_L
163 { 1.0f
, 0.0f
, -1.0f
, 1.0f
}, // FRONT
164 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
168 #if (MAX_SUPPORTED_MOTORS >= 8)
169 static const motorMixer_t mixerOctoFlatP
[] = {
170 { 1.0f
, 0.707107f
, -0.707107f
, 1.0f
}, // FRONT_L
171 { 1.0f
, -0.707107f
, -0.707107f
, 1.0f
}, // FRONT_R
172 { 1.0f
, -0.707107f
, 0.707107f
, 1.0f
}, // REAR_R
173 { 1.0f
, 0.707107f
, 0.707107f
, 1.0f
}, // REAR_L
174 { 1.0f
, 0.0f
, -1.0f
, -1.0f
}, // FRONT
175 { 1.0f
, -1.0f
, 0.0f
, -1.0f
}, // RIGHT
176 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
177 { 1.0f
, 1.0f
, 0.0f
, -1.0f
}, // LEFT
180 static const motorMixer_t mixerOctoFlatX
[] = {
181 { 1.0f
, 1.0f
, -0.414178f
, 1.0f
}, // MIDFRONT_L
182 { 1.0f
, -0.414178f
, -1.0f
, 1.0f
}, // FRONT_R
183 { 1.0f
, -1.0f
, 0.414178f
, 1.0f
}, // MIDREAR_R
184 { 1.0f
, 0.414178f
, 1.0f
, 1.0f
}, // REAR_L
185 { 1.0f
, 0.414178f
, -1.0f
, -1.0f
}, // FRONT_L
186 { 1.0f
, -1.0f
, -0.414178f
, -1.0f
}, // MIDFRONT_R
187 { 1.0f
, -0.414178f
, 1.0f
, -1.0f
}, // REAR_R
188 { 1.0f
, 1.0f
, 0.414178f
, -1.0f
}, // MIDREAR_L
191 static const motorMixer_t mixerOctoX8
[] = {
192 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
193 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
194 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
195 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
196 { 1.0f
, -1.0f
, 1.0f
, 1.0f
}, // UNDER_REAR_R
197 { 1.0f
, -1.0f
, -1.0f
, -1.0f
}, // UNDER_FRONT_R
198 { 1.0f
, 1.0f
, 1.0f
, -1.0f
}, // UNDER_REAR_L
199 { 1.0f
, 1.0f
, -1.0f
, 1.0f
}, // UNDER_FRONT_L
202 #endif //DISABLE_UNCOMMON_MIXERS
204 #if (MAX_SUPPORTED_MOTORS >= 6)
205 static const motorMixer_t mixerHex6X
[] = {
206 { 1.0f
, -0.5f
, 0.866025f
, 1.0f
}, // REAR_R
207 { 1.0f
, -0.5f
, -0.866025f
, 1.0f
}, // FRONT_R
208 { 1.0f
, 0.5f
, 0.866025f
, -1.0f
}, // REAR_L
209 { 1.0f
, 0.5f
, -0.866025f
, -1.0f
}, // FRONT_L
210 { 1.0f
, -1.0f
, 0.0f
, -1.0f
}, // RIGHT
211 { 1.0f
, 1.0f
, 0.0f
, 1.0f
}, // LEFT
215 static const motorMixer_t mixerSingleProp
[] = {
216 { 1.0f
, 0.0f
, 0.0f
, 0.0f
},
219 // Keep synced with mixerMode_e
220 const mixer_t mixers
[] = {
221 // motors, use servo, motor mixer
222 { 0, false, NULL
, true }, // entry 0
223 { 3, true, mixerTricopter
, true }, // MIXER_TRI
224 { 4, false, mixerQuadP
, true }, // MIXER_QUADP
225 { 4, false, mixerQuadX
, true }, // MIXER_QUADX
227 { 0, false, NULL
, false }, // MIXER_BICOPTER
228 { 0, false, NULL
, false }, // MIXER_GIMBAL -> this mixer was never implemented in CF, use feature(FEATURE_SERVO_TILT) instead
229 #if !defined(DISABLE_UNCOMMON_MIXERS) && (MAX_SUPPORTED_MOTORS >= 6)
230 { 6, false, mixerY6
, true }, // MIXER_Y6
231 { 6, false, mixerHex6P
, true }, // MIXER_HEX6
233 { 0, false, NULL
, false }, // MIXER_Y6
234 { 0, false, NULL
, false }, // MIXER_HEX6
236 { 1, true, mixerSingleProp
, true }, // MIXER_FLYING_WING
237 #if !defined(DISABLE_UNCOMMON_MIXERS)
238 { 4, false, mixerY4
, true }, // MIXER_Y4
240 { 0, false, NULL
, false }, // MIXER_Y4
242 #if (MAX_SUPPORTED_MOTORS >= 6)
243 { 6, false, mixerHex6X
, true }, // MIXER_HEX6X
245 { 0, false, NULL
, false }, // MIXER_HEX6X
247 #if !defined(DISABLE_UNCOMMON_MIXERS) && (MAX_SUPPORTED_MOTORS >= 8)
248 { 8, false, mixerOctoX8
, true }, // MIXER_OCTOX8
249 { 8, false, mixerOctoFlatP
, true }, // MIXER_OCTOFLATP
250 { 8, false, mixerOctoFlatX
, true }, // MIXER_OCTOFLATX
252 { 0, false, NULL
, false }, // MIXER_OCTOX8
253 { 0, false, NULL
, false }, // MIXER_OCTOFLATP
254 { 0, false, NULL
, false }, // MIXER_OCTOFLATX
256 { 1, true, mixerSingleProp
, true }, // * MIXER_AIRPLANE
257 { 0, true, NULL
, false }, // * MIXER_HELI_120_CCPM -> disabled, never fully implemented in CF
258 { 0, true, NULL
, false }, // * MIXER_HELI_90_DEG -> disabled, never fully implemented in CF
259 #if !defined(DISABLE_UNCOMMON_MIXERS)
260 { 4, false, mixerVtail4
, true }, // MIXER_VTAIL4
261 #if (MAX_SUPPORTED_MOTORS >= 6)
262 { 6, false, mixerHex6H
, true }, // MIXER_HEX6H
264 { 0, false, NULL
, false }, // MIXER_HEX6H
267 { 0, false, NULL
, false }, // MIXER_VTAIL4
268 { 0, false, NULL
, false }, // MIXER_HEX6H
270 { 0, true, NULL
, false }, // * MIXER_PPM_TO_SERVO -> looks like this is not implemented at all
271 { 0, false, NULL
, false }, // MIXER_DUALCOPTER
272 { 0, false, NULL
, false }, // MIXER_SINGLECOPTER
273 #if !defined(DISABLE_UNCOMMON_MIXERS)
274 { 4, false, mixerAtail4
, true }, // MIXER_ATAIL4
276 { 0, false, NULL
, false }, // MIXER_ATAIL4
278 { 0, false, NULL
, true }, // MIXER_CUSTOM
279 { 2, true, NULL
, true }, // MIXER_CUSTOM_AIRPLANE
280 { 3, true, NULL
, true }, // MIXER_CUSTOM_TRI
282 #endif // USE_QUAD_MIXER_ONLY
286 #define COUNT_SERVO_RULES(rules) (sizeof(rules) / sizeof(servoMixer_t))
287 // mixer rule format servo, input, rate, speed, min, max, box
288 static const servoMixer_t servoMixerAirplane
[] = {
289 { SERVO_FLAPPERON_1
, INPUT_STABILIZED_ROLL
, 100, 0, 0, 100, 0 },
290 { SERVO_FLAPPERON_2
, INPUT_STABILIZED_ROLL
, 100, 0, 0, 100, 0 },
291 { SERVO_RUDDER
, INPUT_STABILIZED_YAW
, 100, 0, 0, 100, 0 },
292 { SERVO_ELEVATOR
, INPUT_STABILIZED_PITCH
, 100, 0, 0, 100, 0 },
293 { SERVO_THROTTLE
, INPUT_STABILIZED_THROTTLE
, 100, 0, 0, 100, 0 },
296 static const servoMixer_t servoMixerFlyingWing
[] = {
297 { SERVO_FLAPPERON_1
, INPUT_STABILIZED_ROLL
, 100, 0, 0, 100, 0 },
298 { SERVO_FLAPPERON_1
, INPUT_STABILIZED_PITCH
, 100, 0, 0, 100, 0 },
299 { SERVO_FLAPPERON_2
, INPUT_STABILIZED_ROLL
, -100, 0, 0, 100, 0 },
300 { SERVO_FLAPPERON_2
, INPUT_STABILIZED_PITCH
, 100, 0, 0, 100, 0 },
301 { SERVO_THROTTLE
, INPUT_STABILIZED_THROTTLE
, 100, 0, 0, 100, 0 },
304 static const servoMixer_t servoMixerTri
[] = {
305 { SERVO_RUDDER
, INPUT_STABILIZED_YAW
, 100, 0, 0, 100, 0 },
308 const mixerRules_t servoMixers
[] = {
309 { 0, 0, 0, NULL
}, // entry 0
310 { COUNT_SERVO_RULES(servoMixerTri
), SERVO_RUDDER
, SERVO_RUDDER
, servoMixerTri
}, // MULTITYPE_TRI
311 { 0, 0, 0, NULL
}, // MULTITYPE_QUADP
312 { 0, 0, 0, NULL
}, // MULTITYPE_QUADX
313 { 0, 0, 0, NULL
}, // MULTITYPE_BI
314 { 0, 0, 0, NULL
}, // MULTITYPE_GIMBAL / MIXER_GIMBAL -> disabled
315 { 0, 0, 0, NULL
}, // MULTITYPE_Y6
316 { 0, 0, 0, NULL
}, // MULTITYPE_HEX6
317 { COUNT_SERVO_RULES(servoMixerFlyingWing
), SERVO_FLAPPERONS_MIN
, SERVO_FLAPPERONS_MAX
, servoMixerFlyingWing
},// * MULTITYPE_FLYING_WING
318 { 0, 0, 0, NULL
}, // MULTITYPE_Y4
319 { 0, 0, 0, NULL
}, // MULTITYPE_HEX6X
320 { 0, 0, 0, NULL
}, // MULTITYPE_OCTOX8
321 { 0, 0, 0, NULL
}, // MULTITYPE_OCTOFLATP
322 { 0, 0, 0, NULL
}, // MULTITYPE_OCTOFLATX
323 { COUNT_SERVO_RULES(servoMixerAirplane
), SERVO_PLANE_INDEX_MIN
, SERVO_PLANE_INDEX_MAX
, servoMixerAirplane
}, // * MULTITYPE_AIRPLANE
324 { 0, 0, 0, NULL
}, // * MIXER_HELI_120_CCPM -> disabled, never fully implemented in CF
325 { 0, 0, 0, NULL
}, // * MIXER_HELI_90_DEG -> disabled, never fully implemented in CF
326 { 0, 0, 0, NULL
}, // MULTITYPE_VTAIL4
327 { 0, 0, 0, NULL
}, // MULTITYPE_HEX6H
328 { 0, 0, 0, NULL
}, // * MULTITYPE_PPM_TO_SERVO
329 { 0, 0, 0, NULL
}, // MULTITYPE_DUALCOPTER
330 { 0, 0, 0, NULL
}, // MULTITYPE_SINGLECOPTER
331 { 0, 0, 0, NULL
}, // MULTITYPE_ATAIL4
332 { 0, 2, 5, NULL
}, // MULTITYPE_CUSTOM
333 { 0, SERVO_PLANE_INDEX_MIN
, SERVO_PLANE_INDEX_MAX
, NULL
}, // MULTITYPE_CUSTOM_PLANE
334 { 0, SERVO_RUDDER
, SERVO_RUDDER
, NULL
}, // MULTITYPE_CUSTOM_TRI
337 static servoMixer_t
*customServoMixers
;
341 static motorMixer_t
*customMixers
;
343 void mixerUseConfigs(
345 servoParam_t
*servoConfToUse
,
346 gimbalConfig_t
*gimbalConfigToUse
,
348 flight3DConfig_t
*flight3DConfigToUse
,
349 escAndServoConfig_t
*escAndServoConfigToUse
,
350 mixerConfig_t
*mixerConfigToUse
,
351 rxConfig_t
*rxConfigToUse
)
354 servoConf
= servoConfToUse
;
355 gimbalConfig
= gimbalConfigToUse
;
357 flight3DConfig
= flight3DConfigToUse
;
358 escAndServoConfig
= escAndServoConfigToUse
;
359 mixerConfig
= mixerConfigToUse
;
360 rxConfig
= rxConfigToUse
;
365 int16_t getFlaperonDirection(uint8_t servoPin
) {
366 if (servoPin
== SERVO_FLAPPERON_2
) {
373 int16_t determineServoMiddleOrForwardFromChannel(servoIndex_e servoIndex
)
375 uint8_t channelToForwardFrom
= servoConf
[servoIndex
].forwardFromChannel
;
377 if (channelToForwardFrom
!= CHANNEL_FORWARDING_DISABLED
&& channelToForwardFrom
< rxRuntimeConfig
.channelCount
) {
378 return rcData
[channelToForwardFrom
];
381 return servoConf
[servoIndex
].middle
;
385 int servoDirection(int servoIndex
, int inputSource
)
387 // determine the direction (reversed or not) from the direction bitfield of the servo
388 if (servoConf
[servoIndex
].reversedSources
& (1 << inputSource
))
395 bool isMixerEnabled(mixerMode_e mixerMode
)
397 #ifdef USE_QUAD_MIXER_ONLY
401 return mixers
[mixerMode
].enabled
;
406 void mixerInit(mixerMode_e mixerMode
, motorMixer_t
*initialCustomMotorMixers
, servoMixer_t
*initialCustomServoMixers
)
410 currentMixerMode
= mixerMode
;
412 // set flag that we're on something with wings
413 if (currentMixerMode
== MIXER_FLYING_WING
||
414 currentMixerMode
== MIXER_AIRPLANE
||
415 currentMixerMode
== MIXER_CUSTOM_AIRPLANE
417 ENABLE_STATE(FIXED_WING
);
419 DISABLE_STATE(FIXED_WING
);
422 if (currentMixerMode
== MIXER_AIRPLANE
|| currentMixerMode
== MIXER_CUSTOM_AIRPLANE
) {
423 ENABLE_STATE(FLAPERON_AVAILABLE
);
425 DISABLE_STATE(FLAPERON_AVAILABLE
);
428 customMixers
= initialCustomMotorMixers
;
429 customServoMixers
= initialCustomServoMixers
;
431 minServoIndex
= servoMixers
[currentMixerMode
].minServoIndex
;
432 maxServoIndex
= servoMixers
[currentMixerMode
].maxServoIndex
;
434 // enable servos for mixes that require them. note, this shifts motor counts.
435 mixerUsesServos
= mixers
[currentMixerMode
].useServo
|| feature(FEATURE_SERVO_TILT
);
437 // if we want camstab/trig, that also enables servos, even if mixer doesn't
438 servoOutputEnabled
= mixerUsesServos
|| feature(FEATURE_CHANNEL_FORWARDING
);
440 // give all servos a default command
441 for (i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
442 servo
[i
] = DEFAULT_SERVO_MIDDLE
;
446 * If mixer has predefined servo mixes, load them
448 if (mixerUsesServos
) {
449 servoRuleCount
= servoMixers
[currentMixerMode
].servoRuleCount
;
450 if (servoMixers
[currentMixerMode
].rule
) {
451 for (i
= 0; i
< servoRuleCount
; i
++)
452 currentServoMixer
[i
] = servoMixers
[currentMixerMode
].rule
[i
];
457 * When we are dealing with CUSTOM mixers, load mixes defined with
458 * smix and update internal variables
460 if (currentMixerMode
== MIXER_CUSTOM_AIRPLANE
||
461 currentMixerMode
== MIXER_CUSTOM_TRI
||
462 currentMixerMode
== MIXER_CUSTOM
)
464 loadCustomServoMixer();
466 // If there are servo rules after all, update variables
467 if (servoRuleCount
> 0) {
468 servoOutputEnabled
= 1;
475 void mixerInit(mixerMode_e mixerMode
, motorMixer_t
*initialCustomMixers
)
477 currentMixerMode
= mixerMode
;
478 customMixers
= initialCustomMixers
;
483 void mixerUsePWMIOConfiguration(void)
489 if (currentMixerMode
== MIXER_CUSTOM
|| currentMixerMode
== MIXER_CUSTOM_TRI
|| currentMixerMode
== MIXER_CUSTOM_AIRPLANE
) {
490 // load custom mixer into currentMixer
491 for (i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
493 if (customMixers
[i
].throttle
== 0.0f
)
495 currentMixer
[i
] = customMixers
[i
];
499 motorCount
= mixers
[currentMixerMode
].motorCount
;
500 // copy motor-based mixers
501 if (mixers
[currentMixerMode
].motor
) {
502 for (i
= 0; i
< motorCount
; i
++)
503 currentMixer
[i
] = mixers
[currentMixerMode
].motor
[i
];
507 // in 3D mode, mixer gain has to be halved
508 if (feature(FEATURE_3D
)) {
509 if (motorCount
> 1) {
510 for (i
= 0; i
< motorCount
; i
++) {
511 currentMixer
[i
].pitch
*= 0.5f
;
512 currentMixer
[i
].roll
*= 0.5f
;
513 currentMixer
[i
].yaw
*= 0.5f
;
518 mixerResetDisarmedMotors();
521 void mixerUsePWMIOConfiguration(void)
525 for (i
= 0; i
< motorCount
; i
++) {
526 currentMixer
[i
] = mixerQuadX
[i
];
528 mixerResetDisarmedMotors();
533 #ifndef USE_QUAD_MIXER_ONLY
535 void loadCustomServoMixer(void)
543 memset(currentServoMixer
, 0, sizeof(currentServoMixer
));
545 // load custom mixer into currentServoMixer
546 for (i
= 0; i
< MAX_SERVO_RULES
; i
++) {
548 if (customServoMixers
[i
].rate
== 0)
551 if (customServoMixers
[i
].targetChannel
< minServoIndex
) {
552 minServoIndex
= customServoMixers
[i
].targetChannel
;
555 if (customServoMixers
[i
].targetChannel
> maxServoIndex
) {
556 maxServoIndex
= customServoMixers
[i
].targetChannel
;
559 currentServoMixer
[i
] = customServoMixers
[i
];
564 void servoMixerLoadMix(int index
, servoMixer_t
*customServoMixers
)
571 for (i
= 0; i
< MAX_SERVO_RULES
; i
++)
572 customServoMixers
[i
].targetChannel
= customServoMixers
[i
].inputSource
= customServoMixers
[i
].rate
= customServoMixers
[i
].box
= 0;
574 for (i
= 0; i
< servoMixers
[index
].servoRuleCount
; i
++) {
575 customServoMixers
[i
] = servoMixers
[index
].rule
[i
];
581 void mixerLoadMix(int index
, motorMixer_t
*customMixers
)
588 for (i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++)
589 customMixers
[i
].throttle
= 0.0f
;
591 // do we have anything here to begin with?
592 if (mixers
[index
].motor
!= NULL
) {
593 for (i
= 0; i
< mixers
[index
].motorCount
; i
++)
594 customMixers
[i
] = mixers
[index
].motor
[i
];
600 void mixerResetDisarmedMotors(void)
603 // set disarmed motor values
604 for (i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++)
605 motor_disarmed
[i
] = feature(FEATURE_3D
) ? flight3DConfig
->neutral3d
: escAndServoConfig
->mincommand
;
610 STATIC_UNIT_TESTED
void forwardAuxChannelsToServos(uint8_t firstServoIndex
)
612 // start forwarding from this channel
613 uint8_t channelOffset
= AUX1
;
616 for (servoOffset
= 0; servoOffset
< MAX_AUX_CHANNEL_COUNT
&& channelOffset
< MAX_SUPPORTED_RC_CHANNEL_COUNT
; servoOffset
++) {
617 pwmWriteServo(firstServoIndex
+ servoOffset
, rcData
[channelOffset
++]);
621 void writeServos(void)
625 bool zeroServoValue
= false;
628 * in case of tricopters, there might me a need to zero servo output when unarmed
630 if ((currentMixerMode
== MIXER_TRI
|| currentMixerMode
== MIXER_CUSTOM_TRI
) && !ARMING_FLAG(ARMED
) && !mixerConfig
->tri_unarmed_servo
) {
631 zeroServoValue
= true;
634 // Write mixer servo outputs
635 // mixerUsesServos might indicate SERVO_TILT, servoRuleCount indicate if mixer alone uses servos
636 if (mixerUsesServos
&& servoRuleCount
) {
637 for (int i
= minServoIndex
; i
<= maxServoIndex
; i
++) {
638 if (zeroServoValue
) {
639 pwmWriteServo(servoIndex
++, 0);
641 pwmWriteServo(servoIndex
++, servo
[i
]);
646 if (feature(FEATURE_SERVO_TILT
)) {
647 pwmWriteServo(servoIndex
++, servo
[SERVO_GIMBAL_PITCH
]);
648 pwmWriteServo(servoIndex
++, servo
[SERVO_GIMBAL_ROLL
]);
651 // forward AUX to remaining servo outputs (not constrained)
652 if (feature(FEATURE_CHANNEL_FORWARDING
)) {
653 forwardAuxChannelsToServos(servoIndex
);
654 servoIndex
+= MAX_AUX_CHANNEL_COUNT
;
659 void writeMotors(void)
663 for (i
= 0; i
< motorCount
; i
++)
664 pwmWriteMotor(i
, motor
[i
]);
667 if (feature(FEATURE_ONESHOT125
)) {
668 pwmCompleteOneshotMotorUpdate(motorCount
);
672 void writeAllMotors(int16_t mc
)
676 // Sends commands to all motors
677 for (i
= 0; i
< motorCount
; i
++)
682 void stopMotors(void)
684 writeAllMotors(feature(FEATURE_3D
) ? flight3DConfig
->neutral3d
: escAndServoConfig
->mincommand
);
686 delay(50); // give the timers and ESCs a chance to react.
689 void StopPwmAllMotors()
691 pwmShutdownPulsesForAllMotors(motorCount
);
698 if (motorCount
>= 4 && mixerConfig
->yaw_jump_prevention_limit
< YAW_JUMP_PREVENTION_LIMIT_HIGH
) {
699 // prevent "yaw jump" during yaw correction
700 axisPID
[YAW
] = constrain(axisPID
[YAW
], -mixerConfig
->yaw_jump_prevention_limit
- ABS(rcCommand
[YAW
]), mixerConfig
->yaw_jump_prevention_limit
+ ABS(rcCommand
[YAW
]));
703 // Initial mixer concept by bdoiron74 reused and optimized for Air Mode
704 int16_t rpyMix
[MAX_SUPPORTED_MOTORS
];
705 int16_t rpyMixMax
= 0; // assumption: symetrical about zero.
706 int16_t rpyMixMin
= 0;
708 // motors for non-servo mixes
709 for (i
= 0; i
< motorCount
; i
++) {
711 axisPID
[PITCH
] * currentMixer
[i
].pitch
+
712 axisPID
[ROLL
] * currentMixer
[i
].roll
+
713 -mixerConfig
->yaw_motor_direction
* axisPID
[YAW
] * currentMixer
[i
].yaw
;
715 if (rpyMix
[i
] > rpyMixMax
) rpyMixMax
= rpyMix
[i
];
716 if (rpyMix
[i
] < rpyMixMin
) rpyMixMin
= rpyMix
[i
];
719 int16_t rpyMixRange
= rpyMixMax
- rpyMixMin
;
720 int16_t throttleRange
, throttleCommand
;
721 int16_t throttleMin
, throttleMax
;
722 static int16_t throttlePrevious
= 0; // Store the last throttle direction for deadband transitions
724 // Find min and max throttle based on condition.
725 if (feature(FEATURE_3D
)) {
726 if (!ARMING_FLAG(ARMED
)) throttlePrevious
= rxConfig
->midrc
; // When disarmed set to mid_rc. It always results in positive direction after arming.
728 if ((rcCommand
[THROTTLE
] <= (rxConfig
->midrc
- flight3DConfig
->deadband3d_throttle
))) { // Out of band handling
729 throttleMax
= flight3DConfig
->deadband3d_low
;
730 throttleMin
= escAndServoConfig
->minthrottle
;
731 throttlePrevious
= throttleCommand
= rcCommand
[THROTTLE
];
732 } else if (rcCommand
[THROTTLE
] >= (rxConfig
->midrc
+ flight3DConfig
->deadband3d_throttle
)) { // Positive handling
733 throttleMax
= escAndServoConfig
->maxthrottle
;
734 throttleMin
= flight3DConfig
->deadband3d_high
;
735 throttlePrevious
= throttleCommand
= rcCommand
[THROTTLE
];
736 } else if ((throttlePrevious
<= (rxConfig
->midrc
- flight3DConfig
->deadband3d_throttle
))) { // Deadband handling from negative to positive
737 throttleCommand
= throttleMax
= flight3DConfig
->deadband3d_low
;
738 throttleMin
= escAndServoConfig
->minthrottle
;
739 } else { // Deadband handling from positive to negative
740 throttleMax
= escAndServoConfig
->maxthrottle
;
741 throttleCommand
= throttleMin
= flight3DConfig
->deadband3d_high
;
744 throttleCommand
= rcCommand
[THROTTLE
];
745 throttleMin
= escAndServoConfig
->minthrottle
;
746 throttleMax
= escAndServoConfig
->maxthrottle
;
749 throttleRange
= throttleMax
- throttleMin
;
751 #define THROTTLE_CLIPPING_FACTOR 0.33f
752 if (rpyMixRange
> throttleRange
) {
753 motorLimitReached
= true;
754 float mixReduction
= (float)throttleRange
/ rpyMixRange
;
756 for (i
= 0; i
< motorCount
; i
++) {
757 rpyMix
[i
] = mixReduction
* rpyMix
[i
];
760 // Allow some clipping on edges to soften correction response
761 throttleMin
= throttleMin
+ (throttleRange
/ 2) - (throttleRange
* THROTTLE_CLIPPING_FACTOR
/ 2);
762 throttleMax
= throttleMin
+ (throttleRange
/ 2) + (throttleRange
* THROTTLE_CLIPPING_FACTOR
/ 2);
764 motorLimitReached
= false;
765 throttleMin
= MIN(throttleMin
+ (rpyMixRange
/ 2), throttleMin
+ (throttleRange
/ 2) - (throttleRange
* THROTTLE_CLIPPING_FACTOR
/ 2));
766 throttleMax
= MAX(throttleMax
- (rpyMixRange
/ 2), throttleMin
+ (throttleRange
/ 2) + (throttleRange
* THROTTLE_CLIPPING_FACTOR
/ 2));
769 // Now add in the desired throttle, but keep in a range that doesn't clip adjusted
770 // roll/pitch/yaw. This could move throttle down, but also up for those low throttle flips.
771 if (ARMING_FLAG(ARMED
)) {
772 bool isFailsafeActive
= failsafeIsActive();
774 for (i
= 0; i
< motorCount
; i
++) {
775 motor
[i
] = rpyMix
[i
] + constrain(throttleCommand
* currentMixer
[i
].throttle
, throttleMin
, throttleMax
);
777 if (isFailsafeActive
) {
778 motor
[i
] = constrain(motor
[i
], escAndServoConfig
->mincommand
, escAndServoConfig
->maxthrottle
);
779 } else if (feature(FEATURE_3D
)) {
780 if (throttlePrevious
<= (rxConfig
->midrc
- flight3DConfig
->deadband3d_throttle
)) {
781 motor
[i
] = constrain(motor
[i
], escAndServoConfig
->minthrottle
, flight3DConfig
->deadband3d_low
);
783 motor
[i
] = constrain(motor
[i
], flight3DConfig
->deadband3d_high
, escAndServoConfig
->maxthrottle
);
786 motor
[i
] = constrain(motor
[i
], escAndServoConfig
->minthrottle
, escAndServoConfig
->maxthrottle
);
789 // Motor stop handling
790 if (feature(FEATURE_MOTOR_STOP
) && ARMING_FLAG(ARMED
) && !feature(FEATURE_3D
)) {
791 if (((rcData
[THROTTLE
]) < rxConfig
->mincheck
)) {
792 motor
[i
] = escAndServoConfig
->mincommand
;
797 for (i
= 0; i
< motorCount
; i
++) {
798 motor
[i
] = motor_disarmed
[i
];
805 void servoMixer(uint16_t flaperon_throw_offset
, uint8_t flaperon_throw_inverted
)
807 int16_t input
[INPUT_SOURCE_COUNT
]; // Range [-500:+500]
808 static int16_t currentOutput
[MAX_SERVO_RULES
];
811 if (FLIGHT_MODE(PASSTHRU_MODE
)) {
812 // Direct passthru from RX
813 input
[INPUT_STABILIZED_ROLL
] = rcCommand
[ROLL
];
814 input
[INPUT_STABILIZED_PITCH
] = rcCommand
[PITCH
];
815 input
[INPUT_STABILIZED_YAW
] = rcCommand
[YAW
];
817 // Assisted modes (gyro only or gyro+acc according to AUX configuration in Gui
818 input
[INPUT_STABILIZED_ROLL
] = axisPID
[ROLL
];
819 input
[INPUT_STABILIZED_PITCH
] = axisPID
[PITCH
];
820 input
[INPUT_STABILIZED_YAW
] = axisPID
[YAW
];
822 // Reverse yaw servo when inverted in 3D mode
823 if (feature(FEATURE_3D
) && (rcData
[THROTTLE
] < rxConfig
->midrc
)) {
824 input
[INPUT_STABILIZED_YAW
] *= -1;
828 input
[INPUT_GIMBAL_PITCH
] = scaleRange(attitude
.values
.pitch
, -1800, 1800, -500, +500);
829 input
[INPUT_GIMBAL_ROLL
] = scaleRange(attitude
.values
.roll
, -1800, 1800, -500, +500);
831 input
[INPUT_STABILIZED_THROTTLE
] = motor
[0] - 1000 - 500; // Since it derives from rcCommand or mincommand and must be [-500:+500]
833 // center the RC input value around the RC middle value
834 // by subtracting the RC middle value from the RC input value, we get:
835 // data - middle = input
836 // 2000 - 1500 = +500
838 // 1000 - 1500 = -500
839 input
[INPUT_RC_ROLL
] = rcData
[ROLL
] - rxConfig
->midrc
;
840 input
[INPUT_RC_PITCH
] = rcData
[PITCH
] - rxConfig
->midrc
;
841 input
[INPUT_RC_YAW
] = rcData
[YAW
] - rxConfig
->midrc
;
842 input
[INPUT_RC_THROTTLE
] = rcData
[THROTTLE
] - rxConfig
->midrc
;
843 input
[INPUT_RC_AUX1
] = rcData
[AUX1
] - rxConfig
->midrc
;
844 input
[INPUT_RC_AUX2
] = rcData
[AUX2
] - rxConfig
->midrc
;
845 input
[INPUT_RC_AUX3
] = rcData
[AUX3
] - rxConfig
->midrc
;
846 input
[INPUT_RC_AUX4
] = rcData
[AUX4
] - rxConfig
->midrc
;
848 for (i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++)
851 // mix servos according to rules
852 for (i
= 0; i
< servoRuleCount
; i
++) {
853 // consider rule if no box assigned or box is active
854 if (currentServoMixer
[i
].box
== 0 || IS_RC_MODE_ACTIVE(BOXSERVO1
+ currentServoMixer
[i
].box
- 1)) {
855 uint8_t target
= currentServoMixer
[i
].targetChannel
;
856 uint8_t from
= currentServoMixer
[i
].inputSource
;
857 uint16_t servo_width
= servoConf
[target
].max
- servoConf
[target
].min
;
858 int16_t min
= currentServoMixer
[i
].min
* servo_width
/ 100 - servo_width
/ 2;
859 int16_t max
= currentServoMixer
[i
].max
* servo_width
/ 100 - servo_width
/ 2;
861 if (currentServoMixer
[i
].speed
== 0) {
862 currentOutput
[i
] = input
[from
];
864 if (currentOutput
[i
] < input
[from
]) {
865 currentOutput
[i
] = constrain(currentOutput
[i
] + currentServoMixer
[i
].speed
, currentOutput
[i
], input
[from
]);
866 } else if (currentOutput
[i
] > input
[from
]) {
867 currentOutput
[i
] = constrain(currentOutput
[i
] - currentServoMixer
[i
].speed
, input
[from
], currentOutput
[i
]);
874 if (FLIGHT_MODE(FLAPERON
) && (target
== SERVO_FLAPPERON_1
|| target
== SERVO_FLAPPERON_2
)) {
875 int8_t multiplier
= 1;
877 if (flaperon_throw_inverted
== 1) {
880 currentOutput
[i
] += flaperon_throw_offset
* getFlaperonDirection(target
) * multiplier
;
883 servo
[target
] += servoDirection(target
, from
) * constrain(((int32_t)currentOutput
[i
] * currentServoMixer
[i
].rate
) / 100, min
, max
);
885 currentOutput
[i
] = 0;
889 for (i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++) {
890 servo
[i
] = ((int32_t)servoConf
[i
].rate
* servo
[i
]) / 100L;
891 servo
[i
] += determineServoMiddleOrForwardFromChannel(i
);
895 void processServoTilt(void) {
896 // center at fixed position, or vary either pitch or roll by RC channel
897 servo
[SERVO_GIMBAL_PITCH
] = determineServoMiddleOrForwardFromChannel(SERVO_GIMBAL_PITCH
);
898 servo
[SERVO_GIMBAL_ROLL
] = determineServoMiddleOrForwardFromChannel(SERVO_GIMBAL_ROLL
);
900 if (IS_RC_MODE_ACTIVE(BOXCAMSTAB
)) {
901 if (gimbalConfig
->mode
== GIMBAL_MODE_MIXTILT
) {
902 servo
[SERVO_GIMBAL_PITCH
] -= (-(int32_t)servoConf
[SERVO_GIMBAL_PITCH
].rate
) * attitude
.values
.pitch
/ 50 - (int32_t)servoConf
[SERVO_GIMBAL_ROLL
].rate
* attitude
.values
.roll
/ 50;
903 servo
[SERVO_GIMBAL_ROLL
] += (-(int32_t)servoConf
[SERVO_GIMBAL_PITCH
].rate
) * attitude
.values
.pitch
/ 50 + (int32_t)servoConf
[SERVO_GIMBAL_ROLL
].rate
* attitude
.values
.roll
/ 50;
905 servo
[SERVO_GIMBAL_PITCH
] += (int32_t)servoConf
[SERVO_GIMBAL_PITCH
].rate
* attitude
.values
.pitch
/ 50;
906 servo
[SERVO_GIMBAL_ROLL
] += (int32_t)servoConf
[SERVO_GIMBAL_ROLL
].rate
* attitude
.values
.roll
/ 50;
911 bool isServoOutputEnabled(void)
913 return servoOutputEnabled
;
916 bool isMixerUsingServos(void) {
917 return mixerUsesServos
;
920 void filterServos(void)
924 if (mixerConfig
->servo_lowpass_enable
) {
925 // Initialize servo lowpass filter (servos are calculated at looptime rate)
926 if (!servoFilterIsSet
) {
927 for (servoIdx
= 0; servoIdx
< MAX_SUPPORTED_SERVOS
; servoIdx
++) {
928 biquadFilterInit(&servoFitlerState
[servoIdx
], mixerConfig
->servo_lowpass_freq
, gyro
.targetLooptime
);
931 servoFilterIsSet
= true;
934 for (servoIdx
= 0; servoIdx
< MAX_SUPPORTED_SERVOS
; servoIdx
++) {
935 // Apply servo lowpass filter and do sanity cheching
936 servo
[servoIdx
] = (int16_t) biquadFilterApply(&servoFitlerState
[servoIdx
], (float)servo
[servoIdx
]);
940 for (servoIdx
= 0; servoIdx
< MAX_SUPPORTED_SERVOS
; servoIdx
++) {
941 servo
[servoIdx
] = constrain(servo
[servoIdx
], servoConf
[servoIdx
].min
, servoConf
[servoIdx
].max
);