# 8. Dealing with Interrupts

This tutorial addresses hardware interrupts. As a basic example, we'll get a task out of the blocked state upon an interrupt event. Let us illustrate this with the user push-button EXTI interrupt.

## 1. Interrupt setup

Make sure that you have an init function for the push-button, that enables EXTI events on the corresponding pin (PC13):

/*
* BSP_PB_Init()
* Initialize Push-Button pin (PC13) as input without Pull-up/Pull-down
*/

void BSP_PB_Init()
{
// Enable GPIOC clock
RCC->AHBENR |= RCC_AHBENR_GPIOCEN;

// Configure PC13 as input
GPIOC->MODER &= ~GPIO_MODER_MODER13_Msk;
GPIOC->MODER |= (0x00 <<GPIO_MODER_MODER13_Pos);

// Disable PC13 Pull-up/Pull-down
GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR13_Msk;

// Enable SYSCFG clock
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;

// Select Port C as interrupt source for EXTI line 13
SYSCFG->EXTICR[3] &= ~ SYSCFG_EXTICR4_EXTI13_Msk;
SYSCFG->EXTICR[3] |=   SYSCFG_EXTICR4_EXTI13_PC;

// Enable EXTI line 13
EXTI->IMR |= EXTI_IMR_IM13;

// Disable Rising / Enable Falling trigger
EXTI->RTSR &= ~EXTI_RTSR_RT13;
EXTI->FTSR |=  EXTI_FTSR_FT13;
}


The corresponding interrupt channel must be enabled at NVIC level. Note that several interrupts are already in use by FreeRTOS itself. Those system interrupts must be kept at higher priority level. Therefore, the user interrupts must use subsequent priority numbers. There's a symbol in FreeRTOSConfig.h that defines the reserved interrupt priorities boundary (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY). It is recommended to use that symbol (+offset) to set the interrupt priority. Remember that unlike task priorities numbering, the higher the number here, the less the priority level. So giving a priority of configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY+1 provides the highest possible priority level for a user interrupt (i.e. after FreeRTOS interrupts). The next one would be configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY+2, and so on...

/*
* BSP_NVIC_Init()
* Setup NVIC controller for desired interrupts
*/

void BSP_NVIC_Init()
{
// Set maximum priority for EXTI line 4 to 15 interrupts
NVIC_SetPriority(EXTI4_15_IRQn, configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY + 1);

// Enable EXTI line 4 to 15 (user button on line 13) interrupts
NVIC_EnableIRQ(EXTI4_15_IRQn);
}


Then you will need the corresponding ISR (a.k.a Interrupt Handler). As a start, let us do something idiot, just printing the '#' character upon interruption, to make sure everything is well configured. Remember that interrupt handlers (ISR) are usually grouped in the stm32f0xx_it.c file:

/******************************************************************************/
/*                 STM32F0xx Peripherals Interrupt Handlers                   */
/*  Add here the Interrupt Handler for the used peripheral(s) (PPP), for the  */
/*  available peripheral interrupt handler's name please refer to the startup */
/*  file (startup_stm32f0xx.s).                                               */
/******************************************************************************/

/**
* This function handles EXTI line 13 interrupt request.
*/

void EXTI4_15_IRQHandler()
{
// Test for line 13 pending interrupt
if ((EXTI->PR & EXTI_PR_PR13_Msk) != 0)
{
// Clear pending bit 13 by writing a '1'
EXTI->PR = EXTI_PR_PR13;

// Do what you need
my_printf("#");
}

}

Finally, just try the following code in main.c. There are only two tasks. Task_1 toggles the LED every 200ms. Task_2 prints a '.' every 100ms. There are no inter-tasks objects involved, only delays.

/*
* main.c
*
*  Created on: 28/02/2018
*      Author: Laurent
*/

#include "main.h"

// Static functions
static void SystemClock_Config	(void);

// Main program
int main()
{
// Configure System Clock
SystemClock_Config();

// Initialize LED pin
BSP_LED_Init();

// Initialize the user Push-Button
BSP_PB_Init();

// Initialize Debug Console
BSP_Console_Init();

// Initialize NVIC
BSP_NVIC_Init();        // <-- Configure NVIC here

// Start Trace Recording
vTraceEnable(TRC_START);

// Start the Scheduler

while(1)
{
// The program should never be here...
}
}

/*
*/
{
while(1)
{
// LED toggle
BSP_LED_Toggle();

// Wait for 200ms
}
}

/*
*/
{

while(1)
{
my_printf(".");

// Wait for 100ms
}
}

As a result, you should see a printed '#' every time you press the user button, in between dots coming from Task_2. If that's working, then everything is correctly set.

## 2. Using a binary semaphore to synchronize a task

Declare a binary semaphore as a global variable:
// Kernel objects
xSemaphoreHandle xSem;

Then, create the semaphore before the scheduler is started:

...
// Create Semaphore object
xSem = xSemaphoreCreateBinary();

// Give a nice name to the Semaphore in the trace recorder
vTraceSetSemaphoreName(xSem, "xSEM");
...

Then edit the ISR so that the semaphore is released upon interrupt. Note that OS specific API functions MUST be used from within interrupt handlers. These function are well named with a 'fromISR' suffix. So instead of the regular xSemaphoreGive() function, we need to call a xSemaphoreGiveFromISR() function. Such '..fromISR()' functions exist for any kind of kernel object (semaphores, queues, event groups,...) when set from inside of an interrupt handler and must be used instead of the regular sister function. The second argument (&xHigherPriorityTaskWoken) permits to identify the highest priority task waiting for the released object. More on this later...

