This article introduces the concept of tasks in the FreeRTOS operating system, including task creation, deletion, and task states.1. TaskFreeRTOS tasks are the core units for implementing concurrent execution of multiple tasks, essentially functions with independent execution environments.Each task is an infinite loop function (or a self-deleting function) that has its own stack space and context (register state), and can be viewed as a “small program” running independently in the system, similar to “threads” in other operating systems.1.1 Task States
- RunningThe task currently occupying the CPU for execution; only one task can be in this state at any given time.
- ReadyThe task is ready to execute but is waiting for scheduling due to a lower priority than the currently running task.
- BlockedThe task is paused due to waiting for an event (such as delay or message queue data). For example, if a task calls vTaskDelay(), it will be blocked (placed in the blocked state) until the delay ends—an event based on time. A task can also be blocked waiting for queues, semaphores, event groups, notifications, or semaphore events. Tasks in the blocked state typically have a “timeout” period, after which they will be unblocked even if the event they were waiting for has not occurred. A task in the “blocked” state does not use any processing time and cannot be selected to enter the running state.
- SuspendedSimilar to tasks in the “blocked” state, tasks in the “suspended” state cannot be selected to enter the running state, but tasks in the suspended state do not have a timeout. Instead, a task will only enter or exit the suspended state when explicitly commanded through the vTaskSuspend() and xTaskResume() API calls.
1.2 Introduction to Task-Related APIs1.2.1 xTaskCreate DynamicCreates a new task and adds it to the list of tasks ready to run
BaseType_t xTaskCreate( TaskFunction_t pvTaskCode, const char * const pcName, const configSTACK_DEPTH_TYPE uxStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask );
Note:configSUPPORT_DYNAMIC_ALLOCATION must be set to 1 or undefined (default is 1) in FreeRTOSConfig.h to use this RTOS API function.
Parameters:
-
pvTaskCode
Pointer to the task entry function (i.e., the name of the function implementing the task). Tasks are typically implemented as infinite loops; the function implementing the task must never attempt to return or exit. However, tasks can delete themselves.
-
pcName
The descriptive name of the task. This parameter is mainly for debugging convenience, but can also be used to obtain the task handle. The maximum length of the task name is defined by configMAX_TASK_NAME_LEN in FreeRTOSConfig.h.
-
uxStackDepth
The number of words (not bytes!) to allocate for the task stack. The product of stack depth and stack width must not exceed the maximum value that a size_t type variable can hold.
-
pvParameters
The value passed as a parameter to the created task. If pvParameters is set to the address of a variable, that variable must still exist when the created task executes, so the address of stack variables cannot be passed.
-
uxPriority
The task will execute at the specified priority.
-
pxCreatedTask
Used to pass the handle to the task created by the xTaskCreate() function. pxCreatedTask is an optional parameter and can be set to NULL.
Note: TaskHandle_t task.h
The type referenced by the task. For example, calling xTaskCreate (via pointer parameter) returns a TaskHandle_t variable, which can then be used as a parameter for vTaskDelete to delete the task.
Returns:
- If the task creation is successful, returns pdPASS,
- otherwise returns errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY.
1.2.2 xTaskCreateStatic StaticCreates a new task and adds it to the list of tasks ready to run.
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, const char * const pcName, const uint32_t ulStackDepth, void * const pvParameters, UBaseType_t uxPriority, StackType_t * const puxStackBuffer, StaticTask_t * const pxTaskBuffer );
Note:
-
configSUPPORT_STATIC_ALLOCATION must be set to 1 in FreeRTOSConfig.h to use this RTOS API function.
-
Each task requires RAM to save its state and to be used as its stack. If a task is created using xTaskCreate(), the required RAM will be automatically allocated from the FreeRTOS heap. If a task is created using xTaskCreateStatic() the RAM is provided by the application writer, which results in more parameters but allows for static RAM allocation at compile time.
Parameters:
-
pxTaskCode
Pointer to the task entry function (i.e., the name of the function implementing the task). Same asxTaskCreate.
-
pcName
The descriptive name of the task. Same asxTaskCreate.
-
ulStackDepth
puxStackBuffer parameter is used to pass an array of StackType_t variables to xTaskCreateStatic(). ulStackDepth must be set to the number of indices in the array. Same as xTaskCreate. -
pvParameters
The value passed as a parameter to the created task. Same asxTaskCreate.
-
uxPriority
The created task will execute at the specified priority. Same asxTaskCreate.
-
puxStackBuffer
Must point to an array of StackType_t containing at least ulStackDepth indices (see the above ulStackDepth parameter), which will be used as the task stack, and must persist (cannot be declared on the function stack).
-
pxTaskBuffer
Must point to a variable of type StaticTask_t. This variable will be used to save the new task’s data structure (TCB), so it must persist (cannot be declared on the function stack).
Returns:
If both puxStackBuffer and pxTaskBuffer are not NULL, the task is created and the task’s handle is returned. If either puxStackBuffer or pxTaskBuffer is NULL, the task will not be created, and NULL will be returned.
1.2.3 vTaskDelete Removes a task from RTOS kernel management. The task to be deleted will be removed from all ready, blocked, suspended, and event lists.
void vTaskDelete( TaskHandle_t xTask );
Note:
-
INCLUDE_vTaskDelete must be defined as 1 to use this function.
-
The idle task is responsible for releasing the memory allocated by the RTOS kernel to the deleted task. Therefore, if the application calls vTaskDelete(), it is essential to ensure that the idle task has enough microcontroller processing time. Memory allocated by the task code will not be automatically released and should be manually freed before the task is deleted.
1.2.3 vTaskDelay Specifies the time the task wants to remain blocked
void vTaskDelay( const TickType_t xTicksToDelay );
Note:
-
INCLUDE_vTaskDelay must be defined as 1 to use this function.
-
Delays the task for a given number of ticks. The actual time the task remains blocked depends on the tick frequency.
-
The constant portTICK_PERIOD_MS can be used to calculate the actual time based on the tick frequency, where the resolution of one tick period is used. Therefore, vTaskDelay() does not control the frequency of periodic tasks well, as the path through the code and other tasks and interrupts will affect the frequency at which vTaskDelay() is called, thus affecting the time of the next execution of the task.
2. Implementing Task Creation
Objective: Based on the STM32H573I-DK development board, create a task that makes the LED light blink every 500ms.
2.1 Three functions created by STMCubeMX to port the FreeRTOS system.
/* Init scheduler */ osKernelInitialize(); /* Call init function for freertos objects (in app_freertos.c) */ MX_FREERTOS_Init(); /* Start scheduler */ osKernelStart();
<span><span>osKernelInitialize</span> </span><span>Initializes the scheduler. Initializes the RTOS kernel. This step completes <span>memory allocation, interrupt setup, and initialization of task control blocks, etc.</span> Only after successful kernel initialization can subsequent task creation and scheduling proceed normally.</span><span><span>MX_FREERTOS_Init</span> </span><span>Mainly used to initialize user-defined FreeRTOS objects. These objects include <span>tasks, queues, semaphores, mutexes, etc.</span> Their definitions are usually located in</span><code><span><span>app_freertos.c</span></span>This function is generally auto-generated by tools like CubeMX, but can also be manually written by developers.<span><span>osKernelStart</span> </span>
Starts the task scheduler. Once the scheduler starts, it begins executing tasks that are in the ready state based on their priority. Once the scheduler starts, control is handed over to the operating system, which is responsible for managing task execution..
2.2 Hardware Introduction
4 User LEDs, high level output from the microcontroller IO means LED off; low level output from the microcontroller IO means LED on.
2.3 Add gpio.h header file inapp_freertos.h
Note: Please perform user code operations in the areas specified by STMCubeMX.
/* USER CODE BEGIN Includes */#include "gpio.h"/* USER CODE END Includes */
2.4 Define LED1TaskHandle task handle and LED1Task_attributes task attributes
/* USER CODE BEGIN Variables *//*LED task define*/osThreadId_t LED1TaskHandle;const osThreadAttr_t LED1Task_attributes = { .name = "LED1Task", .priority = (osPriority_t) osPriorityNormal, .stack_size = 128 * 4};/* USER CODE END Variables */
Where:
typedef void* osThreadId_t;
2.5 Define the task calling function StartLED1Task
/* Private application code --------------------------------------------------*//* USER CODE BEGIN Application *//*** @brief Function implementing the LED1 thread.* @param argument: Not used* @retval None*/void StartLED1Task(void *argument){ HAL_GPIO_WritePin(USER_LED1_GPIO_Port,USER_LED1_Pin,GPIO_PIN_SET); HAL_GPIO_WritePin(USER_LED2_GPIO_Port,USER_LED2_Pin,GPIO_PIN_SET); HAL_GPIO_WritePin(USER_LED3_GPIO_Port,USER_LED3_Pin,GPIO_PIN_SET); HAL_GPIO_WritePin(USER_LED4_GPIO_Port,USER_LED4_Pin,GPIO_PIN_SET); while(1) { HAL_GPIO_TogglePin(USER_LED1_GPIO_Port,USER_LED1_Pin); vTaskDelay(500); }}/* USER CODE END Application */
Note:
- Since the BSP project for the development board is used, macros defined by ST can be directly referenced.
- Using the vTaskDelay function (orosDelay function) will block the current task.osDelay actually wraps the vTaskDelay function.
/* Wait for Timeout (Time Delay).*/osStatus_t osDelay (uint32_t ticks) { osStatus_t stat; if (IRQ_Context() != 0U) { stat = osErrorISR; } else { stat = osOK; if (ticks != 0U) { vTaskDelay(ticks); } } /* Return execution status */ return (stat);}
2.6 Implement task creation osThreadNew
/** * @brief FreeRTOS initialization * @param None * @retval None */void MX_FREERTOS_Init(void) { /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* USER CODE BEGIN RTOS_MUTEX */ /* add mutexes, ... */ /* USER CODE END RTOS_MUTEX */ /* USER CODE BEGIN RTOS_SEMAPHORES */ /* add semaphores, ... */ /* USER CODE END RTOS_SEMAPHORES */ /* USER CODE BEGIN RTOS_TIMERS */ /* start timers, add new ones, ... */ /* USER CODE END RTOS_TIMERS */ /* USER CODE BEGIN RTOS_QUEUES */ /* add queues, ... */ /* USER CODE END RTOS_QUEUES */ /* creation of defaultTask */ defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes); /* USER CODE BEGIN RTOS_THREADS */ /* add threads, ... */ LED1TaskHandle = osThreadNew(StartLED1Task, NULL, &LED1Task_attributes); /* USER CODE END RTOS_THREADS */ /* USER CODE BEGIN RTOS_EVENTS */ /* add events, ... */ /* USER CODE END RTOS_EVENTS */}
/* ==== Thread Management Functions ==== *//* Create a thread and add it to Active Threads. Limitations: - The memory for control block and stack must be provided in the osThreadAttr_t structure in order to allocate object statically. - Attribute osThreadJoinable is not supported, NULL is returned if used.*/osThreadId_t osThreadNew (osThreadFunc_t func, void *argument, const osThreadAttr_t *attr) { const char *name; uint32_t stack; TaskHandle_t hTask; UBaseType_t prio; int32_t mem; hTask = NULL; if ((IRQ_Context() == 0U) && (func != NULL)) { stack = configMINIMAL_STACK_SIZE; prio = (UBaseType_t)osPriorityNormal; name = NULL; mem = -1; if (attr != NULL) { if (attr->name != NULL) { name = attr->name; } if (attr->priority != osPriorityNone) { prio = (UBaseType_t)attr->priority; } if ((prio < osPriorityIdle) || (prio > osPriorityISR) || ((attr->attr_bits & osThreadJoinable) == osThreadJoinable)) { /* Invalid priority or unsupported osThreadJoinable attribute used */ return (NULL); } if (attr->stack_size > 0U) { /* In FreeRTOS stack is not in bytes, but in sizeof(StackType_t) which is 4 on ARM ports. */ /* Stack size should be therefore 4 byte aligned in order to avoid division caused side effects */ stack = attr->stack_size / sizeof(StackType_t); } if ((attr->cb_mem != NULL) && (attr->cb_size >= sizeof(StaticTask_t)) &&& (attr->stack_mem != NULL) &&& (attr->stack_size > 0U)) { /* The memory for control block and stack is provided, use static object */ mem = 1; } else { if ((attr->cb_mem == NULL) &&& (attr->cb_size == 0U) &&& (attr->stack_mem == NULL)) { /* Control block and stack memory will be allocated from the dynamic pool */ mem = 0; } } } else { mem = 0; } if (mem == 1) { #if (configSUPPORT_STATIC_ALLOCATION == 1) hTask = xTaskCreateStatic ((TaskFunction_t)func, name, stack, argument, prio, (StackType_t *)attr->stack_mem, (StaticTask_t *)attr->cb_mem); #endif } else { if (mem == 0) { #if (configSUPPORT_DYNAMIC_ALLOCATION == 1) if (xTaskCreate ((TaskFunction_t)func, name, (configSTACK_DEPTH_TYPE)stack, argument, prio, &hTask) != pdPASS) { hTask = NULL; } #endif } } } /* Return thread ID */ return ((osThreadId_t)hTask);}
Note: The osThreadNew function actually wraps xTaskCreateStatic and xTaskCreate. Calling osThreadNew dynamically creates the LED1Task and implements the conversion between osThreadId_t and TaskHandle_t.At this point, the LED blinking function has been implemented.