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_config.h"
26 #include "common/axis.h"
27 #include "common/maths.h"
29 #include "drivers/gpio.h"
30 #include "drivers/timer.h"
31 #include "drivers/pwm_output.h"
32 #include "drivers/pwm_mapping.h"
33 #include "drivers/sensor.h"
34 #include "drivers/accgyro.h"
38 #include "io/gimbal.h"
39 #include "io/escservo.h"
40 #include "io/rc_controls.h"
42 #include "sensors/sensors.h"
43 #include "sensors/acceleration.h"
45 #include "flight/mixer.h"
46 #include "flight/pid.h"
47 #include "flight/imu.h"
49 #include "config/runtime_config.h"
50 #include "config/config.h"
52 #define GIMBAL_SERVO_PITCH 0
53 #define GIMBAL_SERVO_ROLL 1
55 #define AUX_FORWARD_CHANNEL_TO_SERVO_COUNT 4
57 uint8_t motorCount
= 0;
58 int16_t motor
[MAX_SUPPORTED_MOTORS
];
59 int16_t motor_disarmed
[MAX_SUPPORTED_MOTORS
];
60 int16_t servo
[MAX_SUPPORTED_SERVOS
] = { 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500 };
64 static uint8_t servoCount
;
66 static servoParam_t
*servoConf
;
67 static mixerConfig_t
*mixerConfig
;
68 static flight3DConfig_t
*flight3DConfig
;
69 static escAndServoConfig_t
*escAndServoConfig
;
70 static airplaneConfig_t
*airplaneConfig
;
71 static rxConfig_t
*rxConfig
;
72 static gimbalConfig_t
*gimbalConfig
;
74 static motorMixer_t currentMixer
[MAX_SUPPORTED_MOTORS
];
75 static mixerMode_e currentMixerMode
;
77 static const motorMixer_t mixerQuadX
[] = {
78 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
79 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
80 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
81 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
83 #ifndef USE_QUAD_MIXER_ONLY
84 static const motorMixer_t mixerTri
[] = {
85 { 1.0f
, 0.0f
, 1.333333f
, 0.0f
}, // REAR
86 { 1.0f
, -1.0f
, -0.666667f
, 0.0f
}, // RIGHT
87 { 1.0f
, 1.0f
, -0.666667f
, 0.0f
}, // LEFT
90 static const motorMixer_t mixerQuadP
[] = {
91 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
92 { 1.0f
, -1.0f
, 0.0f
, 1.0f
}, // RIGHT
93 { 1.0f
, 1.0f
, 0.0f
, 1.0f
}, // LEFT
94 { 1.0f
, 0.0f
, -1.0f
, -1.0f
}, // FRONT
97 static const motorMixer_t mixerBi
[] = {
98 { 1.0f
, 1.0f
, 0.0f
, 0.0f
}, // LEFT
99 { 1.0f
, -1.0f
, 0.0f
, 0.0f
}, // RIGHT
102 static const motorMixer_t mixerY6
[] = {
103 { 1.0f
, 0.0f
, 1.333333f
, 1.0f
}, // REAR
104 { 1.0f
, -1.0f
, -0.666667f
, -1.0f
}, // RIGHT
105 { 1.0f
, 1.0f
, -0.666667f
, -1.0f
}, // LEFT
106 { 1.0f
, 0.0f
, 1.333333f
, -1.0f
}, // UNDER_REAR
107 { 1.0f
, -1.0f
, -0.666667f
, 1.0f
}, // UNDER_RIGHT
108 { 1.0f
, 1.0f
, -0.666667f
, 1.0f
}, // UNDER_LEFT
111 static const motorMixer_t mixerHex6P
[] = {
112 { 1.0f
, -0.866025f
, 0.5f
, 1.0f
}, // REAR_R
113 { 1.0f
, -0.866025f
, -0.5f
, -1.0f
}, // FRONT_R
114 { 1.0f
, 0.866025f
, 0.5f
, 1.0f
}, // REAR_L
115 { 1.0f
, 0.866025f
, -0.5f
, -1.0f
}, // FRONT_L
116 { 1.0f
, 0.0f
, -1.0f
, 1.0f
}, // FRONT
117 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
120 static const motorMixer_t mixerY4
[] = {
121 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR_TOP CW
122 { 1.0f
, -1.0f
, -1.0f
, 0.0f
}, // FRONT_R CCW
123 { 1.0f
, 0.0f
, 1.0f
, 1.0f
}, // REAR_BOTTOM CCW
124 { 1.0f
, 1.0f
, -1.0f
, 0.0f
}, // FRONT_L CW
127 static const motorMixer_t mixerHex6X
[] = {
128 { 1.0f
, -0.5f
, 0.866025f
, 1.0f
}, // REAR_R
129 { 1.0f
, -0.5f
, -0.866025f
, 1.0f
}, // FRONT_R
130 { 1.0f
, 0.5f
, 0.866025f
, -1.0f
}, // REAR_L
131 { 1.0f
, 0.5f
, -0.866025f
, -1.0f
}, // FRONT_L
132 { 1.0f
, -1.0f
, 0.0f
, -1.0f
}, // RIGHT
133 { 1.0f
, 1.0f
, 0.0f
, 1.0f
}, // LEFT
136 static const motorMixer_t mixerOctoX8
[] = {
137 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
138 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
139 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
140 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
141 { 1.0f
, -1.0f
, 1.0f
, 1.0f
}, // UNDER_REAR_R
142 { 1.0f
, -1.0f
, -1.0f
, -1.0f
}, // UNDER_FRONT_R
143 { 1.0f
, 1.0f
, 1.0f
, -1.0f
}, // UNDER_REAR_L
144 { 1.0f
, 1.0f
, -1.0f
, 1.0f
}, // UNDER_FRONT_L
147 static const motorMixer_t mixerOctoFlatP
[] = {
148 { 1.0f
, 0.707107f
, -0.707107f
, 1.0f
}, // FRONT_L
149 { 1.0f
, -0.707107f
, -0.707107f
, 1.0f
}, // FRONT_R
150 { 1.0f
, -0.707107f
, 0.707107f
, 1.0f
}, // REAR_R
151 { 1.0f
, 0.707107f
, 0.707107f
, 1.0f
}, // REAR_L
152 { 1.0f
, 0.0f
, -1.0f
, -1.0f
}, // FRONT
153 { 1.0f
, -1.0f
, 0.0f
, -1.0f
}, // RIGHT
154 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
155 { 1.0f
, 1.0f
, 0.0f
, -1.0f
}, // LEFT
158 static const motorMixer_t mixerOctoFlatX
[] = {
159 { 1.0f
, 1.0f
, -0.5f
, 1.0f
}, // MIDFRONT_L
160 { 1.0f
, -0.5f
, -1.0f
, 1.0f
}, // FRONT_R
161 { 1.0f
, -1.0f
, 0.5f
, 1.0f
}, // MIDREAR_R
162 { 1.0f
, 0.5f
, 1.0f
, 1.0f
}, // REAR_L
163 { 1.0f
, 0.5f
, -1.0f
, -1.0f
}, // FRONT_L
164 { 1.0f
, -1.0f
, -0.5f
, -1.0f
}, // MIDFRONT_R
165 { 1.0f
, -0.5f
, 1.0f
, -1.0f
}, // REAR_R
166 { 1.0f
, 1.0f
, 0.5f
, -1.0f
}, // MIDREAR_L
169 static const motorMixer_t mixerVtail4
[] = {
170 { 1.0f
, 0.0f
, 1.0f
, 1.0f
}, // REAR_R
171 { 1.0f
, -1.0f
, -1.0f
, 0.0f
}, // FRONT_R
172 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR_L
173 { 1.0f
, 1.0f
, -1.0f
, -0.0f
}, // FRONT_L
176 static const motorMixer_t mixerAtail4
[] = {
177 { 1.0f
, 0.0f
, 1.0f
, 1.0f
}, // REAR_R
178 { 1.0f
, -1.0f
, -1.0f
, 0.0f
}, // FRONT_R
179 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR_L
180 { 1.0f
, 1.0f
, -1.0f
, -0.0f
}, // FRONT_L
183 static const motorMixer_t mixerHex6H
[] = {
184 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
185 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
186 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
187 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
188 { 1.0f
, 0.0f
, 0.0f
, 0.0f
}, // RIGHT
189 { 1.0f
, 0.0f
, 0.0f
, 0.0f
}, // LEFT
192 static const motorMixer_t mixerDualcopter
[] = {
193 { 1.0f
, 0.0f
, 0.0f
, -1.0f
}, // LEFT
194 { 1.0f
, 0.0f
, 0.0f
, 1.0f
}, // RIGHT
197 // Keep this synced with MultiType struct in mw.h!
198 const mixer_t mixers
[] = {
200 { 0, 0, NULL
}, // entry 0
201 { 3, 1, mixerTri
}, // MIXER_TRI
202 { 4, 0, mixerQuadP
}, // MIXER_QUADP
203 { 4, 0, mixerQuadX
}, // MIXER_QUADX
204 { 2, 1, mixerBi
}, // MIXER_BI
205 { 0, 1, NULL
}, // * MIXER_GIMBAL
206 { 6, 0, mixerY6
}, // MIXER_Y6
207 { 6, 0, mixerHex6P
}, // MIXER_HEX6
208 { 1, 1, NULL
}, // * MIXER_FLYING_WING
209 { 4, 0, mixerY4
}, // MIXER_Y4
210 { 6, 0, mixerHex6X
}, // MIXER_HEX6X
211 { 8, 0, mixerOctoX8
}, // MIXER_OCTOX8
212 { 8, 0, mixerOctoFlatP
}, // MIXER_OCTOFLATP
213 { 8, 0, mixerOctoFlatX
}, // MIXER_OCTOFLATX
214 { 1, 1, NULL
}, // * MIXER_AIRPLANE
215 { 0, 1, NULL
}, // * MIXER_HELI_120_CCPM
216 { 0, 1, NULL
}, // * MIXER_HELI_90_DEG
217 { 4, 0, mixerVtail4
}, // MIXER_VTAIL4
218 { 6, 0, mixerHex6H
}, // MIXER_HEX6H
219 { 0, 1, NULL
}, // * MIXER_PPM_TO_SERVO
220 { 2, 1, mixerDualcopter
}, // MIXER_DUALCOPTER
221 { 1, 1, NULL
}, // MIXER_SINGLECOPTER
222 { 4, 0, mixerAtail4
}, // MIXER_ATAIL4
223 { 0, 0, NULL
}, // MIXER_CUSTOM
227 void mixerUseConfigs(servoParam_t
*servoConfToUse
, flight3DConfig_t
*flight3DConfigToUse
, escAndServoConfig_t
*escAndServoConfigToUse
, mixerConfig_t
*mixerConfigToUse
, airplaneConfig_t
*airplaneConfigToUse
, rxConfig_t
*rxConfigToUse
, gimbalConfig_t
*gimbalConfigToUse
)
229 servoConf
= servoConfToUse
;
230 flight3DConfig
= flight3DConfigToUse
;
231 escAndServoConfig
= escAndServoConfigToUse
;
232 mixerConfig
= mixerConfigToUse
;
233 airplaneConfig
= airplaneConfigToUse
;
234 rxConfig
= rxConfigToUse
;
235 gimbalConfig
= gimbalConfigToUse
;
238 int16_t determineServoMiddleOrForwardFromChannel(int nr
)
240 uint8_t channelToForwardFrom
= servoConf
[nr
].forwardFromChannel
;
242 if (channelToForwardFrom
!= CHANNEL_FORWARDING_DISABLED
&& channelToForwardFrom
< rxRuntimeConfig
.channelCount
) {
243 return rcData
[channelToForwardFrom
];
246 if (nr
< MAX_SUPPORTED_SERVOS
) {
247 return servoConf
[nr
].middle
;
250 return DEFAULT_SERVO_MIDDLE
;
253 int servoDirection(int nr
, int lr
)
255 // servo.rate is overloaded for servos that don't have a rate, but only need direction
256 // bit set = negative, clear = positive
257 // rate[2] = ???_direction
258 // rate[1] = roll_direction
259 // rate[0] = pitch_direction
260 // servo.rate is also used as gimbal gain multiplier (yeah)
261 if (servoConf
[nr
].rate
& lr
)
267 static motorMixer_t
*customMixers
;
270 #ifndef USE_QUAD_MIXER_ONLY
271 void mixerInit(mixerMode_e mixerMode
, motorMixer_t
*initialCustomMixers
)
273 currentMixerMode
= mixerMode
;
275 customMixers
= initialCustomMixers
;
277 // enable servos for mixes that require them. note, this shifts motor counts.
278 useServo
= mixers
[currentMixerMode
].useServo
;
279 // if we want camstab/trig, that also enables servos, even if mixer doesn't
280 if (feature(FEATURE_SERVO_TILT
))
284 void mixerUsePWMOutputConfiguration(pwmOutputConfiguration_t
*pwmOutputConfiguration
)
288 servoCount
= pwmOutputConfiguration
->servoCount
;
290 if (currentMixerMode
== MIXER_CUSTOM
) {
291 // load custom mixer into currentMixer
292 for (i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
294 if (customMixers
[i
].throttle
== 0.0f
)
296 currentMixer
[i
] = customMixers
[i
];
300 motorCount
= mixers
[currentMixerMode
].motorCount
;
301 // copy motor-based mixers
302 if (mixers
[currentMixerMode
].motor
) {
303 for (i
= 0; i
< motorCount
; i
++)
304 currentMixer
[i
] = mixers
[currentMixerMode
].motor
[i
];
308 // in 3D mode, mixer gain has to be halved
309 if (feature(FEATURE_3D
)) {
310 if (motorCount
> 1) {
311 for (i
= 0; i
< motorCount
; i
++) {
312 currentMixer
[i
].pitch
*= 0.5f
;
313 currentMixer
[i
].roll
*= 0.5f
;
314 currentMixer
[i
].yaw
*= 0.5f
;
319 // set flag that we're on something with wings
320 if (currentMixerMode
== MIXER_FLYING_WING
||
321 currentMixerMode
== MIXER_AIRPLANE
)
322 ENABLE_STATE(FIXED_WING
);
324 DISABLE_STATE(FIXED_WING
);
329 void mixerLoadMix(int index
, motorMixer_t
*customMixers
)
336 for (i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++)
337 customMixers
[i
].throttle
= 0.0f
;
339 // do we have anything here to begin with?
340 if (mixers
[index
].motor
!= NULL
) {
341 for (i
= 0; i
< mixers
[index
].motorCount
; i
++)
342 customMixers
[i
] = mixers
[index
].motor
[i
];
348 void mixerInit(mixerMode_e mixerMode
, motorMixer_t
*initialCustomMixers
)
350 currentMixerMode
= mixerMode
;
352 customMixers
= initialCustomMixers
;
357 void mixerUsePWMOutputConfiguration(pwmOutputConfiguration_t
*pwmOutputConfiguration
)
359 UNUSED(pwmOutputConfiguration
);
364 for (i
= 0; i
< motorCount
; i
++) {
365 currentMixer
[i
] = mixerQuadX
[i
];
372 void mixerResetMotors(void)
375 // set disarmed motor values
376 for (i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++)
377 motor_disarmed
[i
] = feature(FEATURE_3D
) ? flight3DConfig
->neutral3d
: escAndServoConfig
->mincommand
;
380 static void updateGimbalServos(void)
382 pwmWriteServo(0, servo
[0]);
383 pwmWriteServo(1, servo
[1]);
386 void writeServos(void)
391 switch (currentMixerMode
) {
393 pwmWriteServo(0, servo
[4]);
394 pwmWriteServo(1, servo
[5]);
398 if (mixerConfig
->tri_unarmed_servo
) {
399 // if unarmed flag set, we always move servo
400 pwmWriteServo(0, servo
[5]);
402 // otherwise, only move servo when copter is armed
403 if (ARMING_FLAG(ARMED
))
404 pwmWriteServo(0, servo
[5]);
406 pwmWriteServo(0, 0); // kill servo signal completely.
410 case MIXER_FLYING_WING
:
411 pwmWriteServo(0, servo
[3]);
412 pwmWriteServo(1, servo
[4]);
416 updateGimbalServos();
419 case MIXER_DUALCOPTER
:
420 pwmWriteServo(0, servo
[4]);
421 pwmWriteServo(1, servo
[5]);
425 case MIXER_SINGLECOPTER
:
426 pwmWriteServo(0, servo
[3]);
427 pwmWriteServo(1, servo
[4]);
428 pwmWriteServo(2, servo
[5]);
429 pwmWriteServo(3, servo
[6]);
433 // Two servos for SERVO_TILT, if enabled
434 if (feature(FEATURE_SERVO_TILT
)) {
435 updateGimbalServos();
441 void writeMotors(void)
445 for (i
= 0; i
< motorCount
; i
++)
446 pwmWriteMotor(i
, motor
[i
]);
449 if (feature(FEATURE_ONESHOT125
)) {
450 pwmCompleteOneshotMotorUpdate(motorCount
);
454 void writeAllMotors(int16_t mc
)
458 // Sends commands to all motors
459 for (i
= 0; i
< motorCount
; i
++)
464 #ifndef USE_QUAD_MIXER_ONLY
465 static void airplaneMixer(void)
467 int16_t flapperons
[2] = { 0, 0 };
470 if (!ARMING_FLAG(ARMED
))
471 servo
[7] = escAndServoConfig
->mincommand
; // Kill throttle when disarmed
473 servo
[7] = constrain(rcCommand
[THROTTLE
], escAndServoConfig
->minthrottle
, escAndServoConfig
->maxthrottle
);
476 if (airplaneConfig
->flaps_speed
) {
477 // configure SERVO3 middle point in GUI to using an AUX channel for FLAPS control
478 // use servo min, servo max and servo rate for proper endpoints adjust
479 static int16_t slow_LFlaps
;
480 int16_t lFlap
= determineServoMiddleOrForwardFromChannel(2);
482 lFlap
= constrain(lFlap
, servoConf
[2].min
, servoConf
[2].max
);
483 lFlap
= escAndServoConfig
->servoCenterPulse
- lFlap
;
484 if (slow_LFlaps
< lFlap
)
485 slow_LFlaps
+= airplaneConfig
->flaps_speed
;
486 else if (slow_LFlaps
> lFlap
)
487 slow_LFlaps
-= airplaneConfig
->flaps_speed
;
489 servo
[2] = ((int32_t)servoConf
[2].rate
* slow_LFlaps
) / 100L;
490 servo
[2] += escAndServoConfig
->servoCenterPulse
;
493 if (FLIGHT_MODE(PASSTHRU_MODE
)) { // Direct passthru from RX
494 servo
[3] = rcCommand
[ROLL
] + flapperons
[0]; // Wing 1
495 servo
[4] = rcCommand
[ROLL
] + flapperons
[1]; // Wing 2
496 servo
[5] = rcCommand
[YAW
]; // Rudder
497 servo
[6] = rcCommand
[PITCH
]; // Elevator
499 // Assisted modes (gyro only or gyro+acc according to AUX configuration in Gui
500 servo
[3] = axisPID
[ROLL
] + flapperons
[0]; // Wing 1
501 servo
[4] = axisPID
[ROLL
] + flapperons
[1]; // Wing 2
502 servo
[5] = axisPID
[YAW
]; // Rudder
503 servo
[6] = axisPID
[PITCH
]; // Elevator
505 for (i
= 3; i
< 7; i
++) {
506 servo
[i
] = ((int32_t)servoConf
[i
].rate
* servo
[i
]) / 100L; // servo rates
507 servo
[i
] += determineServoMiddleOrForwardFromChannel(i
);
517 if (motorCount
> 3) {
518 // prevent "yaw jump" during yaw correction
519 axisPID
[YAW
] = constrain(axisPID
[YAW
], -100 - ABS(rcCommand
[YAW
]), +100 + ABS(rcCommand
[YAW
]));
522 // motors for non-servo mixes
524 for (i
= 0; i
< motorCount
; i
++)
526 rcCommand
[THROTTLE
] * currentMixer
[i
].throttle
+
527 axisPID
[PITCH
] * currentMixer
[i
].pitch
+
528 axisPID
[ROLL
] * currentMixer
[i
].roll
+
529 -mixerConfig
->yaw_direction
* axisPID
[YAW
] * currentMixer
[i
].yaw
;
531 #ifndef USE_QUAD_MIXER_ONLY
532 // airplane / servo mixes
533 switch (currentMixerMode
) {
535 servo
[4] = (servoDirection(4, 2) * axisPID
[YAW
]) + (servoDirection(4, 1) * axisPID
[PITCH
]) + determineServoMiddleOrForwardFromChannel(4); // LEFT
536 servo
[5] = (servoDirection(5, 2) * axisPID
[YAW
]) + (servoDirection(5, 1) * axisPID
[PITCH
]) + determineServoMiddleOrForwardFromChannel(5); // RIGHT
540 servo
[5] = (servoDirection(5, 1) * axisPID
[YAW
]) + determineServoMiddleOrForwardFromChannel(5); // REAR
544 servo
[GIMBAL_SERVO_PITCH
] = (((int32_t)servoConf
[GIMBAL_SERVO_PITCH
].rate
* inclination
.values
.pitchDeciDegrees
) / 50) + determineServoMiddleOrForwardFromChannel(GIMBAL_SERVO_PITCH
);
545 servo
[GIMBAL_SERVO_ROLL
] = (((int32_t)servoConf
[GIMBAL_SERVO_ROLL
].rate
* inclination
.values
.rollDeciDegrees
) / 50) + determineServoMiddleOrForwardFromChannel(GIMBAL_SERVO_ROLL
);
552 case MIXER_FLYING_WING
:
553 if (!ARMING_FLAG(ARMED
))
554 servo
[7] = escAndServoConfig
->mincommand
;
556 servo
[7] = constrain(rcCommand
[THROTTLE
], escAndServoConfig
->minthrottle
, escAndServoConfig
->maxthrottle
);
558 if (FLIGHT_MODE(PASSTHRU_MODE
)) {
559 // do not use sensors for correction, simple 2 channel mixing
560 servo
[3] = (servoDirection(3, 1) * rcCommand
[PITCH
]) + (servoDirection(3, 2) * rcCommand
[ROLL
]);
561 servo
[4] = (servoDirection(4, 1) * rcCommand
[PITCH
]) + (servoDirection(4, 2) * rcCommand
[ROLL
]);
563 // use sensors to correct (gyro only or gyro + acc)
564 servo
[3] = (servoDirection(3, 1) * axisPID
[PITCH
]) + (servoDirection(3, 2) * axisPID
[ROLL
]);
565 servo
[4] = (servoDirection(4, 1) * axisPID
[PITCH
]) + (servoDirection(4, 2) * axisPID
[ROLL
]);
567 servo
[3] += determineServoMiddleOrForwardFromChannel(3);
568 servo
[4] += determineServoMiddleOrForwardFromChannel(4);
571 case MIXER_DUALCOPTER
:
572 for (i
= 4; i
< 6; i
++) {
573 servo
[i
] = axisPID
[5 - i
] * servoDirection(i
, 1); // mix and setup direction
574 servo
[i
] += determineServoMiddleOrForwardFromChannel(i
);
578 case MIXER_SINGLECOPTER
:
579 for (i
= 3; i
< 7; i
++) {
580 servo
[i
] = (axisPID
[YAW
] * servoDirection(i
, 2)) + (axisPID
[(6 - i
) >> 1] * servoDirection(i
, 1)); // mix and setup direction
581 servo
[i
] += determineServoMiddleOrForwardFromChannel(i
);
583 motor
[0] = rcCommand
[THROTTLE
];
592 if (feature(FEATURE_SERVO_TILT
)) {
593 // center at fixed position, or vary either pitch or roll by RC channel
594 servo
[0] = determineServoMiddleOrForwardFromChannel(0);
595 servo
[1] = determineServoMiddleOrForwardFromChannel(1);
597 if (IS_RC_MODE_ACTIVE(BOXCAMSTAB
)) {
598 if (gimbalConfig
->gimbal_flags
& GIMBAL_MIXTILT
) {
599 servo
[0] -= (-(int32_t)servoConf
[0].rate
) * inclination
.values
.pitchDeciDegrees
/ 50 - (int32_t)servoConf
[1].rate
* inclination
.values
.rollDeciDegrees
/ 50;
600 servo
[1] += (-(int32_t)servoConf
[0].rate
) * inclination
.values
.pitchDeciDegrees
/ 50 + (int32_t)servoConf
[1].rate
* inclination
.values
.rollDeciDegrees
/ 50;
602 servo
[0] += (int32_t)servoConf
[0].rate
* inclination
.values
.pitchDeciDegrees
/ 50;
603 servo
[1] += (int32_t)servoConf
[1].rate
* inclination
.values
.rollDeciDegrees
/ 50;
609 for (i
= 0; i
< MAX_SUPPORTED_SERVOS
; i
++)
610 servo
[i
] = constrain(servo
[i
], servoConf
[i
].min
, servoConf
[i
].max
); // limit the values
612 // forward AUX1-4 to servo outputs (not constrained)
613 if (gimbalConfig
->gimbal_flags
& GIMBAL_FORWARDAUX
) {
614 // offset servos based off number already used in mixer types
615 // airplane and servo_tilt together can't be used
616 int8_t firstServo
= servoCount
- AUX_FORWARD_CHANNEL_TO_SERVO_COUNT
;
618 // start forwarding from this channel
619 uint8_t channelOffset
= AUX1
;
622 for (servoOffset
= 0; servoOffset
< AUX_FORWARD_CHANNEL_TO_SERVO_COUNT
; servoOffset
++) {
623 if (firstServo
+ servoOffset
< 0) {
624 continue; // there are not enough servos to forward all the AUX channels.
626 pwmWriteServo(firstServo
+ servoOffset
, rcData
[channelOffset
++]);
631 for (i
= 1; i
< motorCount
; i
++)
632 if (motor
[i
] > maxMotor
)
634 for (i
= 0; i
< motorCount
; i
++) {
635 if (maxMotor
> escAndServoConfig
->maxthrottle
) // this is a way to still have good gyro corrections if at least one motor reaches its max.
636 motor
[i
] -= maxMotor
- escAndServoConfig
->maxthrottle
;
637 if (feature(FEATURE_3D
)) {
638 if ((rcData
[THROTTLE
]) > rxConfig
->midrc
) {
639 motor
[i
] = constrain(motor
[i
], flight3DConfig
->deadband3d_high
, escAndServoConfig
->maxthrottle
);
641 motor
[i
] = constrain(motor
[i
], escAndServoConfig
->mincommand
, flight3DConfig
->deadband3d_low
);
644 motor
[i
] = constrain(motor
[i
], escAndServoConfig
->minthrottle
, escAndServoConfig
->maxthrottle
);
645 if ((rcData
[THROTTLE
]) < rxConfig
->mincheck
) {
646 if (!feature(FEATURE_MOTOR_STOP
))
647 motor
[i
] = escAndServoConfig
->minthrottle
;
649 motor
[i
] = escAndServoConfig
->mincommand
;
652 if (!ARMING_FLAG(ARMED
)) {
653 motor
[i
] = motor_disarmed
[i
];
659 bool isMixerUsingServos(void)