2025-07-30 8:54 AM - edited 2025-08-01 8:37 AM
I have developed code (on the NUCLEO-L433RC-P) that uses shutdown and wake.
The code works when I debug or run, but it stops working after a cold restart (power removed then reconnected).
After a cold restart, the software runs correctly until the first shutdown is called. Once shutdown is called, it seems to crash and I have to perform a reset.
From power up, the MCU should run as follows...
Why does it seem to crash after the first shutdown (step 2 above) ?
I am aware that RTC backup registers are lost when power is removed, but I cannot see this causing the problem.
I have attached a flowchart diagram (wake_shutdown.png) to show how my code works.
The relevant code is also below
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_DAC1_Init();
MX_TIM6_Init();
MX_SPI2_Init();
MX_LPUART1_UART_Init();
MX_SPI1_Init();
MX_I2C1_Init();
MX_USART3_UART_Init();
MX_ADC1_Init();
MX_TIM7_Init();
MX_RTC_Init();
if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB)) {
// woken from low power mode
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
// read wake marker from RTC backup register to determine if woken from sleep or shutdown
wake_marker = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
if (wake_marker == WAKE_FROM_SLEEP_RTC) {
// woken from sleep (RTC wake)
strcpy(prtText, "RTC wake \r\n");
HAL_UART_Transmit(&hlpuart1, (uint8_t*)prtText, strlen(prtText) - 1, HAL_MAX_DELAY);
// Use sizeof(newline) - 1 to avoid sending null terminator
wakeConfigUpdate(); // get time and date and restore reference pressure from RTC backup register
uint32_t tmp = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3); // restore power switch state from RTC backup register
powerSeq = *(uint8_t*)&tmp;
// check if elapsed time is greater then shutdown time
if (elapsedSeconds >= RTC_SHUTDOWN_S) {
strcpy(prtText, "Shutdown \r\n");
HAL_UART_Transmit(&hlpuart1, (uint8_t*)prtText, strlen(prtText) - 1, HAL_MAX_DELAY);
mcuPowerdown();
}
} else if (wake_marker == WAKE_FROM_SHUTDOWN_WKUP) {
// woken from shutdown (power switch pressed during shutdown)
strcpy(prtText, "WKUP pin wake \r\n");
HAL_UART_Transmit(&hlpuart1, (uint8_t*)prtText, strlen(prtText) - 1, HAL_MAX_DELAY);
wakeConfigReset(); // reset time and date and set default reference pressure
}
}
else
{
// woken from reset
strcpy(prtText, "Reset wake \r\n");
HAL_UART_Transmit(&hlpuart1, (uint8_t*)prtText, strlen(prtText) - 1, HAL_MAX_DELAY);
wakeConfigReset(); // reset time and date and set default reference pressure
// store value in RTC backup register to indicate mcu was reset
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, WAKE_FROM_RESET);
}
len = sprintf(prtText, "wake_marker = 0x%0lX\r\n", wake_marker);
HAL_UART_Transmit(&hlpuart1, (uint8_t*)prtText, len, HAL_MAX_DELAY);
len = sprintf(prtText, "startSeconds = %lu\r\n", startSeconds);
HAL_UART_Transmit(&hlpuart1, (uint8_t*)prtText, len, HAL_MAX_DELAY);
len = sprintf(prtText, "currentSeconds = %lu\r\n", currentSeconds);
HAL_UART_Transmit(&hlpuart1, (uint8_t*)prtText, len, HAL_MAX_DELAY);
len = sprintf(prtText, "elapsedSeconds = %lu\r\n", elapsedSeconds);
HAL_UART_Transmit(&hlpuart1, (uint8_t*)prtText, len, HAL_MAX_DELAY);
strcpy(prtText, "\r\n");
HAL_UART_Transmit(&hlpuart1, (uint8_t*)prtText, strlen(prtText), HAL_MAX_DELAY);
while (1) {
}
}
static void MX_RTC_Init(void)
{
/** Initialize RTC Only
*/
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 127;
hrtc.Init.SynchPrediv = 255;
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
hrtc.Init.OutPutRemap = RTC_OUTPUT_REMAP_NONE;
hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
if (HAL_RTC_Init(&hrtc) != HAL_OK)
{
Error_Handler();
}
}
void wakeConfigReset (void) {
// set reference pressure to default value
pressure_ref = REF_PRESS_DEFAULT;
/** Initialize RTC and set the Time and Date
*/
sTime.Hours = 0x0;
sTime.Minutes = 0x0;
sTime.Seconds = 0x0;
sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
sTime.StoreOperation = RTC_STOREOPERATION_RESET;
if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
sDate.WeekDay = RTC_WEEKDAY_MONDAY;
sDate.Month = RTC_MONTH_JANUARY;
sDate.Date = 0x1;
sDate.Year = 0x0;
if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BCD) != HAL_OK)
{
Error_Handler();
}
// get time and date
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
// convert start time and date to unix time
startSeconds = RTC_to_unix(&sTime, &sDate);
// store start time in RTC backup register
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, startSeconds);
}
void wakeConfigUpdate (void) {
// Restore pressure reference after wake
uint32_t tmp = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0);
pressure_ref = *(float*)&tmp;
// get time and date
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
// convert time and date to unix time
currentSeconds = RTC_to_unix(&sTime, &sDate);
// read unix start time from RTC backup register
startSeconds = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
// calculate elapsed time
elapsedSeconds = currentSeconds - startSeconds;
}
void mcuSleep (void){
/* The Following Wakeup sequence is highly recommended prior to each Standby mode entry
mainly when using more than one wakeup source this is to not miss any wakeup event.
- Disable all used wakeup sources,
- Clear all related wakeup flags,
- Re-enable all used wakeup sources,
- Enter the shutdown mode.
*/
// write pressure to RTC backup register
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, *(uint32_t*)&pressure_bak);
// write value to RTC backup register to indicate mcu was in sleep
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, WAKE_FROM_SLEEP_RTC);
// write power switch state to RTC backup register
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR3, *(uint32_t*)&powerSeq);
// Disable all used wakeup sources
HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, RTC_WAKE_SEC, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
// Clear all related wakeup flags
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
// Call sleep (note that the terms 'sleep' and 'powerdown' are only used for readability, both call shutdown mode)
HAL_PWREx_EnterSHUTDOWNMode();
}
void mcuPowerdown (void){
// write value to RTC backup register to indicate mcu was in shutdown
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, WAKE_FROM_SHUTDOWN_WKUP);
// disable RTC wakeup
HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
Enable_PC13_Wakeup();
// Call power down (note that the terms 'sleep' and 'powerdown' are only used for readability, both call shutdown mode)
HAL_PWREx_EnterSHUTDOWNMode();
}
UPDATE 1:
The clock was configured to use LSI (low speed internal), so I changed it to LSE (low speed external) and it has fixed the RTC wake from shutdown after a cold restart.
The ref manual states...
"The RTC is functional in VBAT mode and in all low-power modes when it is clocked by the LSE. When clocked by the LSI, the RTC is not functional in VBAT mode, but is functional in all low-power modes except Shutdown mode."
Everything works correctly in debug/run mode, but when there is a cold reset (power loss) the shutdown after 60 seconds does not work. It just cycles through 10 second wake, 5 second sleep etc.
UPDATE 2:
I modified the code in main (including the version in this post) to send the data over UART for debugging.
I send the values of startSeconds, currentSeconds, elapsedSeconds and whether the wakeup source was RTC, WKUP pin or reset.
I can see after a cold reset that the RTC backup is always zero, so it always thinks its waking up from reset.
Why does the RTC back work in debug/run mode, but not in standalone after a cold reset ?
In debug/run mode the terminal result is:
After cold reset the terminal result is:
Solved! Go to Solution.
2025-08-01 1:10 PM
After a week of trying to debug this and pulling my hair out, I have finally found the problem
To help others who are having problems, this is what I found and the fix.
The problem was with the following code (in main)…
if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB)) {
// woken from low power mode
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
PWR_FLAG_SB does not work in standalone after power reset, it only works in debug / run.
As wake markers are already implemented, I removed the PWR_FLAG_SB check and used the wake markers to detect whether the wake was from reset, RTC or WKUP pin
It appears STM32 low-power modes (especially Shutdown) behave differently between debug and standalone.
The fixed code is below...
wake_marker = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
if (wake_marker == WAKE_FROM_SLEEP_RTC)
{
// woken from sleep (RTC wake)
wakeConfigUpdate(); // get time and date and restore reference pressure from RTC backup register
uint32_t tmp = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3); // restore power switch state from RTC backup register
powerSeq = *(uint8_t*)&tmp;
// check if elapsed time is greater then shutdown time
if (elapsedSeconds >= RTC_SHUTDOWN_S)
{
mcuPowerdown();
}
}
else if (wake_marker == WAKE_FROM_SHUTDOWN_WKUP)
{
// woken from shutdown (power switch pressed during shutdown)
wakeConfigReset(); // reset time and date and set default reference pressure
}
else
{
// woken from reset
wakeConfigReset(); // reset time and date and set default reference pressure
// store value in RTC backup register to indicate mcu was reset
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, WAKE_FROM_RESET);
}
ChatGPT explained the reasoning behind the behaviou....
In debug or run mode (e.g. via ST-Link or when powered via USB), the power is never fully removed — so:
Certain power control and backup domain registers (like PWR->CSR and RTC backup regs) may retain their values.
The SB flag can remain set, giving the impression it survived SHUTDOWN.
Why it fails in standalone after a cold reset:
In SHUTDOWN mode, only the RTC and backup domain are retained via VBAT.
However, on most STM32s:
The SB flag does not survive SHUTDOWN (it's not designed to — unlike in STANDBY mode).
After a cold power cycle, everything except what’s powered by VBAT is reset — including that flag.
Hence, __HAL_PWR_GET_FLAG(PWR_FLAG_SB) returns false.
TL;DR
PWR_FLAG_SB is only valid for detecting wake from STANDBY, not SHUTDOWN.
It might "appear" to work in debug due to partial power retention or debugger interference — but it's not reliable.
Always use backup register markers or the RTC time to infer wake source across SHUTDOWN.
2025-08-01 1:10 PM
After a week of trying to debug this and pulling my hair out, I have finally found the problem
To help others who are having problems, this is what I found and the fix.
The problem was with the following code (in main)…
if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB)) {
// woken from low power mode
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
PWR_FLAG_SB does not work in standalone after power reset, it only works in debug / run.
As wake markers are already implemented, I removed the PWR_FLAG_SB check and used the wake markers to detect whether the wake was from reset, RTC or WKUP pin
It appears STM32 low-power modes (especially Shutdown) behave differently between debug and standalone.
The fixed code is below...
wake_marker = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR2);
if (wake_marker == WAKE_FROM_SLEEP_RTC)
{
// woken from sleep (RTC wake)
wakeConfigUpdate(); // get time and date and restore reference pressure from RTC backup register
uint32_t tmp = HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR3); // restore power switch state from RTC backup register
powerSeq = *(uint8_t*)&tmp;
// check if elapsed time is greater then shutdown time
if (elapsedSeconds >= RTC_SHUTDOWN_S)
{
mcuPowerdown();
}
}
else if (wake_marker == WAKE_FROM_SHUTDOWN_WKUP)
{
// woken from shutdown (power switch pressed during shutdown)
wakeConfigReset(); // reset time and date and set default reference pressure
}
else
{
// woken from reset
wakeConfigReset(); // reset time and date and set default reference pressure
// store value in RTC backup register to indicate mcu was reset
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR2, WAKE_FROM_RESET);
}
ChatGPT explained the reasoning behind the behaviou....
In debug or run mode (e.g. via ST-Link or when powered via USB), the power is never fully removed — so:
Certain power control and backup domain registers (like PWR->CSR and RTC backup regs) may retain their values.
The SB flag can remain set, giving the impression it survived SHUTDOWN.
Why it fails in standalone after a cold reset:
In SHUTDOWN mode, only the RTC and backup domain are retained via VBAT.
However, on most STM32s:
The SB flag does not survive SHUTDOWN (it's not designed to — unlike in STANDBY mode).
After a cold power cycle, everything except what’s powered by VBAT is reset — including that flag.
Hence, __HAL_PWR_GET_FLAG(PWR_FLAG_SB) returns false.
TL;DR
PWR_FLAG_SB is only valid for detecting wake from STANDBY, not SHUTDOWN.
It might "appear" to work in debug due to partial power retention or debugger interference — but it's not reliable.
Always use backup register markers or the RTC time to infer wake source across SHUTDOWN.