x9e with horus bt module (#5214)
[opentx.git] / radio / src / opentx.cpp
blobac5b61ff954724577aae31a408ca9053b439f7af
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"
23 RadioData g_eeGeneral;
24 ModelData g_model;
26 #if defined(SDCARD)
27 Clipboard clipboard;
28 #endif
30 #if !defined(CPUARM)
31 uint8_t g_tmr1Latency_max;
32 uint8_t g_tmr1Latency_min;
33 uint16_t lastMixerDuration;
34 #endif
36 uint8_t unexpectedShutdown = 0;
38 /* AVR: mixer duration in 1/16ms */
39 /* ARM: mixer duration in 0.5us */
40 uint16_t maxMixerDuration;
42 #if defined(AUDIO) && !defined(CPUARM)
43 audioQueue audio;
44 #endif
46 uint8_t heartbeat;
48 #if defined(OVERRIDE_CHANNEL_FUNCTION)
49 safetych_t safetyCh[MAX_OUTPUT_CHANNELS];
50 #endif
52 union ReusableBuffer reusableBuffer;
54 const pm_uint8_t bchout_ar[] PROGMEM = {
55 0x1B, 0x1E, 0x27, 0x2D, 0x36, 0x39,
56 0x4B, 0x4E, 0x63, 0x6C, 0x72, 0x78,
57 0x87, 0x8D, 0x93, 0x9C, 0xB1, 0xB4,
58 0xC6, 0xC9, 0xD2, 0xD8, 0xE1, 0xE4 };
60 uint8_t channel_order(uint8_t x)
62 return ( ((pgm_read_byte(bchout_ar + g_eeGeneral.templateSetup) >> (6-(x-1) * 2)) & 3 ) + 1 );
66 mode1 rud ele thr ail
67 mode2 rud thr ele ail
68 mode3 ail ele thr rud
69 mode4 ail thr ele rud
71 const pm_uint8_t modn12x3[] PROGMEM = {
72 0, 1, 2, 3,
73 0, 2, 1, 3,
74 3, 1, 2, 0,
75 3, 2, 1, 0 };
77 volatile tmr10ms_t g_tmr10ms;
79 #if defined(CPUARM)
80 volatile uint8_t rtc_count = 0;
81 uint32_t watchdogTimeout = 0;
83 void watchdogSuspend(uint32_t timeout)
85 watchdogTimeout = timeout;
87 #endif
89 void per10ms()
91 g_tmr10ms++;
93 #if defined(CPUARM)
94 if (watchdogTimeout) {
95 watchdogTimeout -= 1;
96 wdt_reset(); // Retrigger hardware watchdog
98 #endif
100 #if defined(GUI)
101 if (lightOffCounter) lightOffCounter--;
102 if (flashCounter) flashCounter--;
103 if (noHighlightCounter) noHighlightCounter--;
104 #endif
106 if (trimsCheckTimer) trimsCheckTimer--;
107 if (ppmInputValidityTimer) ppmInputValidityTimer--;
109 #if defined(CPUARM)
110 if (trimsDisplayTimer)
111 trimsDisplayTimer--;
112 else
113 trimsDisplayMask = 0;
114 #endif
116 #if defined(RTCLOCK)
117 /* Update global Date/Time every 100 per10ms cycles */
118 if (++g_ms100 == 100) {
119 g_rtcTime++; // inc global unix timestamp one second
120 #if defined(COPROCESSOR)
121 if (g_rtcTime < 60 || rtc_count<5) {
122 rtcInit();
123 rtc_count++;
125 else {
126 coprocReadData(true);
128 #endif
129 g_ms100 = 0;
131 #endif
133 readKeysAndTrims();
135 #if defined(ROTARY_ENCODER_NAVIGATION)
136 if (IS_ROTARY_ENCODER_NAVIGATION_ENABLE()) {
137 static rotenc_t rePreviousValue;
138 rotenc_t reNewValue = (ROTARY_ENCODER_NAVIGATION_VALUE / ROTARY_ENCODER_GRANULARITY);
139 int8_t scrollRE = reNewValue - rePreviousValue;
140 if (scrollRE) {
141 rePreviousValue = reNewValue;
142 putEvent(scrollRE < 0 ? EVT_ROTARY_LEFT : EVT_ROTARY_RIGHT);
144 #if defined(CPUARM)
145 // rotary encoder navigation speed (acceleration) detection/calculation
146 if (scrollRE) {
147 static uint32_t lastTick = 0;
148 static uint32_t delay = 0;
149 delay = (((get_tmr10ms() - lastTick) << 3) + delay) >> 1; // Modified moving average filter used for smoother change of speed
150 lastTick = get_tmr10ms();
151 if (delay < ROTENC_DELAY_HIGHSPEED)
152 rotencSpeed = ROTENC_HIGHSPEED;
153 else if (delay < ROTENC_DELAY_MIDSPEED)
154 rotencSpeed = ROTENC_MIDSPEED;
155 else
156 rotencSpeed = ROTENC_LOWSPEED;
158 #endif
160 #endif
162 #if defined(TELEMETRY_FRSKY) || defined(TELEMETRY_JETI)
163 if (!IS_DSM2_SERIAL_PROTOCOL(s_current_protocol[0])) {
164 telemetryInterrupt10ms();
166 #endif
168 // These moved here from evalFlightModeMixes() to improve beep trigger reliability.
169 #if defined(PWM_BACKLIGHT)
170 if ((g_tmr10ms&0x03) == 0x00)
171 backlightFade(); // increment or decrement brightness until target brightness is reached
172 #endif
174 #if !defined(AUDIO)
175 if (mixWarning & 1) if(((g_tmr10ms&0xFF)== 0)) AUDIO_MIX_WARNING(1);
176 if (mixWarning & 2) if(((g_tmr10ms&0xFF)== 64) || ((g_tmr10ms&0xFF)== 72)) AUDIO_MIX_WARNING(2);
177 if (mixWarning & 4) if(((g_tmr10ms&0xFF)==128) || ((g_tmr10ms&0xFF)==136) || ((g_tmr10ms&0xFF)==144)) AUDIO_MIX_WARNING(3);
178 #endif
180 #if defined(SDCARD)
181 sdPoll10ms();
182 #endif
184 heartbeat |= HEART_TIMER_10MS;
187 FlightModeData *flightModeAddress(uint8_t idx)
189 return &g_model.flightModeData[idx];
192 ExpoData *expoAddress(uint8_t idx )
194 return &g_model.expoData[idx];
197 MixData *mixAddress(uint8_t idx)
199 return &g_model.mixData[idx];
202 LimitData *limitAddress(uint8_t idx)
204 return &g_model.limitData[idx];
207 #if defined(CPUM64)
208 void memclear(void *ptr, uint8_t size)
210 memset(ptr, 0, size);
212 #endif
214 void memswap(void * a, void * b, uint8_t size)
216 uint8_t * x = (uint8_t *)a;
217 uint8_t * y = (uint8_t *)b;
218 uint8_t temp ;
220 while (size--) {
221 temp = *x;
222 *x++ = *y;
223 *y++ = temp;
227 void generalDefault()
229 memclear(&g_eeGeneral, sizeof(g_eeGeneral));
230 g_eeGeneral.version = EEPROM_VER;
231 g_eeGeneral.variant = EEPROM_VARIANT;
233 #if !defined(PCBHORUS)
234 g_eeGeneral.contrast = LCD_CONTRAST_DEFAULT;
235 #endif
237 #if defined(PCBFLAMENCO)
238 g_eeGeneral.vBatWarn = 33;
239 g_eeGeneral.vBatMin = -60; // 0 is 9.0V
240 g_eeGeneral.vBatMax = -78; // 0 is 12.0V
241 #elif defined(PCBHORUS)
242 #if PCBREV >= 13
243 g_eeGeneral.potsConfig = 0x1B; // S1 = pot, 6P = multipos, S2 = pot with detent
244 #else
245 g_eeGeneral.potsConfig = 0x19; // S1 = pot without detent, 6P = multipos, S2 = pot with detent
246 #endif
247 g_eeGeneral.slidersConfig = 0x0f; // 4 sliders
248 g_eeGeneral.blOffBright = 20;
249 #elif defined(PCBX7)
250 g_eeGeneral.potsConfig = 0x07; // S1 = pot without detent, S2 = pot with detent
251 #elif defined(PCBTARANIS)
252 g_eeGeneral.potsConfig = 0x05; // S1 and S2 = pots with detent
253 g_eeGeneral.slidersConfig = 0x03; // LS and RS = sliders with detent
254 #endif
256 #if defined(PCBX7)
257 g_eeGeneral.switchConfig = 0x000006ff; // 4x3POS, 1x2POS, 1xTOGGLE
258 #elif defined(PCBTARANIS) || defined(PCBHORUS)
259 g_eeGeneral.switchConfig = 0x00007bff; // 6x3POS, 1x2POS, 1xTOGGLE
260 #endif
262 #if defined(PCBX9E)
263 // NI-MH 9.6V
264 g_eeGeneral.vBatWarn = 87;
265 g_eeGeneral.vBatMin = -5;
266 g_eeGeneral.vBatMax = -5;
267 #elif defined(PCBTARANIS)
268 // NI-MH 7.2V
269 g_eeGeneral.vBatWarn = 65;
270 g_eeGeneral.vBatMin = -30;
271 g_eeGeneral.vBatMax = -40;
272 #else
273 g_eeGeneral.vBatWarn = 90;
274 #endif
276 #if defined(DEFAULT_MODE)
277 g_eeGeneral.stickMode = DEFAULT_MODE-1;
278 #endif
280 #if defined(PCBFLAMENCO)
281 g_eeGeneral.templateSetup = 21; /* AETR */
282 #elif defined(PCBTARANIS)
283 g_eeGeneral.templateSetup = 17; /* TAER */
284 #endif
286 #if defined(PCBFLAMENCO)
287 g_eeGeneral.inactivityTimer = 50;
288 #elif !defined(CPUM64)
289 g_eeGeneral.backlightMode = e_backlight_mode_all;
290 g_eeGeneral.lightAutoOff = 2;
291 g_eeGeneral.inactivityTimer = 10;
292 #endif
294 #if defined(CPUARM)
295 g_eeGeneral.ttsLanguage[0] = 'e';
296 g_eeGeneral.ttsLanguage[1] = 'n';
297 g_eeGeneral.wavVolume = 2;
298 g_eeGeneral.backgroundVolume = 1;
299 #endif
301 #if defined(CPUARM)
302 for (int i=0; i<NUM_STICKS; ++i) {
303 g_eeGeneral.trainer.mix[i].mode = 2;
304 g_eeGeneral.trainer.mix[i].srcChn = channel_order(i+1) - 1;
305 g_eeGeneral.trainer.mix[i].studWeight = 100;
307 #endif
309 #if defined(PCBX9E)
310 const int8_t defaultName[] = { 20, -1, -18, -1, -14, -9, -19 };
311 memcpy(g_eeGeneral.bluetoothName, defaultName, sizeof(defaultName));
312 #endif
314 #if !defined(EEPROM)
315 strcpy(g_eeGeneral.currModelFilename, DEFAULT_MODEL_FILENAME);
316 #endif
318 #if defined(PCBHORUS)
319 strcpy(g_eeGeneral.themeName, theme->getName());
320 theme->init();
321 #endif
323 g_eeGeneral.chkSum = 0xFFFF;
326 uint16_t evalChkSum()
328 uint16_t sum = 0;
329 const int16_t *calibValues = (const int16_t *) &g_eeGeneral.calib[0];
330 for (int i=0; i<12; i++)
331 sum += calibValues[i];
332 return sum;
335 #if defined(VIRTUAL_INPUTS)
336 void clearInputs()
338 memset(g_model.expoData, 0, sizeof(g_model.expoData)); // clear all expos
341 void defaultInputs()
343 clearInputs();
345 for (int i=0; i<NUM_STICKS; i++) {
346 uint8_t stick_index = channel_order(i+1);
347 ExpoData *expo = expoAddress(i);
348 expo->srcRaw = MIXSRC_Rud - 1 + stick_index;
349 expo->curve.type = CURVE_REF_EXPO;
350 expo->chn = i;
351 expo->weight = 100;
352 expo->mode = 3; // TODO constant
353 #if defined(TRANSLATIONS_CZ)
354 for (int c=0; c<4; c++) {
355 g_model.inputNames[i][c] = char2idx(STR_INPUTNAMES[1+4*(stick_index-1)+c]);
357 #else
358 for (int c=0; c<3; c++) {
359 g_model.inputNames[i][c] = char2idx(STR_VSRCRAW[2+4*stick_index+c]);
361 #if LEN_INPUT_NAME > 3
362 g_model.inputNames[i][3] = '\0';
363 #endif
364 #endif
366 storageDirty(EE_MODEL);
368 #endif
370 #if defined(TEMPLATES)
371 inline void applyDefaultTemplate()
373 applyTemplate(TMPL_SIMPLE_4CH); // calls storageDirty internally
375 #else
376 void applyDefaultTemplate()
378 #if defined(VIRTUAL_INPUTS)
379 defaultInputs(); // calls storageDirty internally
380 #else
381 storageDirty(EE_MODEL);
382 #endif
384 for (int i=0; i<NUM_STICKS; i++) {
385 MixData * mix = mixAddress(i);
386 mix->destCh = i;
387 mix->weight = 100;
388 #if defined(VIRTUAL_INPUTS)
389 mix->srcRaw = i+1;
390 #else
391 mix->srcRaw = MIXSRC_Rud - 1 + channel_order(i+1);
392 #endif
395 #endif
397 #if defined(CPUARM) && defined(EEPROM)
398 void checkModelIdUnique(uint8_t index, uint8_t module)
400 uint8_t modelId = g_model.header.modelId[module];
401 uint8_t additionalOnes = 0;
402 char * name = reusableBuffer.msgbuf.msg;
404 memset(reusableBuffer.msgbuf.msg, 0, sizeof(reusableBuffer.msgbuf.msg));
406 if (modelId != 0) {
407 for (uint8_t i = 0; i < MAX_MODELS; i++) {
408 if (i != index) {
409 if (modelId == modelHeaders[i].modelId[module]) {
410 if ((WARNING_LINE_LEN - 4 - (name - reusableBuffer.msgbuf.msg)) > (signed)(modelHeaders[i].name[0] ? zlen(modelHeaders[i].name, LEN_MODEL_NAME) : sizeof(TR_MODEL) + 2)) { // you cannot rely exactly on WARNING_LINE_LEN so using WARNING_LINE_LEN-2 (-2 for the ",")
411 if (reusableBuffer.msgbuf.msg[0] != 0) {
412 name = strAppend(name, ", ");
414 if (modelHeaders[i].name[0] == 0) {
415 name = strAppend(name, STR_MODEL);
416 name = strAppendUnsigned(name+strlen(name),i, 2);
418 else {
419 name += zchar2str(name, modelHeaders[i].name, LEN_MODEL_NAME);
422 else {
423 additionalOnes++;
430 if (additionalOnes) {
431 name = strAppend(name, " (+");
432 name = strAppendUnsigned(name, additionalOnes);
433 name = strAppend(name, ")");
436 if (reusableBuffer.msgbuf.msg[0] != 0) {
437 POPUP_WARNING(STR_MODELIDUSED);
438 SET_WARNING_INFO(reusableBuffer.msgbuf.msg, sizeof(reusableBuffer.msgbuf.msg), 0);
441 #endif
443 void modelDefault(uint8_t id)
445 memset(&g_model, 0, sizeof(g_model));
447 applyDefaultTemplate();
449 #if defined(LUA) && defined(PCBTARANIS) //Horus uses menuModelWizard() for wizard
450 if (isFileAvailable(WIZARD_PATH "/" WIZARD_NAME)) {
451 f_chdir(WIZARD_PATH);
452 luaExec(WIZARD_NAME);
454 #endif
456 #if defined(PCBTARANIS) || defined(PCBHORUS)
457 g_model.moduleData[INTERNAL_MODULE].type = MODULE_TYPE_XJT;
458 g_model.moduleData[INTERNAL_MODULE].channelsCount = DEFAULT_CHANNELS(INTERNAL_MODULE);
459 #elif defined(PCBSKY9X)
460 g_model.moduleData[EXTERNAL_MODULE].type = MODULE_TYPE_PPM;
461 #endif
463 #if defined(CPUARM) && defined(EEPROM)
464 for (int i=0; i<NUM_MODULES; i++) {
465 modelHeaders[id].modelId[i] = g_model.header.modelId[i] = id+1;
467 checkModelIdUnique(id, 0);
468 #endif
470 #if defined(CPUARM) && defined(FLIGHT_MODES) && defined(GVARS)
471 for (int p=1; p<MAX_FLIGHT_MODES; p++) {
472 for (int i=0; i<MAX_GVARS; i++) {
473 g_model.flightModeData[p].gvars[i] = GVAR_MAX+1;
476 #endif
478 #if defined(FLIGHT_MODES) && defined(ROTARY_ENCODERS)
479 for (int p=1; p<MAX_FLIGHT_MODES; p++) {
480 for (int i=0; i<ROTARY_ENCODERS; i++) {
481 g_model.flightModeData[p].rotaryEncoders[i] = ROTARY_ENCODER_MAX+1;
484 #endif
486 #if defined(TELEMETRY_MAVLINK)
487 g_model.mavlink.rc_rssi_scale = 15;
488 g_model.mavlink.pc_rssi_en = 1;
489 #endif
491 #if !defined(EEPROM)
492 strcpy(g_model.header.name, "\015\361\374\373\364");
493 g_model.header.name[5] = '\033' + id/10;
494 g_model.header.name[6] = '\033' + id%10;
495 #endif
497 #if defined(PCBHORUS)
498 extern const LayoutFactory * defaultLayout;
499 delete customScreens[0];
500 customScreens[0] = defaultLayout->create(&g_model.screenData[0].layoutData);
501 strcpy(g_model.screenData[0].layoutName, "Layout2P1");
502 extern const WidgetFactory * defaultWidget;
503 customScreens[0]->createWidget(0, defaultWidget);
504 // enable switch warnings
505 for (int i=0; i<NUM_SWITCHES; i++) {
506 g_model.switchWarningState |= (1 << (3*i));
508 #endif
511 #if defined(VIRTUAL_INPUTS)
512 bool isInputRecursive(int index)
514 ExpoData * line = expoAddress(0);
515 for (int i=0; i<MAX_EXPOS; i++, line++) {
516 if (line->chn > index)
517 break;
518 else if (line->chn < index)
519 continue;
520 else if (line->srcRaw >= MIXSRC_FIRST_LOGICAL_SWITCH)
521 return true;
523 return false;
525 #endif
527 #if defined(AUTOSOURCE)
528 int8_t getMovedSource(GET_MOVED_SOURCE_PARAMS)
530 int8_t result = 0;
531 static tmr10ms_t s_move_last_time = 0;
533 #if defined(VIRTUAL_INPUTS)
534 static int16_t inputsStates[MAX_INPUTS];
535 if (min <= MIXSRC_FIRST_INPUT) {
536 for (uint8_t i=0; i<MAX_INPUTS; i++) {
537 if (abs(anas[i] - inputsStates[i]) > 512) {
538 if (!isInputRecursive(i)) {
539 result = MIXSRC_FIRST_INPUT+i;
540 break;
545 #endif
547 static int16_t sourcesStates[NUM_STICKS+NUM_POTS+NUM_SLIDERS+NUM_MOUSE_ANALOGS];
548 if (result == 0) {
549 for (uint8_t i=0; i<NUM_STICKS+NUM_POTS+NUM_SLIDERS; i++) {
550 if (abs(calibratedAnalogs[i] - sourcesStates[i]) > 512) {
551 result = MIXSRC_Rud+i;
552 break;
557 bool recent = ((tmr10ms_t)(get_tmr10ms() - s_move_last_time) > 10);
558 if (recent) {
559 result = 0;
562 if (result || recent) {
563 #if defined(VIRTUAL_INPUTS)
564 memcpy(inputsStates, anas, sizeof(inputsStates));
565 #endif
566 memcpy(sourcesStates, calibratedAnalogs, sizeof(sourcesStates));
569 s_move_last_time = get_tmr10ms();
570 return result;
572 #endif
574 #if defined(FLIGHT_MODES)
575 uint8_t getFlightMode()
577 for (uint8_t i=1; i<MAX_FLIGHT_MODES; i++) {
578 FlightModeData *phase = &g_model.flightModeData[i];
579 if (phase->swtch && getSwitch(phase->swtch)) {
580 return i;
583 return 0;
585 #endif
587 trim_t getRawTrimValue(uint8_t phase, uint8_t idx)
589 FlightModeData * p = flightModeAddress(phase);
590 #if defined(PCBSTD)
591 return (((trim_t)p->trim[idx]) << 2) + ((p->trim_ext >> (2*idx)) & 0x03);
592 #else
593 return p->trim[idx];
594 #endif
597 int getTrimValue(uint8_t phase, uint8_t idx)
599 #if defined(CPUARM)
600 int result = 0;
601 for (uint8_t i=0; i<MAX_FLIGHT_MODES; i++) {
602 trim_t v = getRawTrimValue(phase, idx);
603 if (v.mode == TRIM_MODE_NONE) {
604 return result;
606 else {
607 unsigned int p = v.mode >> 1;
608 if (p == phase || phase == 0) {
609 return result + v.value;
611 else {
612 phase = p;
613 if (v.mode % 2 != 0) {
614 result += v.value;
619 return 0;
620 #else
621 return getRawTrimValue(getTrimFlightMode(phase, idx), idx);
622 #endif
625 #if defined(CPUARM)
626 bool setTrimValue(uint8_t phase, uint8_t idx, int trim)
628 for (uint8_t i=0; i<MAX_FLIGHT_MODES; i++) {
629 trim_t & v = flightModeAddress(phase)->trim[idx];
630 if (v.mode == TRIM_MODE_NONE)
631 return false;
632 unsigned int p = v.mode >> 1;
633 if (p == phase || phase == 0) {
634 v.value = trim;
635 break;
637 else if (v.mode % 2 == 0) {
638 phase = p;
640 else {
641 v.value = limit<int>(TRIM_EXTENDED_MIN, trim - getTrimValue(p, idx), TRIM_EXTENDED_MAX);
642 break;
645 storageDirty(EE_MODEL);
646 return true;
648 #else
649 void setTrimValue(uint8_t phase, uint8_t idx, int trim)
651 #if defined(PCBSTD)
652 FlightModeData *p = flightModeAddress(phase);
653 p->trim[idx] = (int8_t)(trim >> 2);
654 idx <<= 1;
655 p->trim_ext = (p->trim_ext & ~(0x03 << idx)) + (((trim & 0x03) << idx));
656 #else
657 FlightModeData *p = flightModeAddress(phase);
658 p->trim[idx] = trim;
659 #endif
660 storageDirty(EE_MODEL);
662 #endif
664 #if !defined(CPUARM)
665 uint8_t getTrimFlightMode(uint8_t phase, uint8_t idx)
667 for (uint8_t i=0; i<MAX_FLIGHT_MODES; i++) {
668 if (phase == 0) return 0;
669 trim_t trim = getRawTrimValue(phase, idx);
670 if (trim <= TRIM_EXTENDED_MAX) return phase;
671 uint8_t result = trim-TRIM_EXTENDED_MAX-1;
672 if (result >= phase) result++;
673 phase = result;
675 return 0;
677 #endif
679 #if defined(ROTARY_ENCODERS)
680 uint8_t getRotaryEncoderFlightMode(uint8_t idx)
682 uint8_t phase = mixerCurrentFlightMode;
683 for (uint8_t i=0; i<MAX_FLIGHT_MODES; i++) {
684 if (phase == 0) return 0;
685 int16_t value = flightModeAddress(phase)->rotaryEncoders[idx];
686 if (value <= ROTARY_ENCODER_MAX) return phase;
687 uint8_t result = value-ROTARY_ENCODER_MAX-1;
688 if (result >= phase) result++;
689 phase = result;
691 return 0;
694 int16_t getRotaryEncoder(uint8_t idx)
696 return flightModeAddress(getRotaryEncoderFlightMode(idx))->rotaryEncoders[idx];
699 void incRotaryEncoder(uint8_t idx, int8_t inc)
701 rotencValue[idx] += inc;
702 int16_t *value = &(flightModeAddress(getRotaryEncoderFlightMode(idx))->rotaryEncoders[idx]);
703 *value = limit((int16_t)-RESX, (int16_t)(*value + (inc * 8)), (int16_t)+RESX);
704 storageDirty(EE_MODEL);
706 #endif
708 #if defined(CPUARM)
709 getvalue_t convert16bitsTelemValue(source_t channel, ls_telemetry_value_t value)
711 return value;
714 getvalue_t convert8bitsTelemValue(source_t channel, ls_telemetry_value_t value)
716 return value;
719 #if defined(TELEMETRY_FRSKY)
720 ls_telemetry_value_t minTelemValue(source_t channel)
722 return 0;
725 ls_telemetry_value_t maxTelemValue(source_t channel)
727 return 30000;
729 #endif
731 ls_telemetry_value_t max8bitsTelemValue(source_t channel)
733 return 30000;
736 #elif defined(TELEMETRY_FRSKY)
739 ls_telemetry_value_t minTelemValue(uint8_t channel)
741 switch (channel) {
742 case TELEM_TIMER1:
743 case TELEM_TIMER2:
744 return -3600;
745 case TELEM_ALT:
746 case TELEM_MIN_ALT:
747 case TELEM_MAX_ALT:
748 case TELEM_GPSALT:
749 return -500;
750 case TELEM_T1:
751 case TELEM_MAX_T1:
752 case TELEM_T2:
753 case TELEM_MAX_T2:
754 return -30;
755 case TELEM_ACCx:
756 case TELEM_ACCy:
757 case TELEM_ACCz:
758 return -1000;
759 case TELEM_VSPEED:
760 return -3000;
761 default:
762 return 0;
766 ls_telemetry_value_t maxTelemValue(uint8_t channel)
768 switch (channel) {
769 case TELEM_FUEL:
770 case TELEM_RSSI_TX:
771 case TELEM_RSSI_RX:
772 return 100;
773 case TELEM_HDG:
774 return 180;
775 default:
776 return 255;
779 #endif
781 #if !defined(CPUARM)
782 getvalue_t convert8bitsTelemValue(uint8_t channel, ls_telemetry_value_t value)
784 getvalue_t result;
785 switch (channel) {
786 case TELEM_TIMER1:
787 case TELEM_TIMER2:
788 result = value * 5;
789 break;
790 #if defined(TELEMETRY_FRSKY)
791 case TELEM_ALT:
792 case TELEM_GPSALT:
793 case TELEM_MAX_ALT:
794 case TELEM_MIN_ALT:
795 result = value * 8 - 500;
796 break;
797 case TELEM_RPM:
798 case TELEM_MAX_RPM:
799 result = value * 50;
800 break;
801 case TELEM_T1:
802 case TELEM_T2:
803 case TELEM_MAX_T1:
804 case TELEM_MAX_T2:
805 result = (getvalue_t)value - 30;
806 break;
807 case TELEM_CELL:
808 case TELEM_HDG:
809 case TELEM_SPEED:
810 case TELEM_MAX_SPEED:
811 result = value * 2;
812 break;
813 case TELEM_ASPEED:
814 case TELEM_MAX_ASPEED:
815 result = value * 20;
816 break;
817 case TELEM_DIST:
818 case TELEM_MAX_DIST:
819 result = value * 8;
820 break;
821 case TELEM_CURRENT:
822 case TELEM_POWER:
823 case TELEM_MAX_CURRENT:
824 case TELEM_MAX_POWER:
825 result = value * 5;
826 break;
827 case TELEM_CONSUMPTION:
828 result = value * 100;
829 break;
830 case TELEM_VSPEED:
831 result = ((getvalue_t)value - 125) * 10;
832 break;
833 #endif
834 default:
835 result = value;
836 break;
838 return result;
840 #endif
842 #define INAC_STICKS_SHIFT 6
843 #define INAC_SWITCHES_SHIFT 8
844 bool inputsMoved()
846 uint8_t sum = 0;
847 for (uint8_t i=0; i<NUM_STICKS+NUM_POTS+NUM_SLIDERS; i++)
848 sum += anaIn(i) >> INAC_STICKS_SHIFT;
849 for (uint8_t i=0; i<NUM_SWITCHES; i++)
850 sum += getValue(MIXSRC_FIRST_SWITCH+i) >> INAC_SWITCHES_SHIFT;
852 if (abs((int8_t)(sum-inactivity.sum)) > 1) {
853 inactivity.sum = sum;
854 return true;
856 else {
857 return false;
861 void checkBacklight()
863 static uint8_t tmr10ms ;
865 #if defined(PCBSTD) && defined(ROTARY_ENCODER_NAVIGATION)
866 rotencPoll();
867 #endif
869 uint8_t x = g_blinkTmr10ms;
870 if (tmr10ms != x) {
871 tmr10ms = x;
872 if (inputsMoved()) {
873 inactivity.counter = 0;
874 if (g_eeGeneral.backlightMode & e_backlight_mode_sticks) {
875 backlightOn();
879 bool backlightOn = (g_eeGeneral.backlightMode == e_backlight_mode_on || lightOffCounter || isFunctionActive(FUNCTION_BACKLIGHT));
880 if (flashCounter) backlightOn = !backlightOn;
881 if (backlightOn)
882 BACKLIGHT_ENABLE();
883 else
884 BACKLIGHT_DISABLE();
886 #if defined(PCBSTD) && defined(VOICE) && !defined(SIMU)
887 Voice.voice_process() ;
888 #endif
892 #if defined(PCBFLAMENCO)
893 void checkUsbChip()
895 uint8_t reg = i2cReadBQ24195(0x00);
896 if (reg & 0x80) {
897 i2cWriteBQ24195(0x00, reg & 0x7F);
900 #endif
902 void doLoopCommonActions()
904 checkBacklight();
906 #if defined(PCBFLAMENCO)
907 checkUsbChip();
908 #endif
911 void backlightOn()
913 lightOffCounter = ((uint16_t)g_eeGeneral.lightAutoOff*250) << 1;
916 #if MENUS_LOCK == 1
917 bool readonly = true;
918 bool readonlyUnlocked()
920 if (readonly) {
921 POPUP_WARNING(STR_MODS_FORBIDDEN);
922 return false;
924 else {
925 return true;
928 #endif
930 #if defined(SPLASH)
931 void doSplash()
933 #if defined(PWR_BUTTON_PRESS)
934 bool refresh = false;
935 #endif
937 if (SPLASH_NEEDED()) {
938 drawSplash();
940 #if !defined(CPUARM)
941 AUDIO_HELLO();
942 #endif
944 #if defined(PCBSTD)
945 lcdSetContrast();
946 #elif defined(PCBSKY9X)
947 tmr10ms_t curTime = get_tmr10ms() + 10;
948 uint8_t contrast = 10;
949 lcdSetRefVolt(contrast);
950 #endif
952 getADC(); // init ADC array
954 inputsMoved();
956 tmr10ms_t tgtime = get_tmr10ms() + SPLASH_TIMEOUT;
958 while (tgtime > get_tmr10ms()) {
959 #if defined(SIMU)
960 SIMU_SLEEP(1);
961 #elif defined(CPUARM)
962 CoTickDelay(1);
963 #endif
965 getADC();
967 #if defined(FSPLASH)
968 // Splash is forced, we can't skip it
969 if (!(g_eeGeneral.splashMode & 0x04)) {
970 #endif
972 if (keyDown() || inputsMoved()) return;
974 #if defined(FSPLASH)
976 #endif
978 #if defined(PWR_BUTTON_PRESS)
979 uint32_t pwr_check = pwrCheck();
980 if (pwr_check == e_power_off) {
981 break;
983 else if (pwr_check == e_power_press) {
984 refresh = true;
986 else if (pwr_check == e_power_on && refresh) {
987 drawSplash();
988 refresh = false;
990 #else
991 if (pwrCheck() == e_power_off) {
992 return;
994 #endif
996 #if defined(SPLASH_FRSKY)
997 static uint8_t secondSplash = false;
998 if (!secondSplash && get_tmr10ms() >= tgtime-200) {
999 secondSplash = true;
1000 drawSecondSplash();
1002 #endif
1004 #if defined(PCBSKY9X)
1005 if (curTime < get_tmr10ms()) {
1006 curTime += 10;
1007 if (contrast < g_eeGeneral.contrast) {
1008 contrast += 1;
1009 lcdSetRefVolt(contrast);
1012 #endif
1014 doLoopCommonActions();
1018 #else
1019 #define Splash()
1020 #define doSplash()
1021 #endif
1023 #if defined(SDCARD) && defined(CPUARM)
1024 void checkSDVersion()
1026 if (sdMounted()) {
1027 FIL versionFile;
1028 UINT read = 0;
1029 char version[sizeof(REQUIRED_SDCARD_VERSION)-1];
1030 char error[sizeof(TR_WRONG_SDCARDVERSION)+ sizeof(version)];
1032 strAppend(strAppend(error, STR_WRONG_SDCARDVERSION, sizeof(TR_WRONG_SDCARDVERSION)), REQUIRED_SDCARD_VERSION, sizeof(REQUIRED_SDCARD_VERSION));
1033 FRESULT result = f_open(&versionFile, "/opentx.sdcard.version", FA_OPEN_EXISTING | FA_READ);
1034 if (result == FR_OK) {
1035 if (f_read(&versionFile, &version, sizeof(version), &read) != FR_OK ||
1036 read != sizeof(version) ||
1037 strncmp(version, REQUIRED_SDCARD_VERSION, sizeof(version)) != 0) {
1038 TRACE("SD card version mismatch: %.*s, %s", sizeof(REQUIRED_SDCARD_VERSION)-1, version, REQUIRED_SDCARD_VERSION);
1039 ALERT(STR_SD_CARD, error, AU_ERROR);
1041 f_close(&versionFile);
1043 else {
1044 ALERT(STR_SD_CARD, error, AU_ERROR);
1048 #endif
1050 #if defined(PCBTARANIS) || defined(PCBHORUS)
1051 void checkFailsafe()
1053 for (int i=0; i<NUM_MODULES; i++) {
1054 if (IS_MODULE_PXX(i)) {
1055 ModuleData & moduleData = g_model.moduleData[i];
1056 if (HAS_RF_PROTOCOL_FAILSAFE(moduleData.rfProtocol) && moduleData.failsafeMode == FAILSAFE_NOT_SET) {
1057 ALERT(STR_FAILSAFEWARN, STR_NO_FAILSAFE, AU_ERROR);
1058 break;
1063 #else
1064 #define checkFailsafe()
1065 #endif
1066 #if defined(CPUARM)
1067 void checkRSSIAlarmsDisabled()
1069 if (g_model.rssiAlarms.disabled) {
1070 ALERT(STR_RSSIALARM_WARN, STR_NO_RSSIALARM, AU_ERROR);
1073 #endif
1075 #if defined(GUI)
1076 void checkAll()
1078 #if defined(EEPROM_RLC)
1079 checkLowEEPROM();
1080 #endif
1082 #if defined(MODULE_ALWAYS_SEND_PULSES)
1083 startupWarningState = STARTUP_WARNING_THROTTLE;
1084 #else
1085 if (g_eeGeneral.chkSum == evalChkSum()) {
1086 checkTHR();
1088 checkSwitches();
1089 checkFailsafe();
1090 #endif
1091 #if defined(CPUARM)
1092 checkRSSIAlarmsDisabled();
1093 #endif
1095 #if defined(SDCARD) && defined(CPUARM)
1096 checkSDVersion();
1097 #endif
1099 #if defined(CPUARM)
1100 if (g_model.displayChecklist && modelHasNotes()) {
1101 pushModelNotes();
1103 #endif
1105 #if defined(CPUARM)
1106 if (!clearKeyEvents()) {
1107 showMessageBox(STR_KEYSTUCK);
1108 tmr10ms_t tgtime = get_tmr10ms() + 500;
1109 while (tgtime != get_tmr10ms()) {
1110 #if defined(SIMU)
1111 SIMU_SLEEP(1);
1112 #elif defined(CPUARM)
1113 CoTickDelay(1);
1114 #endif
1115 wdt_reset();
1118 #else // #if defined(CPUARM)
1119 clearKeyEvents();
1120 #endif // #if defined(CPUARM)
1122 START_SILENCE_PERIOD();
1124 #endif // GUI
1126 #if defined(MODULE_ALWAYS_SEND_PULSES)
1127 void checkStartupWarnings()
1129 if (startupWarningState < STARTUP_WARNING_DONE) {
1130 if (startupWarningState == STARTUP_WARNING_THROTTLE)
1131 checkTHR();
1132 else
1133 checkSwitches();
1136 #endif
1138 #if defined(EEPROM_RLC)
1139 void checkLowEEPROM()
1141 if (g_eeGeneral.disableMemoryWarning) return;
1142 if (EeFsGetFree() < 100) {
1143 ALERT(STR_STORAGE_WARNING, STR_EEPROMLOWMEM, AU_ERROR);
1146 #endif
1148 void checkTHR()
1150 uint8_t thrchn = ((g_model.thrTraceSrc==0) || (g_model.thrTraceSrc>NUM_POTS+NUM_SLIDERS)) ? THR_STICK : g_model.thrTraceSrc+NUM_STICKS-1;
1151 // throttle channel is either the stick according stick mode (already handled in evalInputs)
1152 // or P1 to P3;
1153 // in case an output channel is choosen as throttle source (thrTraceSrc>NUM_POTS+NUM_SLIDERS) we assume the throttle stick is the input
1154 // no other information available at the moment, and good enough to my option (otherwise too much exceptions...)
1156 #if defined(MODULE_ALWAYS_SEND_PULSES)
1157 int16_t v = calibratedAnalogs[thrchn];
1158 if (v<=THRCHK_DEADBAND-1024 || g_model.disableThrottleWarning || pwrCheck()==e_power_off || keyDown()) {
1159 startupWarningState = STARTUP_WARNING_THROTTLE+1;
1161 else {
1162 calibratedAnalogs[thrchn] = -1024;
1163 #if !defined(VIRTUAL_INPUTS)
1164 if (thrchn < NUM_STICKS) {
1165 rawAnas[thrchn] = anas[thrchn] = calibratedAnalogs[thrchn];
1167 #endif
1168 RAISE_ALERT(STR_THROTTLEWARN, STR_THROTTLENOTIDLE, STR_PRESSANYKEYTOSKIP, AU_THROTTLE_ALERT);
1170 #else
1171 if (g_model.disableThrottleWarning) {
1172 return;
1175 GET_ADC_IF_MIXER_NOT_RUNNING();
1177 evalInputs(e_perout_mode_notrainer); // let do evalInputs do the job
1179 int16_t v = calibratedAnalogs[thrchn];
1180 if (v <= THRCHK_DEADBAND-1024) {
1181 return; // prevent warning if throttle input OK
1184 // first - display warning; also deletes inputs if any have been before
1185 LED_ERROR_BEGIN();
1186 RAISE_ALERT(STR_THROTTLEWARN, STR_THROTTLENOTIDLE, STR_PRESSANYKEYTOSKIP, AU_THROTTLE_ALERT);
1188 #if defined(PWR_BUTTON_PRESS)
1189 bool refresh = false;
1190 #endif
1192 while (1) {
1194 GET_ADC_IF_MIXER_NOT_RUNNING();
1196 evalInputs(e_perout_mode_notrainer); // let do evalInputs do the job
1198 v = calibratedAnalogs[thrchn];
1200 #if defined(PWR_BUTTON_PRESS)
1201 uint32_t pwr_check = pwrCheck();
1202 if (pwr_check == e_power_off) {
1203 break;
1205 else if (pwr_check == e_power_press) {
1206 refresh = true;
1208 else if (pwr_check == e_power_on && refresh) {
1209 RAISE_ALERT(STR_THROTTLEWARN, STR_THROTTLENOTIDLE, STR_PRESSANYKEYTOSKIP, AU_NONE);
1210 refresh = false;
1212 #else
1213 if (pwrCheck() == e_power_off) {
1214 break;
1216 #endif
1218 if (keyDown() || v <= THRCHK_DEADBAND-1024) {
1219 break;
1222 doLoopCommonActions();
1224 wdt_reset();
1226 SIMU_SLEEP(1);
1227 #if defined(CPUARM)
1228 CoTickDelay(10);
1229 #endif
1232 #endif
1234 LED_ERROR_END();
1237 void checkAlarm() // added by Gohst
1239 if (g_eeGeneral.disableAlarmWarning) {
1240 return;
1243 if (IS_SOUND_OFF()) {
1244 ALERT(STR_ALARMSWARN, STR_ALARMSDISABLED, AU_ERROR);
1248 void alert(const pm_char * title, const pm_char * msg ALERT_SOUND_ARG)
1250 LED_ERROR_BEGIN();
1252 TRACE("ALERT %s: %s", title, msg);
1254 RAISE_ALERT(title, msg, STR_PRESSANYKEY, sound);
1256 #if defined(PWR_BUTTON_PRESS)
1257 bool refresh = false;
1258 #endif
1260 while(1) {
1261 SIMU_SLEEP(1);
1262 #if defined(CPUARM)
1263 CoTickDelay(10);
1264 #endif
1266 if (keyDown()) break; // wait for key release
1268 doLoopCommonActions();
1270 wdt_reset();
1272 #if defined(PWR_BUTTON_PRESS)
1273 uint32_t pwr_check = pwrCheck();
1274 if (pwr_check == e_power_off) {
1275 boardOff();
1277 else if (pwr_check == e_power_press) {
1278 refresh = true;
1280 else if (pwr_check == e_power_on && refresh) {
1281 RAISE_ALERT(title, msg, STR_PRESSANYKEY, AU_NONE);
1282 refresh = false;
1284 #else
1285 if (pwrCheck() == e_power_off) {
1286 boardOff(); // turn power off now
1288 #endif
1291 LED_ERROR_END();
1294 #if defined(GVARS)
1295 int8_t trimGvar[NUM_STICKS+NUM_AUX_TRIMS] = { -1, -1, -1, -1 };
1296 #endif
1298 #if defined(CPUARM)
1299 void checkTrims()
1301 event_t event = getEvent(true);
1302 if (event && !IS_KEY_BREAK(event)) {
1303 int8_t k = EVT_KEY_MASK(event) - TRM_BASE;
1304 #else
1305 uint8_t checkTrim(event_t event)
1307 int8_t k = EVT_KEY_MASK(event) - TRM_BASE;
1308 if (k>=0 && k<8 && !IS_KEY_BREAK(event)) {
1309 #endif
1310 // LH_DWN LH_UP LV_DWN LV_UP RV_DWN RV_UP RH_DWN RH_UP
1311 uint8_t idx = CONVERT_MODE((uint8_t)k/2);
1312 uint8_t phase;
1313 int before;
1314 bool thro;
1316 #if defined(CPUARM)
1317 trimsDisplayTimer = 200; // 2 seconds
1318 trimsDisplayMask |= (1<<idx);
1319 #endif
1321 #if defined(GVARS)
1322 if (TRIM_REUSED(idx)) {
1323 #if defined(PCBSTD)
1324 phase = 0;
1325 #else
1326 phase = getGVarFlightMode(mixerCurrentFlightMode, trimGvar[idx]);
1327 #endif
1328 before = GVAR_VALUE(trimGvar[idx], phase);
1329 thro = false;
1331 else {
1332 phase = getTrimFlightMode(mixerCurrentFlightMode, idx);
1333 #if defined(CPUARM)
1334 before = getTrimValue(phase, idx);
1335 #else
1336 before = getRawTrimValue(phase, idx);
1337 #endif
1338 thro = (idx==THR_STICK && g_model.thrTrim);
1340 #else
1341 phase = getTrimFlightMode(mixerCurrentFlightMode, idx);
1342 #if defined(CPUARM)
1343 before = getTrimValue(phase, idx);
1344 #else
1345 before = getRawTrimValue(phase, idx);
1346 #endif
1347 thro = (idx==THR_STICK && g_model.thrTrim);
1348 #endif
1349 int8_t trimInc = g_model.trimInc + 1;
1350 int8_t v = (trimInc==-1) ? min(32, abs(before)/4+1) : (1 << trimInc); // TODO flash saving if (trimInc < 0)
1351 if (thro) v = 4; // if throttle trim and trim trottle then step=4
1352 int16_t after = (k&1) ? before + v : before - v; // positive = k&1
1353 bool beepTrim = false;
1355 if (!thro && before!=0 && ((!(after < 0) == (before < 0)) || after==0)) { //forcing a stop at centerered trim when changing sides
1356 after = 0;
1357 beepTrim = true;
1358 AUDIO_TRIM_MIDDLE();
1359 pauseEvents(event);
1361 else if (before>TRIM_MIN && after<=TRIM_MIN) {
1362 beepTrim = true;
1363 AUDIO_TRIM_MIN();
1364 killEvents(event);
1366 else if (before<TRIM_MAX && after>=TRIM_MAX) {
1367 beepTrim = true;
1368 AUDIO_TRIM_MAX();
1369 killEvents(event);
1372 if ((before<after && after>TRIM_MAX) || (before>after && after<TRIM_MIN)) {
1373 if (!g_model.extendedTrims || TRIM_REUSED(idx)) after = before;
1376 if (after < TRIM_EXTENDED_MIN) {
1377 after = TRIM_EXTENDED_MIN;
1379 if (after > TRIM_EXTENDED_MAX) {
1380 after = TRIM_EXTENDED_MAX;
1383 #if defined(GVARS)
1384 if (TRIM_REUSED(idx)) {
1385 SET_GVAR_VALUE(trimGvar[idx], phase, after);
1387 else
1388 #endif
1390 #if defined(CPUARM)
1391 if (!setTrimValue(phase, idx, after)) {
1392 // we don't play a beep, so we exit now the function
1393 return;
1395 #else
1396 setTrimValue(phase, idx, after);
1397 #endif
1400 if (!beepTrim) {
1401 AUDIO_TRIM_PRESS(after);
1404 #if !defined(CPUARM)
1405 return 0;
1406 #endif
1408 #if !defined(CPUARM)
1409 return event;
1410 #endif
1413 #if !defined(SIMU)
1414 uint16_t s_anaFilt[NUM_ANALOGS];
1415 #endif
1417 #if defined(SIMU)
1418 uint16_t BandGap = 225;
1419 #elif defined(CPUM2560)
1420 // #define STARTADCONV (ADCSRA = (1<<ADEN) | (1<<ADPS0) | (1<<ADPS1) | (1<<ADPS2) | (1<<ADSC) | (1 << ADIE))
1421 // G: Note that the above would have set the ADC prescaler to 128, equating to
1422 // 125KHz sample rate. We now sample at 500KHz, with oversampling and other
1423 // filtering options to produce 11-bit results.
1424 uint16_t BandGap = 2040;
1425 #elif defined(PCBSTD)
1426 uint16_t BandGap;
1427 #endif
1429 #if defined(JITTER_MEASURE)
1430 JitterMeter<uint16_t> rawJitter[NUM_ANALOGS];
1431 JitterMeter<uint16_t> avgJitter[NUM_ANALOGS];
1432 tmr10ms_t jitterResetTime = 0;
1433 #endif
1435 #if defined(VIRTUAL_INPUTS)
1436 #define JITTER_FILTER_STRENGTH 4 // tune this value, bigger value - more filtering (range: 1-5) (see explanation below)
1437 #define ANALOG_SCALE 1 // tune this value, bigger value - more filtering (range: 0-1) (see explanation below)
1439 #define JITTER_ALPHA (1<<JITTER_FILTER_STRENGTH)
1440 #define ANALOG_MULTIPLIER (1<<ANALOG_SCALE)
1441 #define ANA_FILT(chan) (s_anaFilt[chan] / (JITTER_ALPHA * ANALOG_MULTIPLIER))
1442 #if (JITTER_ALPHA * ANALOG_MULTIPLIER > 32)
1443 #error "JITTER_FILTER_STRENGTH and ANALOG_SCALE are too big, their summ should be <= 5 !!!"
1444 #endif
1445 #else
1446 #define ANALOG_SCALE 0
1447 #define JITTER_ALPHA 1
1448 #define ANALOG_MULTIPLIER 1
1449 #define ANA_FILT(chan) (s_anaFilt[chan])
1450 #endif
1453 #if !defined(SIMU)
1454 uint16_t anaIn(uint8_t chan)
1456 #if defined(VIRTUAL_INPUTS)
1457 return ANA_FILT(chan);
1458 #else
1459 #if defined(TELEMETRY_MOD_14051) || defined(TELEMETRY_MOD_14051_SWAPPED)
1460 static const pm_char crossAna[] PROGMEM = {3,1,2,0,4,5,6,0/* shouldn't be used */,TX_VOLTAGE};
1461 #else
1462 static const pm_char crossAna[] PROGMEM = {3,1,2,0,4,5,6,7};
1463 #endif
1464 #if defined(FRSKY_STICKS)
1465 volatile uint16_t temp = s_anaFilt[pgm_read_byte(crossAna+chan)]; // volatile saves here 40 bytes; maybe removed for newer AVR when available
1466 if (chan < NUM_STICKS && (g_eeGeneral.stickReverse & (1 << chan))) {
1467 temp = 2048 - temp;
1469 return temp;
1470 #else
1471 volatile uint16_t *p = &s_anaFilt[pgm_read_byte(crossAna+chan)];
1472 return *p;
1473 #endif
1474 #endif
1477 #if defined(CPUARM)
1478 void getADC()
1480 #if defined(JITTER_MEASURE)
1481 if (JITTER_MEASURE_ACTIVE() && jitterResetTime < get_tmr10ms()) {
1482 // reset jitter measurement every second
1483 for (uint32_t x=0; x<NUM_ANALOGS; x++) {
1484 rawJitter[x].reset();
1485 avgJitter[x].reset();
1487 jitterResetTime = get_tmr10ms() + 100; //every second
1489 #endif
1491 DEBUG_TIMER_START(debugTimerAdcRead);
1492 adcRead();
1493 DEBUG_TIMER_STOP(debugTimerAdcRead);
1495 for (uint8_t x=0; x<NUM_ANALOGS; x++) {
1496 uint16_t v = getAnalogValue(x) >> (1 - ANALOG_SCALE);
1498 #if defined(VIRTUAL_INPUTS)
1499 // Jitter filter:
1500 // * pass trough any big change directly
1501 // * for small change use Modified moving average (MMA) filter
1503 // Explanation:
1505 // Normal MMA filter has this formula:
1506 // <out> = ((ALPHA-1)*<out> + <in>)/ALPHA
1508 // If calculation is done this way with integer arithmetics, then any small change in
1509 // input signal is lost. One way to combat that, is to rearrange the formula somewhat,
1510 // to store a more precise (larger) number between iterations. The basic idea is to
1511 // store undivided value between iterations. Therefore an new variable <filtered> is
1512 // used. The new formula becomes:
1513 // <filtered> = <filtered> - <filtered>/ALPHA + <in>
1514 // <out> = <filtered>/ALPHA (use only when out is needed)
1516 // The above formula with a maximum allowed ALPHA value (we are limited by
1517 // the 16 bit s_anaFilt[]) was tested on the radio. The resulting signal still had
1518 // some jitter (a value of 1 was observed). The jitter might be bigger on other
1519 // radios.
1521 // So another idea is to use larger input values for filtering. So instead of using
1522 // input in a range from 0 to 2047, we use twice larger number (temp[x] is divided less)
1524 // This also means that ALPHA must be lowered (remember 16 bit limit), but test results
1525 // have proved that this kind of filtering gives better results. So the recommended values
1526 // for filter are:
1527 // JITTER_FILTER_STRENGTH 4
1528 // ANALOG_SCALE 1
1530 // Variables mapping:
1531 // * <in> = v
1532 // * <out> = s_anaFilt[x]
1533 uint16_t previous = s_anaFilt[x] / JITTER_ALPHA;
1534 uint16_t diff = (v > previous) ? (v - previous) : (previous - v);
1535 if (!g_eeGeneral.jitterFilter && diff < (10*ANALOG_MULTIPLIER)) { // g_eeGeneral.jitterFilter is inverted, 0 - active
1536 // apply jitter filter
1537 s_anaFilt[x] = (s_anaFilt[x] - previous) + v;
1539 else
1540 #endif // #if defined(VIRTUAL_INPUTS)
1542 //use unfiltered value
1543 s_anaFilt[x] = v * JITTER_ALPHA;
1546 #if defined(JITTER_MEASURE)
1547 if (JITTER_MEASURE_ACTIVE()) {
1548 avgJitter[x].measure(ANA_FILT(x));
1550 #endif
1552 #define ANAFILT_MAX (2 * RESX * JITTER_ALPHA * ANALOG_MULTIPLIER - 1)
1553 StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[x];
1554 if (IS_POT_MULTIPOS(x) && IS_MULTIPOS_CALIBRATED(calib)) {
1555 // TODO: consider adding another low pass filter to eliminate multipos switching glitches
1556 uint8_t vShifted = ANA_FILT(x) >> 4;
1557 s_anaFilt[x] = ANAFILT_MAX;
1558 for (uint32_t i=0; i<calib->count; i++) {
1559 if (vShifted < calib->steps[i]) {
1560 s_anaFilt[x] = (i * ANAFILT_MAX) / calib->count;
1561 break;
1567 #endif // #if defined(CPUARM)
1569 #endif // SIMU
1571 uint8_t g_vbat100mV = 0;
1572 uint16_t lightOffCounter;
1573 uint8_t flashCounter = 0;
1575 uint16_t sessionTimer;
1576 uint16_t s_timeCumThr; // THR in 1/16 sec
1577 uint16_t s_timeCum16ThrP; // THR% in 1/16 sec
1579 uint8_t trimsCheckTimer = 0;
1581 #if defined(CPUARM)
1582 uint8_t trimsDisplayTimer = 0;
1583 uint8_t trimsDisplayMask = 0;
1584 #endif
1586 void flightReset(uint8_t check)
1588 // we don't reset the whole audio here (the hello.wav would be cut, if a prompt is queued before FlightReset, it should be played)
1589 // TODO check if the vario / background music are stopped correctly if switching to a model which doesn't have these functions enabled
1591 if (!IS_MANUAL_RESET_TIMER(0)) {
1592 timerReset(0);
1595 #if TIMERS > 1
1596 if (!IS_MANUAL_RESET_TIMER(1)) {
1597 timerReset(1);
1599 #endif
1601 #if TIMERS > 2
1602 if (!IS_MANUAL_RESET_TIMER(2)) {
1603 timerReset(2);
1605 #endif
1607 #if defined(TELEMETRY_FRSKY)
1608 telemetryReset();
1609 #endif
1611 s_mixer_first_run_done = false;
1613 START_SILENCE_PERIOD();
1615 RESET_THR_TRACE();
1617 logicalSwitchesReset();
1619 if (check) {
1620 checkAll();
1624 #if defined(THRTRACE)
1625 uint8_t s_traceBuf[MAXTRACE];
1626 uint16_t s_traceWr;
1627 uint8_t s_cnt_10s;
1628 uint16_t s_cnt_samples_thr_10s;
1629 uint16_t s_sum_samples_thr_10s;
1630 #endif
1632 void evalTrims()
1634 uint8_t phase = mixerCurrentFlightMode;
1635 for (uint8_t i=0; i<NUM_STICKS+NUM_AUX_TRIMS; i++) {
1636 // do trim -> throttle trim if applicable
1637 int16_t trim = getTrimValue(phase, i);
1638 #if !defined(CPUARM)
1639 if (i==THR_STICK && g_model.thrTrim) {
1640 int16_t trimMin = g_model.extendedTrims ? TRIM_EXTENDED_MIN : TRIM_MIN;
1641 trim = (((g_model.throttleReversed)?(int32_t)(trim+trimMin):(int32_t)(trim-trimMin)) * (RESX-anas[i])) >> (RESX_SHIFT+1);
1643 #endif
1644 if (trimsCheckTimer > 0) {
1645 trim = 0;
1648 trims[i] = trim*2;
1652 #if !defined(PCBSTD)
1653 uint8_t mSwitchDuration[1+NUM_ROTARY_ENCODERS] = { 0 };
1654 #define CFN_PRESSLONG_DURATION 100
1655 #endif
1658 uint8_t s_mixer_first_run_done = false;
1660 void doMixerCalculations()
1662 static tmr10ms_t lastTMR = 0;
1664 tmr10ms_t tmr10ms = get_tmr10ms();
1665 uint8_t tick10ms = (tmr10ms >= lastTMR ? tmr10ms - lastTMR : 1);
1666 // handle tick10ms overrun
1667 // correct overflow handling costs a lot of code; happens only each 11 min;
1668 // therefore forget the exact calculation and use only 1 instead; good compromise
1669 lastTMR = tmr10ms;
1671 DEBUG_TIMER_START(debugTimerGetAdc);
1672 getADC();
1673 DEBUG_TIMER_STOP(debugTimerGetAdc);
1675 DEBUG_TIMER_START(debugTimerGetSwitches);
1676 getSwitchesPosition(!s_mixer_first_run_done);
1677 DEBUG_TIMER_STOP(debugTimerGetSwitches);
1679 #if defined(PCBSKY9X) && !defined(REVA) && !defined(SIMU)
1680 Current_analogue = (Current_analogue*31 + s_anaFilt[8] ) >> 5 ;
1681 if (Current_analogue > Current_max)
1682 Current_max = Current_analogue ;
1683 #endif
1685 #if !defined(CPUARM)
1686 adcPrepareBandgap();
1687 #endif
1689 DEBUG_TIMER_START(debugTimerEvalMixes);
1690 evalMixes(tick10ms);
1691 DEBUG_TIMER_STOP(debugTimerEvalMixes);
1693 #if !defined(CPUARM)
1694 // Bandgap has had plenty of time to settle...
1695 getADC_bandgap();
1696 #endif
1698 DEBUG_TIMER_START(debugTimerMixes10ms);
1699 if (tick10ms) {
1701 #if !defined(CPUM64) && !defined(ACCURAT_THROTTLE_TIMER)
1702 // code cost is about 16 bytes for higher throttle accuracy for timer
1703 // would not be noticable anyway, because all version up to this change had only 16 steps;
1704 // now it has already 32 steps; this define would increase to 128 steps
1705 #define ACCURAT_THROTTLE_TIMER
1706 #endif
1708 /* Throttle trace */
1709 int16_t val;
1711 if (g_model.thrTraceSrc > NUM_POTS+NUM_SLIDERS) {
1712 uint8_t ch = g_model.thrTraceSrc-NUM_POTS-NUM_SLIDERS-1;
1713 val = channelOutputs[ch];
1715 LimitData *lim = limitAddress(ch);
1716 int16_t gModelMax = LIMIT_MAX_RESX(lim);
1717 int16_t gModelMin = LIMIT_MIN_RESX(lim);
1719 if (lim->revert)
1720 val = -val + gModelMax;
1721 else
1722 val = val - gModelMin;
1724 #if defined(PPM_LIMITS_SYMETRICAL)
1725 if (lim->symetrical) {
1726 val -= calc1000toRESX(lim->offset);
1728 #endif
1730 gModelMax -= gModelMin; // we compare difference between Max and Mix for recaling needed; Max and Min are shifted to 0 by default
1731 // usually max is 1024 min is -1024 --> max-min = 2048 full range
1733 #ifdef ACCURAT_THROTTLE_TIMER
1734 if (gModelMax!=0 && gModelMax!=2048) val = (int32_t) (val << 11) / (gModelMax); // rescaling only needed if Min, Max differs
1735 #else
1736 // @@@ open.20.fsguruh optimized calculation; now *8 /8 instead of 10 base; (*16/16 already cause a overrun; unsigned calculation also not possible, because v may be negative)
1737 gModelMax+=255; // force rounding up --> gModelMax is bigger --> val is smaller
1738 gModelMax >>= (10-2);
1740 if (gModelMax!=0 && gModelMax!=8) {
1741 val = (val << 3) / gModelMax; // rescaling only needed if Min, Max differs
1743 #endif
1745 if (val<0) val=0; // prevent val be negative, which would corrupt throttle trace and timers; could occur if safetyswitch is smaller than limits
1747 else {
1748 #if defined(VIRTUAL_INPUTS)
1749 val = RESX + calibratedAnalogs[g_model.thrTraceSrc == 0 ? THR_STICK : g_model.thrTraceSrc+NUM_STICKS-1];
1750 #else
1751 val = RESX + (g_model.thrTraceSrc == 0 ? rawAnas[THR_STICK] : calibratedAnalogs[g_model.thrTraceSrc+NUM_STICKS-1]);
1752 #endif
1755 #if defined(ACCURAT_THROTTLE_TIMER)
1756 val >>= (RESX_SHIFT-6); // calibrate it (resolution increased by factor 4)
1757 #else
1758 val >>= (RESX_SHIFT-4); // calibrate it
1759 #endif
1761 evalTimers(val, tick10ms);
1763 static uint8_t s_cnt_100ms;
1764 static uint8_t s_cnt_1s;
1765 static uint8_t s_cnt_samples_thr_1s;
1766 static uint16_t s_sum_samples_thr_1s;
1768 s_cnt_samples_thr_1s++;
1769 s_sum_samples_thr_1s+=val;
1771 if ((s_cnt_100ms += tick10ms) >= 10) { // 0.1sec
1772 s_cnt_100ms -= 10;
1773 s_cnt_1s += 1;
1775 logicalSwitchesTimerTick();
1776 checkTrainerSignalWarning();
1778 if (s_cnt_1s >= 10) { // 1sec
1779 s_cnt_1s -= 10;
1780 sessionTimer += 1;
1782 struct t_inactivity *ptrInactivity = &inactivity;
1783 FORCE_INDIRECT(ptrInactivity) ;
1784 ptrInactivity->counter++;
1785 if ((((uint8_t)ptrInactivity->counter)&0x07)==0x01 && g_eeGeneral.inactivityTimer && g_vbat100mV>50 && ptrInactivity->counter > ((uint16_t)g_eeGeneral.inactivityTimer*60))
1786 AUDIO_INACTIVITY();
1788 #if defined(AUDIO)
1789 if (mixWarning & 1) if ((sessionTimer&0x03)==0) AUDIO_MIX_WARNING(1);
1790 if (mixWarning & 2) if ((sessionTimer&0x03)==1) AUDIO_MIX_WARNING(2);
1791 if (mixWarning & 4) if ((sessionTimer&0x03)==2) AUDIO_MIX_WARNING(3);
1792 #endif
1794 #if defined(ACCURAT_THROTTLE_TIMER)
1795 val = s_sum_samples_thr_1s / s_cnt_samples_thr_1s;
1796 s_timeCum16ThrP += (val>>3); // s_timeCum16ThrP would overrun if we would store throttle value with higher accuracy; therefore stay with 16 steps
1797 if (val) s_timeCumThr += 1;
1798 s_sum_samples_thr_1s>>=2; // correct better accuracy now, because trace graph can show this information; in case thrtrace is not active, the compile should remove this
1799 #else
1800 val = s_sum_samples_thr_1s / s_cnt_samples_thr_1s;
1801 s_timeCum16ThrP += (val>>1);
1802 if (val) s_timeCumThr += 1;
1803 #endif
1805 #if defined(THRTRACE)
1806 // throttle trace is done every 10 seconds; Tracebuffer is adjusted to screen size.
1807 // in case buffer runs out, it wraps around
1808 // resolution for y axis is only 32, therefore no higher value makes sense
1809 s_cnt_samples_thr_10s += s_cnt_samples_thr_1s;
1810 s_sum_samples_thr_10s += s_sum_samples_thr_1s;
1812 if (++s_cnt_10s >= 10) { // 10s
1813 s_cnt_10s -= 10;
1814 val = s_sum_samples_thr_10s / s_cnt_samples_thr_10s;
1815 s_sum_samples_thr_10s = 0;
1816 s_cnt_samples_thr_10s = 0;
1817 s_traceBuf[s_traceWr % MAXTRACE] = val;
1818 s_traceWr++;
1820 #endif
1822 s_cnt_samples_thr_1s = 0;
1823 s_sum_samples_thr_1s = 0;
1827 #if defined(PXX) || defined(DSM2)
1828 static uint8_t countRangecheck = 0;
1829 for (uint8_t i=0; i<NUM_MODULES; ++i) {
1830 #if defined(MULTIMODULE)
1831 if (moduleFlag[i] != MODULE_NORMAL_MODE || (i == EXTERNAL_MODULE && multiModuleStatus.isBinding())) {
1832 #else
1833 if (moduleFlag[i] != MODULE_NORMAL_MODE) {
1834 #endif
1835 if (++countRangecheck >= 250) {
1836 countRangecheck = 0;
1837 AUDIO_PLAY(AU_SPECIAL_SOUND_CHEEP);
1841 #endif
1843 #if defined(CPUARM)
1844 checkTrims();
1845 #endif
1847 DEBUG_TIMER_STOP(debugTimerMixes10ms);
1849 s_mixer_first_run_done = true;
1852 #if defined(NAVIGATION_STICKS)
1853 uint8_t StickScrollAllowed;
1854 uint8_t StickScrollTimer;
1855 static const pm_uint8_t rate[] PROGMEM = { 0, 0, 100, 40, 16, 7, 3, 1 } ;
1857 uint8_t calcStickScroll( uint8_t index )
1859 uint8_t direction;
1860 int8_t value;
1862 if ( ( g_eeGeneral.stickMode & 1 ) == 0 )
1863 index ^= 3;
1865 value = calibratedAnalogs[index] / 128;
1866 direction = value > 0 ? 0x80 : 0;
1867 if (value < 0)
1868 value = -value; // (abs)
1869 if (value > 7)
1870 value = 7;
1871 value = pgm_read_byte(rate+(uint8_t)value);
1872 if (value)
1873 StickScrollTimer = STICK_SCROLL_TIMEOUT; // Seconds
1874 return value | direction;
1876 #endif
1878 #if defined(CPUARM)
1879 #define OPENTX_START_ARGS uint8_t splash=true
1880 #define OPENTX_START_SPLASH_NEEDED() (splash)
1881 #else
1882 #define OPENTX_START_ARGS
1883 #define OPENTX_START_SPLASH_NEEDED() true
1884 #endif
1886 void opentxStart(OPENTX_START_ARGS)
1888 TRACE("opentxStart");
1890 #if defined(SIMU)
1891 if (main_thread_running == 2) {
1892 return;
1894 #endif
1896 uint8_t calibration_needed = (g_eeGeneral.chkSum != evalChkSum());
1898 #if defined(GUI)
1899 if (!calibration_needed && OPENTX_START_SPLASH_NEEDED()) {
1900 doSplash();
1902 #endif
1904 #if defined(DEBUG_TRACE_BUFFER)
1905 trace_event(trace_start, 0x12345678);
1906 #endif
1908 #if defined(PCBSKY9X) && defined(SDCARD) && !defined(SIMU)
1909 for (int i=0; i<500 && !Card_initialized; i++) {
1910 CoTickDelay(1); // 2ms
1912 #endif
1914 #if defined(NIGHTLY_BUILD_WARNING)
1915 ALERT(STR_NIGHTLY_WARNING, TR_NIGHTLY_NOTSAFE, AU_ERROR);
1916 #endif
1918 #if defined(GUI)
1919 if (calibration_needed) {
1920 chainMenu(menuFirstCalib);
1922 else {
1923 checkAlarm();
1924 checkAll();
1926 #endif
1929 #if defined(CPUARM) || defined(CPUM2560)
1930 void opentxClose(uint8_t shutdown)
1932 TRACE("opentxClose");
1934 if (shutdown) {
1935 #if defined(CPUARM)
1936 watchdogSuspend(2000/*20s*/);
1937 #endif
1938 pausePulses(); // stop mixer task to disable trims processing while in shutdown
1939 AUDIO_BYE();
1940 #if defined(TELEMETRY_FRSKY)
1941 // TODO needed? telemetryEnd();
1942 #endif
1943 #if defined(LUA)
1944 luaClose(&lsScripts);
1945 #if defined(PCBHORUS)
1946 luaClose(&lsWidgets);
1947 #endif
1948 #endif
1949 #if defined(HAPTIC)
1950 hapticOff();
1951 #endif
1954 #if defined(SDCARD)
1955 logsClose();
1956 #endif
1958 storageFlushCurrentModel();
1960 #if defined(CPUARM) && !defined(REVA)
1961 if (sessionTimer > 0) {
1962 g_eeGeneral.globalTimer += sessionTimer;
1963 sessionTimer = 0;
1965 #endif
1967 #if defined(PCBSKY9X)
1968 uint32_t mAhUsed = g_eeGeneral.mAhUsed + Current_used * (488 + g_eeGeneral.txCurrentCalibration) / 8192 / 36;
1969 if (g_eeGeneral.mAhUsed != mAhUsed) {
1970 g_eeGeneral.mAhUsed = mAhUsed;
1972 #endif
1974 g_eeGeneral.unexpectedShutdown = 0;
1975 storageDirty(EE_GENERAL);
1976 storageCheck(true);
1978 #if defined(CPUARM)
1979 while (IS_PLAYING(ID_PLAY_PROMPT_BASE + AU_BYE)) {
1980 CoTickDelay(10);
1982 CoTickDelay(50);
1983 #endif
1985 #if defined(SDCARD)
1986 sdDone();
1987 #endif
1989 #endif
1991 #if defined(USB_MASS_STORAGE)
1992 void opentxResume()
1994 TRACE("opentxResume");
1996 menuHandlers[0] = menuMainView;
1998 sdMount();
1999 storageReadAll();
2000 #if defined(PCBHORUS)
2001 loadTheme();
2002 loadFontCache();
2003 #endif
2005 opentxStart(false);
2007 #if defined(CPUARM)
2008 referenceSystemAudioFiles();
2009 #endif
2011 #if defined(CPUARM) || defined(CPUM2560)
2012 if (!g_eeGeneral.unexpectedShutdown) {
2013 g_eeGeneral.unexpectedShutdown = 1;
2014 storageDirty(EE_GENERAL);
2016 #endif
2018 #endif
2020 #if defined(NAVIGATION_STICKS)
2021 uint8_t getSticksNavigationEvent()
2023 uint8_t evt = 0;
2024 if (StickScrollAllowed) {
2025 if ( StickScrollTimer ) {
2026 static uint8_t repeater;
2027 uint8_t direction;
2028 uint8_t value;
2030 if ( repeater < 128 )
2032 repeater += 1;
2034 value = calcStickScroll( 2 );
2035 direction = value & 0x80;
2036 value &= 0x7F;
2037 if ( value )
2039 if ( repeater > value )
2041 repeater = 0;
2042 if ( evt == 0 )
2044 if ( direction )
2046 evt = EVT_KEY_FIRST(KEY_UP);
2048 else
2050 evt = EVT_KEY_FIRST(KEY_DOWN);
2055 else
2057 value = calcStickScroll( 3 );
2058 direction = value & 0x80;
2059 value &= 0x7F;
2060 if ( value )
2062 if ( repeater > value )
2064 repeater = 0;
2065 if ( evt == 0 )
2067 if ( direction )
2069 evt = EVT_KEY_FIRST(KEY_RIGHT);
2071 else
2073 evt = EVT_KEY_FIRST(KEY_LEFT);
2081 else {
2082 StickScrollTimer = 0; // Seconds
2084 StickScrollAllowed = 1 ;
2085 return evt;
2087 #endif
2089 #if !defined(CPUARM)
2090 void checkBattery()
2092 static uint8_t counter = 0;
2093 #if defined(GUI) && !defined(COLORLCD)
2094 // TODO not the right menu I think ...
2095 if (menuHandlers[menuLevel] == menuRadioDiagAnalogs) {
2096 g_vbat100mV = 0;
2097 counter = 0;
2099 #endif
2100 if (counter-- == 0) {
2101 counter = 10;
2102 int32_t instant_vbat = anaIn(TX_VOLTAGE);
2103 #if defined(CPUM2560)
2104 instant_vbat = (instant_vbat*1112 + instant_vbat*g_eeGeneral.txVoltageCalibration + (BandGap<<2)) / (BandGap<<3);
2105 #else
2106 instant_vbat = (instant_vbat*16 + instant_vbat*g_eeGeneral.txVoltageCalibration/8) / BandGap;
2107 #endif
2109 static uint8_t s_batCheck;
2110 static uint16_t s_batSum;
2112 #if defined(VOICE)
2113 s_batCheck += 8;
2114 #else
2115 s_batCheck += 32;
2116 #endif
2118 s_batSum += instant_vbat;
2120 if (g_vbat100mV == 0) {
2121 g_vbat100mV = instant_vbat;
2122 s_batSum = 0;
2123 s_batCheck = 0;
2125 #if defined(VOICE)
2126 else if (!(s_batCheck & 0x3f)) {
2127 #else
2128 else if (s_batCheck == 0) {
2129 #endif
2130 g_vbat100mV = s_batSum / 8;
2131 s_batSum = 0;
2132 #if defined(VOICE)
2133 if (s_batCheck != 0) {
2134 // no alarms
2136 else
2137 #endif
2138 if (IS_TXBATT_WARNING() && g_vbat100mV>50) {
2139 AUDIO_TX_BATTERY_LOW();
2144 #endif // #if !defined(CPUARM)
2147 #if !defined(SIMU) && !defined(CPUARM)
2149 volatile uint8_t g_tmr16KHz; //continuous timer 16ms (16MHz/1024/256) -- 8-bit counter overflow
2150 ISR(TIMER_16KHZ_VECT, ISR_NOBLOCK)
2152 g_tmr16KHz++; // gruvin: Not 16KHz. Overflows occur at 61.035Hz (1/256th of 15.625KHz)
2153 // to give *16.384ms* intervals. Kind of matters for accuracy elsewhere. ;)
2154 // g_tmr16KHz is used to software-construct a 16-bit timer
2155 // from TIMER-0 (8-bit). See getTmr16KHz, below.
2158 uint16_t getTmr16KHz()
2160 while(1){
2161 uint8_t hb = g_tmr16KHz;
2162 uint8_t lb = COUNTER_16KHZ;
2163 if(hb-g_tmr16KHz==0) return (hb<<8)|lb;
2167 #if defined(PCBSTD) && (defined(AUDIO) || defined(VOICE))
2168 // Clocks every 128 uS
2169 ISR(TIMER_AUDIO_VECT, ISR_NOBLOCK)
2171 cli();
2172 PAUSE_AUDIO_INTERRUPT(); // stop reentrance
2173 sei();
2175 #if defined(AUDIO)
2176 AUDIO_DRIVER();
2177 #endif
2179 #if defined(VOICE)
2180 VOICE_DRIVER();
2181 #endif
2183 cli();
2184 RESUME_AUDIO_INTERRUPT();
2185 sei();
2187 #endif
2189 // Clocks every 10ms
2190 ISR(TIMER_10MS_VECT, ISR_NOBLOCK)
2192 // without correction we are 0,16% too fast; that mean in one hour we are 5,76Sek too fast; we do not like that
2193 static uint8_t accuracyWarble; // because 16M / 1024 / 100 = 156.25. we need to correct the fault; no start value needed
2195 #if defined(AUDIO)
2196 AUDIO_HEARTBEAT();
2197 #endif
2199 #if defined(BUZZER)
2200 BUZZER_HEARTBEAT();
2201 #endif
2203 #if defined(HAPTIC)
2204 HAPTIC_HEARTBEAT();
2205 #endif
2207 per10ms();
2209 uint8_t bump = (!(++accuracyWarble & 0x03)) ? 157 : 156;
2210 TIMER_10MS_COMPVAL += bump;
2213 // Timer3 used for PPM_IN pulse width capture. Counter running at 16MHz / 8 = 2MHz
2214 // equating to one count every half millisecond. (2 counts = 1ms). Control channel
2215 // count delta values thus can range from about 1600 to 4400 counts (800us to 2200us),
2216 // corresponding to a PPM signal in the range 0.8ms to 2.2ms (1.5ms at center).
2217 // (The timer is free-running and is thus not reset to zero at each capture interval.)
2218 ISR(TIMER3_CAPT_vect) // G: High frequency noise can cause stack overflo with ISR_NOBLOCK
2220 uint16_t capture=ICR3;
2222 // Prevent rentrance for this IRQ only
2223 PAUSE_PPMIN_INTERRUPT();
2224 sei(); // enable other interrupts
2226 captureTrainerPulses(capture);
2228 cli(); // disable other interrupts for stack pops before this function's RETI
2229 RESUME_PPMIN_INTERRUPT();
2231 #endif
2233 #if defined(DSM2_SERIAL) && !defined(CPUARM)
2234 FORCEINLINE void DSM2_USART_vect()
2236 UDR0 = *((uint16_t*)pulses2MHzRPtr); // transmit next byte
2238 pulses2MHzRPtr += sizeof(uint16_t);
2240 if (pulses2MHzRPtr == pulses2MHzWPtr) { // if reached end of DSM2 data buffer ...
2241 UCSRB_N(TLM_USART) &= ~(1 << UDRIE_N(TLM_USART)); // disable UDRE interrupt
2244 #endif
2246 #if !defined(SIMU) && !defined(CPUARM)
2248 #if defined(TELEMETRY_FRSKY)
2250 FORCEINLINE void FRSKY_USART_vect()
2252 if (frskyTxBufferCount > 0) {
2253 UDR_N(TLM_USART) = frskyTxBuffer[--frskyTxBufferCount];
2255 else {
2256 UCSRB_N(TLM_USART) &= ~(1 << UDRIE_N(TLM_USART)); // disable UDRE interrupt
2260 // USART0/1 Transmit Data Register Emtpy ISR
2261 ISR(USART_UDRE_vect_N(TLM_USART))
2263 #if defined(TELEMETRY_FRSKY) && defined(DSM2_SERIAL)
2264 if (IS_DSM2_PROTOCOL(g_model.protocol)) { // TODO not s_current_protocol?
2265 DSM2_USART_vect();
2267 else {
2268 FRSKY_USART_vect();
2270 #elif defined(TELEMETRY_FRSKY)
2271 FRSKY_USART_vect();
2272 #else
2273 DSM2_USART_vect();
2274 #endif
2276 #endif
2277 #endif
2279 #if defined(CPUARM)
2280 #define INSTANT_TRIM_MARGIN 10 /* around 1% */
2281 #else
2282 #define INSTANT_TRIM_MARGIN 15 /* around 1.5% */
2283 #endif
2285 void instantTrim()
2287 #if defined(VIRTUAL_INPUTS)
2288 int16_t anas_0[NUM_INPUTS];
2289 evalInputs(e_perout_mode_notrainer | e_perout_mode_nosticks);
2290 memcpy(anas_0, anas, sizeof(anas_0));
2291 #endif
2293 evalInputs(e_perout_mode_notrainer);
2295 for (uint8_t stick=0; stick<NUM_STICKS; stick++) {
2296 if (stick!=THR_STICK) {
2297 // don't instant trim the throttle stick
2298 uint8_t trim_phase = getTrimFlightMode(mixerCurrentFlightMode, stick);
2299 #if defined(VIRTUAL_INPUTS)
2300 int16_t delta = 0;
2301 for (int e=0; e<MAX_EXPOS; e++) {
2302 ExpoData * ed = expoAddress(e);
2303 if (!EXPO_VALID(ed)) break; // end of list
2304 if (ed->srcRaw-MIXSRC_Rud == stick) {
2305 delta = anas[ed->chn] - anas_0[ed->chn];
2306 break;
2309 #else
2310 int16_t delta = anas[stick];
2311 #endif
2312 if (abs(delta) >= INSTANT_TRIM_MARGIN) {
2313 int16_t trim = limit<int16_t>(TRIM_EXTENDED_MIN, (delta + trims[stick]) / 2, TRIM_EXTENDED_MAX);
2314 setTrimValue(trim_phase, stick, trim);
2319 storageDirty(EE_MODEL);
2320 AUDIO_WARNING2();
2323 void copySticksToOffset(uint8_t ch)
2325 pauseMixerCalculations();
2326 int32_t zero = (int32_t)channelOutputs[ch];
2328 evalFlightModeMixes(e_perout_mode_nosticks+e_perout_mode_notrainer, 0);
2329 int32_t val = chans[ch];
2330 LimitData *ld = limitAddress(ch);
2331 limit_min_max_t lim = LIMIT_MIN(ld);
2332 if (val < 0) {
2333 val = -val;
2334 lim = LIMIT_MIN(ld);
2336 #if defined(CPUARM)
2337 zero = (zero*256000 - val*lim) / (1024*256-val);
2338 #else
2339 zero = (zero*25600 - val*lim) / (26214-val);
2340 #endif
2341 ld->offset = (ld->revert ? -zero : zero);
2342 resumeMixerCalculations();
2343 storageDirty(EE_MODEL);
2346 void copyTrimsToOffset(uint8_t ch)
2348 int16_t zero;
2350 pauseMixerCalculations();
2352 evalFlightModeMixes(e_perout_mode_noinput, 0); // do output loop - zero input sticks and trims
2353 zero = applyLimits(ch, chans[ch]);
2355 evalFlightModeMixes(e_perout_mode_noinput-e_perout_mode_notrims, 0); // do output loop - only trims
2357 int16_t output = applyLimits(ch, chans[ch]) - zero;
2358 int16_t v = g_model.limitData[ch].offset;
2359 if (g_model.limitData[ch].revert) output = -output;
2360 #if defined(CPUARM)
2361 v += (output * 125) / 128;
2362 #else
2363 v += output;
2364 #endif
2365 g_model.limitData[ch].offset = limit((int16_t)-1000, (int16_t)v, (int16_t)1000); // make sure the offset doesn't go haywire
2367 resumeMixerCalculations();
2368 storageDirty(EE_MODEL);
2371 void moveTrimsToOffsets() // copy state of 3 primary to subtrim
2373 int16_t zeros[MAX_OUTPUT_CHANNELS];
2375 pauseMixerCalculations();
2377 evalFlightModeMixes(e_perout_mode_noinput, 0); // do output loop - zero input sticks and trims
2378 for (uint8_t i=0; i<MAX_OUTPUT_CHANNELS; i++) {
2379 zeros[i] = applyLimits(i, chans[i]);
2382 evalFlightModeMixes(e_perout_mode_noinput-e_perout_mode_notrims, 0); // do output loop - only trims
2384 for (uint8_t i=0; i<MAX_OUTPUT_CHANNELS; i++) {
2385 int16_t output = applyLimits(i, chans[i]) - zeros[i];
2386 int16_t v = g_model.limitData[i].offset;
2387 if (g_model.limitData[i].revert) output = -output;
2388 #if defined(CPUARM)
2389 v += (output * 125) / 128;
2390 #else
2391 v += output;
2392 #endif
2393 g_model.limitData[i].offset = limit((int16_t)-1000, (int16_t)v, (int16_t)1000); // make sure the offset doesn't go haywire
2396 // reset all trims, except throttle (if throttle trim)
2397 for (uint8_t i=0; i<NUM_STICKS+NUM_AUX_TRIMS; i++) {
2398 if (i!=THR_STICK || !g_model.thrTrim) {
2399 int16_t original_trim = getTrimValue(mixerCurrentFlightMode, i);
2400 for (uint8_t phase=0; phase<MAX_FLIGHT_MODES; phase++) {
2401 #if defined(CPUARM)
2402 trim_t trim = getRawTrimValue(phase, i);
2403 if (trim.mode / 2 == phase)
2404 setTrimValue(phase, i, trim.value - original_trim);
2405 #else
2406 trim_t trim = getRawTrimValue(phase, i);
2407 if (trim <= TRIM_EXTENDED_MAX)
2408 setTrimValue(phase, i, trim - original_trim);
2409 #endif
2414 resumeMixerCalculations();
2416 storageDirty(EE_MODEL);
2417 AUDIO_WARNING2();
2420 #if defined(ROTARY_ENCODERS)
2421 volatile rotenc_t rotencValue[ROTARY_ENCODERS] = {0};
2422 #elif defined(ROTARY_ENCODER_NAVIGATION)
2423 volatile rotenc_t rotencValue[1] = {0};
2424 #endif
2426 #if defined(CPUARM) && defined(ROTARY_ENCODER_NAVIGATION)
2427 uint8_t rotencSpeed;
2428 #endif
2430 #if !defined(CPUARM) && !defined(SIMU)
2431 extern unsigned char __bss_end ;
2432 #define STACKPTR _SFR_IO16(0x3D)
2433 void stackPaint()
2435 // Init Stack while interrupts are disabled
2436 unsigned char *p ;
2437 unsigned char *q ;
2439 p = (unsigned char *) STACKPTR ;
2440 q = &__bss_end ;
2441 p -= 2 ;
2442 while ( p > q ) {
2443 *p-- = 0x55 ;
2447 uint16_t stackAvailable()
2449 unsigned char *p ;
2451 p = &__bss_end + 1 ;
2452 while ( *p++ == 0x55 );
2453 return p - &__bss_end ;
2455 #endif
2457 #if defined(CPUM2560)
2458 #define OPENTX_INIT_ARGS const uint8_t mcusr
2459 #elif defined(PCBSTD)
2460 #define OPENTX_INIT_ARGS const uint8_t mcusr
2461 #else
2462 #define OPENTX_INIT_ARGS
2463 #endif
2465 void opentxInit(OPENTX_INIT_ARGS)
2467 #if defined(DEBUG) && defined(USB_SERIAL)
2468 // CoTickDelay(5000); // 10s
2469 #endif
2471 TRACE("opentxInit");
2473 #if defined(GUI)
2474 menuHandlers[0] = menuMainView;
2475 #if MENUS_LOCK != 2/*no menus*/
2476 menuHandlers[1] = menuModelSelect;
2477 #endif
2478 #endif
2480 #if defined(RTCLOCK) && !defined(COPROCESSOR)
2481 rtcInit(); // RTC must be initialized before rambackupRestore() is called
2482 #endif
2484 #if defined(EEPROM)
2485 storageReadRadioSettings();
2486 #endif
2488 // Radios handle UNEXPECTED_SHUTDOWN() differently:
2489 // * radios with WDT and EEPROM and CPU controlled power use Reset status register
2490 // and eeGeneral.unexpectedShutdown
2491 // * radios with SDCARD model storage use Reset status register and special
2492 // variables in RAM. They can not use eeGeneral.unexpectedShutdown
2493 // * radios without CPU controlled power can only use Reset status register (if available)
2494 if (UNEXPECTED_SHUTDOWN()) {
2495 TRACE("Unexpected Shutdown detected");
2496 unexpectedShutdown = 1;
2499 #if defined(SDCARD) && !defined(PCBMEGA2560)
2500 // SDCARD related stuff, only done if not unexpectedShutdown
2501 if (!unexpectedShutdown) {
2502 sdInit();
2503 logsInit();
2505 #endif
2507 #if defined(EEPROM)
2508 storageReadCurrentModel();
2509 #endif
2511 #if defined(PCBHORUS)
2512 if (!unexpectedShutdown) {
2513 // g_model.topbarData is still zero here (because it was not yet read from SDCARD),
2514 // but we only remember the pointer to in in constructor.
2515 // The storageReadAll() needs topbar object, so it must be created here
2516 #if __clang__
2517 // clang does not like this at all, turn into a warning so that -Werror does not stop here
2518 // taking address of packed member 'topbarData' of class or structure 'ModelData' may result in an unaligned pointer value [-Werror,-Waddress-of-packed-member]
2519 #pragma clang diagnostic push
2520 #pragma clang diagnostic warning "-Waddress-of-packed-member"
2521 #endif
2522 topbar = new Topbar(&g_model.topbarData);
2523 #if __clang__
2524 // Restore warnings
2525 #pragma clang diagnostic pop
2526 #endif
2528 // lua widget state must also be prepared before the call to storageReadAll()
2529 LUA_INIT_THEMES_AND_WIDGETS();
2531 #endif
2533 // handling of storage for radios that have no EEPROM
2534 #if !defined(EEPROM)
2535 #if defined(RAMBACKUP)
2536 if (unexpectedShutdown) {
2537 // SDCARD not available, try to restore last model from RAM
2538 TRACE("rambackupRestore");
2539 rambackupRestore();
2541 else {
2542 storageReadAll();
2544 #else
2545 storageReadAll();
2546 #endif
2547 #endif // #if !defined(EEPROM)
2549 #if defined(SERIAL2)
2550 serial2Init(g_eeGeneral.serial2Mode, modelTelemetryProtocol());
2551 #endif
2553 #if defined(PCBTARANIS)
2554 BACKLIGHT_ENABLE();
2555 #endif
2557 #if MENUS_LOCK == 1
2558 getMovedSwitch();
2559 if (TRIMS_PRESSED() && g_eeGeneral.switchUnlockStates==switches_states) {
2560 readonly = false;
2562 #endif
2564 #if defined(VOICE) && defined(CPUARM)
2565 currentSpeakerVolume = requiredSpeakerVolume = g_eeGeneral.speakerVolume + VOLUME_LEVEL_DEF;
2566 #if !defined(SOFTWARE_VOLUME)
2567 setScaledVolume(currentSpeakerVolume);
2568 #endif
2569 #endif
2571 #if defined(CPUARM)
2572 referenceSystemAudioFiles();
2573 audioQueue.start();
2574 BACKLIGHT_ENABLE();
2575 #endif
2577 #if defined(PCBSKY9X)
2578 // Set ADC gains here
2579 setSticksGain(g_eeGeneral.sticksGain);
2580 #endif
2582 #if defined(PCBSKY9X) && defined(BLUETOOTH)
2583 btInit();
2584 #endif
2586 #if defined(PCBHORUS)
2587 loadTheme();
2588 loadFontCache();
2589 #endif
2591 if (g_eeGeneral.backlightMode != e_backlight_mode_off) {
2592 // on Tx start turn the light on
2593 backlightOn();
2596 if (!unexpectedShutdown) {
2597 opentxStart();
2600 #if defined(CPUARM) || defined(CPUM2560)
2601 // TODO Horus does not need this
2602 if (!g_eeGeneral.unexpectedShutdown) {
2603 g_eeGeneral.unexpectedShutdown = 1;
2604 storageDirty(EE_GENERAL);
2606 #endif
2608 #if defined(GUI)
2609 lcdSetContrast();
2610 #endif
2611 backlightOn();
2613 #if defined(PCBSKY9X) && !defined(SIMU)
2614 init_trainer_capture();
2615 #endif
2617 #if !defined(CPUARM)
2618 doMixerCalculations();
2619 #endif
2621 startPulses();
2623 wdt_enable(WDTO_500MS);
2626 #if defined(SIMU)
2627 void * simuMain(void *)
2628 #else
2629 int main()
2630 #endif
2632 // G: The WDT remains active after a WDT reset -- at maximum clock speed. So it's
2633 // important to disable it before commencing with system initialisation (or
2634 // we could put a bunch more wdt_reset()s in. But I don't like that approach
2635 // during boot up.)
2636 #if defined(CPUM2560) || defined(CPUM2561)
2637 uint8_t mcusr = MCUSR; // save the WDT (etc) flags
2638 MCUSR = 0; // must be zeroed before disabling the WDT
2639 MCUCR = 0x80 ; // Disable JTAG port that can interfere with POT3
2640 MCUCR = 0x80 ; // Must be done twice
2641 #elif defined(PCBSTD)
2642 uint8_t mcusr = MCUCSR;
2643 MCUCSR = 0x80 ; // Disable JTAG port that can interfere with POT3
2644 MCUCSR = 0x80 ; // Must be done twice
2645 #endif
2646 #if defined(PCBTARANIS)
2647 g_eeGeneral.contrast = LCD_CONTRAST_DEFAULT;
2648 #endif
2649 wdt_disable();
2651 boardInit();
2653 #if defined(GUI) && !defined(PCBTARANIS) && !defined(PCBFLAMENCO) && !defined(PCBHORUS)
2654 // TODO remove this
2655 lcdInit();
2656 #endif
2658 #if !defined(SIMU)
2659 stackPaint();
2660 #endif
2662 #if defined(GUI) && !defined(PCBTARANIS)
2663 // lcdSetRefVolt(25);
2664 #endif
2666 #if defined(SPLASH) && (defined(PCBTARANIS) || defined(PCBHORUS))
2667 drawSplash();
2668 #endif
2670 sei(); // interrupts needed now
2672 #if !defined(CPUARM) && defined(TELEMETRY_FRSKY) && !defined(DSM2_SERIAL)
2673 telemetryInit();
2674 #endif
2676 #if defined(DSM2_SERIAL) && !defined(TELEMETRY_FRSKY)
2677 DSM2_Init();
2678 #endif
2680 #if defined(TELEMETRY_JETI)
2681 JETI_Init();
2682 #endif
2684 #if defined(TELEMETRY_ARDUPILOT)
2685 ARDUPILOT_Init();
2686 #endif
2688 #if defined(TELEMETRY_NMEA)
2689 NMEA_Init();
2690 #endif
2692 #if defined(TELEMETRY_MAVLINK)
2693 MAVLINK_Init();
2694 #endif
2696 #if defined(MENU_ROTARY_SW)
2697 init_rotary_sw();
2698 #endif
2700 #if defined(PCBHORUS)
2701 if (!IS_FIRMWARE_COMPATIBLE_WITH_BOARD()) {
2702 runFatalErrorScreen(STR_WRONG_PCBREV);
2704 #endif
2706 #if !defined(EEPROM)
2707 if (!SD_CARD_PRESENT() && !UNEXPECTED_SHUTDOWN()) {
2708 runFatalErrorScreen(STR_NO_SDCARD);
2710 #endif
2712 #if defined(CPUARM)
2713 tasksStart();
2714 #else
2715 opentxInit(mcusr);
2716 #if defined(CPUM2560)
2717 uint8_t shutdown_state = 0;
2718 #endif
2720 #if defined(PCBFLAMENCO)
2721 // TODO not here it's an ARM board ... menuEntryTime = get_tmr10ms() - 200;
2722 #endif
2724 while (1) {
2725 #if defined(CPUM2560)
2726 if ((shutdown_state=pwrCheck()) > e_power_trainer)
2727 break;
2728 #endif
2729 #if defined(SIMU)
2730 sleep(5/*ms*/);
2731 if (main_thread_running == 0)
2732 return 0;
2733 #endif
2735 perMain();
2737 if (heartbeat == HEART_WDT_CHECK) {
2738 wdt_reset();
2739 heartbeat = 0;
2742 #endif
2744 #if defined(CPUM2560)
2745 // Time to switch off
2746 drawSleepBitmap();
2747 opentxClose();
2748 boardOff(); // Only turn power off if necessary
2749 wdt_disable(); // this function is provided by AVR Libc
2750 while(1); // never return from main() - there is no code to return back, if any delays occurs in physical power it does dead loop.
2751 #endif
2753 #if defined(SIMU)
2754 return NULL;
2755 #endif
2758 #if defined(PWR_BUTTON_PRESS)
2759 uint32_t pwr_press_time = 0;
2761 uint32_t pwrPressedDuration()
2763 if (pwr_press_time == 0) {
2764 return 0;
2766 else {
2767 return get_tmr10ms() - pwr_press_time;
2771 uint32_t pwrCheck()
2773 const char * message = NULL;
2775 enum PwrCheckState {
2776 PWR_CHECK_ON,
2777 PWR_CHECK_OFF,
2778 PWR_CHECK_PAUSED,
2781 static uint8_t pwr_check_state = PWR_CHECK_ON;
2783 if (pwr_check_state == PWR_CHECK_OFF) {
2784 return e_power_off;
2786 else if (pwrPressed()) {
2787 if (TELEMETRY_STREAMING()) {
2788 message = STR_MODEL_STILL_POWERED;
2790 if (pwr_check_state == PWR_CHECK_PAUSED) {
2791 // nothing
2793 else if (pwr_press_time == 0) {
2794 pwr_press_time = get_tmr10ms();
2795 if (message && !g_eeGeneral.disableRssiPoweroffAlarm) {
2796 audioEvent(AU_MODEL_STILL_POWERED);
2799 else {
2800 inactivity.counter = 0;
2801 if (g_eeGeneral.backlightMode != e_backlight_mode_off) {
2802 BACKLIGHT_ENABLE();
2804 if (get_tmr10ms() - pwr_press_time > PWR_PRESS_SHUTDOWN_DELAY) {
2805 #if defined(SHUTDOWN_CONFIRMATION)
2806 while (1) {
2807 lcdRefreshWait();
2808 lcdClear();
2809 POPUP_CONFIRMATION("Confirm Shutdown");
2810 event_t evt = getEvent(false);
2811 DISPLAY_WARNING(evt);
2812 lcdRefresh();
2813 if (warningResult == true) {
2814 pwr_check_state = PWR_CHECK_OFF;
2815 return e_power_off;
2817 else if (!warningText) {
2818 // shutdown has been cancelled
2819 pwr_check_state = PWR_CHECK_PAUSED;
2820 return e_power_on;
2823 #else
2824 while ((TELEMETRY_STREAMING() && !g_eeGeneral.disableRssiPoweroffAlarm)) {
2825 lcdRefreshWait();
2826 lcdClear();
2827 POPUP_CONFIRMATION("Confirm Shutdown");
2828 event_t evt = getEvent(false);
2829 DISPLAY_WARNING(evt);
2830 lcdRefresh();
2831 if (warningResult) {
2832 pwr_check_state = PWR_CHECK_OFF;
2833 return e_power_off;
2835 else if (!warningText) {
2836 // shutdown has been cancelled
2837 pwr_check_state = PWR_CHECK_PAUSED;
2838 return e_power_on;
2841 haptic.play(15, 3, PLAY_NOW);
2842 pwr_check_state = PWR_CHECK_OFF;
2843 return e_power_off;
2844 #endif
2846 else {
2847 drawShutdownAnimation(pwrPressedDuration(), message);
2848 return e_power_press;
2852 else {
2853 pwr_check_state = PWR_CHECK_ON;
2854 pwr_press_time = 0;
2857 return e_power_on;
2859 #elif defined(CPUARM)
2860 uint32_t pwrCheck()
2862 #if defined(SOFT_PWR_CTRL)
2863 if (pwrPressed()) {
2864 return e_power_on;
2866 #endif
2868 if (usbPlugged()) {
2869 return e_power_usb;
2872 #if defined(TRAINER_PWR)
2873 if (TRAINER_CONNECTED()) {
2874 return e_power_trainer;
2876 #endif
2878 if (!g_eeGeneral.disableRssiPoweroffAlarm) {
2879 if (TELEMETRY_STREAMING()) {
2880 RAISE_ALERT(STR_MODEL, STR_MODEL_STILL_POWERED, STR_PRESS_ENTER_TO_CONFIRM, AU_MODEL_STILL_POWERED);
2881 while (TELEMETRY_STREAMING()) {
2882 #if defined(CPUARM)
2883 CoTickDelay(10);
2884 #endif
2885 if (pwrPressed()) {
2886 return e_power_on;
2888 else if (readKeys() == (1 << KEY_ENTER)) {
2889 return e_power_off;
2895 return e_power_off;
2897 #endif