Tasks in FreeRTOS

A real time application that uses an RTOS can be structured as a set of independent tasks. Each task is executed within its own context with no coincidental dependency on other tasks within the system or the RTOS scheduler itself. Only one task within the application can be executed at any point in time and the scheduler is responsible for deciding which task this should be. The scheduler may therefore repeatedly start and stop each task (swap each task in and out) as the application executes. As a task has no knowledge of the schedulers activity, it is the responsibility of the scheduler to ensure that the processor context (register values, stack contents, etc.) when a task is swapped in is exactly that as when the same task was swapped out. To achieve this, each task is provided with its own stack. When the task is swapped out, the execution context is saved to it’s stack so it can be exactly restored when the same task is later swapped back in.

The size of a task’s stack is allocated at the time of its creation and can’t be modified later. Therefore, this size has to be big enough to contain the tasks complete state information at any point in time. If a tasks stack exceeds it’s stack size, the XDK will throw a runtime error (a so called stack overflow). This should be avoided by choosing a suitable stack size. An RTOS can multitask using these same principles, but their objectives are very different to those of non real time systems. The different objective is reflected in the scheduling policy. Real time / embedded systems are designed to provide a timely response to real world events. Events occurring in the real world can have deadlines before which the system must respond. The RTOS scheduling policy must ensure these deadlines are met. To achieve this objective, FreeRTOS executes tasks according to their priority, which has to be set by the developer. The scheduling policy of the RTOS is then to simply ensure that the highest priority task, that is able to execute, is given processing time. This may require sharing processing time “fairly” between tasks of equal priority if they are ready to run simultaneously. This section provides basic information and implementations for Tasks.

  1. Define Helpful Constants
  2. Create and Start a Task
  3. xTaskCreate Parameters
  4. Task States and Manipulation
  5. Full Code Example
  6. Appendix
    1. XDK Console Output example

Define Helpful Constants

Before we start with the implementation of a timer, it is particularly advantageous to define some helpful constants in order to get a clearer understanding of the function implementations.

/* constant definitions */
#define DEFAULT_STACKSIZE UINT32_C(500)
#define DEFAULT_PRIORITY tskIDLE_PRIORITY
#define SECONDS(x) ((portTickType) (x * 1000) / portTICK_RATE_MS)

Create and Start a Task

First, we need to use the function xTaskCreate to create (and automatically start) a task. The created task can be accessed by using the handle of type xTaskHandle.

For these functions to work, BCDS_Basics.h and FreeRTOS.h must be included.

#include "FreeRTOS.h"

static xTaskHandle taskHandle = NULL; // globally accessible

void createNewTask(void)
{
  xTaskCreate(
    myTaskFunction,                 // function that implements the task
    (const char * const) "My Task", // a name for the task
    DEFAULT_STACKSIZE,              // depth of the task stack
    NULL,                           // parameters passed to the function
    DEFAULT_PRIORITY,               // task priority
    &taskHandle                     // pointer to a task handle for late reference
  );
}

xTaskCreate Parameters

The function xTaskCreate() receives six parameters as input and returns an error code, which resolves to pdTRUE, if the task was created successfully. In that case, the task can be manipulated using the task handle. Otherwise, an error code will be returned.

Details regarding the input parameters are listed in the table below.

ArgumentDescription
Task FunctionThe function that implements the task. This function has to accept a void pointer as its only parameter and return void.
NameA string constant that will be used as the name of the new task. This name will only be used internally or for debugging purposes. The maximum length of the string is 10 characters.
Stack DepthAn unsigned integer (16 bit) that defines the size of the stack that will be allocated for the new task. In the previous code-snippet, the system defined minimal stack size is used. For more complex tasks, this has to be adjusted.
ParametersA pointer that will be provided as an argument to the task function, used to pass initial data to the new task. Can be NULL.
PriorityThe priority of the new task. This value must be between tskIDLE_PRIORITY (0, lowest) and configMAX_PRIORITIES (4, highest). The priority configMAX_PRIORITIES itself is reserved for interrupt handling.
Task HandleA pointer to a xTaskHandle that will store the created task object.

An example for the implementation of a task function is shown in the next code-snippet. Do note that an endless for-loop is not a good practice for a task. For periodically executed code, timers are recommended.

void myTaskFunction(void *pParameters)
{
  (void) pParameters;
  for (;;) {
    vTaskDelay(SECONDS(3)); // suspend task for 3 seconds
    // perform the task routine here
  }
}

Task States and Manipulation

FreeRTOS offers various functions to control and manipulate tasks. They all expect the handle to a task as input. Alternatively, NULL can be used as input. In the latter case, the task which calls the respective function will be manipulated by the called function.

There are four main states for tasks in FreeRTOS. They are READY, BLOCKED, RUNNING, and SUSPENDED.

