Task Management in FreeRTOS

Task States

There are currently four states for tasks: Running, Ready, Blocked, and Suspended.

  1. 「Running — Running State」This is the state when the task is executing; being in the running state means the task has CPU usage rights. For a single-core CPU, there are no other running tasks at this time.
  2. 「Ready — Ready State」Being in the ready state means that this task can be executed, such as when an event occurs, data arrives in a queue, or the requested resources are valid; however, because a task of the same priority or higher is currently running, this task cannot execute and remains in the ready state.
  3. 「Blocked — Blocked State」If a task is waiting for a timeout or an external event to occur, such as a task calling the vTaskDelay() function, the task will be in the blocked state until the delay time arrives. Of course, tasks waiting on queues, semaphores, event groups, notifications, or semaphore events will also be in the blocked state. Usually, these waits will set a timeout, and when the wait times out, the task exits the blocked state to prevent it from being indefinitely suspended. Tasks in the blocked state do not occupy any CPU time.
  4. 「Suspended — Suspended State」The suspended state cannot enter the running state, and unlike the blocked state, there is no timeout; the suspended state has no timeout unless vTaskResume() is called to exit the suspended state. The corresponding call to vTaskSuspend() suspends a task. The suspended state can be described as “frozen”; unless “unfrozen”, it will never have a chance to execute again.

Here is the state relationship diagram:

Task Management in FreeRTOS

img

Task Priority

Each task must specify a priority from 0 to configMAX_PRIORITIES - 1 when created. Unlike other RTOS, a higher priority value means a lower priority. configMAX_PRIORITIES is defined in the freeRTOSConfig.h header file.

The value of configMAX_PRIORITIES can be any value, meaning that the number of tasks in FreeRTOS is not limited. In fact, since FreeRTOS allows tasks to share the same priority, the size of configMAX_PRIORITIES does not limit the number of tasks. However, for practical applications, even unused priorities in FreeRTOS will still occupy some memory, so the maximum priority value should be close to the actual application needs.

In the freeRTOSConfig.h header file, configUSE_PORT_OPTIMISED_TASK_SELECTION is defined, which has certain architectures with task selection optimization mechanisms; in this case, the maximum priority cannot exceed 32.

The idle task in FreeRTOS always has the lowest priority, so its priority is 0.

The FreeRTOS scheduler always ensures that the task currently getting CPU time has the highest priority; in other words, high-priority tasks in the ready state will always be prioritized to enter the running state.

For tasks sharing the same priority, if configUSE_TIME_SLICING is not defined or defined as 1, FreeRTOS will use a time-slicing mechanism to schedule these tasks.

For more specific configuration information, please refer to the FreeRTOS article on FreeRTOSConfig.h analysis.

Task Creation – xTaskCreate()

The prototype implementation of the xTaskCreate() API function:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )

 BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
    const char * const pcName,
    const uint16_t usStackDepth,
    void * const pvParameters,
    UBaseType_t uxPriority,
    TaskHandle_t * const pxCreatedTask ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
 {
 TCB_t *pxNewTCB;
 BaseType_t xReturn;

  /* If the stack grows down then allocate the stack then the TCB so the stack
  does not grow into the TCB.  Likewise if the stack grows up then allocate
  the TCB then the stack. */
  #if( portSTACK_GROWTH > 0 )
  {
   /* Allocate space for the TCB.  Where the memory comes from depends on
   the implementation of the port malloc function and whether or not static
   allocation is being used. */
   pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

   if( pxNewTCB != NULL )
   {
    /* Allocate space for the stack used by the task being created.
    The base of the stack memory stored in the TCB so the task can
    be deleted later if required. */
    pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

    if( pxNewTCB->pxStack == NULL )
    {
     /* Could not allocate the stack.  Delete the allocated TCB. */
     vPortFree( pxNewTCB );
     pxNewTCB = NULL;
    }
   }
  }
  #else /* portSTACK_GROWTH */
  {
  StackType_t *pxStack;

   /* Allocate space for the stack used by the task being created. */
   pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */

   if( pxStack != NULL )
   {
    /* Allocate space for the TCB. */
    pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */

    if( pxNewTCB != NULL )
    {
     /* Store the stack location in the TCB. */
     pxNewTCB->pxStack = pxStack;
    }
    else
    {
     /* The stack cannot be used as the TCB was not created.  Free
     it again. */
     vPortFree( pxStack );
    }
   }
   else
   {
    pxNewTCB = NULL;
   }
  }
  #endif /* portSTACK_GROWTH */

  if( pxNewTCB != NULL )
  {
   #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
   {
    /* Tasks can be created statically or dynamically, so note this
    task was created dynamically in case it is later deleted. */
    pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
   }
   #endif /* configSUPPORT_STATIC_ALLOCATION */

   prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
   prvAddNewTaskToReadyList( pxNewTCB );
   xReturn = pdPASS;
  }
  else
  {
   xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
  }

  return xReturn;
 }

