Improve multi (#7136)
[opentx.git] / radio / src / main.cpp
blob5f1582b4918ab75e5a5eb6449515b069ef1719bb
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 uint8_t currentSpeakerVolume = 255;
24 uint8_t requiredSpeakerVolume = 255;
25 uint8_t mainRequestFlags = 0;
27 #if defined(STM32)
28 void onUSBConnectMenu(const char *result)
30 if (result == STR_USB_MASS_STORAGE) {
31 setSelectedUsbMode(USB_MASS_STORAGE_MODE);
33 else if (result == STR_USB_JOYSTICK) {
34 setSelectedUsbMode(USB_JOYSTICK_MODE);
36 else if (result == STR_USB_SERIAL) {
37 setSelectedUsbMode(USB_SERIAL_MODE);
40 #endif
42 void handleUsbConnection()
44 #if defined(STM32) && !defined(SIMU)
45 if (!usbStarted() && usbPlugged() && !(getSelectedUsbMode() == USB_UNSELECTED_MODE)) {
46 usbStart();
47 if (getSelectedUsbMode() == USB_MASS_STORAGE_MODE) {
48 opentxClose(false);
49 usbPluggedIn();
52 if (!usbStarted() && usbPlugged() && getSelectedUsbMode() == USB_UNSELECTED_MODE) {
53 if (g_eeGeneral.USBMode == USB_UNSELECTED_MODE && popupMenuItemsCount == 0) {
54 POPUP_MENU_ADD_ITEM(STR_USB_JOYSTICK);
55 POPUP_MENU_ADD_ITEM(STR_USB_MASS_STORAGE);
56 #if defined(DEBUG)
57 POPUP_MENU_ADD_ITEM(STR_USB_SERIAL);
58 #endif
59 POPUP_MENU_START(onUSBConnectMenu);
61 if (g_eeGeneral.USBMode != USB_UNSELECTED_MODE) {
62 setSelectedUsbMode(g_eeGeneral.USBMode);
65 if (usbStarted() && !usbPlugged()) {
66 usbStop();
67 if (getSelectedUsbMode() == USB_MASS_STORAGE_MODE) {
68 opentxResume();
70 #if !defined(BOOT)
71 setSelectedUsbMode(USB_UNSELECTED_MODE);
72 #endif
74 #endif // defined(STM32) && !defined(SIMU)
77 #if defined(JACK_DETECT_GPIO) && !defined(SIMU)
78 bool isJackPlugged()
80 // debounce
81 static bool debounced_state = 0;
82 static bool last_state = 0;
84 if (GPIO_ReadInputDataBit(JACK_DETECT_GPIO, JACK_DETECT_GPIO_PIN)) {
85 if (!last_state) {
86 debounced_state = false;
88 last_state = false;
90 else {
91 if (last_state) {
92 debounced_state = true;
94 last_state = true;
96 return debounced_state;
98 #endif
100 #if defined(PCBXLITES)
101 uint8_t jackState = SPEAKER_ACTIVE;
103 const char STR_JACK_HEADPHONE[] = "Headphone";
104 const char STR_JACK_TRAINER[] = "Trainer";
106 void onJackConnectMenu(const char * result)
108 if (result == STR_JACK_HEADPHONE) {
109 jackState = HEADPHONE_ACTIVE;
110 disableSpeaker();
111 enableHeadphone();
113 else if (result == STR_JACK_TRAINER) {
114 jackState = TRAINER_ACTIVE;
115 enableTrainer();
119 void handleJackConnection()
121 if (jackState == SPEAKER_ACTIVE && isJackPlugged()) {
122 if (g_eeGeneral.jackMode == JACK_HEADPHONE_MODE) {
123 jackState = HEADPHONE_ACTIVE;
124 disableSpeaker();
125 enableHeadphone();
127 else if (g_eeGeneral.jackMode == JACK_TRAINER_MODE) {
128 jackState = TRAINER_ACTIVE;
129 enableTrainer();
131 else if (popupMenuItemsCount == 0) {
132 POPUP_MENU_ADD_ITEM(STR_JACK_HEADPHONE);
133 POPUP_MENU_ADD_ITEM(STR_JACK_TRAINER);
134 POPUP_MENU_START(onJackConnectMenu);
137 else if (jackState == SPEAKER_ACTIVE && !isJackPlugged() && popupMenuItemsCount > 0 && popupMenuHandler == onJackConnectMenu) {
138 popupMenuItemsCount = 0;
140 else if (jackState != SPEAKER_ACTIVE && !isJackPlugged()) {
141 jackState = SPEAKER_ACTIVE;
142 enableSpeaker();
145 #endif
147 void checkSpeakerVolume()
149 if (currentSpeakerVolume != requiredSpeakerVolume) {
150 currentSpeakerVolume = requiredSpeakerVolume;
151 #if !defined(SOFTWARE_VOLUME)
152 setScaledVolume(currentSpeakerVolume);
153 #endif
157 #if defined(EEPROM)
158 void checkEeprom()
160 if (!usbPlugged()) {
161 if (eepromIsWriting())
162 eepromWriteProcess();
163 else if (TIME_TO_WRITE())
164 storageCheck(false);
167 #else
168 void checkEeprom()
170 #if defined(RTC_BACKUP_RAM) && !defined(SIMU)
171 if (TIME_TO_BACKUP_RAM()) {
172 rambackupWrite();
173 rambackupDirtyMsk = 0;
175 #endif
176 if (TIME_TO_WRITE()) {
177 storageCheck(false);
180 #endif
182 #define BAT_AVG_SAMPLES 8
184 void checkBatteryAlarms()
186 // TRACE("checkBatteryAlarms()");
187 if (IS_TXBATT_WARNING()) {
188 AUDIO_TX_BATTERY_LOW();
189 // TRACE("checkBatteryAlarms(): battery low");
191 #if defined(PCBSKY9X)
192 else if (g_eeGeneral.mAhWarn && (g_eeGeneral.mAhUsed + Current_used * (488 + g_eeGeneral.txCurrentCalibration)/8192/36) / 500 >= g_eeGeneral.mAhWarn) { // TODO move calculation into board file
193 AUDIO_TX_MAH_HIGH();
195 #endif
198 void checkBattery()
200 static uint32_t batSum;
201 static uint8_t sampleCount;
202 // filter battery voltage by averaging it
203 if (g_vbat100mV == 0) {
204 g_vbat100mV = (getBatteryVoltage() + 5) / 10;
205 batSum = 0;
206 sampleCount = 0;
208 else {
209 batSum += getBatteryVoltage();
210 // TRACE("checkBattery(): sampled = %d", getBatteryVoltage());
211 if (++sampleCount >= BAT_AVG_SAMPLES) {
212 g_vbat100mV = (batSum + BAT_AVG_SAMPLES * 5 ) / (BAT_AVG_SAMPLES * 10);
213 batSum = 0;
214 sampleCount = 0;
215 // TRACE("checkBattery(): g_vbat100mV = %d", g_vbat100mV);
220 void periodicTick_1s()
222 checkBattery();
225 void periodicTick_10s()
227 checkBatteryAlarms();
228 #if defined(LUA)
229 checkLuaMemoryUsage();
230 #endif
233 void periodicTick()
235 static uint8_t count10s;
236 static uint32_t lastTime;
237 if ( (get_tmr10ms() - lastTime) >= 100 ) {
238 lastTime += 100;
239 periodicTick_1s();
240 if (++count10s >= 10) {
241 count10s = 0;
242 periodicTick_10s();
247 #if defined(GUI) && defined(COLORLCD)
248 void guiMain(event_t evt)
250 bool refreshNeeded = false;
252 #if defined(LUA)
253 uint32_t t0 = get_tmr10ms();
254 static uint32_t lastLuaTime = 0;
255 uint16_t interval = (lastLuaTime == 0 ? 0 : (t0 - lastLuaTime));
256 lastLuaTime = t0;
257 if (interval > maxLuaInterval) {
258 maxLuaInterval = interval;
261 // run Lua scripts that don't use LCD (to use CPU time while LCD DMA is running)
262 DEBUG_TIMER_START(debugTimerLuaBg);
263 luaTask(0, RUN_MIX_SCRIPT | RUN_FUNC_SCRIPT | RUN_TELEM_BG_SCRIPT, false);
264 DEBUG_TIMER_STOP(debugTimerLuaBg);
265 // wait for LCD DMA to finish before continuing, because code from this point
266 // is allowed to change the contents of LCD buffer
268 // WARNING: make sure no code above this line does any change to the LCD display buffer!
270 DEBUG_TIMER_START(debugTimerLcdRefreshWait);
271 lcdRefreshWait();
272 DEBUG_TIMER_STOP(debugTimerLcdRefreshWait);
274 // draw LCD from menus or from Lua script
275 // run Lua scripts that use LCD
277 DEBUG_TIMER_START(debugTimerLuaFg);
278 refreshNeeded = luaTask(evt, RUN_STNDAL_SCRIPT, true);
279 if (!refreshNeeded) {
280 refreshNeeded = luaTask(evt, RUN_TELEM_FG_SCRIPT, true);
282 DEBUG_TIMER_STOP(debugTimerLuaFg);
284 t0 = get_tmr10ms() - t0;
285 if (t0 > maxLuaDuration) {
286 maxLuaDuration = t0;
288 #else
289 lcdRefreshWait(); // WARNING: make sure no code above this line does any change to the LCD display buffer!
290 #endif
292 bool screenshotRequested = (mainRequestFlags & (1u << REQUEST_SCREENSHOT));
294 if (!refreshNeeded) {
295 DEBUG_TIMER_START(debugTimerMenus);
296 while (true) {
297 // normal GUI from menus
298 const char * warn = warningText;
299 uint8_t menu = popupMenuItemsCount;
300 static bool popupDisplayed = false;
301 if (warn || menu) {
302 if (popupDisplayed == false) {
303 menuHandlers[menuLevel](EVT_REFRESH);
304 lcdDrawBlackOverlay();
305 TIME_MEASURE_START(storebackup);
306 lcdStoreBackupBuffer();
307 TIME_MEASURE_STOP(storebackup);
309 if (popupDisplayed == false || evt || screenshotRequested) {
310 popupDisplayed = lcdRestoreBackupBuffer();
311 if (warn) {
312 DISPLAY_WARNING(evt);
314 if (menu) {
315 const char * result = runPopupMenu(evt);
316 if (result) {
317 TRACE("popupMenuHandler(%s)", result);
318 auto handler = popupMenuHandler;
319 if (result != STR_UPDATE_LIST)
320 CLEAR_POPUP();
321 handler(result);
322 if (menuEvent == 0) {
323 evt = EVT_REFRESH;
324 continue;
328 refreshNeeded = true;
331 else {
332 if (popupDisplayed) {
333 if (evt == 0) {
334 evt = EVT_REFRESH;
336 popupDisplayed = false;
338 DEBUG_TIMER_START(debugTimerMenuHandlers);
339 refreshNeeded = menuHandlers[menuLevel](evt);
340 DEBUG_TIMER_STOP(debugTimerMenuHandlers);
343 if (menuEvent == EVT_ENTRY) {
344 menuVerticalPosition = 0;
345 menuHorizontalPosition = 0;
346 evt = menuEvent;
347 menuEvent = 0;
349 else if (menuEvent == EVT_ENTRY_UP) {
350 menuVerticalPosition = menuVerticalPositions[menuLevel];
351 menuHorizontalPosition = 0;
352 evt = menuEvent;
353 menuEvent = 0;
355 else {
356 break;
359 DEBUG_TIMER_STOP(debugTimerMenus);
362 if (screenshotRequested) {
363 writeScreenshot();
364 mainRequestFlags &= ~(1u << REQUEST_SCREENSHOT);
367 if (refreshNeeded) {
368 DEBUG_TIMER_START(debugTimerLcdRefresh);
369 lcdRefresh();
370 DEBUG_TIMER_STOP(debugTimerLcdRefresh);
373 #elif defined(GUI)
374 void handleGui(event_t event) {
375 // if Lua standalone, run it and don't clear the screen (Lua will do it)
376 // else if Lua telemetry view, run it and don't clear the screen
377 // else clear scren and show normal menus
378 #if defined(LUA)
379 if (luaTask(event, RUN_STNDAL_SCRIPT, true)) {
380 // standalone script is active
382 else if (luaTask(event, RUN_TELEM_FG_SCRIPT, true)) {
383 // the telemetry screen is active
384 // prevent events from keys MENU, UP, DOWN, ENT(short) and EXIT(short) from reaching the normal menus,
385 // so Lua telemetry script can fully use them
386 if (event) {
387 uint8_t key = EVT_KEY_MASK(event);
388 #if defined(PCBXLITE)
389 // SHIFT + LEFT/RIGHT LONG used to change telemetry screen on XLITE
390 if ((!IS_KEY_LONG(event) && key == KEY_RIGHT && IS_SHIFT_PRESSED()) || (!IS_KEY_LONG(event) && key == KEY_LEFT && IS_SHIFT_PRESSED()) || (!IS_KEY_LONG(event) && key == KEY_EXIT)) {
391 #else
392 // no need to filter out MENU and ENT(short), because they are not used by menuViewTelemetryFrsky()
393 if (key == KEY_PLUS || key == KEY_MINUS || (!IS_KEY_LONG(event) && key == KEY_EXIT)) {
394 #endif
395 // TRACE("Telemetry script event 0x%02x killed", event);
396 event = 0;
399 menuHandlers[menuLevel](event);
401 else
402 #endif
404 lcdClear();
405 menuHandlers[menuLevel](event);
406 drawStatusLine();
410 void guiMain(event_t evt)
412 #if defined(LUA)
413 // TODO better lua stopwatch
414 uint32_t t0 = get_tmr10ms();
415 static uint32_t lastLuaTime = 0;
416 uint16_t interval = (lastLuaTime == 0 ? 0 : (t0 - lastLuaTime));
417 lastLuaTime = t0;
418 if (interval > maxLuaInterval) {
419 maxLuaInterval = interval;
422 // run Lua scripts that don't use LCD (to use CPU time while LCD DMA is running)
423 luaTask(0, RUN_MIX_SCRIPT | RUN_FUNC_SCRIPT | RUN_TELEM_BG_SCRIPT, false);
425 t0 = get_tmr10ms() - t0;
426 if (t0 > maxLuaDuration) {
427 maxLuaDuration = t0;
429 #endif //#if defined(LUA)
431 // wait for LCD DMA to finish before continuing, because code from this point
432 // is allowed to change the contents of LCD buffer
434 // WARNING: make sure no code above this line does any change to the LCD display buffer!
436 lcdRefreshWait();
438 if (menuEvent) {
439 // we have a popupMenuActive entry or exit event
440 menuVerticalPosition = (menuEvent == EVT_ENTRY_UP) ? menuVerticalPositions[menuLevel] : 0;
441 menuHorizontalPosition = 0;
442 evt = menuEvent;
443 menuEvent = 0;
446 if (isEventCaughtByPopup()) {
447 handleGui(0);
449 else {
450 handleGui(evt);
451 evt = 0;
454 if (warningText) {
455 // show warning on top of the normal menus
456 DISPLAY_WARNING(evt);
458 else if (popupMenuItemsCount > 0) {
459 // show popup menu on top of the normal menus
460 const char * result = runPopupMenu(evt);
461 if (result) {
462 TRACE("popupMenuHandler(%s)", result);
463 auto handler = popupMenuHandler;
464 if (result != STR_UPDATE_LIST)
465 CLEAR_POPUP();
466 handler(result);
470 lcdRefresh();
472 if (mainRequestFlags & (1 << REQUEST_SCREENSHOT)) {
473 writeScreenshot();
474 mainRequestFlags &= ~(1 << REQUEST_SCREENSHOT);
477 #endif
479 void perMain()
481 DEBUG_TIMER_START(debugTimerPerMain1);
483 #if defined(PCBSKY9X)
484 calcConsumption();
485 #endif
486 checkSpeakerVolume();
487 checkEeprom();
488 logsWrite();
489 handleUsbConnection();
490 #if defined(PCBXLITES)
491 handleJackConnection();
492 #endif
493 checkTrainerSettings();
494 periodicTick();
495 DEBUG_TIMER_STOP(debugTimerPerMain1);
497 if (mainRequestFlags & (1 << REQUEST_FLIGHT_RESET)) {
498 TRACE("Executing requested Flight Reset");
499 flightReset();
500 mainRequestFlags &= ~(1 << REQUEST_FLIGHT_RESET);
503 doLoopCommonActions();
505 event_t evt = getEvent(false);
507 #if defined(RTC_BACKUP_RAM)
508 if (globalData.unexpectedShutdown) {
509 drawFatalErrorScreen(STR_EMERGENCY_MODE);
510 return;
512 #endif
514 #if defined(STM32)
515 if (SD_CARD_PRESENT() && !sdMounted()) {
516 sdMount();
518 #endif
520 #if !defined(EEPROM)
521 // In case the SD card is removed during the session
522 if (!SD_CARD_PRESENT() && !globalData.unexpectedShutdown) {
523 drawFatalErrorScreen(STR_NO_SDCARD);
524 return;
526 #endif
528 #if defined(STM32)
529 if (usbPlugged() && getSelectedUsbMode() == USB_MASS_STORAGE_MODE) {
530 // disable access to menus
531 lcdClear();
532 menuMainView(0);
533 lcdRefresh();
534 return;
536 #endif
538 #if defined(GUI)
539 DEBUG_TIMER_START(debugTimerGuiMain);
540 guiMain(evt);
541 DEBUG_TIMER_STOP(debugTimerGuiMain);
542 #endif
544 #if defined(PCBX9E) && !defined(SIMU)
545 toplcdRefreshStart();
546 setTopFirstTimer(getValue(MIXSRC_FIRST_TIMER+g_model.toplcdTimer));
547 setTopSecondTimer(g_eeGeneral.globalTimer + sessionTimer);
548 setTopRssi(TELEMETRY_RSSI());
549 setTopBatteryValue(g_vbat100mV);
550 setTopBatteryState(GET_TXBATT_BARS(10), IS_TXBATT_WARNING());
551 toplcdRefreshEnd();
552 #endif
554 #if defined(INTERNAL_GPS)
555 gpsWakeup();
556 #endif