FreeTRX/MDUV380_firmware/application/source/interfaces/batteryAndPowerManagement.c
2026-07-06 08:45:33 +02:00

866 lines
25 KiB
C

/*
* 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