Communication between Tasks

Tasks often work independent from each other, on different sets of data. This poses no problem to the safety of an application in terms of multiple processes accessing data at the same time. But for most applications, tasks want to exchange data, or even work on the same sets of data. This can result in errors, such as losing updates on the data, or even worse scenarios.

This section introduces two functionalities. The first is Queues, which function as a way to exchange data between tasks. The second is Semaphores, which are used to prevent two tasks from accessing a single set of data at the same time.

  1. Queues
    1. Define Helpful Constants and Includes
    2. Creating a Queue
    3. Send and Receive
  2. Semaphores
    1. Define Helpful Constants and Includes
    2. Creating a Semaphore
    3. Give and Take
  3. Full Code Examples
    1. Queues Code
    2. Semaphores Code

Queues

Queues are the primary form of intertask communication. They can be used to send data between tasks: One task, called the producer, puts data into a queue as soon as it becomes available (e.g. a network stack). Another task, called the consumer, polls the queue for data and handles it (e.g. a packet handling thread). While the queue is empty, the consumer task is blocked. As soon as data becomes available, the consumer starts processing it.

Define Helpful Constants and Includes

To use the Queue-functionalities certain defines are advantageous for understanding the code and certain includes are necessary, as seen in the following code-snippet

/* system header files */
#include "BCDS_Basics.h"

/* additional interface header files */
#include "FreeRTOS.h"
#include "timers.h"
#include "queue.h"

/* constant definitions */
// resolves to 10 Millseconds
#define BLOCK_TIME ((portTickType) 10 / portTICK_RATE_MS)

Creating a Queue

In the following code, a Queue is created using xQueueCreate. This function receives two inputs. The first determines how many items it can hold, and the second determines the size of one item. For uint32_t, the size is 4 bytes, or sizeof( uint32_t ) to be more generic. It returns a handle of type QueueHandle_t, through which the queue can be accessed. If the creation fails, NULL will be returned instead.

QueueHandle_t queueHandle = xQueueCreate(10, sizeof(uint32_t));

Send and Receive

The queue is used to exchange data between tasks. In a generic case, one task sends data using the function xQueueSend(), and another retrieves this data by calling xQueueReceive(). Both functions have the same input signature. They both receive the handle to a queue, a pointer to a buffer which contains or will contain the data, and a timeout-period. It is imperative to check whether data has been actually received before trying to use the data. The following code shows how to use these two functions. Additionally, how to delete a queue is shown as well.

// put the value 42 into the queue (block up to 10 milliseconds)
uint32_t sendingBuffer = 42;
xQueueSend(queueHandle, (void * ) &sendingBuffer, BLOCK_TIME);

// poll the first value from the queue (block up to 10 milliseconds)
uint32_t receivingBuffer = 0;
BaseType_t queueResult = xQueueReceive(queueHandle, &receivingBuffer, BLOCK_TIME);
if (pdPASS != queueResult) {
  assert(false);
  return;
}

vQueueDelete(queueHandle);

Semaphores

Semaphores are used to synchronize tasks and to restrict access to exclusive resources. From an implementation perspective, they are special cases of queues that don’t actually transfer data. The length of the underlying queue depends on the type of the semaphore. Binary semaphores, for example, are queues of length 1. They can be ’taken’ and later ‘given’ back by tasks. Once a semaphore was taken, others can’t take that semaphore until it is given back again. This mechanism can, for example, be used to make one task wait for an event in a different task.

Define Helpful Constants and Includes

To use the Semaphore-functionalities certain defines are advantageous for understanding the code and certain includes are necessary, as seen in the following code-snippet

/* system header files */
#include "BCDS_Basics.h"

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

/* constant definitions */
#define BLOCK_TIME ((portTickType) x / portTICK_RATE_MS)

Creating a Semaphore

In the following code, xSemaphoreCreateBinary() is used to create a new binary semaphore. It returns a handle of type SemaphoreHandle_t, through which the semaphore can be controlled. If the creation is not successful, NULL is returned instead. Constructors for other types of semaphores can be found in the API reference for the semaphore module. Note that xSemaphoreCreateBinary() creates a semaphore that is initially taken, as such a first call of xSemaphoreGive() is necessary before the semaphore can be taken for the first time.

SemaphoreHandle_t semaphore = xSemaphoreCreateBinary();

// give the initially taken semaphore
xSemaphoreGive(semaphore);

Give and Take

A binary Semaphore can only be taken by exactly one task (it’s either taken or available - hence binary). To take a Semaphore the function xSemaphoreTake() has to be called with the inputs being the handle to the semaphore and a timeout-period. The calling task will be blocked for the duration of the timeout-period, and will resume its function afterwards. As such, it is imperative to check if the return was postive, before accessing exclusive data.

If the data is not needed for the task anymore, it can be given away using xSemaphoreGive. In this case, the input is only the handle to the semaphore, since giving has priority over taking. As such, giving will never be blocked.

Finally, the following code shows not only how to take and give, but also how to delete a semaphore that is not needed anymore

BaseType_t semaphoreResult = xSemaphoreTake(semaphore, MILLISECONDS(10));
if (pdPASS == semaphoreResult) {
  // access exlusive Data
}

xSemaphoreGive(semaphore);

vSemaphoreDelete(semaphore);

Full Code Examples

Note: The full code examples are intended for XDK-Workbench versions 3.4.0 and higher.

Queues Code

/* --------------------------------------------------------------------------- |
 * 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"

#include "queue.h"

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

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

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

// resolves to 10 Millseconds
#define BLOCK_TIME ((portTickType) 10 / portTICK_RATE_MS)

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

void createQueueAndUse(void) {
    // create a queue that can store up to 10 items of type uint32_t
    QueueHandle_t queueHandle = xQueueCreate(10, sizeof(uint32_t));
    if (queueHandle == NULL) {
        assert(false);
        return;
    }
    // put the value 42 into the queue (block up to 10 milliseconds)
    uint32_t sendingBuffer = 42;
    xQueueSend(queueHandle, (void * ) &sendingBuffer, BLOCK_TIME);

    // poll the first value from the queue (block up to 10 milliseconds)
    uint32_t receivingBuffer = 0;
    BaseType_t queueResult = xQueueReceive(queueHandle, &receivingBuffer, BLOCK_TIME);
    if (pdPASS != queueResult) {
        assert(false);
        return;
    }
    vQueueDelete(queueHandle);
}

/* --------------------------------------------------------------------------- |
 * 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 */
    createQueueAndUse();
}

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 */
    }
}
/** ************************************************************************* */

Semaphores Code

/* --------------------------------------------------------------------------- |
 * 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

/* own header files */
#include "FullCodeExampleFreertosSemaphores34.h"

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

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

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

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

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

// resolves to 10 Millseconds
#define BLOCK_TIME ((portTickType) 10 / portTICK_RATE_MS)

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

void createSemaphoreAndUse(void) {
    // create a binary semaphore in the taken state
    SemaphoreHandle_t semaphore = xSemaphoreCreateBinary();

    // give back the initially taken semaphore
    xSemaphoreGive(semaphore);

    // take the semaphore (block up to 10 milliseconds)
    BaseType_t semaphoreResult = xSemaphoreTake(semaphore, BLOCK_TIME);
    if (pdPASS == semaphoreResult) {
        // access exlusive Data
    }

    xSemaphoreGive(semaphore);

    vSemaphoreDelete(semaphore);
}

/* --------------------------------------------------------------------------- |
 * 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 */
    createSemaphoreAndUse();
}

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 */
    }
}

/** ************************************************************************* */