STM32 Low Power Best Practices
Introduction
Power consumption is a critical factor in many embedded applications, especially for battery-powered devices. STM32 microcontrollers offer various low-power modes and features that can significantly extend battery life when properly implemented. This guide will explore best practices for minimizing power consumption in STM32-based designs, helping you create more energy-efficient applications.
Understanding STM32 Power Consumption Fundamentals
Before diving into specific techniques, it's important to understand what contributes to power consumption in an STM32 microcontroller:
- Core and peripheral clocks - Higher frequencies mean higher power consumption
- Active peripherals - Each active peripheral consumes power
- I/O pins - The state and configuration of I/O pins affects power consumption
- Supply voltage - Higher voltage means higher power consumption
- Low-power modes - Different modes offer various power-saving levels with different wake-up capabilities
STM32 Low-Power Modes Overview
STM32 microcontrollers typically offer several low-power modes, with specific names and features varying slightly across different STM32 families:
Let's review each mode's characteristics:
| Mode | CPU | Peripherals | RAM | Typical Current | Wake-up Sources | Wake-up Time | 
|---|---|---|---|---|---|---|
| Run | On | On | Maintained | 3-10 mA | N/A | N/A | 
| Low-Power Run | On (reduced) | On | Maintained | 0.5-2 mA | N/A | N/A | 
| Sleep | Off | On | Maintained | 0.5-5 mA | Any interrupt | Fast (μs) | 
| Stop | Off | Off (some on) | Maintained | 5-10 μA | EXTI, RTC, etc. | Medium (μs) | 
| Standby | Off | Off | Lost | 1-3 μA | RTC, WKUP pins | Slow (ms) | 
| Shutdown | Off | Off | Lost | < 1 μA | WKUP pins | Slowest (ms) | 
Best Practice 1: Choose the Right Low-Power Mode
Select the most appropriate low-power mode based on your application's requirements:
// Example: Entering Sleep Mode
void EnterSleepMode(void)
{
  /* Suspend Systick */
  HAL_SuspendTick();
  
  /* Enter Sleep Mode, wake up is done once any interrupt is received */
  HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
  
  /* Resume Systick after wake-up */
  HAL_ResumeTick();
}
// Example: Entering Stop Mode
void EnterStopMode(void)
{
  /* Configure the wake-up source: EXTI Line */
  __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
  
  /* Suspend Systick */
  HAL_SuspendTick();
  
  /* Enter Stop Mode */
  HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
  
  /* Resume Systick and system clock after wake-up */
  HAL_ResumeTick();
  SystemClock_Config(); // Reconfigure clocks after Stop mode
}
Best Practice 2: Optimize Clock Configuration
Clock management is crucial for power savings:
- Use the lowest frequency that meets your performance requirements
- Disable unused clocks to peripherals
- Consider using the MSI (Multi-Speed Internal) clock when available for better power efficiency
- Use the HSI or LSI for less critical timing applications
void OptimizeClocks(void)
{
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
  RCC_OscInitTypeDef RCC_OscInitStruct;
  
  /* Configure the clock source to use MSI with PLL for efficient performance */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
  RCC_OscInitStruct.MSIState = RCC_MSI_ON;
  RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6; // 4 MHz
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
  // PLL configuration to balance performance and power
  
  HAL_RCC_OscConfig(&RCC_OscInitStruct);
  
  /* Select PLL as system clock source and configure bus clocks */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
                               RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}
