Embedded Development Board Learning


Getting Started with FreeRTOS #4 Creating Tasks

Date: marzo 1, 2025

Author: Guillermo Garcia

Categories: RTOS Tags:

FreeRTOS is a widely used real-time operating system (RTOS) designed for embedded systems. One of its core components is the task, which allows developers to run multiple operations concurrently. In this article, we will explore how to create and manage FreeRTOS tasks using its API and how to debug them effectively with SystemView.

What are FreeRTOS Tasks?

A task in FreeRTOS is analogous to a thread in a traditional operating system. It represents an independent execution unit with its own stack, program counter, and context. FreeRTOS uses a scheduler to manage tasks based on their priority and scheduling policy.

Creating a Task in FreeRTOS

Crear tareas o thread en FreeRTOS es muy sencillo unicamente tenemos que hacer una llamada a la API proporcionadas por el Kernel analicemos la estructura de la funcion para crea una tarea.

BaseType_t xTaskCreate(
    TaskFunction_t pxTaskCode,
    const char * const pcName,
    configSTACK_DEPTH_TYPE usStackDepth,
    void *pvParameters,
    UBaseType_t uxPriority,
    TaskHandle_t *pxCreatedTask
);

Let’s analyze each parameter that this function needs. This is important because it is constantly necessary to create tasks in an application.

Parameters

pvTaskCode: Tasks are simply C functions that never exit and, as such, are normally implemented as an infinite loop.

pcName: A descriptive name for the task. This is not used by FreeRTOS in any way. It is included purely as a debugging aid. Identifying a task by a human readable name is much simpler than attempting to identify it by its handle.

usStackDepth: Each task has its own unique stack that is allocated by the kernel to the task when the task is created. The usStackDepth value tells the kernel how large to make the stack.

pvParameters: Task functions accept a parameter of type pointer to void ( void* ). The value assigned to pvParameters will be the value passed into the task. Some examples in this document demonstrate how the parameter can be used.

uxPriority: Defines the priority at which the task will execute. Priorities can be assigned from 0, which is the lowest priority, to (configMAX_PRIORITIES – 1), which is the highest priority. configMAX_PRIORITIES is a user defined constant. There is no upper limit to the number of priorities that can be available (other than the limit of the data types used and the RAM available in your microcontroller), but you should use the lowest number of priorities required, to avoid wasting RAM.

pxCreatedTask: pxCreatedTask can be used to pass out a handle to the task being created. This handle can then be used to reference the task in API calls that, for example, change the task priority or delete the task.

Example: Creating a Simple Task

