When an interrupt occurs, the system temporarily suspends the currently executing task to handle the interrupt. During this time, the CPU is in an interrupt context, which means it’s executing code related to the interrupt.

In an ISR, you want to perform minimal work to ensure timely handling of the interrupt. Blocking the ISR for an extended period is generally not desirable.

When we use the normal functions for example xQueueSend() Function will block the ISR because the function waits for a space available on the Queue to Write. functions designed to be used in an interruption for example xQueueSendFromISR() will not block the current task execution.

Code Example:

#include "bsp.h"

void vGeneratorTask( void *pvParameters );      /* Declaration Task function */          
void vPrinterTask( void *pvParameters );        /* Declaration Task function */

QueueHandle_t xStrQueue;                        /* Queue handler */
QueueHandle_t xIntQueue;                        /* Queue handler */

int main( void )
{
    HAL_Init( );
    /*enable RTT and system view*/
    SEGGER_SYSVIEW_Conf( );
    SEGGER_SYSVIEW_Start( );
    
    xStrQueue = xQueueCreate( 6, sizeof(uint32_t) );                /* Creation of Queue to send numbers */
    xIntQueue = xQueueCreate( 6, sizeof(char*) );                   /* Creation of Queue to send strings */
    
    HAL_NVIC_SetPriority( WWDG_IRQn, 2, 0 );                        /* Set the priority of WWDG interrupt */
    HAL_NVIC_EnableIRQ( WWDG_IRQn );                                /* Enable the interruption */

    xTaskCreate( vGeneratorTask, "task1", 240, NULL, 1, NULL );     /* Register on Kernel task with priority 1 */
    xTaskCreate( vPrinterTask,  "task2", 240, NULL, 3, NULL );      /* Register on Kernel task with priority 3 */

    vTaskStartScheduler();                                          /* Kernel execution */
}

void vGeneratorTask( void *pvParameters )
{
    /* Task generate an interruption each 200ms */
    unsigned long i;

    for(;;)
    {
        vTaskDelay( 500 / portTICK_PERIOD_MS );                                 /* vtask dleay of 200ms */
        
        for( i=0 ; i<4 ; i++ )
        {
            xQueueSend( xIntQueue, &i, 0 );                                    /* Send the number of loop iteration to the Queue of interruption */
        }
        
        SEGGER_RTT_printf(0, "GeneratorTask - Generatin interruption\n" );         
        HAL_NVIC_SetPendingIRQ( WWDG_IRQn );                                   /* Activation of interruption */
        SEGGER_RTT_printf(0, "GeneratorTask - Interruption generated\n" );             
    }
}

void vPrinterTask( void *pvParameters )
{
    /* Task to synchronize the interruption using a Queue */
    char *pcString;

    for(;;)
    {
        xQueueReceive( xStrQueue, &pcString, portMAX_DELAY );
        SEGGER_RTT_printf(0, pcString );             
    }
}

void WWDG_IRQHandler( void ) 
{
    /* Interruption Vector */
    BaseType_t xTaskWoken = pdFALSE;
    static uint32_t ulNumber;                                                   /* Variable to sotore the message from the Queue */
    static const char *pcStrings[4] = { 
                      "String 0\n", "String 1\n", "String 2\n", "String 3\n" }; /* array to send a message according the number received by a Queue */

    /* Read all the messages on the Queue */
    while(xQueueReceiveFromISR(xIntQueue,&ulNumber,&xTaskWoken)==pdTRUE)
    {
    	xQueueSendFromISR(xStrQueue, &pcStrings[ulNumber], &xTaskWoken);        /* Send the messages according the arrival number */
    }

    portEND_SWITCHING_ISR(xTaskWoken);                                          /* Change context if xTaskWoken=pdTRUE */
}

Once again, we have a task responsible for triggering an interrupt every 500ms, but in addition to that, it sends numbers from 0 to 3 to a waiting queue (xIntQueue). This waiting queue will be read by the interrupt vector (vInterruptHandler), which, for this specific purpose, must use the queue reading function from an ISR (xQueueReceiveFromISR). The ISR receives the numbers and then sends character strings (pointers) to another waiting queue (xStrQueue). Upon completion, a context switch occurs as the vPrinterTask, responsible for reading the string queue, has a higher priority.

💡
Before continuing: Analyze why the waiting queues used within an interrupt don't have a timeout and why they accept the variable xTaskWoken as the third parameter.

SystemView Output

The terminal shows similar behavior to the 2 last examples, but now a Queue is used instead of semaphores. Observe that the message is sent in task1 and when task1 calls the interruption, the interruption will send another message to be executed in task2 to after be printed on the terminal. finally, task1 just prints on the terminal to indicate that Interruption and task2 are executed successfully.

Document

Timeline:

Notice that task1 is a little longer compared with the last 2 examples, this is caused by the for loop sending a message to a queue, when the loop finishes task1 executes the interruption, which will read all the messages on the Queue to send another Queue now with strings data, after, the task2 is executed to read the strings message on Queue and print on the terminal the data. Finally, the task resumes to print that interruption was generated.

Document