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.
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.
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 thexTaskCreate()
API function for information on obtaining handles to tasks.Return value
xTaskNotifyGive()
is a macro that callsxTaskNotify()
. The parameters passed intoxTaskNotify()
by the macro are set such thatpdPASS
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 thexTaskCreate()
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
topdTRUE
.If
vTaskNotifyGiveFromISR()
sets this value topdTRUE
, 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 topdFALSE
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 topdTRUE
, then the calling task's notification value will be cleared to zero before the call toulTaskNotifyTake()
returns.If
xClearCountOnExit
is set topdFALSE
, and the calling task's notification value is greater than zero, then the calling task's notification value will be decremented before the call toulTaskNotifyTake()
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
toportMAX_DELAY
will cause the task to wait indefinitely (without timing out), providedINCLUDE_vTaskSuspend
is set to 1 inFreeRTOSConfig.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.
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:
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.
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.
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 toxTaskNotifyGive()
.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 thexTaskCreate()
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 returnpdPASS
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, ifulValue
is set to 0x01, then bit 0 will be set in the receiving task's notification value. As another example, ifulValue
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 simplerxTaskNotifyGive()
API function.eSetValueWithoutOverwrite
If the receiving task had a notification pending before
xTaskNotify()
was called, then no action is taken andxTaskNotify()
will returnpdFAIL
.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 thexTaskNotify()
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 beforexTaskNotify()
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 inulBitsToClearOnEntry
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, settingulBitsToClearOnEntry
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 whenxTaskNotifyWait()
was called, then any bits set inulBitsToClearOnExit
will be cleared in the task's notification value before the task exits thexTaskNotifyWait()
function.The bits are cleared after the task's notification value has been saved in
*pulNotificationValue
(see the description ofpulNotificationValue
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 theulBitsToClearOnExit
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
toportMAX_DELAY
will cause the task to wait indefinitely (without timing out), providedINCLUDE_vTaskSuspend
is set to 1 inFreeRTOSConfig.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 whenxTaskNotifyWait()
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. ThexTxSemaphore
member of the structure is a variable of typeSemaphoreHandle_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 thexUART_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 callingxUART_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 thexUART
structure has been replaced by thexTaskToNotify
member.xTaskToNotify
is a variable of typeTaskHandle_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 ifxTaskToNotify
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
, thenxTaskToNotify
must only be updated from within a critical section—otherwise the interrupt service routine might accessxTaskToNotify
while it is in an inconsistent state. This would be the case ifxTaskToNotify
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 thexUART_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 callingxUART_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. Thewhile()
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 insidexUART_Receive()
does not exceed the block time specified by thexRxTimeout
member of thexUART
structure. The block time is adjusted using the FreeRTOSvTaskSetTimeOutState()
andxTaskCheckForTimeOut()
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 inxADCTaskToNotify
when the task was created.ADC_ConversionEndISR()
usesxTaskNotifyFromISR()
with theeAction
parameter set toeSetValueWithoutOverwrite
to send a task notification to thevADCTask()
task, and write the result of the ADC conversion into the task's notification value.The
vADCTask()
task usesxTaskNotifyWait()
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.
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