githubEdit

10 Task Notifications

10.1 Introduction

It has been seen that applications that use FreeRTOS are structured as a set of independent tasks, and that it is likely that these autonomous tasks will have to communicate with each other so that, collectively, they can provide useful system functionality.

10.1.1 Communicating Through Intermediary Objects

This book has already described various ways in which tasks can communicate with each other. The methods described so far have required the creation of a communication object. Examples of communication objects include queues, event groups, and various different types of semaphore.

When a communication object is used, events and data are not sent directly to a receiving task, or a receiving ISR, but are instead sent to the communication object. Likewise, tasks and ISRs receive events and data from the communication object, rather than directly from the task or ISR that sent the event or data. This is depicted in Figure 76.

Figure 76. A communication object being used to send an event from one task to another

10.1.2 Task Notifications—Direct to Task Communication

'Task Notifications' allow tasks to interact with other tasks, and to synchronize with ISRs, without the need for a separate communication object. By using a task notification, a task or ISR can send an event directly to the receiving task. This is depicted in Figure 77.

Figure 77. A task notification used to send an event directly from one task to another

Task notification functionality is optional. To include task notification functionality set configUSE_TASK_NOTIFICATIONS to 1 in FreeRTOSConfig.h.

When configUSE_TASK_NOTIFICATIONS is set to 1, each task has a 'Notification State', which can be either 'Pending' or 'Not-Pending', and a 'Notification Value', which is a 32-bit unsigned integer. When a task receives a notification, its notification state is set to pending. When a task reads its notification value, its notification state is set to not-pending.

A task can wait in the Blocked state, with an optional time out, for its notification state to become pending.

10.1.3 Scope

This chapter discusses:

  • A task's notification state and notification value.

  • How and when a task notification can be used in place of a communication object, such as a semaphore.

  • The advantages of using a task notification in place of a communication object.

10.2 Task Notifications; Benefits and Limitations

10.2.1 Performance Benefits of Task Notifications

Using a task notification to send an event or data to a task is significantly faster than using a queue, semaphore or event group to perform an equivalent operation.

10.2.2 RAM Footprint Benefits of Task Notifications

Likewise, using a task notification to send an event or data to a task requires significantly less RAM than using a queue, semaphore or event group to perform an equivalent operation. This is because each communication object (queue, semaphore or event group) must be created before it can be used, whereas enabling task notification functionality has a fixed overhead of just eight bytes of RAM per task.

10.2.3 Limitations of Task Notifications

Task notifications are faster and use less RAM than communication objects, but task notifications cannot be used in all scenarios. This section documents the scenarios in which a task notification cannot be used:

  • Sending an event or data to an ISR

    Communication objects can be used to send events and data from an ISR to a task, and from a task to an ISR.

    Task notifications can be used to send events and data from an ISR to a task, but they cannot be used to send events or data from a task to an ISR.

  • Enabling more than one receiving task

    A communication object can be accessed by any task or ISR that knows its handle (which might be a queue handle, semaphore handle, or event group handle). Any number of tasks and ISRs can process events or data sent to any given communication object.

    Task notifications are sent directly to the receiving task, so can only be processed by the task to which the notification is sent. However, this is rarely a limitation in practical cases because, while it is common to have multiple tasks and ISRs sending to the same communication object, it is rare to have multiple tasks and ISRs receiving from the same communication object.

  • Buffering multiple data items

    A queue is a communication object that can hold more than one data item at a time. Data that has been sent to the queue, but not yet received from the queue, is buffered inside the queue object.

    Task notifications send data to a task by updating the receiving task's notification value. A task's notification value can only hold one value at a time.

  • Broadcasting to more than one task

    An event group is a communication object that can be used to send an event to more than one task at a time.

    Task notifications are sent directly to the receiving task, so can only be processed by the receiving task.

  • Waiting in the blocked state for a send to complete

    If a communication object is temporarily in a state that means no more data or events can be written to it (for example, when a queue is full no more data can be sent to the queue), then tasks attempting to write to the object can optionally enter the Blocked state to wait for their write operation to complete.

    If a task attempts to send a task notification to a task that already has a notification pending, then it is not possible for the sending task to wait in the Blocked state for the receiving task to reset its notification state. As will be seen, this is rarely a limitation in practical cases in which a task notification is used.