Best Practice 3: Optimize Peripheral Usage
Only enable peripherals when needed:
void OptimizePeripheralUsage(void)
{
  /* Disable unused peripherals */
  __HAL_RCC_GPIOA_CLK_DISABLE();  // Disable if not using GPIOA
  __HAL_RCC_USART2_CLK_DISABLE(); // Disable if not using USART2
  
  /* Example of enabling a peripheral only when needed */
  void StartADC(void)
  {
    /* Enable ADC clock */
    __HAL_RCC_ADC_CLK_ENABLE();
    
    /* Configure and start ADC */
    // ADC configuration code here
    
    /* Take measurement */
    HAL_ADC_Start(&hadc1);
    HAL_ADC_PollForConversion(&hadc1, 100);
    uint32_t value = HAL_ADC_GetValue(&hadc1);
    
    /* Disable ADC when done */
    HAL_ADC_Stop(&hadc1);
    __HAL_RCC_ADC_CLK_DISABLE();
  }
}
Best Practice 4: Proper GPIO Configuration
GPIO pins can be a significant source of power consumption if not configured properly:
void ConfigureGPIOForLowPower(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  
  /* Configure unused pins as analog inputs to reduce power consumption */
  GPIO_InitStruct.Pin = GPIO_PIN_All;
  GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  // Continue for all unused GPIO ports
  
  /* For used pins, consider push-pull vs open-drain and internal pull-ups/pull-downs */
  GPIO_InitStruct.Pin = GPIO_PIN_5;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;  // Use lowest speed suitable for application
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  
  /* Avoid floating inputs */
  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;  // Define a known state
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
Best Practice 5: Use DMA for Efficient Data Transfers
DMA can reduce CPU intervention and allow the processor to enter low-power modes during data transfers:
void ConfigureDMAForEfficiency(void)
{
  /* Configure DMA for UART reception without CPU intervention */
  hdma_usart2_rx.Instance = DMA1_Channel5;
  hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
  hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
  hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;
  hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  hdma_usart2_rx.Init.Mode = DMA_CIRCULAR;
  hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;
  HAL_DMA_Init(&hdma_usart2_rx);
  
  /* Link DMA to UART */
  __HAL_LINKDMA(&huart2, hdmarx, hdma_usart2_rx);
  
  /* Start DMA reception in background */
  HAL_UART_Receive_DMA(&huart2, rxBuffer, BUFFER_SIZE);
  
  /* CPU can now enter low-power mode while DMA handles data transfer */
  EnterSleepMode();
}
Best Practice 6: Implement Event-Driven Architecture
Use interrupts instead of polling to reduce unnecessary CPU activity:
/* Configure button interrupt to wake up from low-power mode */
void ConfigureExternalInterrupt(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  
  /* Configure button pin as input with interrupt */
  GPIO_InitStruct.Pin = BUTTON_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(BUTTON_GPIO_PORT, &GPIO_InitStruct);
  
  /* Enable and set EXTI line priority */
  HAL_NVIC_SetPriority(BUTTON_EXTI_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(BUTTON_EXTI_IRQn);
}
/* Interrupt handler */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
  if (GPIO_Pin == BUTTON_PIN)
  {
    /* Button pressed, perform necessary tasks */
    ProcessButtonPress();
  }
}
/* Main loop based on events */
int main(void)
{
  /* Initialize system */
  SystemInit();
  ConfigureExternalInterrupt();
  
  while (1)
  {
    /* Enter Stop mode, wait for events */
    EnterStopMode();
    
    /* Code here will run after wake-up from interrupt */
  }
}
Best Practice 7: Use Peripheral Low-Power Features
Many STM32 peripherals have specific low-power features:
void ConfigureLowPowerUART(void)
{
  /* Configure UART with low-power features */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 9600;  // Lower baud rate for power saving
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  /* On some STM32 families: */
  // huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  // huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  HAL_UART_Init(&huart2);
  
  /* Configure UART to wake up from Stop mode */
  HAL_UARTEx_EnableStopMode(&huart2);
  
  /* Configure UART to wake up on address match */
  uint8_t address = 0x01;
  HAL_UARTEx_EnableWakeUpStop(&huart2, UART_WAKEUP_ADDRESS, address);
}
void ConfigureLowPowerRTC(void)
{
  /* Initialize RTC with low-power features */
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 127;  // Higher value for lower power consumption
  hrtc.Init.SynchPrediv = 255;   // Higher value for lower power consumption
  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;
  HAL_RTC_Init(&hrtc);
  
  /* Configure RTC to wake up the system from Stop mode */
  HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 4095, RTC_WAKEUPCLOCK_RTCCLK_DIV16);
}
Best Practice 8: Use the Debugging Tools for Power Analysis
STM32CubeIDE and STM32CubeMX offer power consumption calculation tools that can help estimate power usage:
- 
STM32CubeMX Power Consumption Calculator: - In your STM32CubeMX project, go to "Power Consumption Calculator" tab
- Configure your application's different operating modes
- Analyze the estimated power consumption
 
- 
ST-LINK Power Measurement (on supported boards): - Connect your development board with ST-LINK
- Use the power measurement features in STM32CubeIDE
- Monitor real-time power consumption during application execution
 
