ESP32 FreeRTOS Tasks

API

Tasks

The main job of all operating systems is to run and coordinate user tasks. Like many operating systems, the basic unit of work in FreeRTOS is the task. FreeRTOS uses a Task Control Block (TCB) to represent each task.

Task Control Block (TCB)

The TCB is defined in tasks.c like this:

typedef struct tskTaskControlBlock
{
  volatile portSTACK_TYPE *pxTopOfStack;                  /* Points to the location of
                                                             the last item placed on 
                                                             the tasks stack.  THIS 
                                                             MUST BE THE FIRST MEMBER 
                                                             OF THE STRUCT. */
                                                         
  xListItem    xGenericListItem;                          /* List item used to place 
                                                             the TCB in ready and 
                                                             blocked queues. */
  xListItem    xEventListItem;                            /* List item used to place 
                                                             the TCB in event lists.*/
  unsigned portBASE_TYPE uxPriority;                      /* The priority of the task
                                                             where 0 is the lowest 
                                                             priority. */
  portSTACK_TYPE *pxStack;                                /* Points to the start of 
                                                             the stack. */
  signed char    pcTaskName[ configMAX_TASK_NAME_LEN ];   /* Descriptive name given 
                                                             to the task when created.
                                                             Facilitates debugging 
                                                             only. */

  #if ( portSTACK_GROWTH > 0 )
    portSTACK_TYPE *pxEndOfStack;                         /* Used for stack overflow 
                                                             checking on architectures
                                                             where the stack grows up
                                                             from low memory. */
  #endif

  #if ( configUSE_MUTEXES == 1 )
    unsigned portBASE_TYPE uxBasePriority;                /* The priority last 
                                                             assigned to the task - 
                                                             used by the priority 
                                                             inheritance mechanism. */
  #endif

} tskTCB;


The TCB stores the address of the stack start address in pxStack and the current top of stack in pxTopOfStack. It also stores a pointer to the end of the stack in pxEndOfStack to check for stack overflow if the stack grows “up” to higher addresses. If the stack grows “down” to lower addresses then stack overflow is checked by comparing the current top of stack against the start of stack memory in pxStack.

The TCB stores the initial priority of the task in uxPriority and uxBasePriority. A task is given a priority when it is created, and a task’s priority can be changed. If FreeRTOS implements priority inheritance then it uses uxBasePriority to remember the original priority while the task is temporarily elevated to the “inherited” priority. (See the discussion about mutexes below for more on priority inheritance.)

Each task has two list items for use in FreeRTOS’s various scheduling lists. When a task is inserted into a list FreeRTOS doesn’t insert a pointer directly to the TCB. Instead, it inserts a pointer to either the TCB’s xGenericListItem or xEventListItem. These xListItem variables let the FreeRTOS lists be smarter than if they merely held a pointer to the TCB. We’ll see an example of this when we discuss lists later.

A task can be in one of four states: running, ready to run, suspended, or blocked. You might expect each task to have a variable that tells FreeRTOS what state it’s in, but it doesn’t. Instead, FreeRTOS tracks task state implicitly by putting tasks in the appropriate list: ready list, suspended list, etc. The presence of a task in a particular list indicates the task’s state. As a task changes from one state to another, FreeRTOS simply moves it from one list to another.

xTaskCreatePinnedToCore

Create a new task with a specified affinity.
This function is similar to xTaskCreate, but allows setting task affinity in SMP system.
Return
pdPASS if the task was successfully created and added to a ready list, otherwise an error code defined in the file projdefs.h
Parameters
pvTaskCode: Pointer to the task entry function. Tasks must be implemented to never return (i.e. continuous loop).
pcName: A descriptive name for the task. This is mainly used to facilitate debugging. Max length defined by configMAX_TASK_NAME_LEN – default is 16.
usStackDepth: The size of the task stack specified as the number of bytes.
pvParameters: Pointer that will be used as the parameter for the task being created.
uxPriority: The priority at which the task should run. Systems that include MPU support can optionally create tasks in a privileged (system) mode by setting bit portPRIVILEGE_BIT of the priority parameter. For example, to create a privileged task at priority 2 the uxPriority parameter should be set to ( 2 | portPRIVILEGE_BIT ).
pvCreatedTask: Used to pass back a handle by which the created task can be referenced.
xCoreID: If the value is tskNO_AFFINITY, the created task is not pinned to any CPU, and the scheduler can run it on any core available. Values 0 or 1 indicate the index number of the CPU which the task should be pinned to. Specifying values larger than (portNUM_PROCESSORS – 1) will cause the function to fail.

xTaskCreate