#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

As can be seen, this function is controlled by the macro configSUPPORT_DYNAMIC_ALLOCATION, which is located in the FreeRTOSConfig.h file.

Parameters:

  • 「pvTaskCode」: A pointer to the task function entry, which is a C function that never exits, usually implemented as an infinite loop.
  • 「pcName」: A descriptive task name. This parameter is not used by FreeRTOS; it is simply for debugging assistance; the maximum length of the task name (including the \0 terminator) is defined by the constant config_MAX_TASK_NAME_LEN, which is located in the FreeRTOSConfig.h file.
  • 「usStackDepth」: When the task is created, this tells the kernel how much stack space to allocate for it, specified in words rather than bytes.
  • 「pvParameters」: A pointer to a void that is the value passed into the task.
  • 「uxPriority」: Used to specify the priority at which the task will execute. The priority can range from the lowest priority (0) to the highest priority (configMAX_PRIORITIES – 1); configMAX_PRIORITIES is a user-defined constant located in the FreeRTOSConfig.h file.
  • 「pxCreatedTask」: Used to return the task handle. This handle will be referenced in API calls for the created task, such as changing the task priority or deleting the task; if the application does not need to use this task handle, pxCreatedTask can be set to NULL.

Return values (there are two possible returns):

  • 「pdTRUE」: Indicates that the task was created successfully.
  • 「errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY」: Due to insufficient heap space, FreeRTOS cannot allocate enough space to hold the task structure data and task stack, and thus cannot create the task.

Task Deletion – vTaskDelete()

The prototype implementation of the vTaskDelete() API function:

#if ( INCLUDE_vTaskDelete == 1 )

 void vTaskDelete( TaskHandle_t xTaskToDelete )
 {
 TCB_t *pxTCB;

  taskENTER_CRITICAL();
  {
   /* If null is passed in here then it is the calling task that is
   being deleted. */
   pxTCB = prvGetTCBFromHandle( xTaskToDelete );

   /* Remove task from the ready list. */
   if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
   {
    taskRESET_READY_PRIORITY( pxTCB->uxPriority );
   }
   else
   {
    mtCOVERAGE_TEST_MARKER();
   }

   /* Is the task waiting on an event also? */
   if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
   {
    ( void ) uxListRemove( &( pxTCB->xEventListItem );
   }
   else
   {
    mtCOVERAGE_TEST_MARKER();
   }

   /* Increment the uxTaskNumber also so kernel aware debuggers can
   detect that the task lists need re-generating.  This is done before
   portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will
   not return. */
   uxTaskNumber++;

   if( pxTCB == pxCurrentTCB )
   {
    /* A task is deleting itself.  This cannot complete within the
    task itself, as a context switch to another task is required.
    Place the task in the termination list.  The idle task will
    check the termination list and free up any memory allocated by
    the scheduler for the TCB and stack of the deleted task. */
    vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );

    /* Increment the ucTasksDeleted variable so the idle task knows
    there is a task that has been deleted and that it should therefore
    check the xTasksWaitingTermination list. */
    ++uxDeletedTasksWaitingCleanUp;

    /* The pre-delete hook is primarily for the Windows simulator,
    in which Windows specific clean up operations are performed,
    after which it is not possible to yield away from this task -
    hence xYieldPending is used to latch that a context switch is
    required. */
    portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
   }
   else
   {
    --uxCurrentNumberOfTasks;
    prvDeleteTCB( pxTCB );

    /* Reset the next expected unblock time in case it referred to
    the task that has just been deleted. */
    prvResetNextTaskUnblockTime();
   }

   traceTASK_DELETE( pxTCB );
  }
  taskEXIT_CRITICAL();

  /* Force a reschedule if it is the currently running task that has just
  been deleted. */
  if( xSchedulerRunning != pdFALSE )
  {
   if( pxTCB == pxCurrentTCB )
   {
    configASSERT( uxSchedulerSuspended == 0 );
    portYIELD_WITHIN_API();
   }
   else
   {
    mtCOVERAGE_TEST_MARKER();
   }
  }
 }

#endif /* INCLUDE_vTaskDelete */

Similarly, this function is controlled by the macro INCLUDE_vTaskDelete, which is located in the FreeRTOSConfig.h file.