10.3 Using Task Notifications

10.3.1 Task Notification API Options

Task notifications are a very powerful feature that can often be used in place of a binary semaphore, a counting semaphore, an event group, and sometimes even a queue. This wide range of usage scenarios can be achieved by using the xTaskNotify() API function to send a task notification, and the xTaskNotifyWait() API function to receive a task notification.

However, in the majority of cases, the full flexibility provided by the xTaskNotify() and xTaskNotifyWait() API functions is not required, and simpler functions would suffice. Therefore, the xTaskNotifyGive() API function is provided as a simpler but less flexible alternative to xTaskNotify(), and the ulTaskNotifyTake() API function is provided as a simpler but less flexible alternative to xTaskNotifyWait().

10.3.2 The xTaskNotifyGive() API Function

xTaskNotifyGive() sends a notification directly to a task, and increments (adds one to) the receiving task's notification value. Calling xTaskNotifyGive() will set the receiving task's notification state to pending, if it was not already pending.

The xTaskNotifyGive()27 API function is provided to allow a task notification to be used as a lighter weight and faster alternative to a binary or counting semaphore.

(27): xTaskNotifyGive() is actually implemented as macro, not a function. For simplicity it is referred to as a function throughout this book.



Listing 148. The xTaskNotifyGive() API function prototype

xTaskNotifyGive() parameters and return value

  • xTaskToNotify

    The handle of the task to which the notification is being sent—see the pxCreatedTask parameter of the xTaskCreate() API function for information on obtaining handles to tasks.

  • Return value

    xTaskNotifyGive() is a macro that calls xTaskNotify(). The parameters passed into xTaskNotify() by the macro are set such that pdPASS is the only possible return value. xTaskNotify() is described later in this book.

10.3.3 The vTaskNotifyGiveFromISR() API Function

vTaskNotifyGiveFromISR() is a version of xTaskNotifyGive() that can be used in an interrupt service routine.



Listing 149. The vTaskNotifyGiveFromISR() API function prototype

vTaskNotifyGiveFromISR() parameters and return value

  • xTaskToNotify

    The handle of the task to which the notification is being sent—see the pxCreatedTask parameter of the xTaskCreate() API function for information on obtaining handles to tasks.

  • pxHigherPriorityTaskWoken

    If the task to which the notification is being sent is waiting in the Blocked state to receive a notification, then sending the notification will cause the task to leave the Blocked state.

    If calling vTaskNotifyGiveFromISR() causes a task to leave the Blocked state, and the unblocked task has a priority higher than the priority of the currently executing task (the task that was interrupted), then, internally, vTaskNotifyGiveFromISR() will set *pxHigherPriorityTaskWoken to pdTRUE.

    If vTaskNotifyGiveFromISR() sets this value to pdTRUE, then a context switch should be performed before the interrupt is exited. This will ensure that the interrupt returns directly to the highest priority Ready state task.

    As with all interrupt safe API functions, the pxHigherPriorityTaskWoken parameter must be set to pdFALSE before it is used.

10.3.4 The ulTaskNotifyTake() API Function

ulTaskNotifyTake() allows a task to wait in the Blocked state for its notification value to be greater than zero, and either decrements (subtracts one from) or clears the task's notification value before it returns.

The ulTaskNotifyTake() API function is provided to allow a task notification to be used as a lighter weight and faster alternative to a binary or counting semaphore.



Listing 150. The ulTaskNotifyTake() API function prototype

ulTaskNotifyTake() parameters and return value

  • xClearCountOnExit

    If xClearCountOnExit is set to pdTRUE, then the calling task's notification value will be cleared to zero before the call to ulTaskNotifyTake() returns.

    If xClearCountOnExit is set to pdFALSE, and the calling task's notification value is greater than zero, then the calling task's notification value will be decremented before the call to ulTaskNotifyTake() returns.

  • xTicksToWait

    The maximum amount of time the calling task should remain in the Blocked state to wait for its notification value to be greater than zero.

    The block time is specified in tick periods, so the absolute time it represents is dependent on the tick frequency. The macro pdMS_TO_TICKS() can be used to convert a time specified in milliseconds to a time specified in ticks.

    Setting xTicksToWait to portMAX_DELAY will cause the task to wait indefinitely (without timing out), provided INCLUDE_vTaskSuspend is set to 1 in FreeRTOSConfig.h.

  • Return value

    The returned value is the calling task's notification value before it was either cleared to zero or decremented, as specified by the value of the xClearCountOnExit parameter.

    If a block time was specified (xTicksToWait was not zero), and the return value is not zero, then it is possible the calling task was placed into the Blocked state, to wait for its notification value to be greater than zero, and its notification value was updated before the block time expired.

    If a block time was specified (xTicksToWait was not zero), and the return value is zero, then the calling task was placed into the Blocked state, to wait for its notification value to be greater than zero, but the specified block time expired before that happened.

