Companion: Russian UI (#7180)
[opentx.git] / radio / src / mixer.cpp
blobe4b701c9c22710dc3b7ffa0eb35ec98549372aeb
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include "opentx.h"
22 #include "timers.h"
24 int8_t virtualInputsTrims[MAX_INPUTS];
25 int16_t anas [MAX_INPUTS] = {0};
26 int16_t trims[NUM_TRIMS] = {0};
27 int32_t chans[MAX_OUTPUT_CHANNELS] = {0};
28 BeepANACenter bpanaCenter = 0;
30 int32_t act [MAX_MIXERS] = {0};
31 SwOn swOn [MAX_MIXERS]; // TODO better name later...
33 uint8_t mixWarning;
36 int16_t calibratedAnalogs[NUM_CALIBRATED_ANALOGS];
37 int16_t channelOutputs[MAX_OUTPUT_CHANNELS] = {0};
38 int16_t ex_chans[MAX_OUTPUT_CHANNELS] = {0}; // Outputs (before LIMITS) of the last perMain;
40 #if defined(HELI)
41 int16_t cyc_anas[3] = {0};
42 #endif
44 // #define EXTENDED_EXPO
45 // increases range of expo curve but costs about 82 bytes flash
47 // expo-funktion:
48 // ---------------
49 // kmplot
50 // f(x,k)=exp(ln(x)*k/10) ;P[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
51 // f(x,k)=x*x*x*k/10 + x*(1-k/10) ;P[0,1,2,3,4,5,6,7,8,9,10]
52 // f(x,k)=x*x*k/10 + x*(1-k/10) ;P[0,1,2,3,4,5,6,7,8,9,10]
53 // f(x,k)=1+(x-1)*(x-1)*(x-1)*k/10 + (x-1)*(1-k/10) ;P[0,1,2,3,4,5,6,7,8,9,10]
54 // don't know what this above should be, just confusing in my opinion,
56 // here is the real explanation
57 // actually the real formula is
59 f(x) = exp( ln(x) * 10^k)
60 if it is 10^k or e^k or 2^k etc. just defines the max distortion of the expo curve; I think 10 is useful
61 this gives values from 0 to 1 for x and output; k must be between -1 and +1
62 we do not like to calculate with floating point. Therefore we rescale for x from 0 to 1024 and for k from -100 to +100
63 f(x) = 1024 * ( e^( ln(x/1024) * 10^(k/100) ) )
64 This would be really hard to be calculated by such a microcontroller
65 Therefore Thomas Husterer compared a few usual function something like x^3, x^4*something, which look similar
66 Actually the formula
67 f(x) = k*x^3+x*(1-k)
68 gives a similar form and should have even advantages compared to a original exp curve.
69 This function again expect x from 0 to 1 and k only from 0 to 1
70 Therefore rescaling is needed like before:
71 f(x) = 1024* ((k/100)*(x/1024)^3 + (x/1024)*(100-k)/100)
72 some mathematical tricks
73 f(x) = (k*x*x*x/(1024*1024) + x*(100-k)) / 100
74 for better rounding results we add the 50
75 f(x) = (k*x*x*x/(1024*1024) + x*(100-k) + 50) / 100
77 because we now understand the formula, we can optimize it further
78 --> calc100to256(k) --> eliminates /100 by replacing with /256 which is just a simple shift right 8
79 k is now between 0 and 256
80 f(x) = (k*x*x*x/(1024*1024) + x*(256-k) + 128) / 256
83 // input parameters;
84 // x 0 to 1024;
85 // k 0 to 100;
86 // output between 0 and 1024
87 unsigned int expou(unsigned int x, unsigned int k)
89 #if defined(EXTENDED_EXPO)
90 bool extended;
91 if (k > 80) {
92 extended=true;
94 else {
95 k += (k>>2); // use bigger values before extend, because the effect is anyway very very low
96 extended=false;
98 #endif
100 k = calc100to256(k);
102 uint32_t value = (uint32_t) x*x;
103 value *= (uint32_t)k;
104 value >>= 8;
105 value *= (uint32_t)x;
107 #if defined(EXTENDED_EXPO)
108 if (extended) { // for higher values do more multiplications to get a stronger expo curve
109 value >>= 16;
110 value *= (uint32_t)x;
111 value >>= 4;
112 value *= (uint32_t)x;
114 #endif
116 value >>= 12;
117 value += (uint32_t)(256-k) * x + 128;
119 return value >> 8;
122 int expo(int x, int k)
124 if (k == 0) {
125 return x;
128 int y;
129 bool neg = (x < 0);
131 if (neg) {
132 x = -x;
134 if (x > (int)RESXu) {
135 x = RESXu;
137 if (k < 0) {
138 y = RESXu - expou(RESXu-x, -k);
140 else {
141 y = expou(x, k);
143 return neg ? -y : y;
146 void applyExpos(int16_t * anas, uint8_t mode, uint8_t ovwrIdx, int16_t ovwrValue)
148 int8_t cur_chn = -1;
150 for (uint8_t i=0; i<MAX_EXPOS; i++) {
151 #if defined(BOLD_FONT)
152 if (mode==e_perout_mode_normal) swOn[i].activeExpo = false;
153 #endif
154 ExpoData * ed = expoAddress(i);
155 if (!EXPO_VALID(ed)) break; // end of list
156 if (ed->chn == cur_chn)
157 continue;
158 if (ed->flightModes & (1<<mixerCurrentFlightMode))
159 continue;
160 if (ed->srcRaw >= MIXSRC_FIRST_TRAINER && ed->srcRaw <= MIXSRC_LAST_TRAINER && !IS_TRAINER_INPUT_VALID())
161 continue;
162 if (getSwitch(ed->swtch)) {
163 int32_t v;
164 if (ed->srcRaw == ovwrIdx) {
165 v = ovwrValue;
167 else {
168 v = getValue(ed->srcRaw);
169 if (ed->srcRaw >= MIXSRC_FIRST_TELEM && ed->scale > 0) {
170 v = (v * 1024) / convertTelemValue(ed->srcRaw-MIXSRC_FIRST_TELEM+1, ed->scale);
172 v = limit<int32_t>(-1024, v, 1024);
174 if (EXPO_MODE_ENABLE(ed, v)) {
175 #if defined(BOLD_FONT)
176 if (mode==e_perout_mode_normal) swOn[i].activeExpo = true;
177 #endif
178 cur_chn = ed->chn;
180 //========== CURVE=================
181 if (ed->curve.value) {
182 v = applyCurve(v, ed->curve);
185 //========== WEIGHT ===============
186 int32_t weight = GET_GVAR_PREC1(ed->weight, MIN_EXPO_WEIGHT, 100, mixerCurrentFlightMode);
187 v = div_and_round((int32_t)v * weight, 1000);
189 //========== OFFSET ===============
190 int32_t offset = GET_GVAR_PREC1(ed->offset, -100, 100, mixerCurrentFlightMode);
191 if (offset) v += div_and_round(calc100toRESX(offset), 10);
193 //========== TRIMS ================
194 if (ed->carryTrim < TRIM_ON)
195 virtualInputsTrims[cur_chn] = -ed->carryTrim - 1;
196 else if (ed->carryTrim == TRIM_ON && ed->srcRaw >= MIXSRC_Rud && ed->srcRaw <= MIXSRC_Ail)
197 virtualInputsTrims[cur_chn] = ed->srcRaw - MIXSRC_Rud;
198 else
199 virtualInputsTrims[cur_chn] = -1;
200 anas[cur_chn] = v;
206 // #define PREVENT_ARITHMETIC_OVERFLOW
207 // because of optimizations the reserves before overruns occurs is only the half
208 // this defines enables some checks the greatly improves this situation
209 // It should nearly prevent all overruns (is still a chance for it, but quite low)
210 // negative side is code cost 96 bytes flash
212 // we do it now half way, only in applyLimits, which costs currently 50bytes
213 // according opinion poll this topic is currently not very important
214 // the change below improves already the situation
215 // the check inside mixer would slow down mix a little bit and costs additionally flash
216 // also the check inside mixer still is not bulletproof, there may be still situations a overflow could occur
217 // a bulletproof implementation would take about additional 100bytes flash
218 // therefore with go with this compromize, interested people could activate this define
220 // @@@2 open.20.fsguruh ;
221 // channel = channelnumber -1;
222 // value = outputvalue with 100 mulitplied usual range -102400 to 102400; output -1024 to 1024
223 // changed rescaling from *100 to *256 to optimize performance
224 // rescaled from -262144 to 262144
225 int16_t applyLimits(uint8_t channel, int32_t value)
227 #if defined(OVERRIDE_CHANNEL_FUNCTION)
228 if (safetyCh[channel] != OVERRIDE_CHANNEL_UNDEFINED) {
229 // safety channel available for channel check
230 return calc100toRESX(safetyCh[channel]);
232 #endif
234 if (isFunctionActive(FUNCTION_TRAINER_CHANNELS) && IS_TRAINER_INPUT_VALID()) {
235 return ppmInput[channel] * 2;
238 LimitData * lim = limitAddress(channel);
240 if (lim->curve) {
241 // TODO we loose precision here, applyCustomCurve could work with int32_t on ARM boards...
242 if (lim->curve > 0)
243 value = 256 * applyCustomCurve(value/256, lim->curve-1);
244 else
245 value = 256 * applyCustomCurve(-value/256, -lim->curve-1);
248 int16_t ofs = LIMIT_OFS_RESX(lim);
249 int16_t lim_p = LIMIT_MAX_RESX(lim);
250 int16_t lim_n = LIMIT_MIN_RESX(lim);
252 if (ofs > lim_p) ofs = lim_p;
253 if (ofs < lim_n) ofs = lim_n;
255 // because the rescaling optimization would reduce the calculation reserve we activate this for all builds
256 // it increases the calculation reserve from factor 20,25x to 32x, which it slightly better as original
257 // without it we would only have 16x which is slightly worse as original, we should not do this
259 // thanks to gbirkus, he motivated this change, which greatly reduces overruns
260 // unfortunately the constants and 32bit compares generates about 50 bytes codes; didn't find a way to get it down.
261 value = limit(int32_t(-RESXl*256), value, int32_t(RESXl*256)); // saves 2 bytes compared to other solutions up to now
263 #if defined(PPM_LIMITS_SYMETRICAL)
264 if (value) {
265 int16_t tmp;
266 if (lim->symetrical)
267 tmp = (value > 0) ? (lim_p) : (-lim_n);
268 else
269 tmp = (value > 0) ? (lim_p - ofs) : (-lim_n + ofs);
270 value = (int32_t) value * tmp; // div by 1024*256 -> output = -1024..1024
271 #else
272 if (value) {
273 int16_t tmp = (value > 0) ? (lim_p - ofs) : (-lim_n + ofs);
274 value = (int32_t) value * tmp; // div by 1024*256 -> output = -1024..1024
275 #endif
277 #ifdef CORRECT_NEGATIVE_SHIFTS
278 int8_t sign = (value<0?1:0);
279 value -= sign;
280 tmp = value>>16; // that's quite tricky: the shiftright 16 operation is assmbled just with addressmove; just forget the two least significant bytes;
281 tmp >>= 2; // now one simple shift right for two bytes does the rest
282 tmp += sign;
283 #else
284 tmp = value>>16; // that's quite tricky: the shiftright 16 operation is assmbled just with addressmove; just forget the two least significant bytes;
285 tmp >>= 2; // now one simple shift right for two bytes does the rest
286 #endif
288 ofs += tmp; // ofs can to added directly because already recalculated,
291 if (ofs > lim_p)
292 ofs = lim_p;
293 if (ofs < lim_n)
294 ofs = lim_n;
295 if (lim->revert)
296 ofs = -ofs; // finally do the reverse.
298 return ofs;
301 // TODO same naming convention than the drawSource
303 getvalue_t getValue(mixsrc_t i)
305 if (i == MIXSRC_NONE) {
306 return 0;
308 else if (i <= MIXSRC_LAST_INPUT) {
309 return anas[i-MIXSRC_FIRST_INPUT];
311 #if defined(LUA_INPUTS)
312 else if (i <= MIXSRC_LAST_LUA) {
313 #if defined(LUA_MODEL_SCRIPTS)
314 div_t qr = div(i-MIXSRC_FIRST_LUA, MAX_SCRIPT_OUTPUTS);
315 return scriptInputsOutputs[qr.quot].outputs[qr.rem].value;
316 #else
317 return 0;
318 #endif
320 #endif
322 else if (i <= MIXSRC_LAST_POT + NUM_MOUSE_ANALOGS) {
323 return calibratedAnalogs[i - MIXSRC_Rud];
326 #if defined(GYRO)
327 else if (i == MIXSRC_GYRO1) {
328 return gyro.scaledX();
330 else if (i == MIXSRC_GYRO2) {
331 return gyro.scaledY();
333 #endif
335 else if (i == MIXSRC_MAX) {
336 return 1024;
339 else if (i <= MIXSRC_CYC3) {
340 #if defined(HELI)
341 return cyc_anas[i - MIXSRC_CYC1];
342 #else
343 return 0;
344 #endif
347 else if (i <= MIXSRC_LAST_TRIM) {
348 return calc1000toRESX((int16_t)8 * getTrimValue(mixerCurrentFlightMode, i-MIXSRC_FIRST_TRIM));
351 #if defined(PCBTARANIS) || defined(PCBHORUS)
352 else if (i >= MIXSRC_FIRST_SWITCH && i <= MIXSRC_LAST_SWITCH) {
353 mixsrc_t sw = i - MIXSRC_FIRST_SWITCH;
354 if (SWITCH_EXISTS(sw)) {
355 return (switchState(3*sw) ? -1024 : (IS_CONFIG_3POS(sw) && switchState(3*sw+1) ? 0 : 1024));
357 else {
358 return 0;
361 #else
362 else if (i == MIXSRC_3POS) {
363 return (getSwitch(SW_ID0+1) ? -1024 : (getSwitch(SW_ID1+1) ? 0 : 1024));
365 // don't use switchState directly to give getSwitch possibility to hack values if needed for switch warning
366 else if (i < MIXSRC_SW1) {
367 return getSwitch(SWSRC_THR+i-MIXSRC_THR) ? 1024 : -1024;
369 #endif
371 else if (i <= MIXSRC_LAST_LOGICAL_SWITCH) {
372 return getSwitch(SWSRC_FIRST_LOGICAL_SWITCH + i - MIXSRC_FIRST_LOGICAL_SWITCH) ? 1024 : -1024;
374 else if (i <= MIXSRC_LAST_TRAINER) {
375 int16_t x = ppmInput[i - MIXSRC_FIRST_TRAINER];
376 if (i < MIXSRC_FIRST_TRAINER + NUM_CAL_PPM) {
377 x -= g_eeGeneral.trainer.calib[i - MIXSRC_FIRST_TRAINER];
379 return x * 2;
381 else if (i <= MIXSRC_LAST_CH) {
382 return ex_chans[i - MIXSRC_CH1];
385 else if (i <= MIXSRC_LAST_GVAR) {
386 #if defined(GVARS)
387 return GVAR_VALUE(i - MIXSRC_GVAR1, getGVarFlightMode(mixerCurrentFlightMode, i - MIXSRC_GVAR1));
388 #else
389 return 0;
390 #endif
393 else if (i == MIXSRC_TX_VOLTAGE) {
394 return g_vbat100mV;
396 else if (i < MIXSRC_FIRST_TIMER) {
397 // TX_TIME + SPARES
398 #if defined(RTCLOCK)
399 return (g_rtcTime % SECS_PER_DAY) / 60; // number of minutes from midnight
400 #else
401 return 0;
402 #endif
404 else if (i <= MIXSRC_LAST_TIMER) {
405 return timersStates[i - MIXSRC_FIRST_TIMER].val;
408 else if (i <= MIXSRC_LAST_TELEM) {
409 if (IS_FAI_FORBIDDEN(i)) {
410 return 0;
412 i -= MIXSRC_FIRST_TELEM;
413 div_t qr = div(i, 3);
414 TelemetryItem & telemetryItem = telemetryItems[qr.quot];
415 switch (qr.rem) {
416 case 1:
417 return telemetryItem.valueMin;
418 case 2:
419 return telemetryItem.valueMax;
420 default:
421 return telemetryItem.value;
424 else return 0;
427 void evalInputs(uint8_t mode)
429 BeepANACenter anaCenter = 0;
431 for (uint8_t i = 0; i < NUM_STICKS + NUM_POTS + NUM_SLIDERS; i++) {
432 // normalization [0..2048] -> [-1024..1024]
433 uint8_t ch = (i < NUM_STICKS ? CONVERT_MODE(i) : i);
434 int16_t v = anaIn(i);
436 if (IS_POT_MULTIPOS(i)) {
437 v -= RESX;
439 #if !defined(SIMU)
440 else {
441 CalibData * calib = &g_eeGeneral.calib[i];
442 v -= calib->mid;
443 v = v * (int32_t) RESX / (max((int16_t) 100, (v > 0 ? calib->spanPos : calib->spanNeg)));
445 #endif
447 if (v < -RESX) v = -RESX;
448 if (v > RESX) v = RESX;
450 if (g_model.throttleReversed && ch==THR_STICK) {
451 v = -v;
454 BeepANACenter mask = (BeepANACenter)1 << ch;
456 calibratedAnalogs[ch] = v; // for show in expo
458 // filtering for center beep
459 uint8_t tmp = (uint16_t)abs(v) / 16;
460 if (mode == e_perout_mode_normal) {
461 if (tmp==0 || (tmp==1 && (bpanaCenter & mask))) {
462 anaCenter |= mask;
463 if ((g_model.beepANACenter & mask) && !(bpanaCenter & mask) && !menuCalibrationState) {
464 if (!IS_POT(i) || IS_POT_SLIDER_AVAILABLE(i)) {
465 AUDIO_POT_MIDDLE(i);
471 if (ch < NUM_STICKS) { // only do this for sticks
472 if (mode & e_perout_mode_nosticks) {
473 v = 0;
476 if (mode <= e_perout_mode_inactive_flight_mode && isFunctionActive(FUNCTION_TRAINER_STICK1+ch) && IS_TRAINER_INPUT_VALID()) {
477 // trainer mode
478 TrainerMix* td = &g_eeGeneral.trainer.mix[ch];
479 if (td->mode) {
480 uint8_t chStud = td->srcChn;
481 int32_t vStud = (ppmInput[chStud] - g_eeGeneral.trainer.calib[chStud]);
482 vStud *= td->studWeight;
483 vStud /= 50;
484 switch (td->mode) {
485 case 1:
486 // add-mode
487 v = limit<int16_t>(-RESX, v+vStud, RESX);
488 break;
489 case 2:
490 // subst-mode
491 v = vStud;
492 break;
496 calibratedAnalogs[ch] = v;
500 #if NUM_MOUSE_ANALOGS > 0
501 for (uint8_t i=0; i<NUM_MOUSE_ANALOGS; i++) {
502 uint8_t ch = NUM_STICKS+NUM_POTS+NUM_SLIDERS+i;
503 int16_t v = anaIn(MOUSE1+i);
504 CalibData * calib = &g_eeGeneral.calib[ch];
505 v -= calib->mid;
506 v = v * (int32_t) RESX / (max((int16_t) 100, (v > 0 ? calib->spanPos : calib->spanNeg)));
507 if (v < -RESX) v = -RESX;
508 if (v > RESX) v = RESX;
509 calibratedAnalogs[ch] = v;
511 #endif
513 /* EXPOs */
514 applyExpos(anas, mode);
516 /* TRIMs */
517 evalTrims(); // when no virtual inputs, the trims need the anas array calculated above (when throttle trim enabled)
519 if (mode == e_perout_mode_normal) {
520 bpanaCenter = anaCenter;
524 int getStickTrimValue(int stick, int stickValue)
526 if (stick < 0)
527 return 0;
529 int trim = trims[stick];
530 if (stick == THR_STICK) {
531 if (g_model.thrTrim) {
532 int trimMin = g_model.extendedTrims ? 2*TRIM_EXTENDED_MIN : 2*TRIM_MIN;
533 trim = ((g_model.throttleReversed ? (trim+trimMin) : (trim-trimMin)) * (RESX-stickValue)) >> (RESX_SHIFT+1);
535 if (g_model.throttleReversed) {
536 trim = -trim;
539 return trim;
542 int getSourceTrimValue(int source, int stickValue=0)
544 if (source >= MIXSRC_Rud && source <= MIXSRC_Ail)
545 return getStickTrimValue(source - MIXSRC_Rud, stickValue);
546 else if (source >= MIXSRC_FIRST_INPUT && source <= MIXSRC_LAST_INPUT)
547 return getStickTrimValue(virtualInputsTrims[source - MIXSRC_FIRST_INPUT], stickValue);
548 else
549 return 0;
552 uint8_t mixerCurrentFlightMode;
553 void evalFlightModeMixes(uint8_t mode, uint8_t tick10ms)
555 evalInputs(mode);
557 if (tick10ms)
558 evalLogicalSwitches(mode==e_perout_mode_normal);
560 #if defined(HELI)
561 int heliEleValue = getValue(g_model.swashR.elevatorSource);
562 int heliAilValue = getValue(g_model.swashR.aileronSource);
563 if (g_model.swashR.value) {
564 uint32_t v = ((int32_t)heliEleValue*heliEleValue + (int32_t)heliAilValue*heliAilValue);
565 uint32_t q = calc100toRESX(g_model.swashR.value);
566 q *= q;
567 if (v>q) {
568 uint16_t d = isqrt32(v);
569 int16_t tmp = calc100toRESX(g_model.swashR.value);
570 heliEleValue = (int32_t) heliEleValue*tmp/d;
571 heliAilValue = (int32_t) heliAilValue*tmp/d;
575 #define REZ_SWASH_X(x) ((x) - (x)/8 - (x)/128 - (x)/512) // 1024*sin(60) ~= 886
576 #define REZ_SWASH_Y(x) ((x)) // 1024 => 1024
578 if (g_model.swashR.type) {
579 getvalue_t vp = heliEleValue + getSourceTrimValue(g_model.swashR.elevatorSource);
580 getvalue_t vr = heliAilValue + getSourceTrimValue(g_model.swashR.aileronSource);
581 getvalue_t vc = 0;
582 if (g_model.swashR.collectiveSource)
583 vc = getValue(g_model.swashR.collectiveSource);
585 vp = (vp * g_model.swashR.elevatorWeight) / 100;
586 vr = (vr * g_model.swashR.aileronWeight) / 100;
587 vc = (vc * g_model.swashR.collectiveWeight) / 100;
589 switch (g_model.swashR.type) {
590 case SWASH_TYPE_120:
591 vp = REZ_SWASH_Y(vp);
592 vr = REZ_SWASH_X(vr);
593 cyc_anas[0] = vc - vp;
594 cyc_anas[1] = vc + vp/2 + vr;
595 cyc_anas[2] = vc + vp/2 - vr;
596 break;
597 case SWASH_TYPE_120X:
598 vp = REZ_SWASH_X(vp);
599 vr = REZ_SWASH_Y(vr);
600 cyc_anas[0] = vc - vr;
601 cyc_anas[1] = vc + vr/2 + vp;
602 cyc_anas[2] = vc + vr/2 - vp;
603 break;
604 case SWASH_TYPE_140:
605 vp = REZ_SWASH_Y(vp);
606 vr = REZ_SWASH_Y(vr);
607 cyc_anas[0] = vc - vp;
608 cyc_anas[1] = vc + vp + vr;
609 cyc_anas[2] = vc + vp - vr;
610 break;
611 case SWASH_TYPE_90:
612 vp = REZ_SWASH_Y(vp);
613 vr = REZ_SWASH_Y(vr);
614 cyc_anas[0] = vc - vp;
615 cyc_anas[1] = vc + vr;
616 cyc_anas[2] = vc - vr;
617 break;
618 default:
619 break;
622 #endif
624 memclear(chans, sizeof(chans)); // all outputs to 0
626 //========== MIXER LOOP ===============
627 uint8_t lv_mixWarning = 0;
629 uint8_t pass = 0;
631 bitfield_channels_t dirtyChannels = (bitfield_channels_t)-1; // all dirty when mixer starts
633 do {
634 bitfield_channels_t passDirtyChannels = 0;
636 for (uint8_t i=0; i<MAX_MIXERS; i++) {
637 #if defined(BOLD_FONT)
638 if (mode == e_perout_mode_normal && pass == 0)
639 swOn[i].activeMix = 0;
640 #endif
642 MixData * md = mixAddress(i);
644 if (md->srcRaw == 0)
645 break;
647 mixsrc_t stickIndex = md->srcRaw - MIXSRC_Rud;
649 if (!(dirtyChannels & ((bitfield_channels_t)1 << md->destCh)))
650 continue;
652 // if this is the first calculation for the destination channel, initialize it with 0 (otherwise would be random)
653 if (i == 0 || md->destCh != (md-1)->destCh)
654 chans[md->destCh] = 0;
656 //========== FLIGHT MODE && SWITCH =====
657 bool mixCondition = (md->flightModes != 0 || md->swtch);
658 delayval_t mixEnabled = (!(md->flightModes & (1 << mixerCurrentFlightMode)) && getSwitch(md->swtch)) ? DELAY_POS_MARGIN+1 : 0;
660 #define MIXER_LINE_DISABLE() (mixCondition = true, mixEnabled = 0)
662 if (mixEnabled && md->srcRaw >= MIXSRC_FIRST_TRAINER && md->srcRaw <= MIXSRC_LAST_TRAINER && !IS_TRAINER_INPUT_VALID()) {
663 MIXER_LINE_DISABLE();
666 #if defined(LUA_MODEL_SCRIPTS)
667 // disable mixer if Lua script is used as source and script was killed
668 if (mixEnabled && md->srcRaw >= MIXSRC_FIRST_LUA && md->srcRaw <= MIXSRC_LAST_LUA) {
669 div_t qr = div(md->srcRaw-MIXSRC_FIRST_LUA, MAX_SCRIPT_OUTPUTS);
670 if (scriptInternalData[qr.quot].state != SCRIPT_OK) {
671 MIXER_LINE_DISABLE();
674 #endif
676 //========== VALUE ===============
677 getvalue_t v = 0;
678 if (mode > e_perout_mode_inactive_flight_mode) {
679 if (mixEnabled)
680 v = getValue(md->srcRaw);
681 else
682 continue;
684 else {
685 mixsrc_t srcRaw = MIXSRC_Rud + stickIndex;
686 v = getValue(srcRaw);
687 srcRaw -= MIXSRC_CH1;
688 if (srcRaw <= MIXSRC_LAST_CH-MIXSRC_CH1 && md->destCh != srcRaw) {
689 if (dirtyChannels & ((bitfield_channels_t)1 << srcRaw) & (passDirtyChannels|~(((bitfield_channels_t) 1 << md->destCh)-1)))
690 passDirtyChannels |= (bitfield_channels_t) 1 << md->destCh;
691 if (srcRaw < md->destCh || pass > 0)
692 v = chans[srcRaw] >> 8;
694 if (!mixCondition) {
695 mixEnabled = v;
699 bool applyOffsetAndCurve = true;
701 //========== DELAYS ===============
702 delayval_t _swOn = swOn[i].now;
703 delayval_t _swPrev = swOn[i].prev;
704 bool swTog = (mixEnabled > _swOn+DELAY_POS_MARGIN || mixEnabled < _swOn-DELAY_POS_MARGIN);
705 if (mode == e_perout_mode_normal && swTog) {
706 if (!swOn[i].delay)
707 _swPrev = _swOn;
708 swOn[i].delay = (mixEnabled > _swOn ? md->delayUp : md->delayDown) * 10;
709 swOn[i].now = mixEnabled;
710 swOn[i].prev = _swPrev;
712 if (mode == e_perout_mode_normal && swOn[i].delay > 0) {
713 swOn[i].delay = max<int16_t>(0, (int16_t)swOn[i].delay - tick10ms);
714 if (!mixCondition)
715 v = _swPrev;
716 else if (mixEnabled)
717 continue;
719 else {
720 if (mode==e_perout_mode_normal) {
721 swOn[i].now = swOn[i].prev = mixEnabled;
723 if (!mixEnabled) {
724 if ((md->speedDown || md->speedUp) && md->mltpx!=MLTPX_REP) {
725 if (mixCondition) {
726 v = (md->mltpx == MLTPX_ADD ? 0 : RESX);
727 applyOffsetAndCurve = false;
730 else if (mixCondition) {
731 continue;
736 if (mode==e_perout_mode_normal && (!mixCondition || mixEnabled || swOn[i].delay)) {
737 if (md->mixWarn) lv_mixWarning |= 1 << (md->mixWarn - 1);
738 #if defined(BOLD_FONT)
739 swOn[i].activeMix = true;
740 #endif
743 if (applyOffsetAndCurve) {
745 //========== TRIMS ================
746 if (!(mode & e_perout_mode_notrims)) {
747 if (md->carryTrim == 0) {
748 v += getSourceTrimValue(md->srcRaw, v);
753 int32_t weight = GET_GVAR_PREC1(MD_WEIGHT(md), GV_RANGELARGE_NEG, GV_RANGELARGE, mixerCurrentFlightMode);
754 weight = calc100to256_16Bits(weight);
755 //========== SPEED ===============
756 // now its on input side, but without weight compensation. More like other remote controls
757 // lower weight causes slower movement
759 if (mode <= e_perout_mode_inactive_flight_mode && (md->speedUp || md->speedDown)) { // there are delay values
760 #define DEL_MULT_SHIFT 8
761 // we recale to a mult 256 higher value for calculation
762 int32_t tact = act[i];
763 int16_t diff = v - (tact>>DEL_MULT_SHIFT);
764 if (diff) {
765 // open.20.fsguruh: speed is defined in % movement per second; In menu we specify the full movement (-100% to 100%) = 200% in total
766 // the unit of the stored value is the value from md->speedUp or md->speedDown * 0.1s; e.g. value 4 means 0.4 seconds
767 // because we get a tick each 10msec, we need 100 ticks for one second
768 // the value in md->speedXXX gives the time it should take to do a full movement from -100 to 100 therefore 200%. This equals 2048 in recalculated internal range
769 if (tick10ms || !s_mixer_first_run_done) {
770 // only if already time is passed add or substract a value according the speed configured
771 int32_t rate = (int32_t) tick10ms << (DEL_MULT_SHIFT+11); // = DEL_MULT*2048*tick10ms
772 // rate equals a full range for one second; if less time is passed rate is accordingly smaller
773 // if one second passed, rate would be 2048 (full motion)*256(recalculated weight)*100(100 ticks needed for one second)
774 int32_t currentValue = ((int32_t) v<<DEL_MULT_SHIFT);
775 if (diff > 0) {
776 if (s_mixer_first_run_done && md->speedUp > 0) {
777 // if a speed upwards is defined recalculate the new value according configured speed; the higher the speed the smaller the add value is
778 int32_t newValue = tact+rate/((int16_t)10*md->speedUp);
779 if (newValue<currentValue) currentValue = newValue; // Endposition; prevent toggling around the destination
782 else { // if is <0 because ==0 is not possible
783 if (s_mixer_first_run_done && md->speedDown > 0) {
784 // see explanation in speedUp
785 int32_t newValue = tact-rate/((int16_t)10*md->speedDown);
786 if (newValue>currentValue) currentValue = newValue; // Endposition; prevent toggling around the destination
789 act[i] = tact = currentValue;
790 // open.20.fsguruh: this implementation would save about 50 bytes code
791 } // endif tick10ms ; in case no time passed assign the old value, not the current value from source
792 v = (tact >> DEL_MULT_SHIFT);
796 //========== CURVES ===============
797 if (applyOffsetAndCurve && md->curve.type != CURVE_REF_DIFF && md->curve.value) {
798 v = applyCurve(v, md->curve);
801 //========== WEIGHT ===============
802 int32_t dv = (int32_t)v * weight;
803 dv = div_and_round(dv, 10);
805 //========== OFFSET / AFTER ===============
806 if (applyOffsetAndCurve) {
807 int32_t offset = GET_GVAR_PREC1(MD_OFFSET(md), GV_RANGELARGE_NEG, GV_RANGELARGE, mixerCurrentFlightMode);
808 if (offset) dv += div_and_round(calc100toRESX_16Bits(offset), 10) << 8;
811 //========== DIFFERENTIAL =========
812 if (md->curve.type == CURVE_REF_DIFF && md->curve.value) {
813 dv = applyCurve(dv, md->curve);
816 int32_t * ptr = &chans[md->destCh]; // Save calculating address several times
818 switch (md->mltpx) {
819 case MLTPX_REP:
820 *ptr = dv;
821 #if defined(BOLD_FONT)
822 if (mode==e_perout_mode_normal) {
823 for (uint8_t m=i-1; m<MAX_MIXERS && mixAddress(m)->destCh==md->destCh; m--)
824 swOn[m].activeMix = false;
826 #endif
827 break;
828 case MLTPX_MUL:
829 // @@@2 we have to remove the weight factor of 256 in case of 100%; now we use the new base of 256
830 dv >>= 8;
831 dv *= *ptr;
832 dv >>= RESX_SHIFT; // same as dv /= RESXl;
833 *ptr = dv;
834 break;
835 default: // MLTPX_ADD
836 *ptr += dv; //Mixer output add up to the line (dv + (dv>0 ? 100/2 : -100/2))/(100);
837 break;
838 } // endswitch md->mltpx
839 #ifdef PREVENT_ARITHMETIC_OVERFLOW
841 // a lot of assumptions must be true, for this kind of check; not really worth for only 4 bytes flash savings
842 // this solution would save again 4 bytes flash
843 int8_t testVar=(*ptr<<1)>>24;
844 if ( (testVar!=-1) && (testVar!=0 ) ) {
845 // this devices by 64 which should give a good balance between still over 100% but lower then 32x100%; should be OK
846 *ptr >>= 6; // this is quite tricky, reduces the value a lot but should be still over 100% and reduces flash need
847 } */
850 PACK( union u_int16int32_t {
851 struct {
852 int16_t lo;
853 int16_t hi;
854 } words_t;
855 int32_t dword;
858 u_int16int32_t tmp;
859 tmp.dword=*ptr;
861 if (tmp.dword<0) {
862 if ((tmp.words_t.hi&0xFF80)!=0xFF80) tmp.words_t.hi=0xFF86; // set to min nearly
864 else {
865 if ((tmp.words_t.hi|0x007F)!=0x007F) tmp.words_t.hi=0x0079; // set to max nearly
867 *ptr = tmp.dword;
868 // this implementation saves 18bytes flash
870 /* dv=*ptr>>8;
871 if (dv>(32767-RESXl)) {
872 *ptr=(32767-RESXl)<<8;
873 } else if (dv<(-32767+RESXl)) {
874 *ptr=(-32767+RESXl)<<8;
876 // *ptr=limit( int32_t(int32_t(-1)<<23), *ptr, int32_t(int32_t(1)<<23)); // limit code cost 72 bytes
877 // *ptr=limit( int32_t((-32767+RESXl)<<8), *ptr, int32_t((32767-RESXl)<<8)); // limit code cost 80 bytes
878 #endif
880 } //endfor mixers
882 tick10ms = 0;
883 dirtyChannels &= passDirtyChannels;
885 } while (++pass < 5 && dirtyChannels);
887 mixWarning = lv_mixWarning;
892 #define MAX_ACT 0xffff
893 uint8_t lastFlightMode = 255; // TODO reinit everything here when the model changes, no???
895 tmr10ms_t flightModeTransitionTime;
896 uint8_t flightModeTransitionLast = 255;
898 void evalMixes(uint8_t tick10ms)
900 int32_t sum_chans512[MAX_OUTPUT_CHANNELS];
902 static uint16_t fp_act[MAX_FLIGHT_MODES] = {0};
903 static uint16_t delta = 0;
904 static ACTIVE_PHASES_TYPE flightModesFade = 0;
906 LS_RECURSIVE_EVALUATION_RESET();
908 uint8_t fm = getFlightMode();
910 if (lastFlightMode != fm) {
911 flightModeTransitionTime = get_tmr10ms();
913 if (lastFlightMode == 255) {
914 fp_act[fm] = MAX_ACT;
916 else {
917 uint8_t fadeTime = max(g_model.flightModeData[lastFlightMode].fadeOut, g_model.flightModeData[fm].fadeIn);
918 ACTIVE_PHASES_TYPE transitionMask = ((ACTIVE_PHASES_TYPE)1 << lastFlightMode) + ((ACTIVE_PHASES_TYPE)1 << fm);
919 if (fadeTime) {
920 flightModesFade |= transitionMask;
921 delta = (MAX_ACT / 10) / fadeTime;
923 else {
924 flightModesFade &= ~transitionMask;
925 fp_act[lastFlightMode] = 0;
926 fp_act[fm] = MAX_ACT;
928 logicalSwitchesCopyState(lastFlightMode, fm); // push last logical switches state from old to new flight mode
930 lastFlightMode = fm;
933 if (flightModeTransitionTime && get_tmr10ms() > flightModeTransitionTime+SWITCHES_DELAY()) {
934 flightModeTransitionTime = 0;
935 if (fm != flightModeTransitionLast) {
936 if (flightModeTransitionLast != 255) {
937 PLAY_PHASE_OFF(flightModeTransitionLast);
939 PLAY_PHASE_ON(fm);
940 flightModeTransitionLast = fm;
944 int32_t weight = 0;
945 if (flightModesFade) {
946 memclear(sum_chans512, sizeof(sum_chans512));
947 for (uint8_t p=0; p<MAX_FLIGHT_MODES; p++) {
948 LS_RECURSIVE_EVALUATION_RESET();
949 if (flightModesFade & ((ACTIVE_PHASES_TYPE)1 << p)) {
950 mixerCurrentFlightMode = p;
951 evalFlightModeMixes(p==fm ? e_perout_mode_normal : e_perout_mode_inactive_flight_mode, p==fm ? tick10ms : 0);
952 for (uint8_t i=0; i<MAX_OUTPUT_CHANNELS; i++)
953 sum_chans512[i] += (chans[i] >> 4) * fp_act[p];
954 weight += fp_act[p];
956 LS_RECURSIVE_EVALUATION_RESET();
958 assert(weight);
959 mixerCurrentFlightMode = fm;
961 else {
962 mixerCurrentFlightMode = fm;
963 evalFlightModeMixes(e_perout_mode_normal, tick10ms);
966 //========== FUNCTIONS ===============
967 // must be done after mixing because some functions use the inputs/channels values
968 // must be done before limits because of the applyLimit function: it checks for safety switches which would be not initialized otherwise
969 if (tick10ms) {
970 requiredSpeakerVolume = g_eeGeneral.speakerVolume + VOLUME_LEVEL_DEF;
972 if (!g_model.noGlobalFunctions) {
973 evalFunctions(g_eeGeneral.customFn, globalFunctionsContext);
975 evalFunctions(g_model.customFn, modelFunctionsContext);
978 //========== LIMITS ===============
979 for (uint8_t i=0; i<MAX_OUTPUT_CHANNELS; i++) {
980 // chans[i] holds data from mixer. chans[i] = v*weight => 1024*256
981 // later we multiply by the limit (up to 100) and then we need to normalize
982 // at the end chans[i] = chans[i]/256 => -1024..1024
983 // interpolate value with min/max so we get smooth motion from center to stop
984 // this limits based on v original values and min=-1024, max=1024 RESX=1024
985 int32_t q = (flightModesFade ? (sum_chans512[i] / weight) << 4 : chans[i]);
987 ex_chans[i] = q / 256;
989 int16_t value = applyLimits(i, q); // applyLimits will remove the 256 100% basis
991 channelOutputs[i] = value; // copy consistent word to int-level
994 if (tick10ms && flightModesFade) {
995 uint16_t tick_delta = delta * tick10ms;
996 for (uint8_t p=0; p<MAX_FLIGHT_MODES; p++) {
997 ACTIVE_PHASES_TYPE flightModeMask = ((ACTIVE_PHASES_TYPE)1 << p);
998 if (flightModesFade & flightModeMask) {
999 if (p == fm) {
1000 if (MAX_ACT - fp_act[p] > tick_delta)
1001 fp_act[p] += tick_delta;
1002 else {
1003 fp_act[p] = MAX_ACT;
1004 flightModesFade -= flightModeMask;
1007 else {
1008 if (fp_act[p] > tick_delta)
1009 fp_act[p] -= tick_delta;
1010 else {
1011 fp_act[p] = 0;
1012 flightModesFade -= flightModeMask;