cancel
Showing results for 
Search instead for 
Did you mean: 

Shutdown and wake (RTC / WKUP) work in debug / run but NOT in standalone after power loss

freeflyer
Senior

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...

  1. Run for 10 seconds
  2. Shutdown for 5 seconds
  3. Run for 10 seconds
  4. Shutdown for 5 seconds
  5. And so on.....
  6. This cycle should continue until 60 seconds is reached, when it should shutdown and stay shutdown.
  7. It should not wake again until the WKUP pin is pressed

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

  • main
  • MX_RTC_Init
  • wakeConfigReset
  • wakeConfigUpdate
  • mcuSleep
  • mcuPowerdown

 

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:

freeflyer_0-1754062390285.png

 

After cold reset the terminal result is:

freeflyer_1-1754062547476.png

 

 

 

 

 

 

 

1 ACCEPTED SOLUTION

Accepted Solutions
freeflyer
Senior

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.

View solution in original post

1 REPLY 1
freeflyer
Senior

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.