/* * Copyright (C) 2021-2025 Roger Clark, VK3KYY / G4KYF * Daniel Caujolle-Bert, F1RMB * * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation, * incorporation in a commercial product or incorporation into a product or project which allows commercial use. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "main.h" #include "usb_device.h" #include "interfaces/batteryAndPowerManagement.h" #include "user_interface/uiGlobals.h" #include "user_interface/uiLocalisation.h" #include "user_interface/menuSystem.h" #include "user_interface/uiUtilities.h" #include "hardware/HR-C6000.h" #include "usb/usb_com.h" #include "functions/ticks.h" #include "interfaces/clockManager.h" #include "functions/codeplug.h" #include "hardware/radioHardwareInterface.h" #include "interfaces/gps.h" #include "interfaces/settingsStorage.h" //#define DEBUG_HARDWARE_SCREEN 1 static uint32_t lowBatteryCount = 0; #define LOW_BATTERY_INTERVAL ((1000 * 60) * 5) // 5 minute; #define LOW_BATTERY_WARNING_VOLTAGE_DIFFERENTIAL 6 // Offset between the minimum voltage and when the battery warning audio starts. 6 = 0.6V #define LOW_BATTERY_VOLTAGE_RECOVERY_TIME 30000 // 30 seconds #define SUSPEND_LOW_BATTERY_RATE 1000 // 1 second #define BATTERY_VOLTAGE_TICK_RELOAD 100 #define BATTERY_VOLTAGE_CALLBACK_TICK_RELOAD 20 static int batteryVoltageCallbackTick = 0; static int batteryVoltageTick = BATTERY_VOLTAGE_TICK_RELOAD; volatile float averageBatteryVoltage = 0; static volatile float previousAverageBatteryVoltage; volatile int batteryVoltage = 0; volatile uint16_t micLevel = 0; volatile uint16_t potLevel = 0; volatile uint16_t temperatureLevel = 0; volatile int lastValidBatteryVoltage = 64; volatile uint32_t resumeTicks = 0; #if !defined(PLATFORM_GD77S) ticksTimer_t apoTimer; #endif #if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) bool powerRotarySwitchIsOn(void) { return (batteryVoltage > POWEROFF_VOLTAGE_THRESHOLD); } #endif void showLowBattery(void) { showErrorMessage(currentLanguage->low_battery); } bool batteryIsLowWarning(void) { return (lowBatteryCount > LOW_BATTERY_VOLTAGE_RECOVERY_TIME); } bool batteryIsLowVoltageWarning(void) { return (powerRotarySwitchIsOn() ? (batteryVoltage < (CUTOFF_VOLTAGE_LOWER_HYST + LOW_BATTERY_WARNING_VOLTAGE_DIFFERENTIAL)) : false); } bool batteryIsLowCriticalVoltage(void) { return (powerRotarySwitchIsOn() ? (batteryVoltage < CUTOFF_VOLTAGE_LOWER_HYST) : false); } bool batteryLastReadingIsCritical(void) { if (powerRotarySwitchIsOn() == false) { return (lastValidBatteryVoltage < CUTOFF_VOLTAGE_UPPER_HYST); } lastValidBatteryVoltage = adcGetBatteryVoltage(); return (lastValidBatteryVoltage < CUTOFF_VOLTAGE_UPPER_HYST); } void batteryChecking(uiEvent_t *ev) { static ticksTimer_t lowBatteryBeepTimer = { 0, 0 }; static ticksTimer_t lowBatteryHeaderRedrawTimer = { 0, 0 }; static uint32_t lowBatteryCriticalCount = 0; bool lowBatteryWarning = batteryIsLowVoltageWarning(); bool batIsLow = false; if (powerRotarySwitchIsOn() && (batteryVoltage < 20)) { if (menuSystemGetCurrentMenuNumber() != UI_POWER_OFF) { menuSystemPushNewMenu(UI_POWER_OFF); } return; } // Low battery threshold is reached after 30 seconds, in total, of lowBatteryWarning. // Once reached, another 30 seconds is added to the counter to avoid retriggering on voltage fluctuations. lowBatteryCount += (lowBatteryWarning ? ((lowBatteryCount <= (LOW_BATTERY_VOLTAGE_RECOVERY_TIME * 2)) ? ((lowBatteryCount == LOW_BATTERY_VOLTAGE_RECOVERY_TIME) ? LOW_BATTERY_VOLTAGE_RECOVERY_TIME : 1) : 0) : (lowBatteryCount ? -1 : 0)); // Do we need to redraw the header row now ? batIsLow = batteryIsLowWarning(); if (batIsLow && ticksTimerHasExpired(&lowBatteryHeaderRedrawTimer)) { headerRowIsDirty = true; ticksTimerStart(&lowBatteryHeaderRedrawTimer, 500); } if ((settingsUsbMode != USB_MODE_HOTSPOT) && #if defined(PLATFORM_GD77S) (trxTransmissionEnabled == false) && #else (menuSystemGetCurrentMenuNumber() != UI_TX_SCREEN) && #endif batIsLow && ticksTimerHasExpired(&lowBatteryBeepTimer)) { if (melody_play == NULL) { if (nonVolatileSettings.audioPromptMode < AUDIO_PROMPT_MODE_VOICE_THRESHOLD) { soundSetMelody(MELODY_LOW_BATTERY); } else { voicePromptsInit(); voicePromptsAppendLanguageString(currentLanguage->low_battery); voicePromptsPlay(); } // Let the beep sound, or the VP, to finish to play before resuming the scan (otherwise is could be silent). if (uiDataGlobal.Scan.active) { uiDataGlobal.Scan.active = false; watchdogRun(false); while ((melody_play != NULL) || voicePromptsIsPlaying()) { voicePromptsTick(); soundTickMelody(); osDelay(1); } watchdogRun(true); uiDataGlobal.Scan.active = true; } ticksTimerStart(&lowBatteryBeepTimer, LOW_BATTERY_INTERVAL); } } // Check if the battery has reached critical voltage (power off) bool lowBatteryCritical = powerRotarySwitchIsOn() ? batteryIsLowCriticalVoltage() : false; // Critical battery threshold is reached after 30 seconds, in total, of lowBatteryCritical. lowBatteryCriticalCount += (lowBatteryCritical ? 1 : (lowBatteryCriticalCount ? -1 : 0)); // Low battery or poweroff (non RD-5R) bool powerSwitchIsOff = #if defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) (powerRotarySwitchIsOn() == false); #else false; #endif if ((powerSwitchIsOff || lowBatteryCritical) && (menuSystemGetCurrentMenuNumber() != UI_POWER_OFF)) { // is considered as flat (stable value), now stop the firmware: make it silent if ((lowBatteryCritical && (lowBatteryCriticalCount > LOW_BATTERY_VOLTAGE_RECOVERY_TIME)) || powerSwitchIsOff) { radioAudioAmp(false); soundSetMelody(NULL); } // Avoids delayed power off (on non RD-5R) if the power switch is turned off while in low battery condition if (lowBatteryCritical && (powerSwitchIsOff == false)) { // Now, the battery is considered as flat (stable value), powering off. if (lowBatteryCriticalCount > LOW_BATTERY_VOLTAGE_RECOVERY_TIME) { showLowBattery(); powerOffFinalStage(false, false); } } #if ! defined(PLATFORM_RD5R) else { bool suspend = false; #if !(defined(PLATFORM_GD77S) || defined(STM32F405xx)) suspend = settingsIsOptionBitSet(BIT_POWEROFF_SUSPEND); // Suspend bit is set, but user pressed the SK2, asking for a real poweroff if (suspend && BUTTONCHECK_DOWN(ev, BUTTON_SK2)) { suspend = false; } // Suspend bit is NOT set, but user pressed the SK2, asking for a suspend else if ((suspend == false) && BUTTONCHECK_DOWN(ev, BUTTON_SK2)) { suspend = true; } #endif if (suspend) { powerOffFinalStage(true, false); } else { menuSystemPushNewMenu(UI_POWER_OFF); } } #endif // ! PLATFORM_RD5R } } void batteryUpdate(void) { batteryVoltageTick++; if (batteryVoltageTick >= BATTERY_VOLTAGE_TICK_RELOAD) { if (powerRotarySwitchIsOn()) { if (previousAverageBatteryVoltage != averageBatteryVoltage) { previousAverageBatteryVoltage = averageBatteryVoltage; headerRowIsDirty = true; } batteryVoltageCallbackTick++; if (batteryVoltageCallbackTick >= BATTERY_VOLTAGE_CALLBACK_TICK_RELOAD) { menuRadioInfosPushBackVoltage(averageBatteryVoltage); batteryVoltageCallbackTick = 0; } } batteryVoltageTick = 0; } } void powerDown(bool doNotSavePowerOffState) { if (!doNotSavePowerOffState) { #if defined(PLATFORM_MD9600) uint16_t radioPowerState = RADIO_POWER_STATE_STANDBY_FLAG; batteryRAM_Write(0, (uint8_t *)&radioPowerState, 2U); #endif } settingsSaveSettings(true); codeplugSaveLastUsedChannelInZone(); #if defined(HAS_GPS) #if defined(LOG_GPS_DATA) gpsLoggingStop(); #endif gpsOff(); #if defined(PLATFORM_MD9600) gpsPower(false); #endif #endif // Give it a bit of time to finish to write the flash (avoiding corruptions). uint32_t m = ticksGetMillis(); while (true) { if ((ticksGetMillis() - m) > 300U) { break; } osDelay(1); } gpioSetDisplayBacklightIntensityPercentage(0); displaySetDisplayPowerMode(false); radioPowerOff(true, true); //Reset the display, saves about 1mA HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET); osDelay(20); MX_USB_DEVICE_DeInit(); // Deinit USB HAL_SuspendTick(); HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1); HAL_RTCEx_DeactivateWakeUpTimer(&hrtc); __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); __HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF); HAL_PWR_EnterSTANDBYMode(); HAL_GPIO_WritePin(PWR_SW_GPIO_Port, PWR_SW_Pin, GPIO_PIN_RESET); } void die(bool usbMonitoring, bool maintainRTC, bool forceSuspend, bool safeBoot) { int8_t batteryCriticalCount = 0; #if !defined(PLATFORM_RD5R) && !defined(PLATFORM_GD77S) uint32_t lowBatteryCriticalCount = 0; ticksTimer_t nextPITCounterRunTimer = { .start = ticksGetMillis(), .timeout = SUSPEND_LOW_BATTERY_RATE }; #if defined(DEBUG_HARDWARE_SCREEN) uint16_t y = 8; #else UNUSED_PARAMETER(safeBoot); #endif if (!maintainRTC) { HAL_GPIO_WritePin(PWR_SW_GPIO_Port, PWR_SW_Pin, GPIO_PIN_RESET); // This is normally already done before this function is called. // But do it again, just in case, as its important that the radio will turn off when the power control is turned to off } #endif audioAmpDisable(AUDIO_AMP_CHANNEL_RF); audioAmpDisable(AUDIO_AMP_CHANNEL_BEEP); audioAmpDisable(AUDIO_AMP_CHANNEL_PROMPT); LedWrite(LED_GREEN, 0); LedWrite(LED_RED, 0); trxResetSquelchesState(RADIO_DEVICE_PRIMARY); // Could be put in sleep state and awaken with a signal, so this will re-enable the audio AMP trxPowerUpDownRxAndC6000(false, true, true); if (usbMonitoring) { ticksTimer_t checkBatteryTimer = { .start = ticksGetMillis(), .timeout = (safeBoot ? 500U : 1000U) }; #if defined(DEBUG_HARDWARE_SCREEN) #warning SAFEBOOT HARDWARE DEBUG SCREEN ENABLED if (safeBoot) { #if defined(STM32F405xx) && ! defined(PLATFORM_MD9600) char cpuTypeBuf[SCREEN_LINE_BUFFER_SIZE] = {0}; uint32_t vpHeader[2]; displayClearBuf(); sprintf(cpuTypeBuf, "CPU Type :%s (%u)", ((NumInterruptPriorityBits == 4) ? "STM" : "TYT"), NumInterruptPriorityBits); displayPrintAt(0, y, cpuTypeBuf , FONT_SIZE_2); y += FONT_SIZE_2_HEIGHT; sprintf(cpuTypeBuf, "CPU Sig :0x%04X", cpuGetSignature()); displayPrintAt(0, y, cpuTypeBuf , FONT_SIZE_2); y += FONT_SIZE_2_HEIGHT; sprintf(cpuTypeBuf, "CPU Rev :0x%04X", cpuGetRevision()); displayPrintAt(0, y, cpuTypeBuf , FONT_SIZE_2); y += FONT_SIZE_2_HEIGHT; sprintf(cpuTypeBuf, "CPU Pack :0x%04X", cpuGetPackage()); displayPrintAt(0, y, cpuTypeBuf , FONT_SIZE_2); y += FONT_SIZE_2_HEIGHT; sprintf(cpuTypeBuf, "CPU Flash :%dkb", cpuGetFlashSize()); displayPrintAt(0, y, cpuTypeBuf , FONT_SIZE_2); y += FONT_SIZE_2_HEIGHT; sprintf(cpuTypeBuf, "Flash Type :0x%X", flashChipPartNumber); displayPrintAt(0, y, cpuTypeBuf , FONT_SIZE_2); y += FONT_SIZE_2_HEIGHT; sprintf(cpuTypeBuf, "NVRAM :0x%X %u", nonVolatileSettings.magicNumber, settingsIsOptionBitSet(BIT_SETTINGS_UPDATED)); displayPrintAt(0, y, cpuTypeBuf , FONT_SIZE_2); y += FONT_SIZE_2_HEIGHT; sprintf(cpuTypeBuf, "Codec :%s", (codecIsAvailable() ? "OK" : "KO")); displayPrintAt(0, y, cpuTypeBuf , FONT_SIZE_2); y += FONT_SIZE_2_HEIGHT; SPI_Flash_read(VOICE_PROMPTS_FLASH_HEADER_ADDRESS, (uint8_t *)&vpHeader, sizeof(vpHeader)); sprintf(cpuTypeBuf, "VP :0x%X 0x%X", vpHeader[0], vpHeader[1]); displayPrintAt(0, y, cpuTypeBuf , FONT_SIZE_2); y += FONT_SIZE_2_HEIGHT; displayRender(); #endif } #endif while(true) { tick_com_request(); vTaskDelay((0U / portTICK_PERIOD_MS)); #if defined(DEBUG_HARDWARE_SCREEN) if (safeBoot) { if (ticksTimerHasExpired(&checkBatteryTimer)) // reuse this timer { char buf[SCREEN_LINE_BUFFER_SIZE]; uint16_t ypos = y; uint32_t buttons = 0; int button_event = EVENT_BUTTON_NONE; uint32_t key = keyboardRead(); #if defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) uint8_t rotDir = ' '; #elif defined(PLATFORM_MD2017) uint8_t trackDir = ' '; #endif buttonsCheckButtonsEvent(&buttons, &button_event, false); displayFillRect(0, ypos, DISPLAY_SIZE_X, DISPLAY_SIZE_Y - y, true); sprintf(buf, "Ps :%u, Lvl :%u %d", powerRotarySwitchIsOn(), potLevel, getVolumeControl()); displayPrintAt(0, ypos, buf , FONT_SIZE_2); ypos += FONT_SIZE_2_HEIGHT; sprintf(buf, "V :%d, ADC :%d", lastValidBatteryVoltage, adcGetBatteryVoltage()); displayPrintAt(0, ypos, buf , FONT_SIZE_2); ypos += FONT_SIZE_2_HEIGHT; sprintf(buf, "Btn :0x%X, Evt :%d", buttons, button_event); displayPrintAt(0, ypos, buf , FONT_SIZE_2); ypos += FONT_SIZE_2_HEIGHT; sprintf(buf, "Key :%3d [%c]", key, ((key != 0) ? key : ' ')); displayPrintAt(0, ypos, buf , FONT_SIZE_2); ypos += FONT_SIZE_2_HEIGHT; #if defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) if (rotaryData.Direction != 0) { rotDir = ((rotaryData.Direction == 1) ? '+' : '-'); nonVolatileSettings.displayBacklightPercentage[DAY] += ((rotaryData.Direction > 0) ? (nonVolatileSettings.displayBacklightPercentage[DAY] >= 10 ? 10 : 1) : (nonVolatileSettings.displayBacklightPercentage[DAY] >= 20 ? -10 : -1)); nonVolatileSettings.displayBacklightPercentage[DAY] = CLAMP(nonVolatileSettings.displayBacklightPercentage[DAY], 0, 100); gpioSetDisplayBacklightIntensityPercentage(nonVolatileSettings.displayBacklightPercentage[DAY]); rotaryData.Direction = 0; } sprintf(buf, "Rot :[%c], Bcl :%d%%", rotDir, nonVolatileSettings.displayBacklightPercentage[DAY]); #elif defined(PLATFORM_MD2017) if (trackballData.Count > (2 + 1)) // TRACKBALL_FAST_MOTION + 1 { trackDir = trackballData.Direction; trackballData.Count -= 2; // TRACKBALL_FAST_MOTION } sprintf(buf, "TBall :[%c], Cnt :%d", trackDir, trackballData.Count); #endif displayPrintAt(0, ypos, buf , FONT_SIZE_2); //ypos += FONT_SIZE_2_HEIGHT; displayRenderRows(y / 8, ((ypos / 8) + (FONT_SIZE_2_HEIGHT / 8))); ticksTimerStart(&checkBatteryTimer, 200U); } batteryUpdate(); getVolumeControl(); // continue to average value.. } else #endif { if (ticksTimerHasExpired(&checkBatteryTimer)) { batteryCriticalCount += (batteryLastReadingIsCritical() ? 1 : (batteryCriticalCount ? -1 : 0)); if (batteryCriticalCount > (LOW_BATTERY_VOLTAGE_RECOVERY_TIME / 1000)) { powerDown(true); while(true); // won't reach this. } ticksTimerStart(&checkBatteryTimer, 1000U); } } } } else { uint8_t batteryLowRetries = 50; #if !defined(PLATFORM_GD77S) uint32_t prevPowerSwitchState = #if !defined(PLATFORM_RD5R) (powerRotarySwitchIsOn() ? 0 : 1) #else 1 #endif ; #endif #if defined(HAS_GPS) #if defined(LOG_GPS_DATA) gpsLoggingStop(); #endif gpsOff(); #if defined(PLATFORM_MD9600) gpsPower(false); #endif #endif while(batteryLowRetries-- > 0) { batteryCriticalCount += (batteryLastReadingIsCritical() ? 1 : (batteryCriticalCount ? -1 : 0)); osDelay(1); } bool batteryIsCritical = batteryCriticalCount > 25; clockManagerSetRunMode(kAPP_PowerModeRun, CLOCK_MANAGER_RUN_SUSPEND_MODE); isSuspended = true; /* VK3KYY Need to port watchdogDeinit(); */ if (batteryIsCritical) { powerDown(true); while(true); // won't reach this. } else { displaySetDisplayPowerMode(false); } MX_USB_DEVICE_DeInit(); // Deinit USB while(true) { batteryUpdate(); #if !defined(PLATFORM_GD77S) if (uiDataGlobal.SatelliteAndAlarmData.alarmType == ALARM_TYPE_NONE) { uint32_t powerSwitchState = #if !defined(PLATFORM_RD5R) (powerRotarySwitchIsOn() ? 0 : 1) #else 1 #endif ; // Safe Power On option is ON, user didn't press SK1 on power ON, so // forceSuspend is true. // Now, user just turned OFF the power switch, clear the forceSuspend flag to // be able to handle the power ON event. if ((powerSwitchState != 0) && forceSuspend) { forceSuspend = false; } if ((powerSwitchState == 0) && (forceSuspend == false) && ((settingsIsOptionBitSet(BIT_SAFE_POWER_ON) ? (((buttonsRead() & BUTTON_SK1) != 0) && (powerSwitchState != prevPowerSwitchState)) : true))) { // User wants to go in bootloader mode if (buttonsRead() == (BUTTON_SK1 | BUTTON_PTT)) { NVIC_SystemReset(); } wakeFromSleep(); return; } prevPowerSwitchState = powerSwitchState; } #endif #if !defined(PLATFORM_RD5R) && !defined(PLATFORM_GD77S) if (ticksTimerHasExpired(&nextPITCounterRunTimer)) { // Check if the battery has reached critical voltage (power off) bool powerSwitchIsOn = powerRotarySwitchIsOn(); bool lowBatteryCritical = powerSwitchIsOn ? batteryIsLowCriticalVoltage() : false; // Critical battery threshold is reached after 30 seconds, in total, of lowBatteryCritical. lowBatteryCriticalCount += (lowBatteryCritical ? 1 : (lowBatteryCriticalCount ? -1 : 0)); if ((powerSwitchIsOn == false) || (lowBatteryCritical && (lowBatteryCriticalCount > (LOW_BATTERY_VOLTAGE_RECOVERY_TIME / 1000) /* 30s in total */))) { HAL_GPIO_WritePin(PWR_SW_GPIO_Port, PWR_SW_Pin, GPIO_PIN_RESET); while(true); } ticksTimerStart(&nextPITCounterRunTimer, SUSPEND_LOW_BATTERY_RATE); } if (uiDataGlobal.SatelliteAndAlarmData.alarmType != ALARM_TYPE_NONE) { bool powerSwitchIsOff = (powerRotarySwitchIsOn() == false); if (powerSwitchIsOff) { uiDataGlobal.SatelliteAndAlarmData.alarmType = ALARM_TYPE_NONE; } } if (uiDataGlobal.SatelliteAndAlarmData.alarmType != ALARM_TYPE_NONE) { bool wakingUp = #if defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017) (buttonsRead() & BUTTON_ORANGE); #else // MD-UV3x0 | RT3S ((buttonsRead() & (BUTTON_SK2 | BUTTON_PTT)) == (BUTTON_SK2 | BUTTON_PTT)); #endif if (wakingUp) { // Wait for button(s) release while ( #if (defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)) (buttonsRead() & BUTTON_ORANGE) #else (buttonsRead() & (BUTTON_SK2 | BUTTON_PTT)) #endif ) { osDelay(10); } wakeFromSleep(); return; } } if (uiDataGlobal.SatelliteAndAlarmData.alarmType == ALARM_TYPE_SATELLITE || uiDataGlobal.SatelliteAndAlarmData.alarmType == ALARM_TYPE_CLOCK) { if (uiDataGlobal.dateTimeSecs >= uiDataGlobal.SatelliteAndAlarmData.alarmTime) { wakeFromSleep(); return; } } #endif osDelay(100); } } } void wakeFromSleep(void) { MX_USB_DEVICE_Init(); #if !defined(PLATFORM_RD5R) if (menuSystemGetPreviousMenuNumber() == MENU_SATELLITE) { // clockManagerSetRunMode(kAPP_PowerModeRun, CLOCK_MANAGER_SPEED_HS_RUN); clockManagerSetRunMode(kAPP_PowerModeRun, CLOCK_MANAGER_SPEED_RUN); } else { clockManagerSetRunMode(kAPP_PowerModeRun, CLOCK_MANAGER_SPEED_RUN); } /* GPIO_PinWrite(GPIO_Keep_Power_On, Pin_Keep_Power_On, 1);// This is normally already done before this function is called. // But do it again, just in case, as its important that the radio will turn off when the power control is turned to off */ trxPowerUpDownRxAndC6000(true, true, true); // Reset counters before enabling watchdog hrc6000Task.AliveCount = TASK_FLAGGED_ALIVE; beepTask.AliveCount = TASK_FLAGGED_ALIVE; /* VK3KYY Need to port to MD9600 watchdogInit(); */ displaySetDisplayPowerMode(true); if (trxGetMode() == RADIO_MODE_DIGITAL) { HRC6000ResetTimeSlotDetection(); HRC6000InitDigitalDmrRx(); } #endif #if defined(HAS_GPS) if (SETTINGS_GPS_MODE_GET(nonVolatileSettings) >= GPS_MODE_OFF) { gpsOn(); #if defined(LOG_GPS_DATA) gpsLoggingStart(); #endif } #endif #if !defined(PLATFORM_GD77S) if (nonVolatileSettings.apo > 0) { ticksTimerStart(&apoTimer, ((nonVolatileSettings.apo * 30) * 60000U)); } if ((nonVolatileSettings.autolockTimer > 0) && ticksTimerIsEnabled(&autolockTimer)) { ticksTimerStart(&autolockTimer, (nonVolatileSettings.autolockTimer * 30000U)); } #endif // Trick to compensate the fact that VBat is disconnected from the battery when // the power switch is set to OFF. Hence, we need to display the non averaged // voltage value, to avoid the long ramp up. resumeTicks = (ticksGetMillis() + BATTERY_VOLTAGE_STABILISATION_TIME); voicePromptsInit(); // Flush the VPs isSuspended = false; } void powerOffFinalStage(bool maintainRTC, bool forceSuspend) { uint32_t m; // If TXing, get back to RX (this function can be called on low battery event). if (trxTransmissionEnabled) { trxTransmissionEnabled = false; trxActivateRx(true); trxIsTransmitting = false; LedWrite(LED_RED, 0);//LEDs_PinWrite(GPIO_LEDred, Pin_LEDred, 0); } // Restore DMR filter settings if the radio is turned off whilst in monitor mode if (monitorModeData.isEnabled) { nonVolatileSettings.dmrCcTsFilter = monitorModeData.savedDMRCcTsFilter; nonVolatileSettings.dmrDestinationFilter = monitorModeData.savedDMRDestinationFilter; } // If user was in a private call when they turned the radio off we need to restore the last Tg prior to stating the Private call. // to the nonVolatile Setting overrideTG, otherwise when the radio is turned on again it be in PC mode to that station. if ((trxTalkGroupOrPcId >> 24) == PC_CALL_FLAG) { settingsSet(nonVolatileSettings.overrideTG, uiDataGlobal.tgBeforePcMode); } menuHotspotRestoreSettings(); codeplugSaveLastUsedChannelInZone(); #if defined(LOG_GPS_DATA) gpsLoggingStop(); #endif m = ticksGetMillis(); settingsSaveSettings(true); // Give it a bit of time before pulling the plug as DM-1801 EEPROM looks slower // than GD-77 to write, then quickly power cycling triggers settings reset. while (true) { if ((ticksGetMillis() - m) > 50) { break; } osDelay(1); } displayEnableBacklight(false, 0); #if ! (defined(PLATFORM_RD5R))// || defined(PLATFORM_MD9600) || defined(PLATFORM_MD380) || defined(PLATFORM_MDUV380) || defined(PLATFORM_RT84_DM1701) || defined(PLATFORM_MD2017)) // This turns the power off to the CPU. if (!maintainRTC) { HAL_GPIO_WritePin(PWR_SW_GPIO_Port, PWR_SW_Pin, GPIO_PIN_RESET); } #endif die(false, maintainRTC, forceSuspend, false); } #if !defined(PLATFORM_GD77S) void apoTick(bool eventFromOperator) { if (nonVolatileSettings.apo > 0) { int currentMenu = menuSystemGetCurrentMenuNumber(); // Reset APO timer: // - on events // - when scanning // - when user has set a Satellite alarm // - when transmissing, while in hotspot mode or while using the CPS // - on RF activity if (eventFromOperator || uiDataGlobal.Scan.active || ((currentMenu == UI_TX_SCREEN) || (currentMenu == UI_HOTSPOT_MODE) || (currentMenu == UI_CPS)) || (uiDataGlobal.SatelliteAndAlarmData.alarmType != ALARM_TYPE_NONE)) { if (uiNotificationIsVisible() && (uiNotificationGetId() == NOTIFICATION_ID_USER_APO)) { uiNotificationHide(true); } ticksTimerStart(&apoTimer, ((nonVolatileSettings.apo * 30) * 60000U)); } // No event in the last 'apo' time => Suspend if (ticksTimerHasExpired(&apoTimer)) { powerDown(false); } else { // 1 minute or less is remaining, it's time to show the APO notification if ((ticksTimerRemaining(&apoTimer) <= 60000U) && ((uiNotificationIsVisible() && (uiNotificationGetId() == NOTIFICATION_ID_USER_APO)) == false)) { if (nonVolatileSettings.audioPromptMode < AUDIO_PROMPT_MODE_VOICE_THRESHOLD) { soundSetMelody(MELODY_APO_TRIGGERED); } else { voicePromptsInit(); voicePromptsAppendLanguageString(currentLanguage->auto_power_off); voicePromptsPlay(); } uiNotificationShow(NOTIFICATION_TYPE_MESSAGE, NOTIFICATION_ID_USER_APO, 60000U, currentLanguage->auto_power_off, true); } } } } #endif