cancel
Showing results for 
Search instead for 
Did you mean: 

Part 2: How to create a ThreadX low power project from scratch

Laurids_PETERSEN
Community manager
Community manager

This is part 2 of an article, if you would like to read part 1 click here.

Coding your application using STM32CubeIDE:

3.1 Generate your source files and edit

Note: the full completed project with the source code is attached in the link section [3]. Refer to it if you run into problems or get stuck.

At this point in development, your project should be saved and code should be generated. After generating your project environment with C code, you should mainly be editing the “main.c” “app_threadx.c” and “app_threadx.h.”Figure 19: Project space after generating your code based on your STM32CubeMX ioc file.Figure 19: Project space after generating your code based on your STM32CubeMX ioc file.

The STM32CubMX tool automatically generates the “App_Threadx_Init” where the static memory is allocated for the entire application. A thread is created, and the semaphore is initialized to zero. Since the semaphore is initialized to zero, the main thread initially waits on the semaphore object being incremented to one before running the loop for toggling the GPIO.

Moving on to modifying our project source files, first, we make a small change for the “main.c” as shown in snippet 1. This is intended for error handling and snippet 1 is not the main subject of this article so focus your attention on snippet 2 and beyond.

Snippet 1: main.c: error handler code we add in case of anything in our code fails.

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
	  HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7, GPIO_PIN_RESET);
	  while (1)
	  {
	    HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_2);
	    HAL_Delay(1000);
	 }
  /* USER CODE END Error_Handler_Debug */
}

Add “main.h” inside “app_threadx.h”, to use HAL drivers inside ThreadX application source file.

3.2 Edit "app_threadx.h"

Snippet 2: app_theadx.h: include main header file if not already included

/* USER CODE BEGIN Includes */
#include "main.h"
/* USER CODE END Includes */

3.3 Edit "app_threadx.c"

Add macro definition and helper functional declarations inside the “app_threadx.c” file.

Snippet 3: app_threadx.c: Functions we declare and define for our low power thread application.

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define DUMMY_VALUE (0xFFFFFFFFU)
/* USER CODE END PD */

/* USER CODE BEGIN PFP */
  void SystemClock_Restore(void);
  void Enter_LowPower_Mode(void);
  void Exit_LowPower_Mode(void);
  static VOID App_Delay(ULONG Delay);
/* USER CODE END PFP */

Snippet 4 below shows our thread definition for the RTOS. The semaphore we defined is a binary semaphore, which ensures the GPIO resource is accessed only when the blue user button is pressed. We simply use the semaphore for synchronization of the blue user button and the toggling of the GPIO.

Snippet 4: app_threadx.c: The thread we enter when the MCU is awake - entering this thread when the blue user button is pressed.

/**
  * @brief  Function implementing the MainThread_Entry thread.
  *   thread_input: Not used.
  * @retval None
  */
void MainThread_Entry(ULONG thread_input)
{
  /* USER CODE BEGIN MainThread_Entry */
	(void) thread_input;
	  UINT i = 0;
	  /* Infinite loop */
	  while (1)
	  {
	    if (tx_semaphore_get(&tx_app_semaphore, TX_WAIT_FOREVER) == TX_SUCCESS)
	    {
	      for (i=0; i<10; i++)
	      {
	      /* Toggle LED to indicate status*/
	      HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_7);
	      App_Delay(50);
	      }
	    }
	  }
  /* USER CODE END MainThread_Entry */
}

After enabling the low power feature of ThreadX inside our ioc file, we get empty definitions of function (low power feature “macro” as described by ThreadX). We implement the function definition as shown in snippet 5. The routine is called when the MCU is about to go into low power.

Snippet 5: app_threadx.c: Thread responsible for preparing for low power and for calling stop mode HAL routine.

/**
  * @brief  App_ThreadX_LowPower_Enter
  *   None
  * @retval None
  */
void App_ThreadX_LowPower_Enter(void)
{
  /* USER CODE BEGIN  App_ThreadX_LowPower_Enter */

	Enter_LowPower_Mode();

  /* USER CODE END  App_ThreadX_LowPower_Enter */
}

Similar to the enter function, we also have an exit function to prepare the MCU for exiting the low power.

Snippet 6: app_threadx.c: Thread for calling existing low power mode routine.

/*/**
  * @brief  App_ThreadX_LowPower_Exit
  *   None
  * @retval None
  */
void App_ThreadX_LowPower_Exit(void)
{
  /* USER CODE BEGIN  App_ThreadX_LowPower_Exit */

	Exit_LowPower_Mode();

  /* USER CODE END  App_ThreadX_LowPower_Exit */
}

When the blue user button is pressed, we enter the interrupt callback function as shown in snippet 7. This also sends a wake-up signal to the MCU.

