Introduction
In this article, we cover the needed steps to use the FreeRTOS™ in tickless mode, specifically entering in STOP2 low-power mode with the STM32U5. In this example, we use the X-CUBE-FREERTOS™ pack with the LPUART and EXTI as the wake up sources.
All the implementation was done over STM32CubeIDE 1.12.1 with STM32CubeMX 6.8.1, STM32CubeU5 1.2.0, and X-CUBE-FREERTOS 1.0.1. The NUCLEO-U575ZI-Q with STM32U575ZIT6Q MCU hardware was used but the steps can be easily tailored to any other ST microcontroller, with minor changes.
Development
The first step is to create a project in STM32CubeMX for the target MCU, in this case we create the project for the STM32U575ZIT6Q. After its creation and naming, open the SYS tab. Under System Core, select
a timer as Timebase Source, TIM6 was arbitrarily selected. This step is needed since the SysTick timer is used by FreeRTOS™.
While in the System Core, locate the GPIO tab and set the PC13 as EXTI with External Interrupt Mode with Rising edge trigger detection. There is a push-button (active high) connected into this pin in NUCLEO-U575ZI-Q with a pull-down and a hardware debounce filter.
Under Connectivity tab, enable the LPUART1 as Asynchronous mode and apply your settings. In this tutorial, we use the standard UART settings (8/N/1), but with the 9600 bits/s for the baud rate.
Back to the System Core tab, open the NVIC and enable both EXTI Line 13 and LPUART global interrupts.
Enable the LSE clock source only if you intend to use the LSE as clock source for the LPUART1 while in STOP2 low-power mode. If this applies to your case, in RCC menu select the Crystal/Ceramic Resonator for the LSE.
Change the Power Regulator to SMPS under PWR -> Power saving mode to achieve better current consumption values in low-power.
Now, all peripherals are set. Let’s add and configure the FreeRTOS in our application. For that, open the Select Components menu.
Select the X-CUBE-FREERTOS with Heap_4, then click OK.
Returning to STM32CubeMX, enable the CMSIS RTOS2 under Middleware and Software Packs -> X-CUBE-FREERTOS tab.
Create a semaphore to control the application flow. For that, go to the Timers and Semaphores tab, then click over the Add button under Binary Semaphores section. A menu called New Binary Semaphore appears. Give a name for the semaphore and click OK.
The last setting for the FreeRTOS is to enable the Tickless mode. For that, go to Config parameters tab
and enable the USE_TICKLESS_IDLE.
Now, let’s set the Clock Configuration. Increase the MCU clock frequency as needed by the application and change the LPUART1 clock source. This peripheral clock should be supplied by HSI, LSE or MSIK to allow it to work in STOP2. While the HSI allows higher baud rates, the LSE has lower power consumption. In the image below the LSE is used, but refer to the end of this article to see a power consumption comparison between these two clock sources.
All settings are done! Generate the code for your preferred IDE and open it.
Once opened the project, open the Core/Src/app_freertos.c file. All the code implementations are done in this file. The first code is to include the stdio to use the printf function to log through the LPUART.
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
Create a variable to receive the UART data and get the LPUART handler.
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
__IO uint8_t rxChar;
extern UART_HandleTypeDef hlpuart1;
/* USER CODE END Variables */
Get the SystemClock_Config function from main.c by adding the following function prototype.
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
extern void SystemClock_Config(void);
/* USER CODE END FunctionPrototypes */
Now, in MX_FREERTOS_Init function, add the following code.
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
__HAL_RCC_LPUART1_CLKAM_ENABLE(); //Enable LPUART in Autonomous mode
HAL_UARTEx_EnableStopMode(&hlpuart1); //Retain the LPUART clock in STOP
HAL_UART_Receive_IT(&hlpuart1, (uint8_t*)&rxChar, 1);
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
osSemaphoreAcquire(WakeSemHandle, osWaitForever);
/* USER CODE END RTOS_SEMAPHORES *
Add the DefaultTask code:
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN defaultTask */
/* Local variables */
printf("Default task started...\r\n");
/* Infinite loop */
for(;;)
{
printf("Default task waiting for a semaphore...\r\n");
osSemaphoreAcquire(WakeSemHandle, osWaitForever);
printf("Default task running...\r\n");
HAL_Delay(300);
}
/* USER CODE END defaultTask */
}
Add the printf, EXTI and UART callback codes:
/* Private application code --------------------------------------------------*/
/* USER CODE BEGIN Application */
int __io_putchar(int ch)
{
HAL_UART_Transmit(&hlpuart1, (uint8_t *)&ch, 1, 10);
return ch;
}
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
printf("Woke-up from EXTI\r\n");
osSemaphoreRelease(WakeSemHandle);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
printf("Woke-up from LPUART, message: %c\r\n", rxChar);
HAL_UART_Receive_IT(&hlpuart1, (uint8_t*)&rxChar, 1);
osSemaphoreRelease(WakeSemHandle);
}
/* USER CODE END Application */
Finally, add the code to enter and exit from low-power mode.
/* USER CODE BEGIN PREPOSTSLEEP */
void PreSleepProcessing(uint32_t ulExpectedIdleTime)
{
/* place for user code */
HAL_SuspendTick();
printf("Entering in STOP2 Mode...\r\n");
HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
}
void PostSleepProcessing(uint32_t ulExpectedIdleTime)
{
/* place for user code */
SystemClock_Config();
printf("\nExit from STOP2 Mode\r\n");
HAL_ResumeTick();
}
/* USER CODE END PREPOSTSLEEP */
NOTE1: the ulExpectedIdleTime variable is not used in this case, as it allows us to implement our own Low-power entry code if needed.We are all set! Compile and program the code into MCU. Perform a power-cycle to disable the Debugger to avoid unexpected Wake-up sources and elevated power consumption. Connect the UART into a Terminal and see the results!NOTE2: The LPUART1 is not the default connection to the ST-LINK VCOM embedded in NUCLEO-U575ZI-Q. You should connect this manually to a computer or change the solder bridges as illustrated in this image, extracted from the board’s user manual:
Results
When the application starts, the DefaultTask is started and runs until the osSemaphoreGet instruction. After that, the Idle task is started and the PreSleepProcessing function is called and the MCU can enter in Low-Power Tickless mode.
As set in the project, there are 2 available wake-up interrupt sources. The LPUART1 that waits for a character to be received and the EXTI that waits for the button to be pressed. Whenever an interruption happens, the PostSleepProcessing function is called, where we have the code to prepare the MCU after waking up from STOP2 mode.
The interrupt calls the callback function (according to the wake up source used) which releases a semaphore, letting the DefaultTask back to run mode until the osSemaphoreGet instruction is detected again.
The expected application behavior is illustrated in the image below, showing the TeraTerm view and messages output:
The power consumption can be monitored using the STM32CubeMonitor-Power 1.1.1 along with one of the available hardware for measurement, including: STLINK-V3PWR probe, the X-NUCLEO-LPM01A expansion board, or the energy meter of the STM32L562E-DK Discovery kit. The results are shown in the images below, starting with the LSE to supply the LPUART clock source, the lower consumption registered was around 8µA:
The occurrence of interruptions wakes-up the MCU and the entry of DefaultTask into run mode are indicated by the current peaks, while the low current consumption signifies the periods during which the MCU is in STOP2 Mode.
To offer a quick comparison between the consumption of LSE and HSI as clock sources for the LPUART, the following graph displays the application consumption while utilizing the HSI.
The average consumption of 165µA was observed when the LPUART used the HSI as its clock source, whereas the consumption was around 8µA when the LSE was utilized.
Conclusion
This article demonstrates that the FreeRTOS Tickless mode is a valuable feature when low-power profiles are needed while using RTOS. As demonstrated in the tutorial, the process is straightforward, which the RTOS automatically manages the moment the kernel enters in Idle mode. This allows us to invoke and select the low power mode from the MCU desired, in this case, the STOP2.
That concludes our tutorial! We hope that you enjoyed reading it.