similar to xTaskCreatePinnedToCore but it will work with core 0 like main loop function
Create a new task and add it to the list of tasks that are ready to run.
Internally, within the FreeRTOS implementation, tasks use two blocks of memory. The first block is used to hold the task’s data structures. The second block is used by the task as its stack. If a task is created using xTaskCreate() then both blocks of memory are automatically dynamically allocated inside the xTaskCreate() function. (see http://www.freertos.org/a00111.html). If a task is created using xTaskCreateStatic() then the application writer must provide the required memory. xTaskCreateStatic() therefore allows a task to be created without using any dynamic memory allocation.

vTaskDelete

Remove a task from the RTOS real time kernel’s management.
The task being deleted will be removed from all ready, blocked, suspended and event lists.
INCLUDE_vTaskDelete must be defined as 1 for this function to be available. See the configuration section for more information.
The idle task is responsible for freeing the kernel allocated memory from tasks that have been deleted. It is therefore important that the idle task is not starved of microcontroller processing time if your application makes any calls to vTaskDelete ().
Memory allocated by the task code is not automatically freed, and should be freed before the task is deleted.

create two tasks with different priorities

xTaskHandle task,task2;

void taskFunction(void* parameter){
  for(;;){
    Serial.println("task 1");
    delay(1000);
  }
  vTaskDelete(task); //delete task once you break the loop dont forget to free the memory
}
void taskFunction2(void* parameter){
  for(;;){
    Serial.println("task 2");
    delay(1000);
  }
  vTaskDelete(task);
}

void setup(){
  Serial.begin(9600);
  xTaskCreate(taskFunction,"task1",2048,nullptr,1,&task);
  //at first time resume first but because it has lower priority 
    in the next Time it will be resumed after task2
  xTaskCreate(taskFunction2,"task2",2048,nullptr,2,&task2);
}

void loop(){
}

xTaskCreatePinnedToCore
if value of core is tskNO_AFFINITY, the created task is not pinned to any CPU, and the scheduler can run it on any core available

void taskFunction(void* parameter){
  for(;;){
    Serial.println(xPortGetCoreID());
    vTaskDelay(1000);//similar to delay with arduino
  }
  vTaskDelete(task);
}

void setup(){
  Serial.begin(9600);
  if(xTaskCreatePinnedToCore(taskFunction,"task1",2048,nullptr,2,&task,0)){
    Serial.println("task created and schedualed with ready list ");
  }
}

vTaskDelay and vTaskDelayUntil
vTaskDelay () will cause a task to block for the specified number of ticks from the time vTaskDelay () is called. It is therefore difficult to use vTaskDelay () by itself to generate a fixed execution frequency as the time between a task starting to execute and that task calling vTaskDelay () may not be fixed [the task may take a different path though the code between calls, or may get interrupted or preempted a different number of times each time it executes].
Whereas vTaskDelay () specifies a wake time relative to the time at which the function is called, vTaskDelayUntil () specifies the absolute (exact) time at which it wishes to unblock.

void vTaskFunction( void * pvParameters )
{
TickType_t xLastWakeTime;
const TickType_t xFrequency = 10;

    // Initialise the xLastWakeTime variable with the current time.
    xLastWakeTime = xTaskGetTickCount ();
    for( ;; )
    {
        // Wait for the next cycle.
        vTaskDelayUntil( &xLastWakeTime, xFrequency );

        // Perform action here.
    }
}

eTaskGetState
eRunning = 0
A task is querying the state of itself, so must be running.
eReady
The task being queried is in a read or pending ready list.
eBlocked
The task being queried is in the Blocked state.
eSuspended
The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out.
eDeleted
The task being queried has been deleted, but its TCB(task control block) has not yet been freed.

vTaskSuspend,vTaskResume

void vAFunction( void )
{
TaskHandle_t xHandle;

 // Create a task, storing the handle.
 xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );

 // Use the handle to suspend the created task.
 vTaskSuspend( xHandle );

 // The created task will not run during this period, unless
 // another task calls vTaskResume( xHandle ).

 // Resume the suspended task ourselves.
 vTaskResume( xHandle );

 // The created task will once again get microcontroller processing
 // time in accordance with its priority within the system.
}

vTaskSuspendAll,xTaskResumeAll
Suspends the scheduler without disabling interrupts.
Context switches will not occur while the scheduler is suspended.
After calling vTaskSuspendAll () the calling task will continue to execute without risk of being swapped out until a call to xTaskResumeAll () has been made.
API functions that have the potential to cause a context switch (for example, vTaskDelayUntil(), xQueueSend(), etc.) must not be called while the scheduler is suspended.

void vTask1( void * pvParameters )
{
 for( ;; )
 {
     // Task code goes here.

     // ...

     // At some point the task wants to perform a long operation during
     // which it does not want to get swapped out.  It cannot use
     // taskENTER_CRITICAL ()/taskEXIT_CRITICAL () as the length of the
     // operation may cause interrupts to be missed - including the
     // ticks.

     // Prevent the real time kernel swapping out the task.
     vTaskSuspendAll ();

     // Perform the operation here.  There is no need to use critical
     // sections as we have all the microcontroller processing time.
     // During this time interrupts will still operate and the kernel
     // tick count will be maintained.

     // ...

     // The operation is complete.  Restart the kernel.
     xTaskResumeAll ();
 }
}

xTaskGetTickCount
The count of ticks since vTaskStartScheduler was called
uxTaskGetNumberOfTasks
The number of tasks that the real time kernel is currently managing. This includes all ready, blocked and suspended tasks. A task that has been deleted but not yet freed by the idle task will also be included in the count.
xTaskGetIdleTaskHandle,xTaskGetIdleTaskHandleForCPU
It is not valid to call xTaskGetIdleTaskHandle() before the scheduler has been started.

The Idle Task
The idle task is created automatically when the RTOS scheduler is started to ensure there is always at least one task that is able to run. It is created at the lowest possible priority to ensure it does not use any CPU time if there are higher priority application tasks in the ready state.
The idle task is responsible for freeing memory allocated by the RTOS to tasks that have since been deleted. It is therefore important in applications that make use of the vTaskDelete() function to ensure the idle task is not starved of processing time. The idle task has no other active functions so can legitimately be starved of microcontroller time under all other conditions.
It is possible for application tasks to share the idle task priority (tskIDLE_PRIORITY). See the configIDLE_SHOULD_YIELD configuration parameter for information on how this behaviour can be configured.