In FreeRTOS, task notifications provide a lightweight and efficient mechanism for inter task communication and synchronization. One powerful use case is employing task notifications as a counter semaphore. Unlike traditional semaphores, task notifications offer a faster and more memory-efficient alternative, making them ideal for resource-constrained embedded systems.

When a task uses its notification value as a counter semaphore, it can be incremented by other tasks or interrupts using the xTaskNotifyGive() macro or the xTaskNotify(). This allows the task to count events or resources, similar to a counting semaphore. The task can then count the notification using the ulTaskNotifyTake() function, which decrements the notification value and unblocks the task when the value is greater than zero.

This approach is particularly useful in scenarios where tasks need to synchronize access to shared resources or coordinate events without the traditional semaphore mechanisms. By employing task notifications, can achieve efficient and responsive task synchronization.

Code Example:

#include "bsp.h"

void Task_ReadEvents( void *pvParameters ); /* Task to process the notifications received */

TaskHandle_t TaskH1 = NULL;                 /* Declare the Handler Task to send notifications */

uint32_t LED_pin[3] = { GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2 };   /* Array to store LED pins */

int main( void )
{
    HAL_Init();

    SEGGER_SYSVIEW_Conf();
    SEGGER_SYSVIEW_Start();

    HAL_NVIC_SetPriority( EXTI4_15_IRQn, 2, 0  );                        /* Set the priority of external interrupt */
    HAL_NVIC_EnableIRQ( EXTI4_15_IRQn );                                 /* Enable External interrupt */

    xTaskCreate( Task_ReadEvents, "Read_Event", 240, NULL, 2, &TaskH1 ); /* Register Task to process each notification event */

    vTaskStartScheduler();                                               /* Run Kernel */

    return 0;
}

void Task_ReadEvents( void *pvParameters )
{
    UNUSED(pvParameters);
    uint32_t NotificationCount = 0;                                      /* Variable to count the number of notifications received */

    for(;;)
    {
        NotificationCount = ulTaskNotifyTake( pdFALSE, portMAX_DELAY ); /* If notification is received unblocks the task and execute, 
                                                                           returns the number of notifications */  
        if (NotificationCount <= 3)                                     /* Process the notification event if there are less than 3 notifications */
        {
            HAL_GPIO_WritePin( GPIOC, LED_pin[(NotificationCount-1)], GPIO_PIN_SET );   /* Turn-On an specific LED */
        }
        else if( NotificationCount >= 4 )                               /* If the number of notifications events exceeds the limit */
        {
            HAL_GPIO_WritePin( GPIOC, GPIO_PIN_All, GPIO_PIN_RESET );   /* Clear all the LED status */
            ulTaskNotifyValueClear( TaskH1, 0xFFFFFFFF );               /* Clear all the notifications events */
        }


        vTaskDelay( 2000/ portTICK_PERIOD_MS);                          /* Period to read notification counts */
    }
}

void HAL_GPIO_EXTI_Falling_Callback( uint16_t GPIO_Pin )                     /* Callback of interrupt */
{
    SEGGER_SYSVIEW_RecordEnterISR();                                        /* Record enter of IRS on SytemView timeline */
    BaseType_t xTaskWoken = pdFALSE;

    vTaskNotifyGiveFromISR( TaskH1, &xTaskWoken );                          /* Send a notification to the task */
    
    SEGGER_SYSVIEW_RecordExitISR();                                         /* Record exit of IRS on SytemView timeline */
    portEND_SWITCHING_ISR( xTaskWoken );                                    /* Change context */
}   

This is an example of counting events, similar to the counter semaphores example. In this application, an ISR is configured to send a notification each time a button is pressed, incrementing the notification value if the receiving task is not ready yet. Using this method, each button press generates an event. The receiver task executes every 2000ms. When the receiver task runs, it counts the number of registered events and processes the corresponding actions.

SystemView Output:

The terminal displays the results of various actions executed by the receiver task. These actions vary depending on the number of events generated before the task’s execution. For example, if 3 events are generated, the receiver task turns on a series of LEDs. However, if 4 or more events are generated, the LEDs turn off and the event counter resets to 0.

Document

TimeLine:

Document

The images illustrate the ISR execution. The first two events are generated, and after the last event, note that this event is generated within the execution period of the Receiver Task.



When the time elapsed, the Receiver task execute and find three generated events, and process the corresponding action, which in this example, just turn on a series of LEDs.

This image shows a number of ISR events generated, this number of events cause that receiver task execute another action.

When the time elapsed, the receiver task execute and find that 4 events are generated then execute an action to turn off the LEDs.