2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
27 #include "build/build_config.h"
28 #include "build/debug.h"
30 #include "config/config.h"
31 #include "config/feature.h"
33 #include "drivers/dshot.h"
35 #include "fc/controlrate_profile.h"
36 #include "fc/runtime_config.h"
38 #include "flight/mixer_tricopter.h"
39 #include "flight/pid.h"
43 #include "sensors/battery.h"
45 #include "mixer_init.h"
47 PG_REGISTER_WITH_RESET_TEMPLATE(mixerConfig_t
, mixerConfig
, PG_MIXER_CONFIG
, 0);
49 PG_RESET_TEMPLATE(mixerConfig_t
, mixerConfig
,
50 .mixerMode
= DEFAULT_MIXER
,
51 .yaw_motors_reversed
= false,
52 .crashflip_motor_percent
= 0,
54 .mixer_type
= MIXER_LEGACY
,
57 PG_REGISTER_ARRAY(motorMixer_t
, MAX_SUPPORTED_MOTORS
, customMotorMixer
, PG_MOTOR_MIXER
, 0);
59 mixerMode_e currentMixerMode
;
61 static const motorMixer_t mixerQuadX
[] = {
62 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
63 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
64 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
65 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
67 #ifndef USE_QUAD_MIXER_ONLY
68 static const motorMixer_t mixerTricopter
[] = {
69 { 1.0f
, 0.0f
, 1.333333f
, 0.0f
}, // REAR
70 { 1.0f
, -1.0f
, -0.666667f
, 0.0f
}, // RIGHT
71 { 1.0f
, 1.0f
, -0.666667f
, 0.0f
}, // LEFT
74 static const motorMixer_t mixerQuadP
[] = {
75 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
76 { 1.0f
, -1.0f
, 0.0f
, 1.0f
}, // RIGHT
77 { 1.0f
, 1.0f
, 0.0f
, 1.0f
}, // LEFT
78 { 1.0f
, 0.0f
, -1.0f
, -1.0f
}, // FRONT
81 #if defined(USE_UNCOMMON_MIXERS)
82 static const motorMixer_t mixerBicopter
[] = {
83 { 1.0f
, 1.0f
, 0.0f
, 0.0f
}, // LEFT
84 { 1.0f
, -1.0f
, 0.0f
, 0.0f
}, // RIGHT
87 #define mixerBicopter NULL
90 static const motorMixer_t mixerY4
[] = {
91 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR_TOP CW
92 { 1.0f
, -1.0f
, -1.0f
, 0.0f
}, // FRONT_R CCW
93 { 1.0f
, 0.0f
, 1.0f
, 1.0f
}, // REAR_BOTTOM CCW
94 { 1.0f
, 1.0f
, -1.0f
, 0.0f
}, // FRONT_L CW
98 #if (MAX_SUPPORTED_MOTORS >= 6)
99 static const motorMixer_t mixerHex6X
[] = {
100 { 1.0f
, -0.5f
, 0.866025f
, 1.0f
}, // REAR_R
101 { 1.0f
, -0.5f
, -0.866025f
, 1.0f
}, // FRONT_R
102 { 1.0f
, 0.5f
, 0.866025f
, -1.0f
}, // REAR_L
103 { 1.0f
, 0.5f
, -0.866025f
, -1.0f
}, // FRONT_L
104 { 1.0f
, -1.0f
, 0.0f
, -1.0f
}, // RIGHT
105 { 1.0f
, 1.0f
, 0.0f
, 1.0f
}, // LEFT
108 #if defined(USE_UNCOMMON_MIXERS)
109 static const motorMixer_t mixerHex6H
[] = {
110 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
111 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
112 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
113 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
114 { 1.0f
, 0.0f
, 0.0f
, 0.0f
}, // RIGHT
115 { 1.0f
, 0.0f
, 0.0f
, 0.0f
}, // LEFT
118 static const motorMixer_t mixerHex6P
[] = {
119 { 1.0f
, -0.866025f
, 0.5f
, 1.0f
}, // REAR_R
120 { 1.0f
, -0.866025f
, -0.5f
, -1.0f
}, // FRONT_R
121 { 1.0f
, 0.866025f
, 0.5f
, 1.0f
}, // REAR_L
122 { 1.0f
, 0.866025f
, -0.5f
, -1.0f
}, // FRONT_L
123 { 1.0f
, 0.0f
, -1.0f
, 1.0f
}, // FRONT
124 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
126 static const motorMixer_t mixerY6
[] = {
127 { 1.0f
, 0.0f
, 1.333333f
, 1.0f
}, // REAR
128 { 1.0f
, -1.0f
, -0.666667f
, -1.0f
}, // RIGHT
129 { 1.0f
, 1.0f
, -0.666667f
, -1.0f
}, // LEFT
130 { 1.0f
, 0.0f
, 1.333333f
, -1.0f
}, // UNDER_REAR
131 { 1.0f
, -1.0f
, -0.666667f
, 1.0f
}, // UNDER_RIGHT
132 { 1.0f
, 1.0f
, -0.666667f
, 1.0f
}, // UNDER_LEFT
135 #define mixerHex6H NULL
136 #define mixerHex6P NULL
138 #endif // USE_UNCOMMON_MIXERS
140 #define mixerHex6X NULL
141 #endif // MAX_SUPPORTED_MOTORS >= 6
143 #if defined(USE_UNCOMMON_MIXERS) && (MAX_SUPPORTED_MOTORS >= 8)
144 static const motorMixer_t mixerOctoX8
[] = {
145 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
146 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
147 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
148 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
149 { 1.0f
, -1.0f
, 1.0f
, 1.0f
}, // UNDER_REAR_R
150 { 1.0f
, -1.0f
, -1.0f
, -1.0f
}, // UNDER_FRONT_R
151 { 1.0f
, 1.0f
, 1.0f
, -1.0f
}, // UNDER_REAR_L
152 { 1.0f
, 1.0f
, -1.0f
, 1.0f
}, // UNDER_FRONT_L
155 static const motorMixer_t mixerOctoFlatP
[] = {
156 { 1.0f
, 0.707107f
, -0.707107f
, 1.0f
}, // FRONT_L
157 { 1.0f
, -0.707107f
, -0.707107f
, 1.0f
}, // FRONT_R
158 { 1.0f
, -0.707107f
, 0.707107f
, 1.0f
}, // REAR_R
159 { 1.0f
, 0.707107f
, 0.707107f
, 1.0f
}, // REAR_L
160 { 1.0f
, 0.0f
, -1.0f
, -1.0f
}, // FRONT
161 { 1.0f
, -1.0f
, 0.0f
, -1.0f
}, // RIGHT
162 { 1.0f
, 0.0f
, 1.0f
, -1.0f
}, // REAR
163 { 1.0f
, 1.0f
, 0.0f
, -1.0f
}, // LEFT
166 static const motorMixer_t mixerOctoFlatX
[] = {
167 { 1.0f
, 1.0f
, -0.414178f
, 1.0f
}, // MIDFRONT_L
168 { 1.0f
, -0.414178f
, -1.0f
, 1.0f
}, // FRONT_R
169 { 1.0f
, -1.0f
, 0.414178f
, 1.0f
}, // MIDREAR_R
170 { 1.0f
, 0.414178f
, 1.0f
, 1.0f
}, // REAR_L
171 { 1.0f
, 0.414178f
, -1.0f
, -1.0f
}, // FRONT_L
172 { 1.0f
, -1.0f
, -0.414178f
, -1.0f
}, // MIDFRONT_R
173 { 1.0f
, -0.414178f
, 1.0f
, -1.0f
}, // REAR_R
174 { 1.0f
, 1.0f
, 0.414178f
, -1.0f
}, // MIDREAR_L
177 #define mixerOctoX8 NULL
178 #define mixerOctoFlatP NULL
179 #define mixerOctoFlatX NULL
182 static const motorMixer_t mixerVtail4
[] = {
183 { 1.0f
, -0.58f
, 0.58f
, 1.0f
}, // REAR_R
184 { 1.0f
, -0.46f
, -0.39f
, -0.5f
}, // FRONT_R
185 { 1.0f
, 0.58f
, 0.58f
, -1.0f
}, // REAR_L
186 { 1.0f
, 0.46f
, -0.39f
, 0.5f
}, // FRONT_L
189 static const motorMixer_t mixerAtail4
[] = {
190 { 1.0f
, -0.58f
, 0.58f
, -1.0f
}, // REAR_R
191 { 1.0f
, -0.46f
, -0.39f
, 0.5f
}, // FRONT_R
192 { 1.0f
, 0.58f
, 0.58f
, 1.0f
}, // REAR_L
193 { 1.0f
, 0.46f
, -0.39f
, -0.5f
}, // FRONT_L
196 #if defined(USE_UNCOMMON_MIXERS)
197 static const motorMixer_t mixerDualcopter
[] = {
198 { 1.0f
, 0.0f
, 0.0f
, -1.0f
}, // LEFT
199 { 1.0f
, 0.0f
, 0.0f
, 1.0f
}, // RIGHT
202 #define mixerDualcopter NULL
205 static const motorMixer_t mixerSingleProp
[] = {
206 { 1.0f
, 0.0f
, 0.0f
, 0.0f
},
209 static const motorMixer_t mixerQuadX1234
[] = {
210 { 1.0f
, 1.0f
, -1.0f
, -1.0f
}, // FRONT_L
211 { 1.0f
, -1.0f
, -1.0f
, 1.0f
}, // FRONT_R
212 { 1.0f
, -1.0f
, 1.0f
, -1.0f
}, // REAR_R
213 { 1.0f
, 1.0f
, 1.0f
, 1.0f
}, // REAR_L
216 // Keep synced with mixerMode_e
217 // Some of these entries are bogus when servos (USE_SERVOS) are not configured,
218 // but left untouched to keep ordinals synced with mixerMode_e (and configurator).
219 const mixer_t mixers
[] = {
220 // motors, use servo, motor mixer
221 { 0, false, NULL
}, // entry 0
222 { 3, true, mixerTricopter
}, // MIXER_TRI
223 { 4, false, mixerQuadP
}, // MIXER_QUADP
224 { 4, false, mixerQuadX
}, // MIXER_QUADX
225 { 2, true, mixerBicopter
}, // MIXER_BICOPTER
226 { 0, true, NULL
}, // * MIXER_GIMBAL
227 { 6, false, mixerY6
}, // MIXER_Y6
228 { 6, false, mixerHex6P
}, // MIXER_HEX6
229 { 1, true, mixerSingleProp
}, // * MIXER_FLYING_WING
230 { 4, false, mixerY4
}, // MIXER_Y4
231 { 6, false, mixerHex6X
}, // MIXER_HEX6X
232 { 8, false, mixerOctoX8
}, // MIXER_OCTOX8
233 { 8, false, mixerOctoFlatP
}, // MIXER_OCTOFLATP
234 { 8, false, mixerOctoFlatX
}, // MIXER_OCTOFLATX
235 { 1, true, mixerSingleProp
}, // * MIXER_AIRPLANE
236 { 1, true, mixerSingleProp
}, // * MIXER_HELI_120_CCPM
237 { 0, true, NULL
}, // * MIXER_HELI_90_DEG
238 { 4, false, mixerVtail4
}, // MIXER_VTAIL4
239 { 6, false, mixerHex6H
}, // MIXER_HEX6H
240 { 0, true, NULL
}, // * MIXER_PPM_TO_SERVO
241 { 2, true, mixerDualcopter
}, // MIXER_DUALCOPTER
242 { 1, true, NULL
}, // MIXER_SINGLECOPTER
243 { 4, false, mixerAtail4
}, // MIXER_ATAIL4
244 { 0, false, NULL
}, // MIXER_CUSTOM
245 { 2, true, NULL
}, // MIXER_CUSTOM_AIRPLANE
246 { 3, true, NULL
}, // MIXER_CUSTOM_TRI
247 { 4, false, mixerQuadX1234
},
249 #endif // !USE_QUAD_MIXER_ONLY
251 FAST_DATA_ZERO_INIT mixerRuntime_t mixerRuntime
;
253 uint8_t getMotorCount(void)
255 return mixerRuntime
.motorCount
;
258 bool areMotorsRunning(void)
260 bool motorsRunning
= false;
261 if (ARMING_FLAG(ARMED
)) {
262 motorsRunning
= true;
264 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
265 if (motor_disarmed
[i
] != mixerRuntime
.disarmMotorOutput
) {
266 motorsRunning
= true;
273 return motorsRunning
;
277 bool mixerIsTricopter(void)
279 return (currentMixerMode
== MIXER_TRI
|| currentMixerMode
== MIXER_CUSTOM_TRI
);
283 // All PWM motor scaling is done to standard PWM range of 1000-2000 for easier tick conversion with legacy code / configurator
284 // DSHOT scaling is done to the actual dshot range
285 void initEscEndpoints(void)
287 float motorOutputLimit
= 1.0f
;
288 if (currentPidProfile
->motor_output_limit
< 100) {
289 motorOutputLimit
= currentPidProfile
->motor_output_limit
/ 100.0f
;
291 motorInitEndpoints(motorConfig(), motorOutputLimit
, &mixerRuntime
.motorOutputLow
, &mixerRuntime
.motorOutputHigh
, &mixerRuntime
.disarmMotorOutput
, &mixerRuntime
.deadbandMotor3dHigh
, &mixerRuntime
.deadbandMotor3dLow
);
294 // Initialize pidProfile related mixer settings
296 void mixerInitProfile(void)
299 if (motorConfigMutable()->dev
.useDshotTelemetry
) {
300 mixerRuntime
.dynIdleMinRps
= currentPidProfile
->dyn_idle_min_rpm
* 100.0f
/ 60.0f
;
302 mixerRuntime
.dynIdleMinRps
= 0.0f
;
304 mixerRuntime
.dynIdlePGain
= currentPidProfile
->dyn_idle_p_gain
* 0.00015f
;
305 mixerRuntime
.dynIdleIGain
= currentPidProfile
->dyn_idle_i_gain
* 0.01f
* pidGetDT();
306 mixerRuntime
.dynIdleDGain
= currentPidProfile
->dyn_idle_d_gain
* 0.0000003f
* pidGetPidFrequency();
307 mixerRuntime
.dynIdleMaxIncrease
= currentPidProfile
->dyn_idle_max_increase
* 0.001f
;
308 mixerRuntime
.minRpsDelayK
= 800 * pidGetDT() / 20.0f
; //approx 20ms D delay, arbitrarily suits many motors
309 if (!mixerRuntime
.feature3dEnabled
) {
310 if (mixerRuntime
.dynIdleMinRps
) {
311 mixerRuntime
.motorOutputLow
= DSHOT_MIN_THROTTLE
;
313 mixerRuntime
.motorOutputLow
= DSHOT_MIN_THROTTLE
+ mixerRuntime
.idleThrottleOffset
* DSHOT_RANGE
;
318 #if defined(USE_BATTERY_VOLTAGE_SAG_COMPENSATION)
319 mixerRuntime
.vbatSagCompensationFactor
= 0.0f
;
320 if (currentPidProfile
->vbat_sag_compensation
> 0) {
321 //TODO: Make this voltage user configurable
322 mixerRuntime
.vbatFull
= CELL_VOLTAGE_FULL_CV
;
323 mixerRuntime
.vbatRangeToCompensate
= mixerRuntime
.vbatFull
- batteryConfig()->vbatwarningcellvoltage
;
324 if (mixerRuntime
.vbatRangeToCompensate
> 0) {
325 mixerRuntime
.vbatSagCompensationFactor
= ((float)currentPidProfile
->vbat_sag_compensation
) / 100.0f
;
331 #ifdef USE_LAUNCH_CONTROL
332 // Create a custom mixer for launch control based on the current settings
333 // but disable the front motors. We don't care about roll or yaw because they
334 // are limited in the PID controller.
335 void loadLaunchControlMixer(void)
337 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
338 mixerRuntime
.launchControlMixer
[i
] = mixerRuntime
.currentMixer
[i
];
339 // limit the front motors to minimum output
340 if (mixerRuntime
.launchControlMixer
[i
].pitch
< 0.0f
) {
341 mixerRuntime
.launchControlMixer
[i
].pitch
= 0.0f
;
342 mixerRuntime
.launchControlMixer
[i
].throttle
= 0.0f
;
348 #ifndef USE_QUAD_MIXER_ONLY
350 static void mixerConfigureOutput(void)
352 mixerRuntime
.motorCount
= 0;
354 if (currentMixerMode
== MIXER_CUSTOM
|| currentMixerMode
== MIXER_CUSTOM_TRI
|| currentMixerMode
== MIXER_CUSTOM_AIRPLANE
) {
355 // load custom mixer into currentMixer
356 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
358 if (customMotorMixer(i
)->throttle
== 0.0f
) {
361 mixerRuntime
.currentMixer
[i
] = *customMotorMixer(i
);
362 mixerRuntime
.motorCount
++;
365 mixerRuntime
.motorCount
= mixers
[currentMixerMode
].motorCount
;
366 if (mixerRuntime
.motorCount
> MAX_SUPPORTED_MOTORS
) {
367 mixerRuntime
.motorCount
= MAX_SUPPORTED_MOTORS
;
369 // copy motor-based mixers
370 if (mixers
[currentMixerMode
].motor
) {
371 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++)
372 mixerRuntime
.currentMixer
[i
] = mixers
[currentMixerMode
].motor
[i
];
375 #ifdef USE_LAUNCH_CONTROL
376 loadLaunchControlMixer();
378 mixerResetDisarmedMotors();
381 void mixerLoadMix(int index
, motorMixer_t
*customMixers
)
386 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
387 customMixers
[i
].throttle
= 0.0f
;
389 // do we have anything here to begin with?
390 if (mixers
[index
].motor
!= NULL
) {
391 for (int i
= 0; i
< mixers
[index
].motorCount
; i
++) {
392 customMixers
[i
] = mixers
[index
].motor
[i
];
397 static void mixerConfigureOutput(void)
399 mixerRuntime
.motorCount
= QUAD_MOTOR_COUNT
;
400 for (int i
= 0; i
< mixerRuntime
.motorCount
; i
++) {
401 mixerRuntime
.currentMixer
[i
] = mixerQuadX
[i
];
403 #ifdef USE_LAUNCH_CONTROL
404 loadLaunchControlMixer();
406 mixerResetDisarmedMotors();
408 #endif // USE_QUAD_MIXER_ONLY
410 void mixerInit(mixerMode_e mixerMode
)
412 currentMixerMode
= mixerMode
;
414 mixerRuntime
.feature3dEnabled
= featureIsEnabled(FEATURE_3D
);
418 if (mixerIsTricopter()) {
419 mixerTricopterInit();
424 mixerRuntime
.idleThrottleOffset
= getDigitalIdleOffset(motorConfig());
425 mixerRuntime
.dynIdleI
= 0.0f
;
426 mixerRuntime
.prevMinRps
= 0.0f
;
429 mixerConfigureOutput();
432 void mixerResetDisarmedMotors(void)
434 // set disarmed motor values
435 for (int i
= 0; i
< MAX_SUPPORTED_MOTORS
; i
++) {
436 motor_disarmed
[i
] = mixerRuntime
.disarmMotorOutput
;
440 mixerMode_e
getMixerMode(void)
442 return currentMixerMode
;
445 bool mixerModeIsFixedWing(mixerMode_e mixerMode
)
448 case MIXER_FLYING_WING
:
450 case MIXER_CUSTOM_AIRPLANE
:
461 bool isFixedWing(void)
463 return mixerModeIsFixedWing(currentMixerMode
);
466 float getMotorOutputLow(void)
468 return mixerRuntime
.motorOutputLow
;
471 float getMotorOutputHigh(void)
473 return mixerRuntime
.motorOutputHigh
;