Usually, a task function will be implemented as an infinite loop that blocks until something happens (for example waiting for a value in a Queue or waiting for network requests), then does whatever it was intended to do and then blocks again. While the task is blocked, the operating system can use the available processing time to execute other tasks, or go into idle state if none are available. Apart from being blocked, a task can have three other states, that are shown in the next picture. Immediately after its creation, a task is put into ready state. As soon as the scheduler puts it into running state, the task’s function will be executed or resumed.

Image

Do note that there are no functions to manually put a task into the state RUNNING. Putting a task into this state is reserved for the scheduler.

The next code-snippet shows how each task-controlling function can be called.

xTaskHandle taskHandle = NULL;
// create task and put it into state READY
xTaskCreate(myTaskFunction, (const char * const) "My Task",
DEFAULT_STACKSIZE, NULL, DEFAULT_PRIORITY, &taskHandle);

// put task into state SUSPENDED
vTaskSuspend(taskHandle);

// put task back into state RUNNING
vTaskResume(taskHandle);

// delete task permanently
vTaskDelete(taskHandle);

// delete the CURRENT task (the task calling the function)
vTaskDelete(NULL);

If NULL is passed, instead of a task handle, to one of the task controlling functions it will affect the task that is calling the function!

Full Code Example

Note: The full code example is intended for XDK-Workbench versions 3.4.0 and higher.

/* --------------------------------------------------------------------------- |
 * INCLUDES & DEFINES ******************************************************** |
 * -------------------------------------------------------------------------- */

/* own header files */
#include "XdkAppInfo.h"
#undef BCDS_MODULE_ID  /* Module ID define before including Basics package*/
#define BCDS_MODULE_ID XDK_APP_MODULE_ID_APP_CONTROLLER

/* system header files */
#include <stdio.h>

/* additional interface header files */
#include "BCDS_CmdProcessor.h"
#include "FreeRTOS.h"
#include "task.h"

/* --------------------------------------------------------------------------- |
 * HANDLES ******************************************************************* |
 * -------------------------------------------------------------------------- */

static CmdProcessor_T * AppCmdProcessor;/**< Handle to store the main Command processor handle to be used by run-time event driven threads */

/* --------------------------------------------------------------------------- |
 * VARIABLES ***************************************************************** |
 * -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------- |
 * EXECUTING FUNCTIONS ******************************************************* |
 * -------------------------------------------------------------------------- */

void myTaskFunction(void *pParameters)
{
  (void) pParameters;
  for (;;) {
    vTaskDelay(3000); // suspend task for 3 seconds
    printf("Task fired\n\r");
  }
}

void createNewTask(void)
{
  xTaskHandle taskHandle = NULL;

  xTaskCreate(
    myTaskFunction,                 // function that implements the task
    (const char * const) "My Task", // a name for the task
    configMINIMAL_STACK_SIZE,       // depth of the task stack
    NULL,                           // parameters passed to the function
    tskIDLE_PRIORITY,               // task priority
    taskHandle                      // pointer to a task handle for late reference
  );
}

/* --------------------------------------------------------------------------- |
 * BOOTING- AND SETUP FUNCTIONS ********************************************** |
 * -------------------------------------------------------------------------- */

static void AppControllerEnable(void * param1, uint32_t param2)
{
    BCDS_UNUSED(param1);
    BCDS_UNUSED(param2);

    /* Enable necessary modules for the application and check their return values */
    createNewTask();
}

static void AppControllerSetup(void * param1, uint32_t param2)
{
    BCDS_UNUSED(param1);
    BCDS_UNUSED(param2);
    Retcode_T retcode = RETCODE_OK;

    /* Setup the necessary modules required for the application */

    retcode = CmdProcessor_Enqueue(AppCmdProcessor, AppControllerEnable, NULL, UINT32_C(0));
    if (RETCODE_OK != retcode)
    {
        printf("AppControllerSetup : Failed \r\n");
        Retcode_RaiseError(retcode);
        assert(0); /* To provide LED indication for the user */
    }
}

void AppController_Init(void * cmdProcessorHandle, uint32_t param2)
{
    BCDS_UNUSED(param2);

    Retcode_T retcode = RETCODE_OK;

    if (cmdProcessorHandle == NULL)
    {
        printf("AppController_Init : Command processor handle is NULL \r\n");
        retcode = RETCODE(RETCODE_SEVERITY_ERROR, RETCODE_NULL_POINTER);
    }
    else
    {
        AppCmdProcessor = (CmdProcessor_T *) cmdProcessorHandle;
        retcode = CmdProcessor_Enqueue(AppCmdProcessor, AppControllerSetup, NULL, UINT32_C(0));
    }

    if (RETCODE_OK != retcode)
    {
        Retcode_RaiseError(retcode);
        assert(0); /* To provide LED indication for the user */
    }
}

Appendix

XDK Console Output example

The following console log is an example output of the code that has been implemented in the FreeRTOS Tasks example guide:

Image