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 <io/frsky_firmware_update.h>
24 RadioData g_eeGeneral
;
31 GlobalData globalData
;
33 uint16_t maxMixerDuration
; // step = 0.01ms
36 #if defined(OVERRIDE_CHANNEL_FUNCTION)
37 safetych_t safetyCh
[MAX_OUTPUT_CHANNELS
];
40 // __DMA for the MSC_BOT_Data member
41 union ReusableBuffer reusableBuffer __DMA
;
44 uint8_t* MSC_BOT_Data
= reusableBuffer
.MSC_BOT_Data
;
47 #if defined(DEBUG_LATENCY)
48 uint8_t latencyToggleSwitch
= 0;
51 const uint8_t bchout_ar
[] = {
52 0x1B, 0x1E, 0x27, 0x2D, 0x36, 0x39,
53 0x4B, 0x4E, 0x63, 0x6C, 0x72, 0x78,
54 0x87, 0x8D, 0x93, 0x9C, 0xB1, 0xB4,
55 0xC6, 0xC9, 0xD2, 0xD8, 0xE1, 0xE4 };
57 uint8_t channelOrder(uint8_t setup
, uint8_t x
)
59 return ((*(bchout_ar
+ setup
) >> (6 - (x
- 1) * 2)) & 3) + 1;
62 uint8_t channelOrder(uint8_t x
)
64 return channelOrder(g_eeGeneral
.templateSetup
, x
);
73 const uint8_t modn12x3
[] = {
79 volatile uint8_t rtc_count
= 0;
80 uint32_t watchdogTimeout
= 0;
82 void watchdogSuspend(uint32_t timeout
)
84 watchdogTimeout
= timeout
;
87 #if defined(DEBUG_LATENCY)
88 void toggleLatencySwitch()
90 latencyToggleSwitch
^= 1;
93 if (latencyToggleSwitch
)
94 GPIO_ResetBits(EXTMODULE_TX_GPIO
, EXTMODULE_TX_GPIO_PIN
);
96 GPIO_SetBits(EXTMODULE_TX_GPIO
, EXTMODULE_TX_GPIO_PIN
);
98 if (latencyToggleSwitch
)
101 sportUpdatePowerOff();
110 if (watchdogTimeout
) {
111 watchdogTimeout
-= 1;
112 WDG_RESET(); // Retrigger hardware watchdog
116 if (lightOffCounter
) lightOffCounter
--;
117 if (flashCounter
) flashCounter
--;
118 if (noHighlightCounter
) noHighlightCounter
--;
121 if (trimsCheckTimer
) trimsCheckTimer
--;
122 if (ppmInputValidityTimer
) ppmInputValidityTimer
--;
124 if (trimsDisplayTimer
)
127 trimsDisplayMask
= 0;
129 #if defined(DEBUG_LATENCY_END_TO_END)
130 static tmr10ms_t lastLatencyToggle
= 0;
131 if (g_tmr10ms
- lastLatencyToggle
== 10) {
132 lastLatencyToggle
= g_tmr10ms
;
133 toggleLatencySwitch();
138 /* Update global Date/Time every 100 per10ms cycles */
139 if (++g_ms100
== 100) {
140 g_rtcTime
++; // inc global unix timestamp one second
141 #if defined(COPROCESSOR)
142 if (g_rtcTime
< 60 || rtc_count
<5) {
147 coprocReadData(true);
156 #if defined(ROTARY_ENCODER_NAVIGATION)
157 if (IS_ROTARY_ENCODER_NAVIGATION_ENABLE()) {
158 static rotenc_t rePreviousValue
;
159 static bool cw
= false;
160 rotenc_t reNewValue
= (ROTARY_ENCODER_NAVIGATION_VALUE
/ ROTARY_ENCODER_GRANULARITY
);
161 rotenc_t scrollRE
= reNewValue
- rePreviousValue
;
163 static uint32_t lastEvent
;
164 rePreviousValue
= reNewValue
;
166 bool new_cw
= (scrollRE
< 0) ? false : true;
167 if ((g_tmr10ms
- lastEvent
>= 10) || (cw
== new_cw
)) { // 100ms
169 putEvent(new_cw
? EVT_ROTARY_RIGHT
: EVT_ROTARY_LEFT
);
171 // rotary encoder navigation speed (acceleration) detection/calculation
172 static uint32_t delay
= 2*ROTENC_DELAY_MIDSPEED
;
175 // Modified moving average filter used for smoother change of speed
176 delay
= (((g_tmr10ms
- lastEvent
) << 3) + delay
) >> 1;
179 delay
= 2*ROTENC_DELAY_MIDSPEED
;
182 if (delay
< ROTENC_DELAY_HIGHSPEED
)
183 rotencSpeed
= ROTENC_HIGHSPEED
;
184 else if (delay
< ROTENC_DELAY_MIDSPEED
)
185 rotencSpeed
= ROTENC_MIDSPEED
;
187 rotencSpeed
= ROTENC_LOWSPEED
;
189 lastEvent
= g_tmr10ms
;
195 telemetryInterrupt10ms();
197 // These moved here from evalFlightModeMixes() to improve beep trigger reliability.
198 #if defined(PWM_BACKLIGHT)
199 if ((g_tmr10ms
&0x03) == 0x00)
200 backlightFade(); // increment or decrement brightness until target brightness is reached
204 if (mixWarning
& 1) if(((g_tmr10ms
&0xFF)== 0)) AUDIO_MIX_WARNING(1);
205 if (mixWarning
& 2) if(((g_tmr10ms
&0xFF)== 64) || ((g_tmr10ms
&0xFF)== 72)) AUDIO_MIX_WARNING(2);
206 if (mixWarning
& 4) if(((g_tmr10ms
&0xFF)==128) || ((g_tmr10ms
&0xFF)==136) || ((g_tmr10ms
&0xFF)==144)) AUDIO_MIX_WARNING(3);
213 outputTelemetryBuffer
.per10ms();
215 heartbeat
|= HEART_TIMER_10MS
;
218 FlightModeData
*flightModeAddress(uint8_t idx
)
220 return &g_model
.flightModeData
[idx
];
223 ExpoData
*expoAddress(uint8_t idx
)
225 return &g_model
.expoData
[idx
];
228 MixData
*mixAddress(uint8_t idx
)
230 return &g_model
.mixData
[idx
];
233 LimitData
*limitAddress(uint8_t idx
)
235 return &g_model
.limitData
[idx
];
239 void memswap(void * a
, void * b
, uint8_t size
)
241 uint8_t * x
= (uint8_t *)a
;
242 uint8_t * y
= (uint8_t *)b
;
253 void setDefaultOwnerId()
255 for (uint8_t i
= 0; i
< PXX2_LEN_REGISTRATION_ID
; i
++) {
256 g_eeGeneral
.ownerRegistrationID
[i
] = (cpu_uid
[1 + i
] & 0x3f) - 26;
261 void generalDefault()
263 memclear(&g_eeGeneral
, sizeof(g_eeGeneral
));
264 g_eeGeneral
.version
= EEPROM_VER
;
265 g_eeGeneral
.variant
= EEPROM_VARIANT
;
267 #if defined(PCBHORUS)
268 g_eeGeneral
.blOffBright
= 20;
270 g_eeGeneral
.contrast
= LCD_CONTRAST_DEFAULT
;
273 #if defined(DEFAULT_POTS_CONFIG)
274 g_eeGeneral
.potsConfig
= DEFAULT_POTS_CONFIG
;
277 #if defined(DEFAULT_SWITCH_CONFIG)
278 g_eeGeneral
.switchConfig
= DEFAULT_SWITCH_CONFIG
;
281 #if defined(DEFAULT_SLIDERS_CONFIG)
282 g_eeGeneral
.slidersConfig
= DEFAULT_SLIDERS_CONFIG
;
285 // vBatWarn is voltage in 100mV, vBatMin is in 100mV but with -9V offset, vBatMax has a -12V offset
286 g_eeGeneral
.vBatWarn
= BATTERY_WARN
;
287 if (BATTERY_MIN
!= 90)
288 g_eeGeneral
.vBatMin
= BATTERY_MIN
- 90;
289 if (BATTERY_MAX
!= 120)
290 g_eeGeneral
.vBatMax
= BATTERY_MAX
- 120;
292 #if defined(DEFAULT_MODE)
293 g_eeGeneral
.stickMode
= DEFAULT_MODE
- 1;
296 #if defined(FRSKY_RELEASE)
297 g_eeGeneral
.templateSetup
= 17; /* TAER */
300 g_eeGeneral
.backlightMode
= e_backlight_mode_all
;
301 g_eeGeneral
.lightAutoOff
= 2;
302 g_eeGeneral
.inactivityTimer
= 10;
304 g_eeGeneral
.ttsLanguage
[0] = 'e';
305 g_eeGeneral
.ttsLanguage
[1] = 'n';
306 g_eeGeneral
.wavVolume
= 2;
307 g_eeGeneral
.backgroundVolume
= 1;
309 for (int i
=0; i
<NUM_STICKS
; ++i
) {
310 g_eeGeneral
.trainer
.mix
[i
].mode
= 2;
311 g_eeGeneral
.trainer
.mix
[i
].srcChn
= channelOrder(i
+1) - 1;
312 g_eeGeneral
.trainer
.mix
[i
].studWeight
= 100;
316 const int8_t defaultName
[] = { 20, -1, -18, -1, -14, -9, -19 };
317 memcpy(g_eeGeneral
.bluetoothName
, defaultName
, sizeof(defaultName
));
321 strcpy(g_eeGeneral
.currModelFilename
, DEFAULT_MODEL_FILENAME
);
324 #if defined(COLORLCD)
325 strcpy(g_eeGeneral
.themeName
, theme
->getName());
333 g_eeGeneral
.chkSum
= 0xFFFF;
336 uint16_t evalChkSum()
339 const int16_t *calibValues
= (const int16_t *) &g_eeGeneral
.calib
[0];
340 for (int i
=0; i
<12; i
++)
341 sum
+= calibValues
[i
];
347 memset(g_model
.expoData
, 0, sizeof(g_model
.expoData
)); // clear all expos
354 for (int i
=0; i
<NUM_STICKS
; i
++) {
355 uint8_t stick_index
= channelOrder(i
+1);
356 ExpoData
*expo
= expoAddress(i
);
357 expo
->srcRaw
= MIXSRC_Rud
- 1 + stick_index
;
358 expo
->curve
.type
= CURVE_REF_EXPO
;
361 expo
->mode
= 3; // TODO constant
362 #if defined(TRANSLATIONS_CZ)
363 for (int c
=0; c
<4; c
++) {
364 g_model
.inputNames
[i
][c
] = char2zchar(STR_INPUTNAMES
[1+4*(stick_index
-1)+c
]);
367 for (int c
=0; c
<3; c
++) {
368 g_model
.inputNames
[i
][c
] = char2zchar(STR_VSRCRAW
[2 + 4 * stick_index
+ c
]);
370 #if LEN_INPUT_NAME > 3
371 g_model
.inputNames
[i
][3] = '\0';
375 storageDirty(EE_MODEL
);
378 void applyDefaultTemplate()
380 defaultInputs(); // calls storageDirty internally
382 for (int i
=0; i
<NUM_STICKS
; i
++) {
383 MixData
* mix
= mixAddress(i
);
391 void checkModelIdUnique(uint8_t index
, uint8_t module
)
393 if (isModuleXJTD8(module
))
396 uint8_t modelId
= g_model
.header
.modelId
[module
];
397 uint8_t additionalOnes
= 0;
398 char * name
= reusableBuffer
.moduleSetup
.msg
;
400 memset(reusableBuffer
.moduleSetup
.msg
, 0, sizeof(reusableBuffer
.moduleSetup
.msg
));
403 for (uint8_t i
= 0; i
< MAX_MODELS
; i
++) {
405 if (modelId
== modelHeaders
[i
].modelId
[module
]) {
406 if ((WARNING_LINE_LEN
- 4 - (name
- reusableBuffer
.moduleSetup
.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 ",")
407 if (reusableBuffer
.moduleSetup
.msg
[0] != '\0') {
408 name
= strAppend(name
, ", ");
410 if (modelHeaders
[i
].name
[0] == 0) {
411 name
= strAppend(name
, STR_MODEL
);
412 name
= strAppendUnsigned(name
+strlen(name
), i
+ 1, 2);
415 name
+= zchar2str(name
, modelHeaders
[i
].name
, LEN_MODEL_NAME
);
426 if (additionalOnes
) {
427 name
= strAppend(name
, " (+");
428 name
= strAppendUnsigned(name
, additionalOnes
);
429 strAppend(name
, ")");
432 if (reusableBuffer
.moduleSetup
.msg
[0] != '\0') {
433 POPUP_WARNING(STR_MODELIDUSED
);
434 SET_WARNING_INFO(reusableBuffer
.moduleSetup
.msg
, sizeof(reusableBuffer
.moduleSetup
.msg
), 0);
438 uint8_t findNextUnusedModelId(uint8_t index
, uint8_t module
)
440 // assume 63 is the highest Model ID
442 uint8_t usedModelIds
[8];
443 memset(usedModelIds
, 0, sizeof(usedModelIds
));
445 for (uint8_t mod_i
= 0; mod_i
< MAX_MODELS
; mod_i
++) {
450 uint8_t id
= modelHeaders
[mod_i
].modelId
[module
];
455 for (uint8_t i
= 1; i
< (id
& 7); i
++)
458 usedModelIds
[id
>> 3] |= mask
;
462 uint8_t tst_mask
= 1;
463 for (;new_id
< getMaxRxNum(module
); new_id
++) {
464 if (!(usedModelIds
[new_id
>> 3] & tst_mask
)) {
468 if ((tst_mask
<<= 1) == 0)
472 // failed finding something...
477 void modelDefault(uint8_t id
)
479 memset(&g_model
, 0, sizeof(g_model
));
481 applyDefaultTemplate();
483 memcpy(g_model
.modelRegistrationID
, g_eeGeneral
.ownerRegistrationID
, PXX2_LEN_REGISTRATION_ID
);
485 #if defined(LUA) && defined(PCBTARANIS) //Horus uses menuModelWizard() for wizard
486 if (isFileAvailable(WIZARD_PATH
"/" WIZARD_NAME
)) {
487 f_chdir(WIZARD_PATH
);
488 luaExec(WIZARD_NAME
);
492 #if defined(FRSKY_RELEASE)
493 g_model
.moduleData
[INTERNAL_MODULE
].type
= IS_PXX2_INTERNAL_ENABLED() ? MODULE_TYPE_ISRM_PXX2
: MODULE_TYPE_XJT_PXX1
;
494 g_model
.moduleData
[INTERNAL_MODULE
].channelsCount
= defaultModuleChannels_M8(INTERNAL_MODULE
);
497 #if defined(PCBXLITE)
498 g_model
.trainerData
.mode
= TRAINER_MODE_MASTER_BLUETOOTH
;
502 for (int i
=0; i
<NUM_MODULES
; i
++) {
503 modelHeaders
[id
].modelId
[i
] = g_model
.header
.modelId
[i
] = id
+1;
505 checkModelIdUnique(id
, 0);
508 #if defined(FLIGHT_MODES) && defined(GVARS)
509 for (int p
=1; p
<MAX_FLIGHT_MODES
; p
++) {
510 for (int i
=0; i
<MAX_GVARS
; i
++) {
511 g_model
.flightModeData
[p
].gvars
[i
] = GVAR_MAX
+1;
517 strcpy(g_model
.header
.name
, "\015\361\374\373\364");
518 g_model
.header
.name
[5] = '\033' + id
/10;
519 g_model
.header
.name
[6] = '\033' + id
%10;
522 #if defined(PCBHORUS)
523 extern const LayoutFactory
* defaultLayout
;
524 delete customScreens
[0];
525 customScreens
[0] = defaultLayout
->create(&g_model
.screenData
[0].layoutData
);
526 strcpy(g_model
.screenData
[0].layoutName
, "Layout2P1");
527 extern const WidgetFactory
* defaultWidget
;
528 customScreens
[0]->createWidget(0, defaultWidget
);
529 // enable switch warnings
530 for (int i
=0; i
<NUM_SWITCHES
; i
++) {
531 g_model
.switchWarningState
|= (1 << (3*i
));
536 bool isInputRecursive(int index
)
538 ExpoData
* line
= expoAddress(0);
539 for (int i
=0; i
<MAX_EXPOS
; i
++, line
++) {
540 if (line
->chn
> index
)
542 else if (line
->chn
< index
)
544 else if (line
->srcRaw
>= MIXSRC_FIRST_LOGICAL_SWITCH
)
550 #if defined(AUTOSOURCE)
551 int8_t getMovedSource(GET_MOVED_SOURCE_PARAMS
)
554 static tmr10ms_t s_move_last_time
= 0;
556 static int16_t inputsStates
[MAX_INPUTS
];
557 if (min
<= MIXSRC_FIRST_INPUT
) {
558 for (uint8_t i
=0; i
<MAX_INPUTS
; i
++) {
559 if (abs(anas
[i
] - inputsStates
[i
]) > 512) {
560 if (!isInputRecursive(i
)) {
561 result
= MIXSRC_FIRST_INPUT
+i
;
568 static int16_t sourcesStates
[NUM_STICKS
+NUM_POTS
+NUM_SLIDERS
+NUM_MOUSE_ANALOGS
];
570 for (uint8_t i
=0; i
<NUM_STICKS
+NUM_POTS
+NUM_SLIDERS
; i
++) {
571 if (abs(calibratedAnalogs
[i
] - sourcesStates
[i
]) > 512) {
572 result
= MIXSRC_Rud
+i
;
578 bool recent
= ((tmr10ms_t
)(get_tmr10ms() - s_move_last_time
) > 10);
583 if (result
|| recent
) {
584 memcpy(inputsStates
, anas
, sizeof(inputsStates
));
585 memcpy(sourcesStates
, calibratedAnalogs
, sizeof(sourcesStates
));
588 s_move_last_time
= get_tmr10ms();
593 #if defined(FLIGHT_MODES)
594 uint8_t getFlightMode()
596 for (uint8_t i
=1; i
<MAX_FLIGHT_MODES
; i
++) {
597 FlightModeData
*phase
= &g_model
.flightModeData
[i
];
598 if (phase
->swtch
&& getSwitch(phase
->swtch
)) {
606 trim_t
getRawTrimValue(uint8_t phase
, uint8_t idx
)
608 FlightModeData
* p
= flightModeAddress(phase
);
612 int getTrimValue(uint8_t phase
, uint8_t idx
)
615 for (uint8_t i
=0; i
<MAX_FLIGHT_MODES
; i
++) {
616 trim_t v
= getRawTrimValue(phase
, idx
);
617 if (v
.mode
== TRIM_MODE_NONE
) {
621 unsigned int p
= v
.mode
>> 1;
622 if (p
== phase
|| phase
== 0) {
623 return result
+ v
.value
;
627 if (v
.mode
% 2 != 0) {
636 bool setTrimValue(uint8_t phase
, uint8_t idx
, int trim
)
638 for (uint8_t i
=0; i
<MAX_FLIGHT_MODES
; i
++) {
639 trim_t
& v
= flightModeAddress(phase
)->trim
[idx
];
640 if (v
.mode
== TRIM_MODE_NONE
)
642 unsigned int p
= v
.mode
>> 1;
643 if (p
== phase
|| phase
== 0) {
647 else if (v
.mode
% 2 == 0) {
651 v
.value
= limit
<int>(TRIM_EXTENDED_MIN
, trim
- getTrimValue(p
, idx
), TRIM_EXTENDED_MAX
);
655 storageDirty(EE_MODEL
);
659 getvalue_t
convert16bitsTelemValue(source_t channel
, ls_telemetry_value_t value
)
664 ls_telemetry_value_t
maxTelemValue(source_t channel
)
669 #define INAC_STICKS_SHIFT 6
670 #define INAC_SWITCHES_SHIFT 8
674 for (uint8_t i
=0; i
<NUM_STICKS
+NUM_POTS
+NUM_SLIDERS
; i
++)
675 sum
+= anaIn(i
) >> INAC_STICKS_SHIFT
;
676 for (uint8_t i
=0; i
<NUM_SWITCHES
; i
++)
677 sum
+= getValue(MIXSRC_FIRST_SWITCH
+i
) >> INAC_SWITCHES_SHIFT
;
679 for (uint8_t i
=0; i
<2; i
++)
680 sum
+= getValue(MIXSRC_GYRO1
+i
) >> INAC_STICKS_SHIFT
;
683 if (abs((int8_t)(sum
-inactivity
.sum
)) > 1) {
684 inactivity
.sum
= sum
;
692 void checkBacklight()
694 static uint8_t tmr10ms
;
696 uint8_t x
= g_blinkTmr10ms
;
700 inactivity
.counter
= 0;
701 if (g_eeGeneral
.backlightMode
& e_backlight_mode_sticks
) {
706 bool backlightOn
= (g_eeGeneral
.backlightMode
== e_backlight_mode_on
|| (g_eeGeneral
.backlightMode
!= e_backlight_mode_off
&& lightOffCounter
) || isFunctionActive(FUNCTION_BACKLIGHT
));
707 if (flashCounter
) backlightOn
= !backlightOn
;
715 void doLoopCommonActions()
722 lightOffCounter
= ((uint16_t)g_eeGeneral
.lightAutoOff
*250) << 1;
726 bool readonly
= true;
727 bool readonlyUnlocked()
730 POPUP_WARNING(STR_MODS_FORBIDDEN
);
742 #if defined(PWR_BUTTON_PRESS)
743 bool refresh
= false;
746 if (SPLASH_NEEDED()) {
750 #if defined(PCBSKY9X)
751 tmr10ms_t curTime
= get_tmr10ms() + 10;
752 uint8_t contrast
= 10;
753 lcdSetRefVolt(contrast
);
756 getADC(); // init ADC array
760 tmr10ms_t tgtime
= get_tmr10ms() + SPLASH_TIMEOUT
;
762 while (tgtime
> get_tmr10ms()) {
767 if (keyDown() || inputsMoved()) return;
769 #if defined(PWR_BUTTON_PRESS)
770 uint32_t pwr_check
= pwrCheck();
771 if (pwr_check
== e_power_off
) {
774 else if (pwr_check
== e_power_press
) {
777 else if (pwr_check
== e_power_on
&& refresh
) {
782 if (pwrCheck() == e_power_off
) {
787 #if defined(FRSKY_RELEASE)
788 static uint8_t secondSplash
= false;
789 if (!secondSplash
&& get_tmr10ms() >= tgtime
-200) {
795 #if defined(PCBSKY9X)
796 if (curTime
< get_tmr10ms()) {
798 if (contrast
< g_eeGeneral
.contrast
) {
800 lcdSetRefVolt(contrast
);
805 doLoopCommonActions();
815 void checkSDVersion()
820 char version
[sizeof(REQUIRED_SDCARD_VERSION
)-1];
821 char error
[sizeof(TR_WRONG_SDCARDVERSION
)+ sizeof(version
)];
823 strAppend(strAppend(error
, STR_WRONG_SDCARDVERSION
, sizeof(TR_WRONG_SDCARDVERSION
)), REQUIRED_SDCARD_VERSION
, sizeof(REQUIRED_SDCARD_VERSION
));
824 FRESULT result
= f_open(&versionFile
, "/opentx.sdcard.version", FA_OPEN_EXISTING
| FA_READ
);
825 if (result
== FR_OK
) {
826 if (f_read(&versionFile
, &version
, sizeof(version
), &read
) != FR_OK
||
827 read
!= sizeof(version
) ||
828 strncmp(version
, REQUIRED_SDCARD_VERSION
, sizeof(version
)) != 0) {
829 TRACE("SD card version mismatch: %.*s, %s", sizeof(REQUIRED_SDCARD_VERSION
)-1, version
, REQUIRED_SDCARD_VERSION
);
830 ALERT(STR_SD_CARD
, error
, AU_ERROR
);
832 f_close(&versionFile
);
835 ALERT(STR_SD_CARD
, error
, AU_ERROR
);
841 #if defined(MULTIMODULE)
842 void checkMultiLowPower()
844 if (isModuleMultimodule(EXTERNAL_MODULE
) && g_model
.moduleData
[EXTERNAL_MODULE
].multi
.lowPowerMode
) {
845 ALERT("MULTI", STR_WARN_MULTI_LOWPOWER
, AU_ERROR
);
848 #if defined(INTERNAL_MODULE_MULTI)
849 if (isModuleMultimodule(INTERNAL_MODULE
) && g_model
.moduleData
[INTERNAL_MODULE
].multi
.lowPowerMode
) {
850 ALERT("MULTI", STR_WARN_MULTI_LOWPOWER
, AU_ERROR
);
857 static void checkRTCBattery()
859 if (isVBatBridgeEnabled()) {
860 if (getRTCBatteryVoltage() < 200) {
861 ALERT(STR_BATTERY
, STR_WARN_RTC_BATTERY_LOW
, AU_ERROR
);
868 #if defined(PCBTARANIS) || defined(PCBHORUS)
871 for (int i
=0; i
<NUM_MODULES
; i
++) {
872 #if defined(MULTIMODULE)
873 if (isModuleMultimodule(i
)) {
874 getMultiModuleStatus(i
).requiresFailsafeCheck
= true;
878 if (isModuleFailsafeAvailable(i
)) {
879 ModuleData
& moduleData
= g_model
.moduleData
[i
];
880 if (moduleData
.failsafeMode
== FAILSAFE_NOT_SET
) {
881 ALERT(STR_FAILSAFEWARN
, STR_NO_FAILSAFE
, AU_ERROR
);
888 #define checkFailsafe()
890 void checkRSSIAlarmsDisabled()
892 if (g_model
.rssiAlarms
.disabled
) {
893 ALERT(STR_RSSIALARM_WARN
, STR_NO_RSSIALARM
, AU_ERROR
);
900 #if defined(EEPROM_RLC)
904 // we don't check the throttle stick if the radio is not calibrated
905 if (g_eeGeneral
.chkSum
== evalChkSum()) {
906 checkThrottleStick();
911 checkRSSIAlarmsDisabled();
918 if (!g_eeGeneral
.disableRtcWarning
)
922 if (g_model
.displayChecklist
&& modelHasNotes()) {
926 #if defined(MULTIMODULE)
927 checkMultiLowPower();
930 if (!waitKeysReleased()) {
931 showMessageBox(STR_KEYSTUCK
);
932 tmr10ms_t tgtime
= get_tmr10ms() + 500;
933 while (tgtime
!= get_tmr10ms()) {
939 #if defined(EXTERNAL_ANTENNA) && defined(INTERNAL_MODULE_PXX1)
940 checkExternalAntenna();
943 START_SILENCE_PERIOD();
948 #if defined(EEPROM_RLC)
949 void checkLowEEPROM()
951 if (g_eeGeneral
.disableMemoryWarning
) return;
952 if (EeFsGetFree() < 100) {
953 ALERT(STR_STORAGE_WARNING
, STR_EEPROMLOWMEM
, AU_ERROR
);
958 bool isThrottleWarningAlertNeeded()
960 if (g_model
.disableThrottleWarning
) {
964 // throttle channel is either the stick according stick mode (already handled in evalInputs)
966 // in case an output channel is choosen as throttle source (thrTraceSrc>NUM_POTS+NUM_SLIDERS) we assume the throttle stick is the input
967 // no other information available at the moment, and good enough to my option (otherwise too much exceptions...)
968 uint8_t thrchn
= ((g_model
.thrTraceSrc
==0) || (g_model
.thrTraceSrc
>NUM_POTS
+NUM_SLIDERS
)) ? THR_STICK
: g_model
.thrTraceSrc
+NUM_STICKS
-1;
970 GET_ADC_IF_MIXER_NOT_RUNNING();
971 evalInputs(e_perout_mode_notrainer
); // let do evalInputs do the job
973 int16_t v
= calibratedAnalogs
[thrchn
];
974 if (g_model
.thrTraceSrc
&& g_model
.throttleReversed
) { // TODO : proper review of THR source definition and handling
977 return v
> THRCHK_DEADBAND
- 1024;
980 void checkThrottleStick()
982 if (!isThrottleWarningAlertNeeded()) {
986 // first - display warning; also deletes inputs if any have been before
988 RAISE_ALERT(STR_THROTTLEWARN
, STR_THROTTLENOTIDLE
, STR_PRESSANYKEYTOSKIP
, AU_THROTTLE_ALERT
);
990 #if defined(PWR_BUTTON_PRESS)
991 bool refresh
= false;
994 while (!getEvent()) {
995 if (!isThrottleWarningAlertNeeded()) {
999 #if defined(PWR_BUTTON_PRESS)
1000 uint32_t power
= pwrCheck();
1001 if (power
== e_power_off
) {
1006 else if (power
== e_power_press
) {
1009 else if (power
== e_power_on
&& refresh
) {
1010 RAISE_ALERT(STR_THROTTLEWARN
, STR_THROTTLENOTIDLE
, STR_PRESSANYKEYTOSKIP
, AU_NONE
);
1014 if (pwrCheck() == e_power_off
) {
1019 doLoopCommonActions();
1029 void checkAlarm() // added by Gohst
1031 if (g_eeGeneral
.disableAlarmWarning
) {
1035 if (IS_SOUND_OFF()) {
1036 ALERT(STR_ALARMSWARN
, STR_ALARMSDISABLED
, AU_ERROR
);
1040 void alert(const char * title
, const char * msg
, uint8_t sound
)
1044 TRACE("ALERT %s: %s", title
, msg
);
1046 RAISE_ALERT(title
, msg
, STR_PRESSANYKEY
, sound
);
1048 #if defined(PWR_BUTTON_PRESS)
1049 bool refresh
= false;
1055 if (keyDown()) // wait for key release
1058 doLoopCommonActions();
1062 const uint32_t pwr_check
= pwrCheck();
1063 if (pwr_check
== e_power_off
) {
1066 return; // only happens in SIMU, required for proper shutdown
1068 #if defined(PWR_BUTTON_PRESS)
1069 else if (pwr_check
== e_power_press
) {
1072 else if (pwr_check
== e_power_on
&& refresh
) {
1073 RAISE_ALERT(title
, msg
, STR_PRESSANYKEY
, AU_NONE
);
1084 int8_t trimGvar
[NUM_TRIMS
] = { -1, -1, -1, -1, -1, -1 };
1085 #elif NUM_TRIMS == 4
1086 int8_t trimGvar
[NUM_TRIMS
] = { -1, -1, -1, -1 };
1087 #elif NUM_TRIMS == 2
1088 int8_t trimGvar
[NUM_TRIMS
] = { -1, -1 };
1094 event_t event
= getEvent(true);
1095 if (event
&& !IS_KEY_BREAK(event
)) {
1096 int8_t k
= EVT_KEY_MASK(event
) - TRM_BASE
;
1097 // LH_DWN LH_UP LV_DWN LV_UP RV_DWN RV_UP RH_DWN RH_UP
1098 uint8_t idx
= CONVERT_MODE_TRIMS((uint8_t)k
/2);
1103 trimsDisplayTimer
= 200; // 2 seconds
1104 trimsDisplayMask
|= (1<<idx
);
1107 if (TRIM_REUSED(idx
)) {
1108 phase
= getGVarFlightMode(mixerCurrentFlightMode
, trimGvar
[idx
]);
1109 before
= GVAR_VALUE(trimGvar
[idx
], phase
);
1113 phase
= getTrimFlightMode(mixerCurrentFlightMode
, idx
);
1114 before
= getTrimValue(phase
, idx
);
1115 thro
= (idx
==THR_STICK
&& g_model
.thrTrim
);
1118 phase
= getTrimFlightMode(mixerCurrentFlightMode
, idx
);
1119 before
= getTrimValue(phase
, idx
);
1120 thro
= (idx
==THR_STICK
&& g_model
.thrTrim
);
1122 int8_t trimInc
= g_model
.trimInc
+ 1;
1123 int8_t v
= (trimInc
==-1) ? min(32, abs(before
)/4+1) : (1 << trimInc
); // TODO flash saving if (trimInc < 0)
1124 if (thro
) v
= 4; // if throttle trim and trim trottle then step=4
1126 if (TRIM_REUSED(idx
)) v
= 1;
1128 int16_t after
= (k
&1) ? before
+ v
: before
- v
; // positive = k&1
1129 bool beepTrim
= false;
1131 if (!thro
&& before
!=0 && ((!(after
< 0) == (before
< 0)) || after
==0)) { //forcing a stop at centerered trim when changing sides
1134 AUDIO_TRIM_MIDDLE();
1139 if (TRIM_REUSED(idx
)) {
1140 int8_t gvar
= trimGvar
[idx
];
1141 int16_t vmin
= GVAR_MIN
+ g_model
.gvars
[gvar
].min
;
1142 int16_t vmax
= GVAR_MAX
- g_model
.gvars
[gvar
].max
;
1149 else if (after
> vmax
) {
1156 SET_GVAR_VALUE(gvar
, phase
, after
);
1161 if (before
>TRIM_MIN
&& after
<=TRIM_MIN
) {
1166 else if (before
<TRIM_MAX
&& after
>=TRIM_MAX
) {
1172 if ((before
<after
&& after
>TRIM_MAX
) || (before
>after
&& after
<TRIM_MIN
)) {
1173 if (!g_model
.extendedTrims
) after
= before
;
1176 if (after
< TRIM_EXTENDED_MIN
) {
1177 after
= TRIM_EXTENDED_MIN
;
1179 else if (after
> TRIM_EXTENDED_MAX
) {
1180 after
= TRIM_EXTENDED_MAX
;
1183 if (!setTrimValue(phase
, idx
, after
)) {
1184 // we don't play a beep, so we exit now the function
1190 AUDIO_TRIM_PRESS(after
);
1197 uint16_t s_anaFilt
[NUM_ANALOGS
];
1200 #if defined(JITTER_MEASURE)
1201 JitterMeter
<uint16_t> rawJitter
[NUM_ANALOGS
];
1202 JitterMeter
<uint16_t> avgJitter
[NUM_ANALOGS
];
1203 tmr10ms_t jitterResetTime
= 0;
1206 #define JITTER_FILTER_STRENGTH 4 // tune this value, bigger value - more filtering (range: 1-5) (see explanation below)
1207 #define ANALOG_SCALE 1 // tune this value, bigger value - more filtering (range: 0-1) (see explanation below)
1209 #define JITTER_ALPHA (1<<JITTER_FILTER_STRENGTH)
1210 #define ANALOG_MULTIPLIER (1<<ANALOG_SCALE)
1211 #define ANA_FILT(chan) (s_anaFilt[chan] / (JITTER_ALPHA * ANALOG_MULTIPLIER))
1212 #if (JITTER_ALPHA * ANALOG_MULTIPLIER > 32)
1213 #error "JITTER_FILTER_STRENGTH and ANALOG_SCALE are too big, their summ should be <= 5 !!!"
1217 uint16_t anaIn(uint8_t chan
)
1219 return ANA_FILT(chan
);
1224 #if defined(JITTER_MEASURE)
1225 if (JITTER_MEASURE_ACTIVE() && jitterResetTime
< get_tmr10ms()) {
1226 // reset jitter measurement every second
1227 for (uint32_t x
=0; x
<NUM_ANALOGS
; x
++) {
1228 rawJitter
[x
].reset();
1229 avgJitter
[x
].reset();
1231 jitterResetTime
= get_tmr10ms() + 100; //every second
1235 DEBUG_TIMER_START(debugTimerAdcRead
);
1237 DEBUG_TIMER_STOP(debugTimerAdcRead
);
1239 for (uint8_t x
=0; x
<NUM_ANALOGS
; x
++) {
1240 uint16_t v
= getAnalogValue(x
) >> (1 - ANALOG_SCALE
);
1243 // * pass trough any big change directly
1244 // * for small change use Modified moving average (MMA) filter
1248 // Normal MMA filter has this formula:
1249 // <out> = ((ALPHA-1)*<out> + <in>)/ALPHA
1251 // If calculation is done this way with integer arithmetics, then any small change in
1252 // input signal is lost. One way to combat that, is to rearrange the formula somewhat,
1253 // to store a more precise (larger) number between iterations. The basic idea is to
1254 // store undivided value between iterations. Therefore an new variable <filtered> is
1255 // used. The new formula becomes:
1256 // <filtered> = <filtered> - <filtered>/ALPHA + <in>
1257 // <out> = <filtered>/ALPHA (use only when out is needed)
1259 // The above formula with a maximum allowed ALPHA value (we are limited by
1260 // the 16 bit s_anaFilt[]) was tested on the radio. The resulting signal still had
1261 // some jitter (a value of 1 was observed). The jitter might be bigger on other
1264 // So another idea is to use larger input values for filtering. So instead of using
1265 // input in a range from 0 to 2047, we use twice larger number (temp[x] is divided less)
1267 // This also means that ALPHA must be lowered (remember 16 bit limit), but test results
1268 // have proved that this kind of filtering gives better results. So the recommended values
1270 // JITTER_FILTER_STRENGTH 4
1273 // Variables mapping:
1275 // * <out> = s_anaFilt[x]
1276 uint16_t previous
= s_anaFilt
[x
] / JITTER_ALPHA
;
1277 uint16_t diff
= (v
> previous
) ? (v
- previous
) : (previous
- v
);
1278 if (!g_eeGeneral
.jitterFilter
&& diff
< (10*ANALOG_MULTIPLIER
)) { // g_eeGeneral.jitterFilter is inverted, 0 - active
1279 // apply jitter filter
1280 s_anaFilt
[x
] = (s_anaFilt
[x
] - previous
) + v
;
1283 // use unfiltered value
1284 s_anaFilt
[x
] = v
* JITTER_ALPHA
;
1287 #if defined(JITTER_MEASURE)
1288 if (JITTER_MEASURE_ACTIVE()) {
1289 avgJitter
[x
].measure(ANA_FILT(x
));
1293 #define ANAFILT_MAX (2 * RESX * JITTER_ALPHA * ANALOG_MULTIPLIER - 1)
1294 StepsCalibData
* calib
= (StepsCalibData
*) &g_eeGeneral
.calib
[x
];
1295 if (IS_POT_MULTIPOS(x
) && IS_MULTIPOS_CALIBRATED(calib
)) {
1296 // TODO: consider adding another low pass filter to eliminate multipos switching glitches
1297 uint8_t vShifted
= ANA_FILT(x
) >> 4;
1298 s_anaFilt
[x
] = ANAFILT_MAX
;
1299 for (uint32_t i
=0; i
<calib
->count
; i
++) {
1300 if (vShifted
< calib
->steps
[i
]) {
1301 s_anaFilt
[x
] = (i
* ANAFILT_MAX
) / calib
->count
;
1311 uint8_t g_vbat100mV
= 0;
1312 uint16_t lightOffCounter
;
1313 uint8_t flashCounter
= 0;
1315 uint16_t sessionTimer
;
1316 uint16_t s_timeCumThr
; // THR in 1/16 sec
1317 uint16_t s_timeCum16ThrP
; // THR% in 1/16 sec
1319 uint8_t trimsCheckTimer
= 0;
1321 uint8_t trimsDisplayTimer
= 0;
1322 uint8_t trimsDisplayMask
= 0;
1324 void flightReset(uint8_t check
)
1326 // 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)
1327 // TODO check if the vario / background music are stopped correctly if switching to a model which doesn't have these functions enabled
1329 if (!IS_MANUAL_RESET_TIMER(0)) {
1334 if (!IS_MANUAL_RESET_TIMER(1)) {
1340 if (!IS_MANUAL_RESET_TIMER(2)) {
1347 s_mixer_first_run_done
= false;
1349 START_SILENCE_PERIOD();
1353 logicalSwitchesReset();
1360 #if defined(THRTRACE)
1361 uint8_t s_traceBuf
[MAXTRACE
];
1364 uint16_t s_cnt_samples_thr_10s
;
1365 uint16_t s_sum_samples_thr_10s
;
1370 uint8_t phase
= mixerCurrentFlightMode
;
1371 for (uint8_t i
=0; i
<NUM_TRIMS
; i
++) {
1372 // do trim -> throttle trim if applicable
1373 int16_t trim
= getTrimValue(phase
, i
);
1374 if (trimsCheckTimer
> 0) {
1382 uint8_t s_mixer_first_run_done
= false;
1384 void doMixerCalculations()
1386 static tmr10ms_t lastTMR
= 0;
1388 tmr10ms_t tmr10ms
= get_tmr10ms();
1390 #if defined(DEBUG_LATENCY_MIXER_RF) || defined(DEBUG_LATENCY_RF_ONLY)
1391 static tmr10ms_t lastLatencyToggle
= 0;
1392 if (tmr10ms
- lastLatencyToggle
>= 10) {
1393 lastLatencyToggle
= tmr10ms
;
1394 toggleLatencySwitch();
1398 uint8_t tick10ms
= (tmr10ms
>= lastTMR
? tmr10ms
- lastTMR
: 1);
1399 // handle tick10ms overrun
1400 // correct overflow handling costs a lot of code; happens only each 11 min;
1401 // therefore forget the exact calculation and use only 1 instead; good compromise
1404 DEBUG_TIMER_START(debugTimerGetAdc
);
1406 DEBUG_TIMER_STOP(debugTimerGetAdc
);
1408 DEBUG_TIMER_START(debugTimerGetSwitches
);
1409 getSwitchesPosition(!s_mixer_first_run_done
);
1410 DEBUG_TIMER_STOP(debugTimerGetSwitches
);
1412 #if defined(PCBSKY9X) && !defined(SIMU)
1413 Current_analogue
= (Current_analogue
*31 + s_anaFilt
[8] ) >> 5 ;
1414 if (Current_analogue
> Current_max
)
1415 Current_max
= Current_analogue
;
1419 DEBUG_TIMER_START(debugTimerEvalMixes
);
1420 evalMixes(tick10ms
);
1421 DEBUG_TIMER_STOP(debugTimerEvalMixes
);
1423 DEBUG_TIMER_START(debugTimerMixes10ms
);
1425 /* Throttle trace */
1428 if (g_model
.thrTraceSrc
> NUM_POTS
+NUM_SLIDERS
) {
1429 uint8_t ch
= g_model
.thrTraceSrc
-NUM_POTS
-NUM_SLIDERS
-1;
1430 val
= channelOutputs
[ch
];
1432 LimitData
* lim
= limitAddress(ch
);
1433 int16_t gModelMax
= LIMIT_MAX_RESX(lim
);
1434 int16_t gModelMin
= LIMIT_MIN_RESX(lim
);
1437 val
= -val
+ gModelMax
;
1439 val
= val
- gModelMin
;
1441 #if defined(PPM_LIMITS_SYMETRICAL)
1442 if (lim
->symetrical
) {
1443 val
-= calc1000toRESX(lim
->offset
);
1447 gModelMax
-= gModelMin
; // we compare difference between Max and Mix for recaling needed; Max and Min are shifted to 0 by default
1448 // usually max is 1024 min is -1024 --> max-min = 2048 full range
1450 if (gModelMax
!= 0 && gModelMax
!= 2048)
1451 val
= (int32_t) (val
<< 11) / (gModelMax
); // rescaling only needed if Min, Max differs
1454 val
=0; // prevent val be negative, which would corrupt throttle trace and timers; could occur if safetyswitch is smaller than limits
1457 val
= RESX
+ calibratedAnalogs
[g_model
.thrTraceSrc
== 0 ? THR_STICK
: g_model
.thrTraceSrc
+NUM_STICKS
-1];
1460 val
>>= (RESX_SHIFT
-6); // calibrate it (resolution increased by factor 4)
1462 evalTimers(val
, tick10ms
);
1464 static uint8_t s_cnt_100ms
;
1465 static uint8_t s_cnt_1s
;
1466 static uint8_t s_cnt_samples_thr_1s
;
1467 static uint16_t s_sum_samples_thr_1s
;
1469 s_cnt_samples_thr_1s
++;
1470 s_sum_samples_thr_1s
+=val
;
1472 if ((s_cnt_100ms
+= tick10ms
) >= 10) { // 0.1sec
1476 logicalSwitchesTimerTick();
1477 checkTrainerSignalWarning();
1479 if (s_cnt_1s
>= 10) { // 1sec
1482 inactivity
.counter
++;
1483 if ((((uint8_t)inactivity
.counter
) & 0x07) == 0x01 && g_eeGeneral
.inactivityTimer
&& inactivity
.counter
> ((uint16_t)g_eeGeneral
.inactivityTimer
* 60))
1487 if (mixWarning
& 1) if ((sessionTimer
&0x03)==0) AUDIO_MIX_WARNING(1);
1488 if (mixWarning
& 2) if ((sessionTimer
&0x03)==1) AUDIO_MIX_WARNING(2);
1489 if (mixWarning
& 4) if ((sessionTimer
&0x03)==2) AUDIO_MIX_WARNING(3);
1492 val
= s_sum_samples_thr_1s
/ s_cnt_samples_thr_1s
;
1493 s_timeCum16ThrP
+= (val
>>3); // s_timeCum16ThrP would overrun if we would store throttle value with higher accuracy; therefore stay with 16 steps
1496 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
1498 #if defined(THRTRACE)
1499 // throttle trace is done every 10 seconds; Tracebuffer is adjusted to screen size.
1500 // in case buffer runs out, it wraps around
1501 // resolution for y axis is only 32, therefore no higher value makes sense
1502 s_cnt_samples_thr_10s
+= s_cnt_samples_thr_1s
;
1503 s_sum_samples_thr_10s
+= s_sum_samples_thr_1s
;
1505 if (++s_cnt_10s
>= 10) { // 10s
1507 val
= s_sum_samples_thr_10s
/ s_cnt_samples_thr_10s
;
1508 s_sum_samples_thr_10s
= 0;
1509 s_cnt_samples_thr_10s
= 0;
1510 s_traceBuf
[s_traceWr
% MAXTRACE
] = val
;
1515 s_cnt_samples_thr_1s
= 0;
1516 s_sum_samples_thr_1s
= 0;
1520 #if defined(PXX) || defined(DSM2)
1521 static uint8_t countRangecheck
= 0;
1522 for (uint8_t i
=0; i
<NUM_MODULES
; ++i
) {
1523 #if defined(MULTIMODULE)
1524 if (moduleState
[i
].mode
>= MODULE_MODE_BEEP_FIRST
|| getMultiModuleStatus(i
).isBinding()) {
1526 if (moduleState
[i
].mode
>= MODULE_MODE_BEEP_FIRST
) {
1528 if (++countRangecheck
>= 250) {
1529 countRangecheck
= 0;
1530 AUDIO_PLAY(AU_SPECIAL_SOUND_CHEEP
);
1538 DEBUG_TIMER_STOP(debugTimerMixes10ms
);
1540 s_mixer_first_run_done
= true;
1543 #if !defined(OPENTX_START_DEFAULT_ARGS)
1544 #define OPENTX_START_DEFAULT_ARGS 0
1547 void opentxStart(const uint8_t startOptions
= OPENTX_START_DEFAULT_ARGS
)
1549 TRACE("opentxStart(%u)", startOptions
);
1551 uint8_t calibration_needed
= !(startOptions
& OPENTX_START_NO_CALIBRATION
) && (g_eeGeneral
.chkSum
!= evalChkSum());
1554 if (!calibration_needed
&& !(startOptions
& OPENTX_START_NO_SPLASH
)) {
1559 #if defined(DEBUG_TRACE_BUFFER)
1560 trace_event(trace_start
, 0x12345678);
1563 #if defined(PCBSKY9X) && defined(SDCARD) && !defined(SIMU)
1564 for (int i
=0; i
<500 && !Card_initialized
; i
++) {
1565 RTOS_WAIT_MS(2); // 2ms
1569 #if defined(NIGHTLY_BUILD_WARNING)
1570 ALERT(STR_NIGHTLY_WARNING
, TR_NIGHTLY_NOTSAFE
, AU_ERROR
);
1574 if (calibration_needed
) {
1575 chainMenu(menuFirstCalib
);
1577 else if (!(startOptions
& OPENTX_START_NO_CHECKS
)) {
1585 void opentxClose(uint8_t shutdown
)
1587 TRACE("opentxClose");
1590 watchdogSuspend(2000/*20s*/);
1591 pausePulses(); // stop mixer task to disable trims processing while in shutdown
1593 // TODO needed? telemetryEnd();
1595 luaClose(&lsScripts
);
1596 #if defined(COLORLCD)
1597 luaClose(&lsWidgets
);
1609 storageFlushCurrentModel();
1611 if (sessionTimer
> 0) {
1612 g_eeGeneral
.globalTimer
+= sessionTimer
;
1616 #if defined(PCBSKY9X)
1617 uint32_t mAhUsed
= g_eeGeneral
.mAhUsed
+ Current_used
* (488 + g_eeGeneral
.txCurrentCalibration
) / 8192 / 36;
1618 if (g_eeGeneral
.mAhUsed
!= mAhUsed
) {
1619 g_eeGeneral
.mAhUsed
= mAhUsed
;
1623 g_eeGeneral
.unexpectedShutdown
= 0;
1624 storageDirty(EE_GENERAL
);
1627 while (IS_PLAYING(ID_PLAY_PROMPT_BASE
+ AU_BYE
)) {
1641 TRACE("opentxResume");
1643 menuHandlers
[0] = menuMainView
;
1648 #if defined(COLORLCD)
1653 // removed to avoid the double warnings (throttle, switch, etc.)
1654 // opentxStart(OPENTX_START_NO_SPLASH | OPENTX_START_NO_CALIBRATION | OPENTX_START_NO_CHECKS);
1656 referenceSystemAudioFiles();
1658 if (!g_eeGeneral
.unexpectedShutdown
) {
1659 g_eeGeneral
.unexpectedShutdown
= 1;
1660 storageDirty(EE_GENERAL
);
1665 #define INSTANT_TRIM_MARGIN 10 /* around 1% */
1669 int16_t anas_0
[MAX_INPUTS
];
1670 evalInputs(e_perout_mode_notrainer
| e_perout_mode_nosticks
);
1671 memcpy(anas_0
, anas
, sizeof(anas_0
));
1673 evalInputs(e_perout_mode_notrainer
);
1675 for (uint8_t stick
=0; stick
<NUM_STICKS
; stick
++) {
1676 if (stick
!=THR_STICK
) {
1677 // don't instant trim the throttle stick
1678 uint8_t trim_phase
= getTrimFlightMode(mixerCurrentFlightMode
, stick
);
1680 for (int e
=0; e
<MAX_EXPOS
; e
++) {
1681 ExpoData
* ed
= expoAddress(e
);
1682 if (!EXPO_VALID(ed
)) break; // end of list
1683 if (ed
->srcRaw
-MIXSRC_Rud
== stick
) {
1684 delta
= anas
[ed
->chn
] - anas_0
[ed
->chn
];
1688 if (abs(delta
) >= INSTANT_TRIM_MARGIN
) {
1689 int16_t trim
= limit
<int16_t>(TRIM_EXTENDED_MIN
, (delta
+ trims
[stick
]) / 2, TRIM_EXTENDED_MAX
);
1690 setTrimValue(trim_phase
, stick
, trim
);
1695 storageDirty(EE_MODEL
);
1699 void copySticksToOffset(uint8_t ch
)
1701 pauseMixerCalculations();
1702 int32_t zero
= (int32_t)channelOutputs
[ch
];
1704 evalFlightModeMixes(e_perout_mode_nosticks
+e_perout_mode_notrainer
, 0);
1705 int32_t val
= chans
[ch
];
1706 LimitData
*ld
= limitAddress(ch
);
1707 limit_min_max_t lim
= LIMIT_MIN(ld
);
1710 lim
= LIMIT_MIN(ld
);
1712 zero
= (zero
*256000 - val
*lim
) / (1024*256-val
);
1713 ld
->offset
= (ld
->revert
? -zero
: zero
);
1714 resumeMixerCalculations();
1715 storageDirty(EE_MODEL
);
1718 void copyTrimsToOffset(uint8_t ch
)
1722 pauseMixerCalculations();
1724 evalFlightModeMixes(e_perout_mode_noinput
, 0); // do output loop - zero input sticks and trims
1725 zero
= applyLimits(ch
, chans
[ch
]);
1727 evalFlightModeMixes(e_perout_mode_noinput
-e_perout_mode_notrims
, 0); // do output loop - only trims
1729 int16_t output
= applyLimits(ch
, chans
[ch
]) - zero
;
1730 int16_t v
= g_model
.limitData
[ch
].offset
;
1731 if (g_model
.limitData
[ch
].revert
) output
= -output
;
1732 v
+= (output
* 125) / 128;
1733 g_model
.limitData
[ch
].offset
= limit((int16_t)-1000, (int16_t)v
, (int16_t)1000); // make sure the offset doesn't go haywire
1735 resumeMixerCalculations();
1736 storageDirty(EE_MODEL
);
1740 #if defined(STARTUP_ANIMATION)
1742 inline uint32_t PWR_PRESS_DURATION_MIN()
1744 if (g_eeGeneral
.version
!= EEPROM_VER
)
1747 return (2 - g_eeGeneral
.pwrOnSpeed
) * 100;
1750 constexpr uint32_t PWR_PRESS_DURATION_MAX
= 500; // 5s
1752 void runStartupAnimation()
1754 tmr10ms_t start
= get_tmr10ms();
1755 tmr10ms_t duration
= 0;
1756 bool isPowerOn
= false;
1758 while (pwrPressed()) {
1759 duration
= get_tmr10ms() - start
;
1760 if (duration
< PWR_PRESS_DURATION_MIN()) {
1761 drawStartupAnimation(duration
, PWR_PRESS_DURATION_MIN());
1763 else if (duration
>= PWR_PRESS_DURATION_MAX
) {
1767 else if (!isPowerOn
) {
1770 haptic
.play(15, 3, PLAY_NOW
);
1774 if (duration
< PWR_PRESS_DURATION_MIN() || duration
>= PWR_PRESS_DURATION_MAX
) {
1780 void moveTrimsToOffsets() // copy state of 3 primary to subtrim
1782 int16_t zeros
[MAX_OUTPUT_CHANNELS
];
1784 pauseMixerCalculations();
1786 evalFlightModeMixes(e_perout_mode_noinput
, 0); // do output loop - zero input sticks and trims
1787 for (uint8_t i
=0; i
<MAX_OUTPUT_CHANNELS
; i
++) {
1788 zeros
[i
] = applyLimits(i
, chans
[i
]);
1791 evalFlightModeMixes(e_perout_mode_noinput
-e_perout_mode_notrims
, 0); // do output loop - only trims
1793 for (uint8_t i
=0; i
<MAX_OUTPUT_CHANNELS
; i
++) {
1794 int16_t output
= applyLimits(i
, chans
[i
]) - zeros
[i
];
1795 int16_t v
= g_model
.limitData
[i
].offset
;
1796 if (g_model
.limitData
[i
].revert
) output
= -output
;
1797 v
+= (output
* 125) / 128;
1798 g_model
.limitData
[i
].offset
= limit((int16_t)-1000, (int16_t)v
, (int16_t)1000); // make sure the offset doesn't go haywire
1801 // reset all trims, except throttle (if throttle trim)
1802 for (uint8_t i
=0; i
<NUM_TRIMS
; i
++) {
1803 if (i
!= THR_STICK
|| !g_model
.thrTrim
) {
1804 int16_t original_trim
= getTrimValue(mixerCurrentFlightMode
, i
);
1805 for (uint8_t fm
=0; fm
<MAX_FLIGHT_MODES
; fm
++) {
1806 trim_t trim
= getRawTrimValue(fm
, i
);
1807 if (trim
.mode
/ 2 == fm
)
1808 setTrimValue(fm
, i
, trim
.value
- original_trim
);
1813 resumeMixerCalculations();
1815 storageDirty(EE_MODEL
);
1819 #if defined(ROTARY_ENCODER_NAVIGATION)
1820 volatile rotenc_t rotencValue
= 0;
1821 uint8_t rotencSpeed
;
1826 TRACE("opentxInit");
1829 menuHandlers
[0] = menuMainView
;
1830 #if MENUS_LOCK != 2/*no menus*/
1831 menuHandlers
[1] = menuModelSelect
;
1836 bool radioSettingsValid
= storageReadRadioSettings(false);
1839 BACKLIGHT_ENABLE(); // we start the backlight during the startup animation
1841 #if defined(STARTUP_ANIMATION)
1842 if (WAS_RESET_BY_WATCHDOG_OR_SOFTWARE()) {
1846 runStartupAnimation();
1848 #else // defined(PWR_BUTTON_PRESS)
1852 // Radios handle UNEXPECTED_SHUTDOWN() differently:
1853 // * radios with WDT and EEPROM and CPU controlled power use Reset status register
1854 // and eeGeneral.unexpectedShutdown
1855 // * radios with SDCARD model storage use Reset status register and special
1856 // variables in RAM. They can not use eeGeneral.unexpectedShutdown
1857 // * radios without CPU controlled power can only use Reset status register (if available)
1858 if (UNEXPECTED_SHUTDOWN()) {
1859 TRACE("Unexpected Shutdown detected");
1860 globalData
.unexpectedShutdown
= 1;
1863 #if defined(RTC_BACKUP_RAM)
1864 SET_POWER_REASON(0);
1868 // SDCARD related stuff, only done if not unexpectedShutdown
1869 if (!globalData
.unexpectedShutdown
) {
1872 #if defined(AUTOUPDATE)
1873 sportStopSendByteLoop();
1874 if (f_stat(AUTOUPDATE_FILENAME
, nullptr) == FR_OK
) {
1875 FrSkyFirmwareInformation information
;
1876 if (readFrSkyFirmwareInformation(AUTOUPDATE_FILENAME
, information
) == nullptr) {
1877 #if defined(BLUETOOTH)
1878 if (information
.productFamily
== FIRMWARE_FAMILY_BLUETOOTH_CHIP
) {
1879 if (bluetooth
.flashFirmware(AUTOUPDATE_FILENAME
) == nullptr)
1880 f_unlink(AUTOUPDATE_FILENAME
);
1883 #if defined(HARDWARE_POWER_MANAGEMENT_UNIT)
1884 if (information
.productFamily
== FIRMWARE_FAMILY_POWER_MANAGEMENT_UNIT
) {
1885 FrskyChipFirmwareUpdate device
;
1886 if (device
.flashFirmware(AUTOUPDATE_FILENAME
, false) == nullptr)
1887 f_unlink(AUTOUPDATE_FILENAME
);
1899 if (!radioSettingsValid
)
1900 storageReadRadioSettings();
1901 storageReadCurrentModel();
1904 #if defined(COLORLCD)
1905 if (!globalData
.unexpectedShutdown
) {
1906 // g_model.topbarData is still zero here (because it was not yet read from SDCARD),
1907 // but we only remember the pointer to in in constructor.
1908 // The storageReadAll() needs topbar object, so it must be created here
1909 topbar
= new Topbar(&g_model
.topbarData
);
1911 // lua widget state must also be prepared before the call to storageReadAll()
1912 LUA_INIT_THEMES_AND_WIDGETS();
1916 // handling of storage for radios that have no EEPROM
1917 #if !defined(EEPROM)
1918 #if defined(RTC_BACKUP_RAM) && !defined(SIMU)
1919 if (globalData
.unexpectedShutdown
) {
1920 // SDCARD not available, try to restore last model from RAM
1921 TRACE("rambackupRestore");
1930 #endif // #if !defined(EEPROM)
1932 #if defined(AUX_SERIAL)
1933 auxSerialInit(g_eeGeneral
.auxSerialMode
, modelTelemetryProtocol());
1938 if (TRIMS_PRESSED() && g_eeGeneral
.switchUnlockStates
==switches_states
) {
1943 currentSpeakerVolume
= requiredSpeakerVolume
= g_eeGeneral
.speakerVolume
+ VOLUME_LEVEL_DEF
;
1944 #if !defined(SOFTWARE_VOLUME)
1945 setScaledVolume(currentSpeakerVolume
);
1948 referenceSystemAudioFiles();
1952 #if defined(PCBSKY9X)
1953 // Set ADC gains here
1954 setSticksGain(g_eeGeneral
.sticksGain
);
1957 #if defined(PCBSKY9X) && defined(BLUETOOTH)
1961 #if defined(COLORLCD)
1966 if (g_eeGeneral
.backlightMode
!= e_backlight_mode_off
) {
1967 // on Tx start turn the light on
1971 if (!globalData
.unexpectedShutdown
) {
1975 #if !defined(RTC_BACKUP_RAM)
1976 if (!g_eeGeneral
.unexpectedShutdown
) {
1977 g_eeGeneral
.unexpectedShutdown
= 1;
1978 storageDirty(EE_GENERAL
);
1990 WDG_ENABLE(WDG_DURATION
);
2000 TRACE("reusableBuffer: modelSel=%d, moduleSetup=%d, calib=%d, sdManager=%d, hardwareAndSettings=%d, spectrumAnalyser=%d, usb=%d",
2001 sizeof(reusableBuffer
.modelsel
),
2002 sizeof(reusableBuffer
.moduleSetup
),
2003 sizeof(reusableBuffer
.calib
),
2004 sizeof(reusableBuffer
.sdManager
),
2005 sizeof(reusableBuffer
.hardwareAndSettings
),
2006 sizeof(reusableBuffer
.spectrumAnalyser
),
2007 sizeof(reusableBuffer
.MSC_BOT_Data
));
2010 // G: The WDT remains active after a WDT reset -- at maximum clock speed. So it's
2011 // important to disable it before commencing with system initialisation (or
2012 // we could put a bunch more WDG_RESET()s in. But I don't like that approach
2014 #if defined(PCBTARANIS)
2015 g_eeGeneral
.contrast
= LCD_CONTRAST_DEFAULT
;
2020 #if defined(COLORLCD)
2028 #if defined(SPLASH) && !defined(STARTUP_ANIMATION)
2029 if (!UNEXPECTED_SHUTDOWN()) {
2034 #if defined(PCBHORUS)
2035 if (!IS_FIRMWARE_COMPATIBLE_WITH_BOARD()) {
2036 runFatalErrorScreen(STR_WRONG_PCBREV
);
2040 #if !defined(EEPROM)
2041 if (!SD_CARD_PRESENT() && !UNEXPECTED_SHUTDOWN()) {
2042 runFatalErrorScreen(STR_NO_SDCARD
);
2050 #if defined(PWR_BUTTON_PRESS)
2052 inline uint32_t PWR_PRESS_SHUTDOWN_DELAY()
2054 return (2 - g_eeGeneral
.pwrOffSpeed
) * 100;
2057 uint32_t pwr_press_time
= 0;
2059 uint32_t pwrPressedDuration()
2061 if (pwr_press_time
== 0) {
2065 return get_tmr10ms() - pwr_press_time
;
2071 const char * message
= nullptr;
2073 enum PwrCheckState
{
2079 static uint8_t pwr_check_state
= PWR_CHECK_ON
;
2081 if (pwr_check_state
== PWR_CHECK_OFF
) {
2084 else if (pwrPressed()) {
2085 if (TELEMETRY_STREAMING()) {
2086 message
= STR_MODEL_STILL_POWERED
;
2088 if (pwr_check_state
== PWR_CHECK_PAUSED
) {
2091 else if (pwr_press_time
== 0) {
2092 pwr_press_time
= get_tmr10ms();
2093 if (message
&& !g_eeGeneral
.disableRssiPoweroffAlarm
) {
2094 audioEvent(AU_MODEL_STILL_POWERED
);
2098 inactivity
.counter
= 0;
2099 if (g_eeGeneral
.backlightMode
!= e_backlight_mode_off
) {
2102 if (get_tmr10ms() - pwr_press_time
> PWR_PRESS_SHUTDOWN_DELAY()) {
2103 #if defined(SHUTDOWN_CONFIRMATION)
2106 while ((TELEMETRY_STREAMING() && !g_eeGeneral
.disableRssiPoweroffAlarm
)) {
2111 POPUP_CONFIRMATION(STR_MODEL_SHUTDOWN
, nullptr);
2112 SET_WARNING_INFO(STR_MODEL_STILL_POWERED
, sizeof(TR_MODEL_STILL_POWERED
), 0);
2113 event_t evt
= getEvent(false);
2114 DISPLAY_WARNING(evt
);
2117 if (warningResult
) {
2118 pwr_check_state
= PWR_CHECK_OFF
;
2121 else if (!warningText
) {
2122 // shutdown has been cancelled
2123 pwr_check_state
= PWR_CHECK_PAUSED
;
2127 haptic
.play(15, 3, PLAY_NOW
);
2128 pwr_check_state
= PWR_CHECK_OFF
;
2132 drawShutdownAnimation(pwrPressedDuration(), PWR_PRESS_SHUTDOWN_DELAY(), message
);
2133 return e_power_press
;
2138 pwr_check_state
= PWR_CHECK_ON
;
2147 #if defined(SOFT_PWR_CTRL)
2157 #if defined(TRAINER_PWR)
2158 if (TRAINER_CONNECTED()) {
2159 return e_power_trainer
;
2163 if (!g_eeGeneral
.disableRssiPoweroffAlarm
) {
2164 if (TELEMETRY_STREAMING()) {
2165 RAISE_ALERT(STR_MODEL
, STR_MODEL_STILL_POWERED
, STR_PRESS_ENTER_TO_CONFIRM
, AU_MODEL_STILL_POWERED
);
2166 while (TELEMETRY_STREAMING()) {
2167 resetForcePowerOffRequest();
2172 else if (readKeys() == (1 << KEY_ENTER
)) {
2181 #endif // defined(PWR_BUTTON_PRESS)
2182 #endif // !defined(SIMU)