10.3.5 Example 24. Using a task notification in place of a semaphore, method 1

Example 16 used a binary semaphore to unblock a task from within an interrupt service routine—effectively synchronizing the task with the interrupt. This example replicates the functionality of Example 16, but uses a direct to task notification in place of the binary semaphore.

Listing 151 shows the implementation of the task that is synchronized with the interrupt. The call to xSemaphoreTake() that was used in Example 16 has been replaced by a call to ulTaskNotifyTake().

The ulTaskNotifyTake() xClearCountOnExit parameter is set to pdTRUE, which results in the receiving task's notification value being cleared to zero before ulTaskNotifyTake() returns. It is therefore necessary to process all the events that are already available between each call to ulTaskNotifyTake(). In Example 16, because a binary semaphore was used, the number of pending events had to be determined from the hardware, which is not always practical. In Example 24, the number of pending events is returned from ulTaskNotifyTake().

Interrupt events that occur between calls to ulTaskNotifyTake are latched in the task's notification value, and calls to ulTaskNotifyTake() will return immediately if the calling task already has notifications pending.



Listing 151. The implementation of the task to which the interrupt processing is deferred (the task that synchronizes with the interrupt) in Example 24

The periodic task used to generate software interrupts prints a message before the interrupt is generated, and again after the interrupt has been generated. This allows the sequence of execution to be observed in the output produced.

Listing 152 shows the interrupt handler. This does very little other than send a notification directly to the task to which interrupt handling is deferred.



Listing 152. The implementation of the interrupt service routine used in Example 24

The output produced when Example 24 is executed is shown in Figure 78. As expected, it is identical to that produced when Example 16 is executed. vHandlerTask() enters the Running state as soon as the interrupt is generated, so the output from the task splits the output produced by the periodic task. Further explanation is provided in Figure 79.

Figure 78. The output produced when Example 16 is executed

Figure 79. The sequence of execution when Example 24 is executed

10.3.6 Example 25. Using a task notification in place of a semaphore, method 2

In Example 24, the ulTaskNotifyTake() xClearOnExit parameter was set to pdTRUE. Example 25 modifies Example 24 slightly to demonstrate the behavior when the ulTaskNotifyTake() xClearOnExit parameter is instead set to pdFALSE.

When xClearOnExit is pdFALSE, calling ulTaskNotifyTake() will only decrement (reduce by one) the calling task's notification value, instead of clearing it to zero. The notification count is therefore the difference between the number of events that have occurred, and the number of events that have been processed. That allows the structure of vHandlerTask() to be simplified in two ways:

  1. The number of events waiting to be processed is held in the notification value, so it does not need to be held in a local variable.

  2. It is only necessary to process one event between each call to ulTaskNotifyTake().

The implementation of vHandlerTask() used in Example 25 is shown in Listing 153.



Listing 153. The implementation of the task to which the interrupt processing is deferred (the task that synchronizes with the interrupt) in Example 25

For demonstration purposes, the interrupt service routine has also been modified to send more than one task notification per interrupt, and in so doing, simulate multiple interrupts occurring at high frequency. The implementation of the interrupt service routine used in Example 25 is shown in Listing 154.



Listing 154. The implementation of the interrupt service routine used in Example 25

The output produced when Example 25 is executed is shown in Figure 80. As can be seen, vHandlerTask() processes all three events each time an interrupt is generated.

Figure 80. The output produced when Example 25 is executed

10.3.7 The xTaskNotify() and xTaskNotifyFromISR() API Functions