Now, we discuss the steps inside the callback. First, we make sure that the semaphore is not incremented to 1 already (this can happen if you press the user button multiple times). We check using semaphore get function and save the semaphore current value into “currentValue” variable. After the check, we increment the semaphore to 1, using the semaphore put function. When the semaphore is incremented by 1 this signal our main thread to begin the toggling the green LED1.

Snippet 7: app_threadx.c: Interrupt callback routine for the blue user button being pushed

/**
  * @brief EXTI line detection callbacks
  *  GPIO_Pin: Specifies the pins connected EXTI line
  * @retval None
  */
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
  ULONG currentValue = 0;
  if (GPIO_Pin == GPIO_PIN_13)
  {
    /* Add additional checks to avoid multiple semaphore puts by successively
    clicking on the user button */
    tx_semaphore_info_get(&tx_app_semaphore, NULL, &currentValue, NULL, NULL, NULL);
    if (currentValue == 0)
    {
      /* Put the semaphore to release the MainThread */
      tx_semaphore_put(&tx_app_semaphore);
    }
  }
}

In addition, we must define our own delay function as shown in snippet 8 because the standard thread sleep function defined by ThreadX interferes with our low power experiment. Furthermore, we cannot use the HAL_Delay function because ThreadX schedular interferes with the interrupts generated by hardware timer implemented by HAL_Delay function.

The defined timer delay function uses the SysTick timer with 100 ticks/second so 50 ticks of wait time is equivalent to 500ms in our ThreadX application.

Snippet 8: app_threadx.c: THREADX custom delay function (not the same as tx_thread_sleep) for our specific application. 

/**
  * @brief  Application Delay function.
  *   Delay : number of ticks to wait
  * @retval None
  */
void App_Delay(ULONG Delay)
{
  ULONG initial_time = tx_time_get();
  while ((tx_time_get() - initial_time) < Delay);
}

Snippet 9 below shows all the steps that we take before putting the MCU into stop 2 low power mode. For example, we disabled all the unnecessary SRAMs and SRAM pages. We place all the GPIO pins into analog mode to save further power. Furthermore, we disable all the unused peripheral clocks.

Snippet 9: app_threadx.c: Part 1 of the C function: disable peripherals to save power and enter stop 2 mode.

/**
  * @brief  Enter LowPower Mode configuration
  *   None
  * @retval None
  */
void Enter_LowPower_Mode(void)
{
	GPIO_InitTypeDef   GPIO_InitStruct;

	/* Enable Power Clock */
	__HAL_RCC_PWR_CLK_ENABLE();

	/* Enable only 8KB SRAM2 and icache full retention in Stop2 mode */
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM1_PAGE1_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM1_PAGE2_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM1_PAGE3_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM2_PAGE2_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM3_PAGE1_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM3_PAGE2_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM3_PAGE3_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM3_PAGE4_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM3_PAGE5_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM3_PAGE6_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM3_PAGE7_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM3_PAGE8_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_SRAM4_FULL_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_DCACHE1_FULL_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_DMA2DRAM_FULL_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_PKA32RAM_FULL_STOP_RETENTION);
	HAL_PWREx_DisableRAMsContentStopRetention(PWR_PERIPHRAM_FULL_STOP_RETENTION);

	HAL_PWREx_EnableRAMsContentStopRetention(PWR_ICACHE_FULL_STOP_RETENTION);
	HAL_PWREx_EnableRAMsContentStopRetention(PWR_SRAM2_PAGE1_STOP_RETENTION);

	/* Switch to SMPS */
	if (HAL_PWREx_ConfigSupply(PWR_SMPS_SUPPLY) != HAL_OK)
	{
		Error_Handler();
	}

	/* Configuration of the LPM read mode */
	if (HAL_FLASHEx_ConfigLowPowerRead(FLASH_LPM_ENABLE) != HAL_OK)
	{
		Error_Handler();
	}

