cancel
Showing results for 
Search instead for 
Did you mean: 

STM32WB: Enter STOP2 Mode and Wake-Up via EXTI, Ensuring Peripheral Continuity

Dhruvbusa
Associate II

I'm currently working on an STM32WB project and would appreciate guidance on the correct sequence to enter STOP2 low-power mode and wake up via an EXTI interrupt, ensuring all peripherals resume normal operation post-wake-up.

Specifically, I’m looking for:

  1. The recommended steps to enter STOP2 mode, including any pre-conditions or configurations.

  2. The proper configuration for EXTI-based wake-up (I’m using a GPIO line for this).

  3. Best practices to ensure that all necessary peripherals (UART, SPI, ADC, USB, etc.) are correctly restored after wake-up.

  4. Any important considerations related to Core 2 (BLE stack), clock sources, or peripheral clocks that should be handled manually before or after STOP2.

I’ve reviewed the reference manual and some ST examples, but a consolidated, recommended sequence or reference to an app note/code example that handles these aspects cleanly would be very helpful.

Thank you in advance for your support!

Refrence code:

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
  /* Config code for STM32_WPAN (HSE Tuning must be done before system clock configuration) */
  MX_APPE_Config();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

/* Configure the peripherals common clocks */
  PeriphCommonClock_Config();

  /* IPCC initialisation */
  MX_IPCC_Init();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_RF_Init();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_LPUART1_UART_Init();
  MX_RTC_Init();
  MX_SPI1_Init();
  MX_ADC1_Init();
  MX_I2C1_Init();
  MX_USART1_UART_Init();
  MX_RNG_Init();
  /* USER CODE BEGIN 2 */
  MX_USB_Device_Init();
  /* USER CODE END 2 */

  /* Init scheduler */
  osKernelInitialize();
  /* Init code for STM32_WPAN */
  MX_APPE_Init();

  /* Call init function for freertos objects (in cmsis_os2.c) */
  MX_FREERTOS_Init();

  /* Start scheduler */
  osKernelStart();

  /* We should never get here as control is now taken by the scheduler */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure LSE Drive Capability
  */
  HAL_PWR_EnableBkUpAccess();
  __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_MEDIUMHIGH);

  /** Configure the main internal regulator output voltage
  */
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSI1
                              |RCC_OSCILLATORTYPE_HSE|RCC_OSCILLATORTYPE_LSE
                              |RCC_OSCILLATORTYPE_MSI;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.LSEState = RCC_LSE_ON;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.MSIState = RCC_MSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
  RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV1;
  RCC_OscInitStruct.PLL.PLLN = 32;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure the SYSCLKSource, HCLK, PCLK1 and PCLK2 clocks dividers
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK4|RCC_CLOCKTYPE_HCLK2
                              |RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.AHBCLK2Divider = RCC_SYSCLK_DIV2;
  RCC_ClkInitStruct.AHBCLK4Divider = RCC_SYSCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_3) != HAL_OK)
  {
    Error_Handler();
  }

  /** Enable MSI Auto calibration
  */
  HAL_RCCEx_EnableMSIPLLMode();
}

/**
  * @brief Peripherals Common Clock Configuration
  * @retval None
  */
void PeriphCommonClock_Config(void)
{
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

  /** Initializes the peripherals clock
  */
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SMPS|RCC_PERIPHCLK_RFWAKEUP
                              |RCC_PERIPHCLK_USB|RCC_PERIPHCLK_ADC;
  PeriphClkInitStruct.PLLSAI1.PLLN = 24;
  PeriphClkInitStruct.PLLSAI1.PLLP = RCC_PLLP_DIV2;
  PeriphClkInitStruct.PLLSAI1.PLLQ = RCC_PLLQ_DIV2;
  PeriphClkInitStruct.PLLSAI1.PLLR = RCC_PLLR_DIV2;
  PeriphClkInitStruct.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_USBCLK|RCC_PLLSAI1_ADCCLK;
  PeriphClkInitStruct.UsbClockSelection = RCC_USBCLKSOURCE_PLLSAI1;
  PeriphClkInitStruct.AdcClockSelection = RCC_ADCCLKSOURCE_PLLSAI1;
  PeriphClkInitStruct.RFWakeUpClockSelection = RCC_RFWKPCLKSOURCE_HSE_DIV1024;
  PeriphClkInitStruct.SmpsClockSelection = RCC_SMPSCLKSOURCE_HSI;
  PeriphClkInitStruct.SmpsDivSelection = RCC_SMPSCLKDIV_RANGE1;

  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN Smps */
 /**
   * This prevents the CPU2 to disable the HSI48 oscillator when
   * it does not use anymore the RNG IP
   */
  LL_HSEM_1StepLock( HSEM , CFG_HW_CLK48_CONFIG_SEMID);
  /* USER CODE END Smps */
}



1 ACCEPTED SOLUTION

Accepted Solutions
FilipKremen
ST Employee

Hello,

firstly, I recommend having a look at BLE_HeartRateFreeRTOS example.

This example uses LP manager which allows you to easily manage different LP modes.

Please visit this AN for more information and don't hesitate to ask further if you have any question.

How to build wireless applications with STM32WB MCUs - Application note

 

Best regards,

ST support

 

View solution in original post

3 REPLIES 3
FilipKremen
ST Employee

Hello,

firstly, I recommend having a look at BLE_HeartRateFreeRTOS example.

This example uses LP manager which allows you to easily manage different LP modes.

Please visit this AN for more information and don't hesitate to ask further if you have any question.

How to build wireless applications with STM32WB MCUs - Application note

 

Best regards,

ST support

 

SLevi.1
Associate III

Did you ever get anywhere? I too find the BLE_Heartrate example difficult to follow. There is a distinct lack of explanation for the ST example code.

A good first step is to review the Low Power Manager code stm32_lpm_if.c.
That file controls how clocks and cores enter sleep, and if its configuration doesn’t match your project, the BLE core may never transition to low power even though the M4 is in STOP2.

In my case, this is exactly where the problem came from.

I eventually solved it, and the issue was related to how the BLE core goes to sleep.

In the BLE_HeartRate example, when the HSI clock is stopped the BLE core automatically enters its own low-power state, so the current drops correctly. But in my project the BLE core never actually went to sleep, even though the Cortex-M4 was entering STOP2. As a result, the current stayed higher than expected.

According to ST, there is an LPM configuration on the BLE side that should allow it to manage sleep autonomously but in my case that mechanism was not working as intended.

What worked reliably for me was:

  1. Before entering STOP2, I explicitly de-initialized the BLE stack so the BLE core would shut down properly.

  2. Then I entered STOP2.

  3. After wake-up, I re-initialized BLE and continued normal operation.

With this flow, the BLE core actually powered down during sleep and the current consumption became what I expected.

So if STOP2 is working but current is still high, it’s worth checking whether the BLE core is still running. If it is, try shutting BLE down before sleep and enabling it again after wake-up that made the difference in my case.