FreeRTOS Task Notifications: A Lightweight Method for Waking Threads

I was recently implementing a FreeRTOS-based system and needed a simple way to wake my thread from an ISR. I was poking around the FreeRTOS API manual looking at semaphores when I discovered a new feature: task notifications.

FreeRTOS claims that waking up a task using the new notification system is ~45% faster and uses less RAM than using a binary semaphore.

The following APIs are used to interact with task notifications:

The notification system utilizes the 32-bit task handle (TaskHandle_t) that is set during the task creation process:

BaseType_t xTaskCreate(    TaskFunction_t pvTaskCode,
                            const char * const pcName,
                            unsigned short usStackDepth,
                            void *pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t *pxCreatedTask
                          );

Task notifications can be used to emulate mailboxes, binary semaphores, counting semaphores, and event groups.

While task notifications have speed and RAM advantages over other FreeRTOS structures, they are limited in that they can only be used to wake a single task with an event. If multiple tasks must be woken by an event, you must utilize the traditional structures.

Real World Example

I took advantage of the task notifications to implement a simple thread-waking scheme.

I have a button which can be used to request a system power state change. The power state changes can take quite a bit of time, and we don't want to be locked up inside of our interrupt handler for 1-10s.

Instead, I created a simple thread which sleeps until a notification is received. When the thread wakes, we check to see if there is an action we need to take:

void powerTaskEntry(__unused void const* argument)
{
    static uint32_t thread_notification;

    while(1)
    {
        /* Sleep until we are notified of a state change by an 
        * interrupt handler. Note the first parameter is pdTRUE, 
        * which has the effect of clearing the task's notification 
        * value back to 0, making the notification value act like
        * a binary (rather than a counting) semaphore.  */

        thread_notification = ulTaskNotifyTake(pdTRUE, 
                        portMAX_DELAY);

        if(thread_notification)
        {
            system_power_evaluate_state();
        }
    }
}

In order to wake the thread, we send a notification from the interrupt handler:

void system_power_interrupt_handler(uint32_t time)
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    if(time < SYS_RESET_SHUTDOWN_THRESHOLD_MS)
    {
        // Perform a reset of computers
        computer_reset_request_ = RESET_ALL_COMPUTERS;
    }
    else if((time >= SYS_RESET_SHUTDOWN_THRESHOLD_MS) &&
            (time < SYS_RESET_HARD_CUTOFF_THRESHOLD_MS))
    {
        system_power_request_state(SYSTEM_POWER_LOW);
    }
    else
    {
        system_power_request_state(SYSTEM_POWER_OFF_HARD);
    }

    // Notify the thread so it will wake up when the ISR is complete
    vTaskNotifyGiveFromISR(powerTaskHandle, 
                         &xHigherPriorityTaskWoken);
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

Note the use of portYIELD_FROM_ISR(). This is required when waking a task from an interrupt handler. If vTaskNotifyGiveFromISR indicates that a higher priority task is being woken, the portYIELD_FROM_ISR() routine will context switch to that task after returning from the ISR.

Failure to use this function will result in execution will resuming at the previous point rather than switching to the new context.

Further Reading