Snippet 10: app_threadx.c: Part 2 of the C function: disable peripherals to save power and enter stop 2 mode.

	/* Set all GPIO in analog state to reduce power consumption,                */
	/*   except GPIOC to keep user button interrupt enabled                     */
	/* Note: Debug using ST-Link is not possible during the execution of this   */
	/*       example because communication between ST-link and the device       */
	/*       under test is done through UART. All GPIO pins are disabled (set   */
	/*       to analog input mode) including UART I/O pins.                    */
	/* Enable all GPIO clocks */
	__HAL_RCC_GPIOA_CLK_ENABLE();
	__HAL_RCC_GPIOB_CLK_ENABLE();
	__HAL_RCC_GPIOD_CLK_ENABLE();
	__HAL_RCC_GPIOE_CLK_ENABLE();
	__HAL_RCC_GPIOF_CLK_ENABLE();
	__HAL_RCC_GPIOG_CLK_ENABLE();
	__HAL_RCC_GPIOH_CLK_ENABLE();
	__HAL_RCC_GPIOI_CLK_ENABLE();

	/* Set parameters to be configured */
	GPIO_InitStruct.Mode  = GPIO_MODE_ANALOG;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	GPIO_InitStruct.Pull  = GPIO_NOPULL;
	GPIO_InitStruct.Pin   = GPIO_PIN_ALL;

	/* Initialize all GPIO pins */
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
	HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
	HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
	HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
	HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);
	HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);

	/* Disable all GPIO clocks */
	__HAL_RCC_GPIOA_CLK_DISABLE();
	__HAL_RCC_GPIOB_CLK_DISABLE();
	__HAL_RCC_GPIOD_CLK_DISABLE();
	__HAL_RCC_GPIOE_CLK_DISABLE();
	__HAL_RCC_GPIOF_CLK_DISABLE();
	__HAL_RCC_GPIOG_CLK_DISABLE();
	__HAL_RCC_GPIOH_CLK_DISABLE();
	__HAL_RCC_GPIOI_CLK_DISABLE();

Snippet 11: app_threadx.c: Part 3 of the C function: disable peripherals to save power and enter stop 2 mode.

/* Disable all unused peripheral clocks */
	MODIFY_REG(RCC->AHB1ENR, DUMMY_VALUE, RCC_AHB1ENR_FLASHEN);
	MODIFY_REG(RCC->AHB2ENR1, DUMMY_VALUE, RCC_AHB2ENR1_SRAM2EN);
	MODIFY_REG(RCC->AHB2ENR2, DUMMY_VALUE, 0U);
	MODIFY_REG(RCC->AHB3ENR, DUMMY_VALUE, RCC_AHB3ENR_PWREN);
	MODIFY_REG(RCC->APB1ENR1, DUMMY_VALUE, RCC_APB1ENR1_TIM6EN);
	MODIFY_REG(RCC->APB1ENR2, DUMMY_VALUE, 0U);
	MODIFY_REG(RCC->APB2ENR, DUMMY_VALUE, 0U);
	MODIFY_REG(RCC->APB3ENR, DUMMY_VALUE, 0U);
	MODIFY_REG(RCC->AHB1SMENR, DUMMY_VALUE, 0U);
	MODIFY_REG(RCC->AHB2SMENR1, DUMMY_VALUE, 0U);
	MODIFY_REG(RCC->AHB2SMENR2, DUMMY_VALUE, 0U);
	MODIFY_REG(RCC->AHB3SMENR, DUMMY_VALUE, 0U);
	MODIFY_REG(RCC->APB1SMENR1, DUMMY_VALUE, 0U);
	MODIFY_REG(RCC->APB1SMENR2, DUMMY_VALUE, 0U);
	MODIFY_REG(RCC->APB2SMENR, DUMMY_VALUE, 0U);
	MODIFY_REG(RCC->APB3SMENR, DUMMY_VALUE, 0U);

	/* Disable AHB APB Peripheral Clock */
	__HAL_RCC_AHB1_CLK_DISABLE();
	__HAL_RCC_AHB2_1_CLK_DISABLE();
	__HAL_RCC_AHB2_2_CLK_DISABLE();

	__HAL_RCC_APB1_CLK_DISABLE();
	__HAL_RCC_APB2_CLK_DISABLE();

	/* Enter to the stop mode */
	HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
}

When exiting the low power mode from stop mode 2, we must reinitialize the clock and the GPIO peripheral.

Snippet 12: app_threadx.c: Re-initialize clock and peripherals

/**
  * @brief  Exit LowPower Mode configuration
  *   None
  * @retval None
  */
void Exit_LowPower_Mode(void)
{
  /* Enable AHB APB Peripheral Clock */
  __HAL_RCC_AHB1_CLK_ENABLE();
  __HAL_RCC_AHB2_1_CLK_ENABLE();
  __HAL_RCC_AHB2_2_CLK_ENABLE();

  __HAL_RCC_APB1_CLK_ENABLE();
  __HAL_RCC_APB2_CLK_ENABLE();

  /* Reconfigure the system clock*/
  SystemClock_Restore();

  /**/
  MX_GPIO_Init();
}

Note: please see "6.1.1 Missing snippet from the article" to add the additional snippet that's missing from the article. Without it, your project will not compile.

In snippet 13 below, we modify the linker script to place some of the code data into SRAM2 and most of the other data into flash. The details were discussed in figure 11. 

Snippet 13: STM32U575ZITXQ_FLASH.ld: Use 8K of SRAM2.