Parameters:

  • 「pxTaskToDelete」: The handle of the task to be deleted (target task). If NULL is passed, it is the calling task that is being deleted (i.e., deleting itself).

Code Setup

A task in FreeRTOS is the basic unit of execution, and each task consists of a C function, meaning you need to define a C function and then use the xTaskCreate() API to create a task. This C function has several characteristics: its return value must be void, and it is usually contained in an infinite loop (while, for), where all work related to this task is performed, and this function will not have a return; FreeRTOS does not allow tasks to end themselves (using return or executing to the last line); once a task is created, it will be configured with its own stack space and stack variable (which are the variables defined within the function).

/*
    FreeRTOS V9.0.0 - Copyright (C) 2016 Real Time Engineers Ltd.
    All rights reserved

    VISIT http://www.FreeRTOS.org TO ENSURE YOU ARE USING THE LATEST VERSION.

    This file is part of the FreeRTOS distribution.

    FreeRTOS is free software; you can redistribute it and/or modify it under
    the terms of the GNU General Public License (version 2) as published by the
    Free Software Foundation >>>> AND MODIFIED BY <<<< the FreeRTOS exception.

    ***************************************************************************
    >>!   NOTE: The modification to the GPL is included to allow you to     !<<
    >>!   distribute a combined work that includes FreeRTOS without being   !<<
    >>!   obliged to provide the source code for proprietary components     !<<
    >>!   outside of the FreeRTOS kernel.                                   !<<
    ***************************************************************************

    FreeRTOS is distributed in the hope that it will be useful, but WITHOUT ANY
    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    FOR A PARTICULAR PURPOSE.  Full license text is available on the following
    link: http://www.freertos.org/a00114.html

    **************************************************************************
     *                                                                       *
     *    FreeRTOS provides completely free yet professionally developed,    *
     *    robust, strictly quality controlled, supported, and cross          *
     *    platform software that is more than just the market leader, it     *
     *    is the industry's de facto standard.                               *
     *                                                                       *
     *    Help yourself get started quickly while simultaneously helping     *
     *    to support the FreeRTOS project by purchasing a FreeRTOS           *
     *    tutorial book, reference manual, or both:                          *
     *    http://www.FreeRTOS.org/Documentation                              *
     *                                                                       *
    ***************************************************************************

    http://www.FreeRTOS.org/FAQHelp.html - Having a problem?  Start by reading
    the FAQ page "My application does not run, what could be wrong?".  Have you
    defined configASSERT()?

    http://www.FreeRTOS.org/support - In return for receiving this top quality
    embedded software for free we request you assist our global community by
    participating in the support forum.

    http://www.FreeRTOS.org/training - Investing in training allows your team to
    be as productive as possible as early as possible.  Now you can receive
    FreeRTOS training directly from Richard Barry, CEO of Real Time Engineers
    Ltd, and the world's leading authority on the world's leading RTOS.

    http://www.FreeRTOS.org/plus - A selection of FreeRTOS ecosystem products,
    including FreeRTOS+Trace - an indispensable productivity tool, a DOS
    compatible FAT file system, and our tiny thread aware UDP/IP stack.

    http://www.FreeRTOS.org/labs - Where new FreeRTOS products go to incubate.
    Come and try FreeRTOS+TCP, our new open source TCP/IP stack for FreeRTOS.

    http://www.OpenRTOS.com - Real Time Engineers ltd. license FreeRTOS to High
    Integrity Systems ltd. to sell under the OpenRTOS brand.  Low cost OpenRTOS
    licenses offer ticketed support, indemnification and commercial middleware.

    http://www.SafeRTOS.com - High Integrity Systems also provide a safety
    engineered and independently SIL3 certified version for use in safety and
    mission critical applications that require provable dependability.

    1 tab == 4 spaces!
*/


/* Standard includes. */
#include <stdio.h>

/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

/* Library includes. */
#include "stm32f10x_it.h"

/* Private app includes. */
#include "bsp_uart.h"
#include "bsp_time.h"
#include "bsp_gpio.h"


/*----------------------------- End -----------------------------*/

/*
 * User Private Task.
 */
static void prvUser_Task( void *pvParameters );

/*
 * Configure the clocks, GPIO and other peripherals as required by the demo.
 */
static void prvSetupHardware( void );

/*----------------------------- End -----------------------------*/


/************************************************
函数名称 : main
功    能 : 主函数入口
参    数 : 无
返 回 值 : 无
*************************************************/
int main( void )
{
#ifdef DEBUG
  debug();
#endif

 prvSetupHardware();

 /* Start the tasks defined within this file/specific to this demo. */
 xTaskCreate( prvUser_Task, "prvUser_Task", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );

 /* Start the scheduler. */
 vTaskStartScheduler();

 /* Will only get here if there was not enough heap space to create the
 idle task. */
 return 0;
}
/*----------------------------- End -----------------------------*/