xTaskNotify() is a more capable version of xTaskNotifyGive() that can be used to update the receiving task's notification value in any of the following ways:

  • Increment (add one to) the receiving task's notification value, in which case xTaskNotify() is equivalent to xTaskNotifyGive().

  • Set one or more bits in the receiving task's notification value. This allows a task's notification value to be used as a lighter weight and faster alternative to an event group.

  • Write a completely new number into the receiving task's notification value, but only if the receiving task has read its notification value since it was last updated. This allows a task's notification value to provide similar functionality to that provided by a queue that has a length of one.

  • Write a completely new number into the receiving task's notification value, even if the receiving task has not read its notification value since it was last updated. This allows a task's notification value to provide similar functionality to that provided by the xQueueOverwrite() API function. The resultant behavior is sometimes referred to as a 'mailbox'.

xTaskNotify() is more flexible and powerful than xTaskNotifyGive(), and because of that extra flexibility and power, it is also a little more complex to use.

xTaskNotifyFromISR() is a version of xTaskNotify() that can be used in an interrupt service routine, and therefore has an additional pxHigherPriorityTaskWoken parameter.

Calling xTaskNotify() will always set the receiving task's notification state to pending, if it was not already pending.



Listing 155. Prototypes for the xTaskNotify() and xTaskNotifyFromISR() API functions

xTaskNotify() parameters and return value

  • xTaskToNotify

    The handle of the task to which the notification is being sent—see the pxCreatedTask parameter of the xTaskCreate() API function for information on obtaining handles to tasks.

  • ulValue

    How ulValue is used is dependent on the eNotifyAction value. See Table 52.

  • eNotifyAction

    An enumerated type that specifies how to update the receiving task's notification value. See below.

  • Return value

    xTaskNotify() will return pdPASS except in the one case noted below.

Valid xTaskNotify() eNotifyAction Parameter Values, and Their Resultant Effect on the Receiving Task's Notification Value

  • eNoAction

    The receiving task's notification state is set to pending without it's notification value being updated. The xTaskNotify() ulValue parameter is not used.

    The eNoAction action allows a task notification to be used as a faster and lighter weight alternative to a binary semaphore.

  • eSetBits

    The receiving task's notification value is bitwise OR'ed with the value passed in the xTaskNotify() ulValue parameter. For example, if ulValue is set to 0x01, then bit 0 will be set in the receiving task's notification value. As another example, if ulValue is 0x06 (binary 0110) then bit 1 and bit 2 will be set in the receiving task's notification value.

    The eSetBits action allows a task notification to be used as a faster and lighter weight alternative to an event group.

  • eIncrement

    The receiving task's notification value is incremented. The xTaskNotify() ulValue parameter is not used.

    The eIncrement action allows a task notification to be used as a faster and lighter weight alternative to a binary or counting semaphore, and is equivalent to the simpler xTaskNotifyGive() API function.

  • eSetValueWithoutOverwrite

    If the receiving task had a notification pending before xTaskNotify() was called, then no action is taken and xTaskNotify() will return pdFAIL.

    If the receiving task did not have a notification pending before xTaskNotify() was called, then the receiving task's notification value is set to the value passed in the xTaskNotify() ulValue parameter.

  • eSetValueWithOverwrite

    The receiving task's notification value is set to the value passed in the xTaskNotify() ulValue parameter, regardless of whether the receiving task had a notification pending before xTaskNotify() was called or not.

10.3.8 The xTaskNotifyWait() API Function

xTaskNotifyWait() is a more capable version of ulTaskNotifyTake(). It allows a task to wait, with an optional timeout, for the calling task's notification state to become pending, should it not already be pending. xTaskNotifyWait() provides options for bits to be cleared in the calling task's notification value both on entry to the function, and on exit from the function.



Listing 156. The xTaskNotifyWait() API function prototype

