Low Power Modes in STM32
By default, the microcontroller is in Run mode after a system or a power-on reset. In Run mode, the CPU is clocked by HCLK and the program code is executed. STM32 have Several low power modes are available to save power, when the CPU does not need to be kept running, for example when waiting for an external event. Today in this tutorial we are going to discuss these modes.
There are 3 Low Poer Modes available in STM32, and they are as follows
- SLEEP MODE -> FPU core stopped, peripherals kept running
- STOP MODE -> all clocks are stopped
- STANDBY MODE -> 1.2 V domain powered off
SLEEP MODE
I will first start with the simplest one, which is SLEEP MODE. In this mode, CPU CLK is turned OFF and there is no effect on other clocks or analog clock sources. The current consumption is HIGHEST in this mode, compared to other Low Power Modes.
Entry
In order to enter the SLEEP MODE, we must disable the systick interrupt first, or else this interrupt will wake the MCU every time the interrupt gets triggered.
HAL_SuspendTick();
Next, we will enter the sleep mode by executing the WFI (Wait For Interrupt), or WFE (Wait For Event) instructions. If the WFI instruction was used to enter the SLEEP MODE, any peripheral interrupt acknowledged by the NVIC can wake up the device.
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
Wakeup
As mentioned above, I have entered the SLEEP MODE using the WFI instruction, so the device will wakeup whenever any interrupt is triggered.
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
HAL_ResumeTick();
}
Inside the callback function we can resume the systick, so that we can use the delay function again for the rest of the code.
SLEEPONEXIT
This is another feature available in SLEEP MODE, where the MCU will wake up when the interrupt is triggered, it will process the ISR, and go back to sleep when the ISR exits. This is useful when we want the controller to run only in the interrupt mode.
HAL_PWR_EnableSleepOnExit ();
The above function must be called before going into the SLEEP MODE to activate the sleeponexit feature. We can disable it by calling
HAL_PWR_DisableSleepOnExit ();
The entire code for the SLEEP MODE, with SLEEPONEXIT feature is shown below
uint8_t Rx_data;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Receive_IT(huart, &Rx_data, 1);
str = "WakeUP from SLEEP by UART\r\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
str = "WakeUP from SLEEP by EXTI\r\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
HAL_PWR_DisableSleepOnExit ();
}
int main ()
{
......
......
HAL_UART_Receive_IT(&huart2, &Rx_data, 1);
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
str = "Going into SLEEP MODE in 5 seconds\r\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 1);
HAL_Delay(5000);
/* Suspend Tick increment to prevent wakeup by Systick interrupt.
Otherwise the Systick interrupt will wake up the device within 1ms (HAL time base)
*/
HAL_SuspendTick();
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 0); // Just to indicate that the sleep mode is activated
HAL_PWR_EnableSleepOnExit ();
// Enter Sleep Mode , wake up is done once User push-button is pressed
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
// Resume Tick interrupt if disabled prior to sleep mode entry
HAL_ResumeTick();
str = "WakeUP from SLEEP\r\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
for (int i=0; i<20; i++)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(100);
}
}
}
Below is the result of the above code.
Basically, when the MCU wakes up because of the UART interrupt, it goes back to sleep after processing the ISR (i.e after printing the string), but when it wakes up due to the EXTI, the SLEEPONEXIT is disabled, and rest of the main function is executed as usual.
STOP MODE
In Stop mode, all clocks in the 1.2 V domain are stopped, the PLLs, the HSI and the HSE RC oscillators are disabled. Internal SRAM and register contents are preserved. STOP MODE have may different categories depending on what should be turned off. Below is the picture from the STM32F446RE reference manual
Entry
Just like sleep mode, In order to enter the STOP MODE, we must disable the systick interrupt, or else this interrupt will wake the MCU every time the interrupt gets triggered.
HAL_SuspendTick();
Next, we will enter the sleep mode by executing the WFI (Wait For Interrupt), or WFE (Wait For Event) instructions. If the WFI instruction was used to enter the STOP MODE, EXTI, Independent watchdog (IWDG), or RTC can wake up the device. Also I am using the first mode as shown in the picture above i.e. The main Regulator will be turned off and only the LOW Power Regulator will be running.
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
Wakeup
As mentioned above, I have entered the STOP MODE using the WFI instruction, so the device will wakeup whenever any interrupt is triggered by an EXTI, Independent watchdog (IWDG), or RTC.
We must reconfigure the SYSTEM CLOCKS after wakeup, as they were disabled when entering the STOP MODE. Also don’t forget to resume the systick.
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_13)
{
SystemClock_Config ();
HAL_ResumeTick();
}
}
SLEEPONEXIT
SleeponExit works the same way as it does for the SLEEP mode. You can check the implementation above under the SLEEP mode.
The entire code for STOP MODE is as shown below
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
SystemClock_Config ();
HAL_ResumeTick();
char *str = "WAKEUP FROM RTC\n NOW GOING IN STOP MODE AGAIN\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *) str, strlen (str), HAL_MAX_DELAY);
}
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_13)
{
SystemClock_Config ();
HAL_ResumeTick();
char *str = "WAKEUP FROM EXTII\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *) str, strlen (str), HAL_MAX_DELAY);
HAL_PWR_DisableSleepOnExit();
}
}
int main ()
{
.......
.......
.......
/*## Configure the Wake up timer ###########################################*/
/* RTC Wake-up Interrupt Generation:
Wake-up Time Base = (RTC_WAKEUPCLOCK_RTCCLK_DIV /(LSI))
==> WakeUpCounter = Wake-up Time / Wake-up Time Base
To configure the wake up timer to 20s the WakeUpCounter is set to 0xA017:
RTC_WAKEUPCLOCK_RTCCLK_DIV = RTCCLK_Div16 = 16
Wake-up Time Base = 16 /(32KHz) = 0.0005 seconds
==> WakeUpCounter = ~10s/0.0005s = 20000 = 0x4E20 */
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0x4E20, RTC_WAKEUPCLOCK_RTCCLK_DIV16);
/****** Suspend the Ticks before entering the STOP mode or else this can wake the device up **********/
HAL_SuspendTick();
HAL_PWR_EnableSleepOnExit();
/* Enter Stop Mode */
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
for (int i=0; i<10; i++)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(1000);
}
while (1)
{
.....
}
}
The RTC Setup is very long, and it is explained in the VIDEO. DO check the video below to understand it.
When the MCU enters the STOP MODE, it can be woken up by either RTC periodic trigger, or by EXTI line. When the former wakes the MCU, the interrupt is processed, where it will print the string, and than goes back in the STOP MODE. Whereas, when the EXTI line wakes the device, the sleeponexit will be disabled, and the main loop will run.
STANDBY MODE
The Standby mode allows to achieve the lowest power consumption. It is based on the Cortex®-M4 with FPU deepsleep mode, with the voltage regulator disabled. The 1.2 V domain is consequently powered off. The PLLs, the HSI oscillator and the HSE oscillator are also switched off.
Entry
Before entering the STANDBY MODE, we must disable the wakeup flags as shown below
/* Clear the WU FLAG */
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* clear the RTC Wake UP (WU) flag */
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
Next, enable the wakeup pin, or the RTC periodic wakeup (if you want to use RTC)
/* Enable the WAKEUP PIN */
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
/* enable the RTC Wakeup */
/* RTC Wake-up Interrupt Generation:
Wake-up Time Base = (RTC_WAKEUPCLOCK_RTCCLK_DIV /(LSI))
==> WakeUpCounter = Wake-up Time / Wake-up Time Base
To configure the wake up timer to 5s the WakeUpCounter is set to 0x2710:
RTC_WAKEUPCLOCK_RTCCLK_DIV = RTCCLK_Div16 = 16
Wake-up Time Base = 16 /(32KHz) = 0.0005 seconds
==> WakeUpCounter = ~5s/0.0005s = 20000 = 0x2710
*/
if (HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0x2710, RTC_WAKEUPCLOCK_RTCCLK_DIV16) != HAL_OK)
{
Error_Handler();
}
and finally enter the STANDBY MODE
HAL_PWR_EnterSTANDBYMode();
Wakeup
The standby wakeup is same as a system RESET. The entire code runs from the beginning just as if it was a RESET. The only difference between a reset and a STANDBY wakeup is that, when the MCU wakesup, The SBF status flag in the PWR power control/status register (PWR_CSR) is set. The wakeup can be triggered by WKUP pin rising edge, RTC alarm (Alarm A and Alarm B), RTC wakeup, tamper event, time stamp event, external reset in NRST pin, IWDG reset.
The entire code for the STANDBY MODE is as shown below
int main ()
{
..............
.............
if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET)
{
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB); // clear the flag
/** display the string **/
char *str = "Wakeup from the STANDBY MODE\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
/** Blink the LED **/
for (int i=0; i<20; i++)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(200);
}
/** Disable the WWAKEUP PIN **/
HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1); // disable PA0
/** Deactivate the RTC wakeup **/
HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
}
/** Now enter the standby mode **/
/* Clear the WU FLAG */
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
/* clear the RTC Wake UP (WU) flag */
__HAL_RTC_WAKEUPTIMER_CLEAR_FLAG(&hrtc, RTC_FLAG_WUTF);
/* Display the string */
char *str = "About to enter the STANDBY MODE\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str, strlen (str), HAL_MAX_DELAY);
/* Blink the LED */
for (int i=0; i<5; i++)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
HAL_Delay(750);
}
/* Enable the WAKEUP PIN */
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
/* enable the RTC Wakeup */
/* RTC Wake-up Interrupt Generation:
Wake-up Time Base = (RTC_WAKEUPCLOCK_RTCCLK_DIV /(LSI))
==> WakeUpCounter = Wake-up Time / Wake-up Time Base
To configure the wake up timer to 5s the WakeUpCounter is set to 0x2710:
RTC_WAKEUPCLOCK_RTCCLK_DIV = RTCCLK_Div16 = 16
Wake-up Time Base = 16 /(32KHz) = 0.0005 seconds
==> WakeUpCounter = ~5s/0.0005s = 20000 = 0x2710
*/
if (HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 0x2710, RTC_WAKEUPCLOCK_RTCCLK_DIV16) != HAL_OK)
{
Error_Handler();
}
/* one last string to be sure */
char *str2 = "STANDBY MODE is ON\n\n";
HAL_UART_Transmit(&huart2, (uint8_t *)str2, strlen (str2), HAL_MAX_DELAY);
/* Finally enter the standby mode */
HAL_PWR_EnterSTANDBYMode();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}