FreeRTOS - Interrupt management
An interrupt service routine is a hardware feature because the hardware controls which interrupt service routine will run, and when it will run. Tasks will only run when there are no ISRs running, so the lowest priority interrupt will interrupt the highest priority task, and there is no way for a task to pre-empt an ISR.
The Interrupt Safe API
Often it is necessary to use the functionality provided by a FreeRTOS API function from an interrupt service routine (ISR), but many FreeRTOS API functions perform actions that are not valid inside an ISR — the most notable of which is placing the task that called the API function into the Blocked state; if an API function is called from an ISR, then it is not being called from a task, so there is no calling task that can be placed into the Blocked state.
FreeRTOS solves this problem by providing two versions of some API functions; one version for use from tasks, and one version for use from ISRs. Functions intended for use from ISRs have “FromISR” appended to their name.
Note: Never call a FreeRTOS API function that does not have “FromISR” in its name from an ISR
Deferred Interrupt Processing
It is normally considered best practice to keep ISRs as short as possible. Reasons for this include:
- Even if tasks have been assigned a very high priority, they will only run if no interrupts are being serviced by the hardware.
- ISRs can disrupt both the start time, and the execution time, of a task.
- It might not be possible to accept any new interrupts, or at least a subset of new interrupts, while an ISR is executing.
- Guarding against, resources such as variables, peripherals, and memory buffers being accessed by a task and an ISR at the same time.
- Interrupt nesting can increase complexity and reduce predictability. The shorter an interrupt is, the less likely it is to nest.
An interrupt service routine must record the cause of the interrupt, and clear the interrupt. Any other processing necessitated by the interrupt can often be performed in a task, allowing the interrupt service routine to exit as quickly as is practical. This is called ‘deferred interrupt processing’, because the processing necessitated by the interrupt is ‘deferred’ from the ISR to a task.
Deferring interrupt processing to a task also allows the application writer to prioritize the processing relative to other tasks in the application, and use all the FreeRTOS API functions.
If the priority of the task to which interrupt processing is deferred is above the priority of any other task, then the processing will be performed immediately, just as if the processing had been performed in the ISR itself.
This scenario is shown in the following figure, in which Task 1 is a normal application task, and Task 2 is the task to which interrupt processing is deferred.
In the above figure, interrupt processing starts at time t2, and effectively ends at time t4, but only the period between times t2 and t3 is spent in the ISR. If deferred interrupt processing had not been used then the entire period between times t2 and t4 would have been spent in the ISR.
The following figure shows how the execution of the deferred processing task can be controlled using a semaphore.
In the interrupt synchronization scenario, the binary semaphore can be considered conceptually as a queue with a length of one. The queue can contain a maximum of one item at any time, so is always either empty or full (hence, binary). By calling xSemaphoreTake(), the task to which interrupt processing is deferred effectively attempts to read from the queue with a block time, causing the task to enter the Blocked state if the queue is empty. When the event occurs, the ISR uses the xSemaphoreGiveFromISR() function to place a token (the semaphore) into the queue, making the queue full. This causes the task to exit the Blocked state and remove the token, leaving the queue empty once more. When the task has completed its processing, it once more attempts to read from the queue and, finding the queue empty, re-enters the Blocked state to wait for the next event. This sequence is demonstrated in the following figure.
Binary Semaphores are used for Synchronization. A Binary Semaphore can be used to unblock a task each time a particular interrupt occurs:
- The majority of the interrupt event processing can be implemented within the synchronized task in the ISR
- Only a very fast and short portion remaining directly
- The interrupt processing is said to have been ‘deferred’ to a ‘handler’ task
- The handler task uses a blocking ‘take’ call to a semaphore and enters the Blocked state to wait for the event to occur
- When the event occurs, the ISR uses a ‘give’ operation on the same semaphore to unblock the task
Create a Binary Semaphore
Use the vSemaphoreCreateBinary() API function (macro) to create a binary semaphore
void vSemaphoreCreateBinary(xSemaphoreHandle xSemaphore);
‘Take’ a Semaphore
Use xSemaphoreTake() to ‘take’ a semaphore
portBASE_TYPE xSemaphoreTake(xSemaphoreHandle xSemaphore, portTickType xTicksToWait );
‘Give’ a Semaphore
Use xSemaphoreGive() to ‘give’ a semaphore or xSemaphoreGiveFromISR() when in an ISR
portBASE_TYPE xSemaphoreGiveFromISR(xSemaphoreHandle xSemaphore, portBASE_TYPE *pxHigherPriorityTaskWoken );
When interrupts come at a speed faster than the handler task can process, events will be lost. Counting semaphore can be thought of as queues that have a length of more than one. Each time a counting semaphore is ‘given’, another space in its queue is used, count value is the length of the queue.
Counting semaphores are typically used for two things:
- Counting events
- The count value is the difference between the number of events that have occurred and the number that have been processed
- Resource management
- The count value indicates the number of resources available; a task must first obtains a semaphore before obtains the control of a resource; a task returns a semaphore when finishing with the resource.
Using a counting semaphore to ‘count’ events
Create a Counting Semaphore
Use xSemaphoreCreateCounting() to create a counting semaphore
xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_TYPE uxMaxCount, unsigned portBASE_TYPE uxInitialCount );