refactor motor idle, rename dshot_idle_value to motor_idle (#13936)
[betaflight.git] / src / main / flight / mixer_init.c
blob642fc73f859845dd58b3471ae496f6cd3a6a9f14
1 /*
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)
8 * any later version.
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/>.
21 #include <stdbool.h>
22 #include <stdint.h>
23 #include <string.h>
25 #include "platform.h"
27 #include "build/build_config.h"
28 #include "build/debug.h"
30 #include "common/maths.h"
32 #include "config/config.h"
33 #include "config/feature.h"
35 #include "drivers/dshot.h"
37 #include "fc/controlrate_profile.h"
38 #include "fc/runtime_config.h"
40 #include "mixer.h"
41 #include "flight/mixer_tricopter.h"
42 #include "flight/pid.h"
44 #include "rx/rx.h"
46 #include "sensors/battery.h"
48 #include "mixer_init.h"
50 PG_REGISTER_WITH_RESET_FN(mixerConfig_t, mixerConfig, PG_MIXER_CONFIG, 1);
52 void pgResetFn_mixerConfig(mixerConfig_t *mixerConfig)
54 mixerConfig->mixerMode = DEFAULT_MIXER;
55 mixerConfig->yaw_motors_reversed = false;
56 mixerConfig->crashflip_motor_percent = 0;
57 #ifdef USE_RACE_PRO
58 mixerConfig->crashflip_rate = 30;
59 #else
60 mixerConfig->crashflip_rate = 0;
61 #endif
62 mixerConfig->mixer_type = MIXER_LEGACY;
63 #ifdef USE_RPM_LIMIT
64 mixerConfig->rpm_limit = false;
65 mixerConfig->rpm_limit_p = 25;
66 mixerConfig->rpm_limit_i = 10;
67 mixerConfig->rpm_limit_d = 8;
68 mixerConfig->rpm_limit_value = 18000;
69 #endif
72 PG_REGISTER_ARRAY(motorMixer_t, MAX_SUPPORTED_MOTORS, customMotorMixer, PG_MOTOR_MIXER, 0);
74 mixerMode_e currentMixerMode;
76 static const motorMixer_t mixerQuadX[] = {
77 { 1.0f, -1.0f, 1.0f, -1.0f }, // REAR_R
78 { 1.0f, -1.0f, -1.0f, 1.0f }, // FRONT_R
79 { 1.0f, 1.0f, 1.0f, 1.0f }, // REAR_L
80 { 1.0f, 1.0f, -1.0f, -1.0f }, // FRONT_L
82 #ifndef USE_QUAD_MIXER_ONLY
83 static const motorMixer_t mixerTricopter[] = {
84 { 1.0f, 0.0f, 1.333333f, 0.0f }, // REAR
85 { 1.0f, -1.0f, -0.666667f, 0.0f }, // RIGHT
86 { 1.0f, 1.0f, -0.666667f, 0.0f }, // LEFT
89 static const motorMixer_t mixerQuadP[] = {
90 { 1.0f, 0.0f, 1.0f, -1.0f }, // REAR
91 { 1.0f, -1.0f, 0.0f, 1.0f }, // RIGHT
92 { 1.0f, 1.0f, 0.0f, 1.0f }, // LEFT
93 { 1.0f, 0.0f, -1.0f, -1.0f }, // FRONT
96 #if defined(USE_UNCOMMON_MIXERS)
97 static const motorMixer_t mixerBicopter[] = {
98 { 1.0f, 1.0f, 0.0f, 0.0f }, // LEFT
99 { 1.0f, -1.0f, 0.0f, 0.0f }, // RIGHT
101 #else
102 #define mixerBicopter NULL
103 #endif
105 static const motorMixer_t mixerY4[] = {
106 { 1.0f, 0.0f, 1.0f, -1.0f }, // REAR_TOP CW
107 { 1.0f, -1.0f, -1.0f, 0.0f }, // FRONT_R CCW
108 { 1.0f, 0.0f, 1.0f, 1.0f }, // REAR_BOTTOM CCW
109 { 1.0f, 1.0f, -1.0f, 0.0f }, // FRONT_L CW
113 #if (MAX_SUPPORTED_MOTORS >= 6)
114 static const motorMixer_t mixerHex6X[] = {
115 { 1.0f, -0.5f, 0.866025f, 1.0f }, // REAR_R
116 { 1.0f, -0.5f, -0.866025f, 1.0f }, // FRONT_R
117 { 1.0f, 0.5f, 0.866025f, -1.0f }, // REAR_L
118 { 1.0f, 0.5f, -0.866025f, -1.0f }, // FRONT_L
119 { 1.0f, -1.0f, 0.0f, -1.0f }, // RIGHT
120 { 1.0f, 1.0f, 0.0f, 1.0f }, // LEFT
123 #if defined(USE_UNCOMMON_MIXERS)
124 static const motorMixer_t mixerHex6H[] = {
125 { 1.0f, -1.0f, 1.0f, -1.0f }, // REAR_R
126 { 1.0f, -1.0f, -1.0f, 1.0f }, // FRONT_R
127 { 1.0f, 1.0f, 1.0f, 1.0f }, // REAR_L
128 { 1.0f, 1.0f, -1.0f, -1.0f }, // FRONT_L
129 { 1.0f, 0.0f, 0.0f, 0.0f }, // RIGHT
130 { 1.0f, 0.0f, 0.0f, 0.0f }, // LEFT
133 static const motorMixer_t mixerHex6P[] = {
134 { 1.0f, -0.866025f, 0.5f, 1.0f }, // REAR_R
135 { 1.0f, -0.866025f, -0.5f, -1.0f }, // FRONT_R
136 { 1.0f, 0.866025f, 0.5f, 1.0f }, // REAR_L
137 { 1.0f, 0.866025f, -0.5f, -1.0f }, // FRONT_L
138 { 1.0f, 0.0f, -1.0f, 1.0f }, // FRONT
139 { 1.0f, 0.0f, 1.0f, -1.0f }, // REAR
141 static const motorMixer_t mixerY6[] = {
142 { 1.0f, 0.0f, 1.333333f, 1.0f }, // REAR
143 { 1.0f, -1.0f, -0.666667f, -1.0f }, // RIGHT
144 { 1.0f, 1.0f, -0.666667f, -1.0f }, // LEFT
145 { 1.0f, 0.0f, 1.333333f, -1.0f }, // UNDER_REAR
146 { 1.0f, -1.0f, -0.666667f, 1.0f }, // UNDER_RIGHT
147 { 1.0f, 1.0f, -0.666667f, 1.0f }, // UNDER_LEFT
149 #else
150 #define mixerHex6H NULL
151 #define mixerHex6P NULL
152 #define mixerY6 NULL
153 #endif // USE_UNCOMMON_MIXERS
154 #else
155 #define mixerHex6X NULL
156 #endif // MAX_SUPPORTED_MOTORS >= 6
158 #if defined(USE_UNCOMMON_MIXERS) && (MAX_SUPPORTED_MOTORS >= 8)
159 static const motorMixer_t mixerOctoX8[] = {
160 { 1.0f, -1.0f, 1.0f, -1.0f }, // REAR_R
161 { 1.0f, -1.0f, -1.0f, 1.0f }, // FRONT_R
162 { 1.0f, 1.0f, 1.0f, 1.0f }, // REAR_L
163 { 1.0f, 1.0f, -1.0f, -1.0f }, // FRONT_L
164 { 1.0f, -1.0f, 1.0f, 1.0f }, // UNDER_REAR_R
165 { 1.0f, -1.0f, -1.0f, -1.0f }, // UNDER_FRONT_R
166 { 1.0f, 1.0f, 1.0f, -1.0f }, // UNDER_REAR_L
167 { 1.0f, 1.0f, -1.0f, 1.0f }, // UNDER_FRONT_L
170 static const motorMixer_t mixerOctoX8P[] = {
171 { 1.0f, 0.0f, 1.0f, -1.0f }, // REAR
172 { 1.0f, -1.0f, 0.0f, 1.0f }, // RIGHT
173 { 1.0f, 1.0f, 0.0f, 1.0f }, // LEFT
174 { 1.0f, 0.0f, -1.0f, -1.0f }, // FRONT
175 { 1.0f, 0.0f, 1.0f, 1.0f }, // UNDER_REAR
176 { 1.0f, -1.0f, 0.0f, -1.0f }, // UNDER_RIGHT
177 { 1.0f, 1.0f, 0.0f, -1.0f }, // UNDER_LEFT
178 { 1.0f, 0.0f, -1.0f, 1.0f }, // UNDER_FRONT
181 static const motorMixer_t mixerOctoFlatP[] = {
182 { 1.0f, 0.707107f, -0.707107f, 1.0f }, // FRONT_L
183 { 1.0f, -0.707107f, -0.707107f, 1.0f }, // FRONT_R
184 { 1.0f, -0.707107f, 0.707107f, 1.0f }, // REAR_R
185 { 1.0f, 0.707107f, 0.707107f, 1.0f }, // REAR_L
186 { 1.0f, 0.0f, -1.0f, -1.0f }, // FRONT
187 { 1.0f, -1.0f, 0.0f, -1.0f }, // RIGHT
188 { 1.0f, 0.0f, 1.0f, -1.0f }, // REAR
189 { 1.0f, 1.0f, 0.0f, -1.0f }, // LEFT
192 static const motorMixer_t mixerOctoFlatX[] = {
193 { 1.0f, 1.0f, -0.414178f, 1.0f }, // MIDFRONT_L
194 { 1.0f, -0.414178f, -1.0f, 1.0f }, // FRONT_R
195 { 1.0f, -1.0f, 0.414178f, 1.0f }, // MIDREAR_R
196 { 1.0f, 0.414178f, 1.0f, 1.0f }, // REAR_L
197 { 1.0f, 0.414178f, -1.0f, -1.0f }, // FRONT_L
198 { 1.0f, -1.0f, -0.414178f, -1.0f }, // MIDFRONT_R
199 { 1.0f, -0.414178f, 1.0f, -1.0f }, // REAR_R
200 { 1.0f, 1.0f, 0.414178f, -1.0f }, // MIDREAR_L
202 #else
203 #define mixerOctoX8 NULL
204 #define mixerOctoX8P NULL
205 #define mixerOctoFlatP NULL
206 #define mixerOctoFlatX NULL
207 #endif
209 static const motorMixer_t mixerVtail4[] = {
210 { 1.0f, -0.58f, 0.58f, 1.0f }, // REAR_R
211 { 1.0f, -0.46f, -0.39f, -0.5f }, // FRONT_R
212 { 1.0f, 0.58f, 0.58f, -1.0f }, // REAR_L
213 { 1.0f, 0.46f, -0.39f, 0.5f }, // FRONT_L
216 static const motorMixer_t mixerAtail4[] = {
217 { 1.0f, -0.58f, 0.58f, -1.0f }, // REAR_R
218 { 1.0f, -0.46f, -0.39f, 0.5f }, // FRONT_R
219 { 1.0f, 0.58f, 0.58f, 1.0f }, // REAR_L
220 { 1.0f, 0.46f, -0.39f, -0.5f }, // FRONT_L
223 #if defined(USE_UNCOMMON_MIXERS)
224 static const motorMixer_t mixerDualcopter[] = {
225 { 1.0f, 0.0f, 0.0f, -1.0f }, // LEFT
226 { 1.0f, 0.0f, 0.0f, 1.0f }, // RIGHT
228 #else
229 #define mixerDualcopter NULL
230 #endif
232 static const motorMixer_t mixerSingleProp[] = {
233 { 1.0f, 0.0f, 0.0f, 0.0f },
236 static const motorMixer_t mixerQuadX1234[] = {
237 { 1.0f, 1.0f, -1.0f, -1.0f }, // FRONT_L
238 { 1.0f, -1.0f, -1.0f, 1.0f }, // FRONT_R
239 { 1.0f, -1.0f, 1.0f, -1.0f }, // REAR_R
240 { 1.0f, 1.0f, 1.0f, 1.0f }, // REAR_L
243 // Keep synced with mixerMode_e
244 // Some of these entries are bogus when servos (USE_SERVOS) are not configured,
245 // but left untouched to keep ordinals synced with mixerMode_e (and configurator).
246 const mixer_t mixers[] = {
247 // motors, use servo, motor mixer
248 { 0, false, NULL }, // entry 0
249 { 3, true, mixerTricopter }, // MIXER_TRI
250 { 4, false, mixerQuadP }, // MIXER_QUADP
251 { 4, false, mixerQuadX }, // MIXER_QUADX
252 { 2, true, mixerBicopter }, // MIXER_BICOPTER
253 { 0, true, NULL }, // * MIXER_GIMBAL
254 { 6, false, mixerY6 }, // MIXER_Y6
255 { 6, false, mixerHex6P }, // MIXER_HEX6
256 { 1, true, mixerSingleProp }, // * MIXER_FLYING_WING
257 { 4, false, mixerY4 }, // MIXER_Y4
258 { 6, false, mixerHex6X }, // MIXER_HEX6X
259 { 8, false, mixerOctoX8 }, // MIXER_OCTOX8
260 { 8, false, mixerOctoFlatP }, // MIXER_OCTOFLATP
261 { 8, false, mixerOctoFlatX }, // MIXER_OCTOFLATX
262 { 1, true, mixerSingleProp }, // * MIXER_AIRPLANE
263 { 1, true, mixerSingleProp }, // * MIXER_HELI_120_CCPM
264 { 0, true, NULL }, // * MIXER_HELI_90_DEG
265 { 4, false, mixerVtail4 }, // MIXER_VTAIL4
266 { 6, false, mixerHex6H }, // MIXER_HEX6H
267 { 0, true, NULL }, // * MIXER_PPM_TO_SERVO
268 { 2, true, mixerDualcopter }, // MIXER_DUALCOPTER
269 { 1, true, NULL }, // MIXER_SINGLECOPTER
270 { 4, false, mixerAtail4 }, // MIXER_ATAIL4
271 { 0, false, NULL }, // MIXER_CUSTOM
272 { 2, true, NULL }, // MIXER_CUSTOM_AIRPLANE
273 { 3, true, NULL }, // MIXER_CUSTOM_TRI
274 { 4, false, mixerQuadX1234 }, // MIXER_QUADX_1234
275 { 8, false, mixerOctoX8P }, // MIXER_OCTOX8P
277 #endif // !USE_QUAD_MIXER_ONLY
279 FAST_DATA_ZERO_INIT mixerRuntime_t mixerRuntime;
281 bool hasServos(void)
283 return mixers[currentMixerMode].useServo;
286 uint8_t getMotorCount(void)
288 return mixerRuntime.motorCount;
291 bool areMotorsRunning(void)
293 bool motorsRunning = false;
294 if (ARMING_FLAG(ARMED)) {
295 motorsRunning = true;
296 } else {
297 for (int i = 0; i < mixerRuntime.motorCount; i++) {
298 if (motor_disarmed[i] != mixerRuntime.disarmMotorOutput) {
299 motorsRunning = true;
300 break;
304 return motorsRunning;
307 bool areMotorsSaturated(void)
309 for (int i = 0; i < getMotorCount(); i++) {
310 if (motor[i] >= motorConfig()->maxthrottle) {
311 return true;
314 return false;
317 #ifdef USE_SERVOS
318 bool mixerIsTricopter(void)
320 return (currentMixerMode == MIXER_TRI || currentMixerMode == MIXER_CUSTOM_TRI);
322 #endif
324 // All PWM motor scaling is done to standard PWM range of 1000-2000 for easier tick conversion with legacy code / configurator
325 // DSHOT scaling is done to the actual dshot range
326 void initEscEndpoints(void)
328 float motorOutputLimit = 1.0f;
329 if (currentPidProfile->motor_output_limit < 100) {
330 motorOutputLimit = currentPidProfile->motor_output_limit / 100.0f;
332 motorInitEndpoints(motorConfig(), motorOutputLimit, &mixerRuntime.motorOutputLow, &mixerRuntime.motorOutputHigh, &mixerRuntime.disarmMotorOutput, &mixerRuntime.deadbandMotor3dHigh, &mixerRuntime.deadbandMotor3dLow);
335 // Initialize pidProfile related mixer settings
337 void mixerInitProfile(void)
339 #ifdef USE_DYN_IDLE
340 if (useDshotTelemetry) {
341 mixerRuntime.dynIdleMinRps = currentPidProfile->dyn_idle_min_rpm * 100.0f / 60.0f;
342 } else {
343 mixerRuntime.dynIdleMinRps = 0.0f;
345 mixerRuntime.dynIdlePGain = currentPidProfile->dyn_idle_p_gain * 0.00015f;
346 mixerRuntime.dynIdleIGain = currentPidProfile->dyn_idle_i_gain * 0.01f * pidGetDT();
347 mixerRuntime.dynIdleDGain = currentPidProfile->dyn_idle_d_gain * 0.0000003f * pidGetPidFrequency();
348 mixerRuntime.dynIdleMaxIncrease = currentPidProfile->dyn_idle_max_increase * 0.001f;
349 // before takeoff, use the static idle value as the dynamic idle limit.
350 // whoop users should first adjust static idle to ensure reliable motor start before enabling dynamic idle
351 mixerRuntime.dynIdleStartIncrease = motorConfig()->motorIdle * 0.0001f;
352 mixerRuntime.minRpsDelayK = 800 * pidGetDT() / 20.0f; //approx 20ms D delay, arbitrarily suits many motors
353 if (!mixerRuntime.feature3dEnabled && mixerRuntime.dynIdleMinRps) {
354 mixerRuntime.motorOutputLow = DSHOT_MIN_THROTTLE; // Override value set by initEscEndpoints to allow zero motor drive
356 #endif
358 #if defined(USE_BATTERY_VOLTAGE_SAG_COMPENSATION)
359 mixerRuntime.vbatSagCompensationFactor = 0.0f;
360 if (currentPidProfile->vbat_sag_compensation > 0 && !RPM_LIMIT_ACTIVE) {
361 //TODO: Make this voltage user configurable
362 mixerRuntime.vbatFull = CELL_VOLTAGE_FULL_CV;
363 mixerRuntime.vbatRangeToCompensate = mixerRuntime.vbatFull - batteryConfig()->vbatwarningcellvoltage;
364 if (mixerRuntime.vbatRangeToCompensate > 0) {
365 mixerRuntime.vbatSagCompensationFactor = ((float)currentPidProfile->vbat_sag_compensation) / 100.0f;
368 #endif
370 #ifdef USE_RPM_LIMIT
371 mixerRuntime.rpmLimiterRpmLimit = mixerConfig()->rpm_limit_value;
372 mixerRuntime.rpmLimiterPGain = mixerConfig()->rpm_limit_p * 15e-6f;
373 mixerRuntime.rpmLimiterIGain = mixerConfig()->rpm_limit_i * 1e-3f * pidGetDT();
374 mixerRuntime.rpmLimiterDGain = mixerConfig()->rpm_limit_d * 3e-7f * pidGetPidFrequency();
375 mixerRuntime.rpmLimiterI = 0.0;
376 pt1FilterInit(&mixerRuntime.rpmLimiterAverageRpmFilter, pt1FilterGain(6.0f, pidGetDT()));
377 pt1FilterInit(&mixerRuntime.rpmLimiterThrottleScaleOffsetFilter, pt1FilterGain(2.0f, pidGetDT()));
378 mixerResetRpmLimiter();
379 #endif
381 mixerRuntime.ezLandingThreshold = 2.0f * currentPidProfile->ez_landing_threshold / 100.0f;
382 mixerRuntime.ezLandingLimit = currentPidProfile->ez_landing_limit / 100.0f;
383 mixerRuntime.ezLandingSpeed = 2.0f * currentPidProfile->ez_landing_speed / 10.0f;
386 #ifdef USE_RPM_LIMIT
387 void mixerResetRpmLimiter(void)
389 mixerRuntime.rpmLimiterI = 0.0;
390 mixerRuntime.rpmLimiterThrottleScale = constrainf(mixerRuntime.rpmLimiterRpmLimit / motorEstimateMaxRpm(), 0.0f, 1.0f);
391 mixerRuntime.rpmLimiterInitialThrottleScale = mixerRuntime.rpmLimiterThrottleScale;
394 #endif // USE_RPM_LIMIT
396 #ifdef USE_LAUNCH_CONTROL
397 // Create a custom mixer for launch control based on the current settings
398 // but disable the front motors. We don't care about roll or yaw because they
399 // are limited in the PID controller.
400 void loadLaunchControlMixer(void)
402 for (int i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
403 mixerRuntime.launchControlMixer[i] = mixerRuntime.currentMixer[i];
404 // limit the front motors to minimum output
405 if (mixerRuntime.launchControlMixer[i].pitch < 0.0f) {
406 mixerRuntime.launchControlMixer[i].pitch = 0.0f;
407 mixerRuntime.launchControlMixer[i].throttle = 0.0f;
411 #endif
413 #ifndef USE_QUAD_MIXER_ONLY
415 static void mixerConfigureOutput(void)
417 mixerRuntime.motorCount = 0;
419 if (currentMixerMode == MIXER_CUSTOM || currentMixerMode == MIXER_CUSTOM_TRI || currentMixerMode == MIXER_CUSTOM_AIRPLANE) {
420 // load custom mixer into currentMixer
421 for (int i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
422 // check if done
423 if (customMotorMixer(i)->throttle == 0.0f) {
424 break;
426 mixerRuntime.currentMixer[i] = *customMotorMixer(i);
427 mixerRuntime.motorCount++;
429 } else {
430 mixerRuntime.motorCount = mixers[currentMixerMode].motorCount;
431 if (mixerRuntime.motorCount > MAX_SUPPORTED_MOTORS) {
432 mixerRuntime.motorCount = MAX_SUPPORTED_MOTORS;
434 // copy motor-based mixers
435 if (mixers[currentMixerMode].motor) {
436 for (int i = 0; i < mixerRuntime.motorCount; i++)
437 mixerRuntime.currentMixer[i] = mixers[currentMixerMode].motor[i];
440 #ifdef USE_LAUNCH_CONTROL
441 loadLaunchControlMixer();
442 #endif
443 mixerResetDisarmedMotors();
446 void mixerLoadMix(int index, motorMixer_t *customMixers)
448 // we're 1-based
449 index++;
450 // clear existing
451 for (int i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
452 customMixers[i].throttle = 0.0f;
454 // do we have anything here to begin with?
455 if (mixers[index].motor != NULL) {
456 for (int i = 0; i < mixers[index].motorCount; i++) {
457 customMixers[i] = mixers[index].motor[i];
461 #else
462 static void mixerConfigureOutput(void)
464 mixerRuntime.motorCount = QUAD_MOTOR_COUNT;
465 for (int i = 0; i < mixerRuntime.motorCount; i++) {
466 mixerRuntime.currentMixer[i] = mixerQuadX[i];
468 #ifdef USE_LAUNCH_CONTROL
469 loadLaunchControlMixer();
470 #endif
471 mixerResetDisarmedMotors();
473 #endif // USE_QUAD_MIXER_ONLY
475 void mixerInit(mixerMode_e mixerMode)
477 currentMixerMode = mixerMode;
479 mixerRuntime.feature3dEnabled = featureIsEnabled(FEATURE_3D);
481 initEscEndpoints();
482 #ifdef USE_SERVOS
483 if (mixerIsTricopter()) {
484 mixerTricopterInit();
486 #endif
488 #ifdef USE_DYN_IDLE
489 mixerRuntime.dynIdleI = 0.0f;
490 mixerRuntime.prevMinRps = 0.0f;
491 #endif
493 mixerConfigureOutput();
496 void mixerResetDisarmedMotors(void)
498 // set disarmed motor values
499 for (int i = 0; i < MAX_SUPPORTED_MOTORS; i++) {
500 motor_disarmed[i] = mixerRuntime.disarmMotorOutput;
504 mixerMode_e getMixerMode(void)
506 return currentMixerMode;
509 bool mixerModeIsFixedWing(mixerMode_e mixerMode)
511 switch (mixerMode) {
512 case MIXER_FLYING_WING:
513 case MIXER_AIRPLANE:
514 case MIXER_CUSTOM_AIRPLANE:
515 return true;
516 break;
517 default:
518 return false;
519 break;
523 bool isFixedWing(void)
525 return mixerModeIsFixedWing(currentMixerMode);
528 float getMotorOutputLow(void)
530 return mixerRuntime.motorOutputLow;
533 float getMotorOutputHigh(void)
535 return mixerRuntime.motorOutputHigh;