One of the versatile uses of task notifications is to emulate event groups. An event group is essentially a collection of binary flags (bits) that tasks can set, clear, and wait on. These flags can represent various events or conditions within the system.

When using task notifications as an event group, the notification value of a task acts as the event group. Each bit in the notification value represents a different event flag. Tasks can wait for specific bits to be set, clear bits, or set bits to signal events.

Code Example:

#include "bsp.h"

/* Definitions of the events */
#define BUTTON1_PIN  GPIO_PIN_5     /* Event generated by the first button */
#define BUTTON2_PIN  GPIO_PIN_7     /* Event generated by the second button */
#define BUTTON3_PIN  GPIO_PIN_15    /* Event generated by the third button */

#define ALL_EVENTS  ( BUTTON1_PIN | BUTTON2_PIN | BUTTON3_PIN ) /* All events together */

TaskHandle_t xTaskHandle = NULL;    /* Task handler to send notifications events */

void vTask(void *pvParameters);     /* declaration of Task function */

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(vTask, "Receiver_Task", 240, NULL, 1, &xTaskHandle); /* Register the Receiver Task */

    vTaskStartScheduler();                                              /* Start Kernel */
}

void vTask(void *pvParameters) {
    uint32_t ulNotificationValue;                                       /* Variable to receive the notification value */
    
    for (;;) {
        xTaskNotifyWait( 0, 0, &ulNotificationValue, portMAX_DELAY );   /* Wait for the notification of any button */

        if ((ulNotificationValue & ALL_EVENTS) == ALL_EVENTS)           /* Verify that the three buttons were pressed */
        {
            HAL_GPIO_TogglePin( GPIOC, GPIO_PIN_0 );                    /* switch the LED state */
            ulTaskNotifyValueClear( xTaskHandle, 0xffffffff );          /* Clear the notification value to read again three events */
        }
    }
}

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

    if( GPIO_PIN_5 == GPIO_Pin )                                                /* Check if first button was pressed */
    {
        xTaskNotifyFromISR(xTaskHandle, BUTTON1_PIN , eSetBits, &xTaskWoken );  /* Send the notification for the specific event */
    }
    else if( GPIO_PIN_7 == GPIO_Pin )                                           /* Check if second button was pressed */
    {
        xTaskNotifyFromISR(xTaskHandle, BUTTON2_PIN , eSetBits, &xTaskWoken );  /* Send the notification for the specific event */
    }
    else                                                                        /* check if the third button was pressed */
    {
        xTaskNotifyFromISR(xTaskHandle, BUTTON3_PIN , eSetBits, &xTaskWoken );  /* Send the notification for the specific event */
    }

    SEGGER_SYSVIEW_RecordExitISR();                                         /* Record exit of IRS on SytemView timeline */
    portEND_SWITCHING_ISR( xTaskWoken );                                    /* Change context */
}

This application manages three different events generated by buttons, when the three buttons are pressed the Receiver_Task executes a specific action for example for this code, the Receiver_Task switches the state of an LED.

Notice in the callback function that the function xTaskNotifyFromISR() sends the notification to the task, but the parameter eSetBits allows combining the bits from each button, which means that the bits of button 1 can be set with the bits of button 2 and button 3. The Receiver_Task waits for each bit of button but will execute only the action when the three buttons are pressed.

SytemView Output:

For the system to display this message, it is necessary that the three buttons be pressed at least once. This ensures that each event has occurred at least once, which is a prerequisite for the message to be shown. Each independent button pressed is considered an event.

Document

TimeLine:

Document

When the first button is pressed the ISR callback sends the notification to the Receiver_Task, observe on the image that the ISR is generated sending the notification value of 32dec or 0x0020 hex which is the pin button register.
Subsequently, the Receiver_Task detects the event but is programmed to execute when three buttons are pressed.

The second button is pressed, and observe the ISR callback sends the value of 128dec or 0x0080 hex as a notification value. In the same form, the Receiver_Task executes detecting the Event received.
At this point, two button events have been detected.

When the third button is pressed a parasitic ISR is generated from the same button, this ISR callback sends to the notification value 32768dec or 0x8000hex.
Now the Receiver_Task detects that the three events have occurred, executing a specific action which sends a message to the terminal and switches the LED state.