
1. Basic Concepts
In FreeRTOS, there is an efficient and lightweight inter-task communication mechanism: <span>Task Notification</span>. This mechanism is used in FreeRTOS to implement unidirectional communication, event flags, counting semaphores, binary semaphores, and message passing.
The message notification mechanism in FreeRTOS can also be referred to as “Direct to Task Notifications”.
Each task (<span>TaskHandle_t</span>) has a built-in notification value (notification value), similar to a <span>uint32_t</span> type variable, initialized to 0. FreeRTOS reserves such a variable for each task to facilitate the passing of notification values.
2. Implementation Principles
The implementation principle of FreeRTOS’s <span>Task Notification</span> mechanism is a classic lightweight design paradigm. It utilizes a <span>uint32_t</span> variable within each task control block (<span>TCB</span>) in the kernel to achieve efficient inter-task synchronization and communication.
<1>. Infrastructure
In FreeRTOS, each task’s control block <span>TCB_t</span> contains a field for task notifications:
typedef struct tskTaskControlBlock
{
...
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
...
} TCB_t;
By default, <span>configTASK_NOTIFICATION_ARRAY_ENTRIES == 1</span>, meaning each task has one notification slot (one 32-bit notification value + one state bit). This can be configured for multiple slots, allowing tasks to have multiple “notification channels”.
<2>. Notification Value and State
Each notification slot consists of two parts:
| Field | Description |
|---|---|
<span>ulNotifiedValue</span> |
Notification value (can be a count, bitmap, or a numeric value) |
<span>ucNotifyState</span> |
Notification state, three types: <span>taskNOT_WAITING_NOTIFICATION</span>, <span>taskWAITING_NOTIFICATION</span>, <span>taskNOTIFICATION_RECEIVED</span> |
State Transition Process
Initial state: taskNOT_WAITING_NOTIFICATION
-> Task calls ulTaskNotifyTake() or xTaskNotifyWait()
-> State changes to: taskWAITING_NOTIFICATION
-> Other tasks or ISR send notifications
-> Notification value is modified (increment, set bits, overwrite, etc.)
-> State changes to: taskNOTIFICATION_RECEIVED
-> Task unblocks and processes notification
-> State returns to: taskNOT_WAITING_NOTIFICATION
<3>. Comparison of Solutions
Why is it more efficient than semaphores or queues? This is mainly reflected in the following points:
| Feature | Message Notification | Queue/Semaphore |
|---|---|---|
| Memory Overhead | Uses only 1 field in the task control block | Requires additional structure memory |
| Context Switching | Minimized, occurs only when necessary | Sometimes involves enqueue/dequeue switching |
| Multifunctionality | Can act as events, counters, binary semaphores, etc. | Single purpose |
| Speed | Extremely fast, minimal inline functions | Involves more boundary checks and copy operations |
3. Interface Definitions
<1>. xTaskNotify()
BaseType_t xTaskNotify(
TaskHandle_t xTaskToNotify,
uint32_t ulValue,
eNotifyAction eAction
)
Main Internal Operations
- <1>. Find the corresponding
<span>TCB_t</span>through<span>xTaskToNotify</span> - <2>. Operate on
<span>ulNotifiedValue</span>based on<span>eAction</span>: <span>eSetBits: |= ulValue</span><span>eIncrement: ++</span><span>eSetValueWithOverwrite: = ulValue</span><span>eSetValueWithoutOverwrite</span>: Only write if<span>state != RECEIVED</span>- <3>. Set
<span>ucNotifyState = taskNOTIFICATION_RECEIVED</span> - <4>. If the target task is in
<span>taskWAITING_NOTIFICATION</span>state: - Remove it from the blocked queue and place it in the ready queue
- Determine if a context switch should be triggered (e.g., use
<span>portYIELD_FROM_ISR()</span>in<span>ISR</span>)
<2>. xTaskNotifyWait()
BaseType_t xTaskNotifyWait(
uint32_t ulBitsToClearOnEntry,
uint32_t ulBitsToClearOnExit,
uint32_t *pulNotificationValue,
TickType_t xTicksToWait
)
Main Internal Logic
- <1>. Clear the bits specified by entry:
<span>ulNotifiedValue &= ~ulBitsToClearOnEntry</span> - <2>. If
<span>ucNotifyState != taskNOTIFICATION_RECEIVED</span>: - Set task state to
<span>taskWAITING_NOTIFICATION</span> - Add the task to the delay list (blocked)
- <3> When notification arrives, the task unblocks:
- Assign
<span>ulNotifiedValue</span>to the user pointer - Clear the bits specified by exit
<span>ucNotifyState = taskNOT_WAITING_NOTIFICATION</span>
<3>. ulTaskNotifyTake()
uint32_t ulTaskNotifyTake(BaseType_t xClearCountOnExit, TickType_t xTicksToWait);
Function: Simulate Counting Semaphore
- If
<span>ulNotifiedValue > 0</span>: - Return that value (or 1)
- If
<span>xClearCountOnExit == pdTRUE</span>, clear it to 0 - Otherwise decrement by 1
- If the value is 0:
- Set task state to
<span>taskWAITING_NOTIFICATION</span> - Add to delay list, waiting for notification
<4>. xTaskNotifyGive()
BaseType_t xTaskNotifyGive(TaskHandle_t xTaskToNotify);
Send a “notification” to a specific task
Calling xTaskNotifyGive() will perform the following operations:
- Increment the notification value of the target task
<span>ulNotifiedValue[0]</span>(equivalent to giving a counting semaphore); - Set the notification state of that task
<span>ucNotifyState[0]</span>to<span>taskNOTIFICATION_RECEIVED</span>; - If the target task is in the waiting notification state of
<span>ulTaskNotifyTake()</span>, it will be removed from the blocked list and become ready; - If the target task has a higher priority than the current task, it can trigger a context switch.
4. Application Examples
4.1. Notification Types
FreeRTOS supports various ways of task notifications to simulate different communication/synchronization models:
<1>. Binary Semaphore Mode
- Use
<span>xTaskNotifyGive()</span>and<span>ulTaskNotifyTake()</span>. - One notification corresponds to one release (similar to
<span>binary semaphore</span>).
<2>. Counting Semaphore Mode
- Increase count with multiple
<span>xTaskNotifyGive()</span>. <span>ulTaskNotifyTake(pdTRUE, xTicksToWait)</span>will clear the value to 0 after reading.
<3>. Event Flag Mode
- Use
<span>xTaskNotify()</span>with<span>eSetBits</span>mode to set certain bits of the notification value. <span>xTaskNotifyWait()</span>combined with<span>ulBitsToClearOnEntry</span>and<span>ulBitsToClearOnExit</span>to handle multiple event bits.
<4>. Message Mode
- Use
<span>xTaskNotify()</span>with<span>eSetValueWithOverwrite</span>or<span>eSetValueWithoutOverwrite</span>. - The notification value is passed as a message to the task.
- Often combined with
<span>xTaskNotifyWait()</span>or<span>ulTaskNotifyTake()</span>to retrieve.
4.2. Signal Definitions
typedef enum
{
eNoAction = 0, // No action, just wake up the task
eSetBits, // Set specified bit positions in the notification value (event group)
eIncrement, // Notification value +1 (similar to counting semaphore)
eSetValueWithOverwrite, // Overwrite notification value
eSetValueWithoutOverwrite // Do not overwrite notification value, fail if notification has not been taken
} eNotifyAction;
4.3. Code Examples
Simple Signal (e.g., ISR notifying a task)
void ISR_Handler(void)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
vTaskNotifyGiveFromISR(xTaskHandle, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void Task(void *pvParameters)
{
for(;;)
{
// Block waiting for notification, block for up to 100 ticks
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(100));
// Execute task processing after being notified
}
}
Event Bit Mode
// Sender sets event bits
xTaskNotify(taskHandle, 0x04, eSetBits);
// Receiver waits for event bits
uint32_t ulNotificationValue;
xTaskNotifyWait(0x00, // Do not clear the value before entry
0xFFFFFFFF, // Clear all bits on exit
&ulNotificationValue,
portMAX_DELAY);
if (ulNotificationValue & 0x04) {
// Received specific event bit
}
4.4. Application Summary
Advantages
- Efficient (no heap memory allocation, minimal context switching)
- Lightweight (each task has its own notification value)
- Supports various synchronization/communication models
Considerations
- Each task has only one notification value.
- Cannot buffer multiple messages like queues, suitable for high real-time, low data volume communication.
- When using
<span>eSetValueWithoutOverwrite</span>, be cautious whether the notification value has been taken.

Previous Recommendations
RECOMMEND

[1]. 【FreeRTOS Development】Message Buffer: An Advanced Choice for Task Communication
[2]. 【FreeRTOS Development】In-Depth Analysis of Stream Buffers: A Powerful Tool for Real-Time Byte Stream Communication!
[3]. 【Linux Basics】How to Analyze CPU Performance in Embedded Systems
[4]. 【FreeRTOS Development】Completely Understand Event Flag Groups: A Powerful Tool for Multi-Task Synchronization
I am Aike, an embedded software engineer.
Follow me for more embedded insights.
Remember to like, share, and click to see more,
Your encouragement is my greatest motivation to continue sharing!
See you next time.
Looking forward to your
sharing
likes
views
NEWS
WeChat ID|aike_eureka
Baijiahao|Master Embedded Systems