xTaskNotifyWait() parameters and return value

  • ulBitsToClearOnEntry

    If the calling task did not have a notification pending before it called xTaskNotifyWait(), then any bits set in ulBitsToClearOnEntry will be cleared in the task's notification value on entry to the function.

    For example, if ulBitsToClearOnEntry is 0x01, then bit 0 of the task's notification value will be cleared. As another example, setting ulBitsToClearOnEntry to 0xffffffff (ULONG_MAX) will clear all the bits in the task's notification value, effectively clearing the value to 0.

  • ulBitsToClearOnExit

    If the calling task exits xTaskNotifyWait() because it received a notification, or because it already had a notification pending when xTaskNotifyWait() was called, then any bits set in ulBitsToClearOnExit will be cleared in the task's notification value before the task exits the xTaskNotifyWait() function.

    The bits are cleared after the task's notification value has been saved in *pulNotificationValue (see the description of pulNotificationValue below).

    For example, if ulBitsToClearOnExit is 0x03, then bit 0 and bit 1 of the task's notification value will be cleared before the function exits.

    Setting ulBitsToClearOnExit to 0xffffffff (ULONG_MAX) will clear all the bits in the task's notification value, effectively clearing the value to 0.

  • pulNotificationValue

    Used to pass out the task's notification value. The value copied to *pulNotificationValue is the task's notification value as it was before any bits were cleared due to the ulBitsToClearOnExit setting.

    `pulNotificationValue is an optional parameter and can be set to NULL if it is not required.

  • xTicksToWait

    The maximum amount of time the calling task should remain in the Blocked state to wait for its notification state to become pending.

    The block time is specified in tick periods, so the absolute time it represents is dependent on the tick frequency. The macro pdMS_TO_TICKS() can be used to convert a time specified in milliseconds to a time specified in ticks.

    Setting xTicksToWait to portMAX_DELAY will cause the task to wait indefinitely (without timing out), provided INCLUDE_vTaskSuspend is set to 1 in FreeRTOSConfig.h.

  • Return value

    There are two possible return values:

    • pdTRUE

      This indicates xTaskNotifyWait() returned because a notification was received, or because the calling task already had a notification pending when xTaskNotifyWait() was called.

      If a block time was specified (xTicksToWait was not zero), then it is possible that the calling task was placed into the Blocked state, to wait for its notification state to become pending, but its notification state was set to pending before the block time expired.

    • pdFALSE

      This indicates that xTaskNotifyWait() returned without the calling task receiving a task notification.

      If xTicksToWait was not zero then the calling task will have been held in the Blocked state to wait for its notification state to become pending, but the specified block time expired before that happened.

10.3.9 Task Notifications Used in Peripheral Device Drivers: UART Example

Peripheral driver libraries provide functions that perform common operations on hardware interfaces. Examples of peripherals for which such libraries are often provided include Universal Asynchronous Receivers and Transmitters (UARTs), Serial Peripheral Interface (SPI) ports, analog to digital converters (ADCs), and Ethernet ports. Examples of functions typically provided by such libraries include functions to initialize a peripheral, send data to a peripheral, and receive data from a peripheral.

Some operations on peripherals take a relatively long time to complete. Examples of such operations include a high precision ADC conversion, and the transmission of a large data packet on a UART. In these cases the driver library function could be implemented to poll (repeatedly read) the peripheral's status registers to determine when the operation has completed. However, polling in this manner is nearly always wasteful as it utilizes 100% of the processor's time while no productive processing is being performed. The waste is particularly expensive in a multi-tasking system, where a task that is polling a peripheral might be preventing the execution of a lower priority task that does have productive processing to perform.

To avoid the potential for wasted processing time, an efficient RTOS aware device driver should be interrupt driven, and give a task that initiates a lengthy operation the option of waiting in the Blocked state for the operation to complete. That way, lower priority tasks can execute while the task performing the lengthy operation is in the Blocked state, and no tasks use processing time unless they can use it productively.

It is common practice for RTOS aware driver libraries to use a binary semaphore to place tasks into the Blocked state. The technique is demonstrated by the pseudo code shown in Listing 157, which provides the outline of an RTOS aware library function that transmits data on a UART port. In Listing 157:

  • xUART is a structure that describes the UART peripheral, and holds state information. The xTxSemaphore member of the structure is a variable of type SemaphoreHandle_t. It is assumed the semaphore has already been created.

  • The xUART_Send() function does not include any mutual exclusion logic. If more than one task is going to use the xUART_Send() function, then the application writer will have to manage mutual exclusion within the application itself. For example, a task may be required to obtain a mutex before calling xUART_Send().

  • The xSemaphoreTake() API function is used to place the calling task into the Blocked state after the UART transmission has been initiated.

  • The xSemaphoreGiveFromISR() API function is used to remove the task from the Blocked state after the transmission has completed, which is when the UART peripheral's transmit end interrupt service routine executes.



Listing 157. Pseudo code demonstrating how a binary semaphore can be used in a driver library transmit function

The technique demonstrated in Listing 157 is perfectly workable, and indeed common practice, but it has some drawbacks:

  • The library uses multiple semaphores, which increases its RAM footprint.

  • Semaphores cannot be used until they have been created, so a library that uses semaphores cannot be used until it has been explicitly initialized.

  • Semaphores are generic objects that are applicable to a wide range of use cases; they include logic to allow any number of tasks to wait in the Blocked state for the semaphore to become available, and to select (in a deterministic manner) which task to remove from the Blocked state when the semaphore does become available. Executing that logic takes a finite time, and that processing overhead is unnecessary in the scenario shown is Listing 157, in which there cannot be more than one task waiting for the semaphore at any given time.

Listing 158 demonstrates how to avoid these drawbacks by using a task notification in place of a binary semaphore.

Note: If a library uses task notifications, then the library's documentation must clearly state that calling a library function can change the calling task's notification state and notification value.

In Listing 158:

  • The xTxSemaphore member of the xUART structure has been replaced by the xTaskToNotify member. xTaskToNotify is a variable of type TaskHandle_t, and is used to hold the handle of the task that is waiting for the UART operation to complete.

  • The xTaskGetCurrentTaskHandle() FreeRTOS API function is used to obtain the handle of the task that is in the Running state.

  • The library does not create any FreeRTOS objects, so does not incur a RAM overhead, and does not need to be explicitly initialized.

  • The task notification is sent directly to the task that is waiting for the UART operation to complete, so no unnecessary logic is executed.

The xTaskToNotify member of the xUART structure is accessed from both a task and an interrupt service routine, requiring that consideration be given as to how the processor will update its value:

  • If xTaskToNotify is updated by a single memory write operation, then it can be updated outside of a critical section, exactly as shown in Listing 158. This would be the case if xTaskToNotify is a 32-bit variable (TaskHandle_t was a 32-bit type), and the processor on which FreeRTOS is running is a 32-bit processor.

  • If more than one memory write operation is required to update xTaskToNotify, then xTaskToNotify must only be updated from within a critical section—otherwise the interrupt service routine might access xTaskToNotify while it is in an inconsistent state. This would be the case if xTaskToNotify is a 32-bit variable, and the processor on which FreeRTOS is running is a 16-bit processor, as it would require two 16-bit memory write operations to update all 32-bits.

Internally, within the FreeRTOS implementation, TaskHandle_t is a pointer, so sizeof( TaskHandle_t ) always equals sizeof( void * ).



Listing 158. Pseudo code demonstrating how a task notification can be used in a driver library transmit function

Task notifications can also replace semaphores in receive functions, as demonstrated in pseudo code Listing 159, which provides the outline of an RTOS aware library function that receives data on a UART port. Referring to Listing 159:

  • The xUART_Receive() function does not include any mutual exclusion logic. If more than one task is going to use the xUART_Receive() function, then the application writer will have to manage mutual exclusion within the application itself. For example, a task may be required to obtain a mutex before calling xUART_Receive().

  • The UART's receive interrupt service routine places the characters that are received by the UART into a RAM buffer. The xUART_Receive() function returns characters from the RAM buffer.

  • The xUART_Receive() uxWantedBytes parameter is used to specify the number of characters to receive. If the RAM buffer does not already contain the requested number characters, then the calling task is placed into the Blocked state to wait to be notified that the number of characters in the buffer has increased. The while() loop is used to repeat this sequence until either the receive buffer contains the requested number of characters, or a timeout occurs.

  • The calling task may enter the Blocked state more than once. The block time is therefore adjusted to take into account the amount of time that has already passed since xUART_Receive() was called. The adjustments ensure the total time spent inside xUART_Receive() does not exceed the block time specified by the xRxTimeout member of the xUART structure. The block time is adjusted using the FreeRTOS vTaskSetTimeOutState() and xTaskCheckForTimeOut() helper functions.



Listing 159. Pseudo code demonstrating how a task notification can be used in a driver library receive function

10.3.10 Task Notifications Used in Peripheral Device Drivers: ADC Example

The previous section demonstrated how to use vTaskNotifyGiveFromISR() to send a task notification from an interrupt to a task. vTaskNotifyGiveFromISR() is a simple function to use, but its capabilities are limited; it can only send a task notification as a valueless event, it cannot send data. This section demonstrates how to use xTaskNotifyFromISR() to send data with a task notification event. The technique is demonstrated by the pseudo code shown in Listing 160, which provides the outline of an RTOS aware interrupt service routine for an Analog to Digital Converter (ADC). In Listing 160:

  • It is assumed an ADC conversion is started at least every 50 milliseconds.

  • ADC_ConversionEndISR() is the interrupt service routine for the ADC's conversion end interrupt, which is the interrupt that executes each time a new ADC value is available.

  • The task implemented by vADCTask() processes each value generated by the ADC. It is assumed the task's handle was stored in xADCTaskToNotify when the task was created.

  • ADC_ConversionEndISR() uses xTaskNotifyFromISR() with the eAction parameter set to eSetValueWithoutOverwrite to send a task notification to the vADCTask() task, and write the result of the ADC conversion into the task's notification value.

  • The vADCTask() task uses xTaskNotifyWait() to wait to be notified that a new ADC value is available, and to retrieve the result of the ADC conversion from its notification value.



Listing 160. Pseudo code demonstrating how a task notification can be used to pass a value to a task

10.3.11 Task Notifications Used Directly Within an Application

This section reinforces the power of task notifications by demonstrating their use in a hypothetical application that includes the following functionality:

  • The application communicates across a slow internet connection to send data to, and request data from, a remote data server. From here on, the remote data server is referred to as the cloud server.

  • After requesting data from the cloud server, the requesting task must wait in the Blocked state for the requested data to be received.

  • After sending data to the cloud server, the sending task must wait in the Blocked state for an acknowledgement that the cloud server received the data correctly.

A schematic of the software design is shown in Figure 81. In Figure 81:

  • The complexity of handling multiple internet connections to the cloud server is encapsulated within a single FreeRTOS task. The task acts as a proxy server within the FreeRTOS application, and is referred to as the server task.

  • Application tasks read data from the cloud server by calling CloudRead(). CloudRead() does not communicate with the cloud server directly, but instead sends the read request to the server task on a queue, and receives the requested data from the server task as a task notification.

  • Application tasks write date to the cloud server by calling CloudWrite(). CloudWrite() does not communicate with the cloud server directly, but instead sends the write request to the server task on a queue, and receives the result of the write operation from the server task as a task notification.

The structure sent to the server task by the CloudRead() and CloudWrite() functions is shown in Listing 161.

Figure 81 The communication paths from the application tasks to the cloud server, and back again



Listing 161. The structure and data type sent on a queue to the server task

Pseudo code for CloudRead() is shown in Listing 162. The function sends its request to the server task, then calls xTaskNotifyWait() to wait in the Blocked state until it is notified that the requested data is available.

Pseudo code showing how the server task manages a read request is shown in Listing 163. When the data has been received from the cloud server, the server task unblocks the application task, and sends the received data to the application task, by calling xTaskNotify() with the eAction parameter set to eSetValueWithOverwrite.

Listing 163 shows a simplified scenario, as it assumes GetCloudData() does not have to wait to obtain a value from the cloud server.



Listing 162. The Implementation of the Cloud Read API Function



Listing 163. The Server Task Processing a Read Request

Pseudo code for CloudWrite() is shown in Listing 164. For the purpose of demonstration, CloudWrite() returns a bitwise status code, where each bit in the status code is assigned a unique meaning. Four example status bits are shown by the #define statements at the top of Listing 164.

The task clears the four status bits, sends its request to the server task, then calls xTaskNotifyWait() to wait in the Blocked state for the status notification.



Listing 164. The Implementation of the Cloud Write API Function

Pseudo code demonstrating how the server task manages a write request is shown in Listing 165. When the data has been sent to the cloud server, the server task unblocks the application task, and sends the bitwise status code to the application task, by calling xTaskNotify() with the eAction parameter set to eSetBits. Only the bits defined by the CLOUD_WRITE_STATUS_BIT_MASK constant can get altered in the receiving task's notification value, so the receiving task can use other bits in its notification value for other purposes.

Listing 165 shows a simplified scenario, as it assumes SetCloudData() does not have to wait to obtain an acknowledgement from the remote cloud server.



Listing 165. The Server Task Processing a Send Request

Last updated