static void prvUser_Task( void *pvParameters )
{
    /* User-defined private tasks */

    while(1){
        /* do something here */
    }
 
    /* 
    * If your task needs to leave the loop and end
    * you need to use vTaskDelete to delete yourself instead of using return or naturally ending (executing to the last line)
    * The NULL value for this parameter indicates itself 
    */
    vTaskDelete(NULL);  // Delete itself
}

static void prvSetupHardware( void )
{
 /* Start with the clocks in their expected state. */
 RCC_DeInit();

 /* Enable HSE (high speed external clock). */
 RCC_HSEConfig( RCC_HSE_ON );

 /* Wait till HSE is ready. */
 while( RCC_GetFlagStatus( RCC_FLAG_HSERDY ) == RESET )
 {
 }

 /* 2 wait states required on the flash. */
 *( ( unsigned long * ) 0x40022000 ) = 0x02;

 /* HCLK = SYSCLK */
 RCC_HCLKConfig( RCC_SYSCLK_Div1 );

 /* PCLK2 = HCLK */
 RCC_PCLK2Config( RCC_HCLK_Div1 );

 /* PCLK1 = HCLK/2 */
 RCC_PCLK1Config( RCC_HCLK_Div2 );

 /* PLLCLK = 8MHz * 9 = 72 MHz. */
 RCC_PLLConfig( RCC_PLLSource_HSE_Div1, RCC_PLLMul_9 );

 /* Enable PLL. */
 RCC_PLLCmd( ENABLE );

 /* Wait till PLL is ready. */
 while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
 {
 }

 /* Select PLL as system clock source. */
 RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK );

 /* Wait till PLL is used as system clock source. */
 while( RCC_GetSYSCLKSource() != 0x08 )
 {
 }

 /* Configure HCLK clock as SysTick clock source. */
 SysTick_CLKSourceConfig( SysTick_CLKSource_HCLK );
 
 /*
  * STM32中断优先级分组为 4,即 4bit都用来表示抢占优先级,范围为:0~15
  * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
  * 都统一用这个优先级分组,千万不要再分组,切忌。
  */
 NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
 
 /* Other peripheral configuration */
 vSetupTimer();
 vSetupUSART();
 vSetupParPort();
}
/*----------------------------- End -----------------------------*/

#ifdef  DEBUG
/* Keep the linker happy. */
void assert_failed( unsigned char* pcFile, unsigned long ulLine )
{
 for( ;; )
 {
 }
}
#endif


/*---------------------------- END OF FILE ----------------------------*/

First, the prvSetupHardware() function configures the clocks and initializes hardware configurations (i.e., the implementations of vSetupTimer(); vSetupUSART(); vSetupParPort(); are just encapsulations of the previous STM32 notes on time, UART, GPIO initialization configurations).

/************************************************
函数名称 : vSetupTimer
功    能 : Timer初始化接口
参    数 : 无
返 回 值 : 无
*************************************************/
void vSetupTimer( void )
{
 Timer2_Config();
}

/************************************************
函数名称 : vSetupUSART
功    能 : UART初始化接口
参    数 : 无
返 回 值 : 无
*************************************************/
void vSetupUSART( void )
{
 UART1_Config();
// UART2_Config();
}

/************************************************
函数名称 : vSetupParPort
功    能 : 基础 IO初始化接口
参    数 : 无
返 回 值 : 无
*************************************************/
void vSetupParPort( void )
{
 LED_Config();
 Key_Config();
}

Then to the implementation of the main function, it calls prvSetupHardware() to initialize all hardware-related configurations, creates a prvUser_Task task to manage all subsequent created tasks (actually just for convenience), since there are no other functions created here, it places an infinite loop in it, and finally calls vTaskDelete(NULL); to delete itself.

Here there might be a question: clearly there is an infinite loop in prvUser_Task, which cannot execute the vTaskDelete() function, rendering it meaningless. Although this is the case, it is best to place the vTaskDelete() function at the end of each task to ensure safety (with exceptions). Also, because there is an infinite loop in the prvUser_Task task, the code after the while should not be executable, including vTaskStartScheduler(); after the prvUser_Task task. However, this is in the context of using RTOS, calling xTaskCreate() to create a task does not mean it is already executing; it merely informs the kernel that a task has been created, and the system actually starts executing after calling vTaskStartScheduler(); to start the scheduler.

Leave a Comment

×