FreeRTOS offers special mechanisms to handle interrupts and ensure that the code executed within them works harmoniously with the rest of the created tasks. Semaphores, both binary and counting, are employed to synchronize an interrupt and a task. When data exchange is required, dedicated queues for interrupts can be utilized. It's crucial to note that priority levels play a significant role within the FreeRTOS framework.

A binary semaphore is a mechanism for both, mutual exclusion (Protect shared resources) and synchronization purposes. This semaphore only counts to 1.

Although mutual exclusion is possible using a binary semaphore is not recommended, a semaphore is given and taken by any task and is better to use as a signal mechanism to synchronize tasks, for example, is perfect to use with interrupts because a binary semaphore does not block task.

Code example:

#include "bsp.h"

void vPeriodicTask( void *pvParameters );   /* Declaration Task function */
void vHandlerTask( void *pvParameters );    /* Declaration Task function */

SemaphoreHandle_t xBinSemaphore;            /* Binary semaphore handler */

int main( void )
{
    HAL_Init( );
    /*enable RTT and system view*/
    SEGGER_SYSVIEW_Conf( );
    SEGGER_SYSVIEW_Start( );
    
    xBinSemaphore = xSemaphoreCreateBinary();                   /* Create Binary semaphore */
    
    HAL_NVIC_SetPriority( WWDG_IRQn, 2, 0 );                    /* Set the priority of WWDG interrupt */
    HAL_NVIC_EnableIRQ( WWDG_IRQn );                            /* Enable the interruption */

    xTaskCreate( vPeriodicTask, "task1", 240, NULL, 1, NULL );  /* Register on Kernel task with priority 1 */
    xTaskCreate( vHandlerTask,  "task2", 240, NULL, 2, NULL );  /* Register on Kernel task with priority 2 */

    vTaskStartScheduler();                                      /* Kernel execution */
}

void vPeriodicTask( void *pvParameters )
{
    /* Task generate an interruption each 500ms */
    for(;;)
    {
        vTaskDelay( 500 / portTICK_PERIOD_MS );                             /* vtask dleay of 500ms */
        SEGGER_RTT_printf( 0, "PeriodicTask - Generatin interruption\n" );  
        HAL_NVIC_SetPendingIRQ( WWDG_IRQn );                                /* Activation of interruption */
        SEGGER_RTT_printf( 0, "PeriodicTask - Interruption generated\n" );            
    }
}

void vHandlerTask( void *pvParameters )
{
    /* Task to synchronize the interruption using a semaphore */
    for(;;)
    {
        /* The Task blocks until the interruption release the semaphore */
        xSemaphoreTake( xBinSemaphore, portMAX_DELAY );
        SEGGER_RTT_printf( 0, "HandlerTask - Processing event\n" );             
    }
}

void WWDG_IRQHandler( void )                                /* Callback of the interrupt */
{ 
    /* Interruption Vector */
    BaseType_t xTaskWoken = pdFALSE;                        

    xSemaphoreGiveFromISR( xBinSemaphore, &xTaskWoken );    /* Release semaphore */
    
    portEND_SWITCHING_ISR(xTaskWoken);                      /* Change context if xTaskWoken=pdTRUE */
}
💡
NOTE: If you observe the callback function of the interrupt, you will note in lines 50 and 54, a variable declaration and a new function. BaseType_t xTaskWoken = pdFALSE; Declaration of this variable helps to know if xSemaphoreGiveFromISR was successful. portEND_SWITCHING_ISR(xTaskWoken); Function to change the context, if xTaskWoken is pdTRUE function will change the context immediately, Otherwise the change will not be imminent.

In this program, two tasks are created and an interrupt vector is managed. The vHandlerTask task executes first and attempts to take the semaphore within the for loop, but it has not been released, causing the task to be suspended indefinitely. The vPeriodicTask task executes by triggering an interrupt, which is immediately handled by the vInterruptHandler function. This interrupt releases the semaphore and triggers a context switch (line 53), allowing the higher-priority vHandlerTask to preempt the vPeriodicTask (which was in the running state), eventually taking the semaphore, printing to the screen, and repeating the cycle.

💡
Before continuing: What would happen if line 51 is commented out? The interrupt causes a context switch because the task waiting for the semaphore has higher priority. What would happen if the task had equal or lower priority than vPeriodicTask?
💡
NOTE: If you're using SEGGER SystemView, you can use the macros traceISR_ENTER() and traceISR_EXIT() to visualize the interrupt.

SytemView Output

Terminal Shows the order in which the tasks are running although task2 has the highest priority. Terminal shows that Task2 is executed only when the interruption is generated.

Document

Timeline:

Task 2 is blocked by the binary semaphore, observe that task1 is executed to generate an interruption where the semaphore will be given, when the callback of the interruption executes will change the context, and task2 now can take CPU time, to after take binary semaphore blocking itself again, finally task1 resume the execution to print that the interrupt was generated.

Document