void vTaskFunction(void *pvParameters) {
    while (1) {
        printf("Task running...\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void main() {
    xTaskCreate(vTaskFunction, "Task1", 1000, NULL, 1, NULL);
    vTaskStartScheduler();
}

FreeRTOS Tasks Priorities

The uxPriority parameter of the xTaskCreate() API function assigns an initial priority to the task being created.

The maximum number of priorities available is set by the application-defined configMAX_PRIORITIES compile time configuration constant within FreeRTOSConfig.h.

/* This is in the FreeRTOSConfig.h file */
 #define configMAX_PRIORITIES			( 5 )

Low numeric priority values denote low-priority tasks, with priority 0 being the lowest priority possible. Therefore, the range of available priorities is 0 to (configMAX_PRIORITIES – 1).


The scheduler will always ensure that the highest priority task that is able to run is the task selected to enter the Running state. Where more than one task of the same priority is able to run, the scheduler will transition each task into and out of the Running state, in turn.

FreeRTOS Tasks States

Tasks in FreeRTOS can be in one of four states at any time: Running, Ready, Suspended, and Blocked, shows the possible task states and also shows how a task can move from one state to another state.

Tasks States

The Blocked State: A task that is waiting for an event is said to be in the ‘Blocked’ state, which is a sub-state of the Not Running state. Tasks can enter the Blocked state to wait for two different types of event:

  • Synchronization events—where the events originate from another task or interrupt. For
    example, a task may enter the Blocked state to wait for data to arrive on a queue.
    Synchronization events cover a broad range of event types.
  • Temporal (time-related) events—the event being either a delay period expiring, or an
    absolute time being reached. For example, a task may enter the Blocked state to wait
    for 10 milliseconds to pass.

The Suspended State: ‘Suspended’ is also a sub-state of Not Running. Tasks in the Suspended state are not available to the scheduler. The only way into the Suspended state is through a call to the vTaskSuspend() API function, the only way out being through a call to the vTaskResume() or xTaskResumeFromISR() API functions. Most applications do not use the Suspended state.

The Ready State: Tasks that are in the Not Running state but are not Blocked or Suspended are said to be in the Ready state. They are able to run, and therefore ‘ready’ to run, but are not currently in the Running state.

FreeRTOS Tasks API

FreeRTOS provides various APIs to manage tasks efficiently:

APIs to control tasks:

Task Management

Once a task has been created, its possible to control and modify its behavior. You have to enable each task management function with the following includes in your FreeRTOSConfig.h.

/* This is in the FreeRTOSConfig.h file */
#define INCLUDE_vTaskPrioritySet			0
#define INCLUDE_uxTaskPriorityGet			0
#define INCLUDE_vTaskDelete			        0
#define INCLUDE_vTaskCleanUpResources		    	0
#define INCLUDE_vTaskSuspend			        0
#define INCLUDE_vTaskDelayUntil			       	0
#define INCLUDE_vTaskDelay			        1
#define INCLUDE_uxTaskGetStackHighWaterMark		0
#define INCLUDE_eTaskGetState			      	0

Debugging FreeRTOS Tasks with SystemView

Crearemos dos tareas para ver el comportamiento cuando asignamos diferentes prioridades a cada tareas para esto visualizaremos esto desde SystemView.

Tasks with the same priority

Vamos a crear dos tareas llamadas Tarea1 y Tarea2 con la misma prioridad ambas tareas envian un mensaje cuando estan utilizando tiempo de CPU.

#include "CreateTask.h"

void Task1(void* NotUsed)
{
  while(1)
  {   
    HAL_GPIO_TogglePin(LD3_GPIO_Port,LD3_Pin);
    SEGGER_SYSVIEW_Print("Task 1 running\n");

    /* Delay for a period. This time a call to vTaskDelay() is used which
    places the task into the Blocked state until the delay period has expired.
    The delay period is specified in 'ticks', but the constant
    portTICK_RATE_MS can be used to convert this to a more user friendly value
    in milliseconds. In this case a period of 250 milliseconds is being
    specified. */
    vTaskDelay( 250 / portTICK_RATE_MS ); 
    
  }

}

void Task2(void* NotUsed)
{
  while(1)
  {   
    HAL_GPIO_TogglePin(LD4_GPIO_Port,LD4_Pin);
    SEGGER_SYSVIEW_Print("Task 2 running\n");
    
    /* Delay for a period. This time a call to vTaskDelay() is used which
    places the task into the Blocked state until the delay period has expired.
    The delay period is specified in 'ticks', but the constant
    portTICK_RATE_MS can be used to convert this to a more user friendly value
    in milliseconds. In this case a period of 250 milliseconds is being
    specified. */
    vTaskDelay( 250 / portTICK_RATE_MS ); 
  }

}

void Creating_Tasks(void)
{
  /* Create two task */
  xTaskCreate(Task1,"Tarea1",configMINIMAL_STACK_SIZE,NULL,tskIDLE_PRIORITY+1,NULL);
  xTaskCreate(Task2,"Tarea2",configMINIMAL_STACK_SIZE,NULL,tskIDLE_PRIORITY+1,NULL);
}

int main()
{
  HAL_SYSTICK_Config(SystemCoreClock / (1000U / (uint32_t)uwTickFreq));
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0U);
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

  traceSTART();
  Creating_Tasks();
  vTaskStartScheduler();
}

Let’s see how the CPU time is distributed and how many tasks are in the system we can see this in the context section of SystemView.

Tasks CPU time

We can see that task 1 and task 2 have equal CPU load since both have the same priority and block time is the same. We see that the SysTick has the largest CPU load since it is the FreeRTOS Tick that is periodically generated by calling the scheduler and performing the context switch.

Tarea 1 esta en ejecucion en enseguida viene la llamada a vTaskDelay( 250 / portTICK_RATE_MS ); lo que provoca que la tarea entre en modo de bloque.

Tasks CPU time task1

La tarea1 esta en modo Bloqueado en ese momento se reaunuda la tarea2 entra en modo de ejecucion.

Tasks CPU time task2

Closing

Creating tasks is very simple but you have to experiment with changing the priority and blocking times. In this way, we can learn how the scheduler behaves and begin to understand how to create our most complex applications. Thanks to SystemView, we can see this behavior graphically.



Card image cap
Guillermo Garcia Thanks for reading! I am one of the editors of this site and I am committed to providing you with the best information possible by sharing my experience in embedded systems.


Comments... There are no comments.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Publicidad