/**
* This function handles EXTI line 13 interrupt request.
*/

extern xSemaphoreHandle xSem;

void EXTI4_15_IRQHandler()
{

// Test for line 13 pending interrupt
if ((EXTI->PR & EXTI_PR_PR13_Msk) != 0)
{
// Clear pending bit 13 by writing a '1'
EXTI->PR = EXTI_PR_PR13;

// Release the semaphore
}
}


Finally, edit Task_2 so that it tries to take xSem for 100ms. In case of a success, we print '#'. Otherwise, if no semaphore was available for 100ms, we print '.':

/*
*/
{
portBASE_TYPE	xStatus;

while(1)
{
// Wait here for Semaphore with 100ms timeout
xStatus = xSemaphoreTake(xSem, 100);

// Test the result of the take attempt
if (xStatus == pdPASS)
{
// The semaphore was taken as expected

// Display console message
my_printf("#");
}

else
{
// The 100ms timeout elapsed without Semaphore being taken

// Display another message
my_printf(".");
}
}
}

As a result, we get the same behavior as before. This is the right way implementing an interrupt-to-task synchronization mechanism.

 Commit name "Interrupt"  Push onto Gitlab
Take a look at the trace below. An additional lane appears for the ISR. ISR can be considered as an 'above all' task in terms of priority. It cannot be preempted by anything other than another ISR of higher priority (in this case, it is not OS preemption that's involved but simply NVIC doing its job by the way).
We observe a significant delay between ISR execution and Task_2 getting active (about 700µs at first glance). This is because whenever the interrupt occurs, the information is processed by the scheduler on next OS tick event (i.e. every 1ms here). Therefore a worst case delay of 1ms is expected between the task getting ready, and the task getting active. Given that hardware interrupt processes are designed to process event quite 'instantly', such delay is barely acceptable!

In order to address the above raised issue, FreeRTOS features a dedicated mechanism to shortcut scheduler operation and get the waiting task active as soon as the ISR returns. Try this:

/**
* This function handles EXTI line 13 interrupt request.
*/

extern xSemaphoreHandle xSem;

void EXTI4_15_IRQHandler()
{

// Test for line 13 pending interrupt
if ((EXTI->PR & EXTI_PR_PR13_Msk) != 0)
{
// Clear pending bit 13 by writing a '1'
EXTI->PR = EXTI_PR_PR13;

// Release the semaphore

// Perform a context switch to the waiting task
}
}

The portEND_SWITCHING_ISR() function should be placed at the very end of the interrupt handler. It produces an instant context switch, bypassing the scheduler in order to give attention to the waiting task. See the result. Now Task_2 get active right after ISR completes.

 Commit name "Interrupt with context switching"  Push onto Gitlab

## 3. A potential issue when using interrupts

Well done, but we have still a potential issue in the above example. As it is now, the main() function enables the EXTI interrupt event and the corresponding NVIC channels BEFORE the scheduler is started. If an interrupt occurs in the meantime (small chance, but still), the ISR executes and the OS API functions xSemaphoreGiveFromISR() is called before the scheduler has been fired. That would lead to a crash.

The good practice is to configure and allow interrupts in the init part of the tasks, instead of in the main() function. When a task starts, you are 100% sure that scheduler has already been fired because it is the latter that actually get that task active. Doing so, it is no more possible regroup NVIC settings in a one-and-only function. You can use the code below as an example:

Let-us remove NVIC channel init from the common BSP function:
/*
* BSP_NVIC_Init()
* Setup NVIC controller for desired interrupts
*/

void BSP_NVIC_Init()
{
// Remove interrupt channel settings from here
}

Then perform interrupt settings as a part of Task_2 initializations:

/*
* main.c
*
*  Created on: 28/02/2018
*      Author: Laurent
*/

#include "main.h"

// Static functions
static void SystemClock_Config	(void);

// Kernel objects
xSemaphoreHandle xSem;

// Main program
int main()
{
// Configure System Clock
SystemClock_Config();

// Initialize LED pin
BSP_LED_Init();

// Initialize Debug Console
BSP_Console_Init();

// Start Trace Recording
vTraceEnable(TRC_START);

// Create Semaphore object
xSem = xSemaphoreCreateBinary();

// Give a nice name to the Semaphore in the trace recorder
vTraceSetSemaphoreName(xSem, "xSEM");

// Start the Scheduler

while(1)
{
// The program should never be here...
}
}

/*
*/
{
while(1)
{
// LED toggle
BSP_LED_Toggle();

// Wait for 200ms
}
}

/*
*/
{
portBASE_TYPE	xStatus;

// Initialize the user Push-Button
BSP_PB_Init();

// Set maximum priority for EXTI line 4 to 15 interrupts
NVIC_SetPriority(EXTI4_15_IRQn, configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY + 1);

// Enable EXTI line 4 to 15 (user button on line 13) interrupts
NVIC_EnableIRQ(EXTI4_15_IRQn);

// Now enter the task loop
while(1)
{
// Wait here for Semaphore with 100ms timeout
xStatus = xSemaphoreTake(xSem, 100);

// Test the result of the take attempt
if (xStatus == pdPASS)
{
// The semaphore was taken as expected

// Display console message
my_printf("#");
}

else
{
// The 100ms timeout elapsed without Semaphore being taken

// Display another message
my_printf(".");
}
}
}


Make sure the project builds and works as before. Now you've got everything you need to correctly deal with interrupts within FreeRTOS context.

 Commit name "Interrupt with init in task"  Push onto Gitlab