Real-World Application Example: Battery-Powered Sensor Node
Let's build a complete example of a battery-powered temperature sensor that wakes up periodically, takes measurements, transmits data via UART, and returns to low-power mode:
/* Global variables */
UART_HandleTypeDef huart2;
ADC_HandleTypeDef hadc1;
RTC_HandleTypeDef hrtc;
float temperature;
uint8_t transmitComplete = 0;
/* Initialize system for low power */
void SystemInit(void)
{
  /* Configure system clock for optimal power consumption */
  ConfigureSystemClock();
  
  /* Configure unused GPIO pins as analog inputs */
  ConfigureGPIOForLowPower();
  
  /* Configure ADC for temperature measurement */
  ConfigureADC();
  
  /* Configure UART for data transmission */
  ConfigureUART();
  
  /* Configure RTC for periodic wakeup */
  ConfigureRTC();
}
/* Configure RTC for wakeup every 60 seconds */
void ConfigureRTC(void)
{
  hrtc.Instance = RTC;
  hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
  hrtc.Init.AsynchPrediv = 127;
  hrtc.Init.SynchPrediv = 255;
  hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
  HAL_RTC_Init(&hrtc);
  
  /* Set wakeup timer to 60 seconds */
  HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 60 * 2048, RTC_WAKEUPCLOCK_RTCCLK_DIV16);
}
/* Read temperature sensor */
float ReadTemperature(void)
{
  uint32_t adcValue;
  float tempCelsius;
  
  /* Enable ADC clock */
  __HAL_RCC_ADC_CLK_ENABLE();
  
  /* Start ADC and read value */
  HAL_ADC_Start(&hadc1);
  HAL_ADC_PollForConversion(&hadc1, 100);
  adcValue = HAL_ADC_GetValue(&hadc1);
  HAL_ADC_Stop(&hadc1);
  
  /* Disable ADC clock */
  __HAL_RCC_ADC_CLK_DISABLE();
  
  /* Convert ADC value to temperature (simplified) */
  tempCelsius = (adcValue * 3.3f / 4096 - 0.76f) / 0.0025f + 25.0f;
  
  return tempCelsius;
}
/* Transmit temperature data over UART */
void TransmitData(float temperature)
{
  char buffer[32];
  
  /* Format temperature string */
  sprintf(buffer, "Temperature: %.2f C\r
", temperature);
  
  /* Enable UART clock */
  __HAL_RCC_USART2_CLK_ENABLE();
  
  /* Transmit data */
  transmitComplete = 0;
  HAL_UART_Transmit_IT(&huart2, (uint8_t*)buffer, strlen(buffer));
  
  /* Wait for transmission to complete */
  while (!transmitComplete) {
    /* Enter Sleep mode while waiting for UART to finish */
    HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
  }
  
  /* Disable UART clock */
  __HAL_RCC_USART2_CLK_DISABLE();
}
/* UART transmission complete callback */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART2) {
    transmitComplete = 1;
  }
}
/* RTC wakeup interrupt handler */
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
  /* This function will be called when RTC wakeup occurs */
}
/* Main function */
int main(void)
{
  /* Initialize the system */
  SystemInit();
  
  while (1)
  {
    /* Read temperature sensor */
    temperature = ReadTemperature();
    
    /* Transmit data */
    TransmitData(temperature);
    
    /* Enter Stop mode, wait for RTC wakeup */
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    
    /* System will wake up when RTC alarm triggers */
  }
}
Best Practice 9: Create a Power Profile Table
For comprehensive power management, create a power profile table for your application's different states:
| Operating State | Description | Active Components | Power Mode | Duration | Current | 
|---|---|---|---|---|---|
| Idle | Waiting for event | RTC only | Stop | 59.5 sec/min | 5 μA | 
| Wake-up | System initialization | Core, RTC | Run | 5 ms/min | 5 mA | 
| Measurement | Reading sensor | Core, ADC, RTC | Run | 10 ms/min | 8 mA | 
| Transmission | Sending data | Core, UART, RTC | Run | 20 ms/min | 10 mA | 
This profile helps in estimating battery life and identifying optimization opportunities.
Best Practice 10: Periodic Code Reviews for Power Optimization
Regularly review your code for power optimization:
- Check for unintended blocking delays
- Look for polling loops that could be replaced with interrupts
- Verify that all peripherals are properly disabled when not in use
- Confirm appropriate use of low-power modes
- Check GPIO configurations for power leaks
Summary
Implementing low-power best practices in STM32 applications requires a holistic approach:
- Choose the right low-power mode for your application requirements
- Optimize clock configuration for the minimum necessary speed
- Only enable peripherals when needed
- Configure GPIO pins properly, especially unused ones
- Use DMA to reduce CPU intervention
- Implement an event-driven architecture with interrupts
- Utilize peripheral-specific low-power features
- Use debugging tools to analyze power consumption
- Create a power profile for your application
- Regularly review code for power optimization opportunities
By consistently applying these best practices, you can significantly extend the battery life of your STM32-based applications while maintaining the necessary functionality and performance.
Additional Resources
- STM32 Low-Power Modes Application Note (AN4621)
- STM32 Ultra Low Power Features Application Note (AN4445)
- STM32 Power Consumption Optimization Application Note (AN4365)
- STM32CubeMX User Manual
- STM32 Online Training on Low-Power Modes
- STMicroelectronics Community Forums
Exercises
- Modify the sensor node example to add a button interrupt for on-demand readings
- Implement a data logging application that stores measurements in flash memory with optimal power consumption
- Create a power consumption calculator for a specific battery-powered application
- Experiment with different clock configurations and measure their impact on power consumption
- Implement dynamic frequency scaling based on application workload
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!