/* Memories definition */
MEMORY
{
  RAM	(xrw)	: ORIGIN = 0x20030000, LENGTH = 8K
  FLASH (rx)	: ORIGIN = 0x08000000, LENGTH = 2048K
}

At this stage, save and compile (hammer icon) your code. Next, click on the green play icon to upload and flash the code into the MCU, as shown on the STM32CubeIDE interface.

4. Test your project using STM32CubeMonitor-Power (optional)

If you are new to STM32CubeMonitor-Power, please refer to this link [8] for a detailed STMicroelectronics video on this topic. The video demonstrates using the “X-NUCLEO-LPM01A” power measuring tool, but we use the STLINK-V3PWR instead. The procedure is the same and much simpler with the STLINK-V3PWR.

We use the STLINK-V3PWR to make measurements for confirming our results. Figure 20 – 22 show the observed results. When the application is in low power mode, figure 20 is observed. When the blue user button on the Nucleo board is pressed, figure 21 and 22 (zoomed in snapshot) is observed.

Figure 20: STM32CubeMonitor-Power: Ten second time window power consumption (average 4.842uA) when idle.Figure 20: STM32CubeMonitor-Power: Ten second time window power consumption (average 4.842uA) when idle.
Figure 21: STM32CubeMonitor-Power: Ten second time window power consumption (average 3548.295uA) when blue user button pressed + also counting idle state.Figure 21: STM32CubeMonitor-Power: Ten second time window power consumption (average 3548.295uA) when blue user button pressed + also counting idle state.

 

Figure 22: STM32CubeMonitor-Power: Power consumption (average 7130.474uA) during the active phase of the MCU only. Idle state ignored.Figure 22: STM32CubeMonitor-Power: Power consumption (average 7130.474uA) during the active phase of the MCU only. Idle state ignored.

5. Related links

  1. https://www.st.com/resource/en/schematic_pack/mb1549-u575ziq-c03_schematic.pdf
  2. https://www.st.com/resource/en/reference_manual/rm0456-stm32u5-series-armbased-32bit-mcus-stmicroelectronics.pdf
  3. https://www.st.com/content/dam/AME/2023/MDG/low-power-threadx-example-v1.zip
  4. https://learn.microsoft.com/en-us/azure/rtos/threadx/chapter1
  5. https://www.st.com/en/development-tools/stm32cubemx.html
  6. https://www.st.com/en/development-tools/stm32cubeide.html
  7. https://www.st.com/en/development-tools/stm32cubemonpwr.html
  8. https://www.youtube.com/watch?v=COOi_BiPE5U

6. Appendix

6.1 Compilation error fix

If you run into any compilation errors then please add the following code to your project:

/**
  * @brief  Restore system clock after wake-up from STOP: enable MSI, PLL
  *         and select PLL as system clock source.
  *   None
  * @retval None
  */
void SystemClock_Restore(void)
{
	  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
	  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

	  /** Configure the main internal regulator output voltage
	  */
	  if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
	  {
	    Error_Handler();
	  }

	  /** Initializes the CPU, AHB and APB buses clocks
	  */
	  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
	  RCC_OscInitStruct.MSIState = RCC_MSI_ON;
	  RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
	  RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_4;
	  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
	  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
	  RCC_OscInitStruct.PLL.PLLMBOOST = RCC_PLLMBOOST_DIV1;
	  RCC_OscInitStruct.PLL.PLLM = 1;
	  RCC_OscInitStruct.PLL.PLLN = 80;
	  RCC_OscInitStruct.PLL.PLLP = 2;
	  RCC_OscInitStruct.PLL.PLLQ = 2;
	  RCC_OscInitStruct.PLL.PLLR = 2;
	  RCC_OscInitStruct.PLL.PLLRGE = RCC_PLLVCIRANGE_0;
	  RCC_OscInitStruct.PLL.PLLFRACN = 0;
	  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
	  {
	    Error_Handler();
	  }

	  /** Initializes the CPU, AHB and APB buses clocks
	  */
	  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
	                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
	                              |RCC_CLOCKTYPE_PCLK3;
	  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.APB3CLKDivider = RCC_HCLK_DIV1;

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

 

Comments
jacopokunak
Associate III

Very interesting application.

Just one thing:

among the snippets presented to modify "app_threadx.c" , the one in which the SystemClockRestore function is defined is missing, so if you follow the steps the project doesnt compile.

The code of the function can be found in the project folder attached to this article in part 3.

Maybe you can edit the article to include the missing snippet.

Laurids_PETERSEN
Community manager
Community manager

Hi @jacopokunak,

Thanks for your feedback. The snippet has been added to the bottom of this article.

Best regards,
Laurids

Version history
Last update:
‎2024-03-14 05:30 AM
Updated by: