FreeRTOS relies on both the SysTick and PendSV exceptions to implement preemptive multitasking. However, these are not the only interrupts that may occur during the execution of a FreeRTOS application. In fact, most applications built on FreeRTOS will also handle interrupts from a variety of other sources. Understanding how FreeRTOS tasks interact with Interrupt Service Routines (ISRs) is essential for developing applications that leverage both the real-time capabilities of the OS and the asynchronous nature of external interrupts.
The following excerpt provides some important differences in a task and an interrupt service routine.
It is important to draw a distinction between the priority of a task, and the priority of an interrupt:
A task is a software feature that is unrelated to the hardware on which FreeRTOS is running. The priority of a task is assigned in software by the application writer, and a software algorithm (the scheduler) decides which task will be placed in the Running state.
Although written in software, 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
[1] Mastering the FreeRTOS Real-Timer Kernel v1.1.0, Pg 157
The SysTick timer is configured to generate interrupts at 1-millisecond intervals and operates at the highest hardware priority level. Running at this priority ensures the precise timekeeping required by an RTOS. Each time the SysTick interrupt occurs, its Interrupt Service Routine (ISR) increments the system tick count and then triggers a PendSV interrupt to initiate context switching between tasks.
The PendSV interrupt is configured to run at the lowest possible interrupt priority, ensuring that context switching between tasks does not interfere with non-RTOS-related ISRs. As a result, the execution of the PendSV handler may be delayed by higher-priority interrupts, but the only consequence is a brief postponement in task switching.
The image below has been provided to summarize the relative priority level of tasks and interrupts in a FreeRTOS application.
Top-Half/Bottom-Half Organization
To effectively manage interrupts in FreeRTOS, we use a top-half/bottom-half arrangement. In this model, the Interrupt Service Routine (ISR) serves as the high-priority top half, responsible for quickly handling the immediate hardware event.
The top-half ISR is designed so that it is as fast and efficient as possible in servicing the interrupt, efficiently buffering data, clearing the interrupt in hardware, and alerting the bottom half so it can process data.
The bottom half is implemented as a dedicated, high-priority FreeRTOS task that performs the more time-consuming data processing. This separation ensures that ISRs remain short and responsive, while deferring complex logic to a task that can run safely within the FreeRTOS scheduler.
Example Top-Half Interrupt Service Routine
The following example illustrates how an Interrupt Service Routine (ISR) can be structured using a top-half/bottom-half configuration. While the specifics of data buffering are omitted—since multiple valid buffering strategies exist—the focus here is on how to properly structure the ISR to quickly capture incoming data and alert the bottom-half task for deferred processing. This approach ensures minimal ISR execution time, allowing the system to remain responsive while delegating more complex or time-consuming operations to non-ISR code running in a FreeRTOS task.
The key takeaway from this example is that the interrupt-safe variants of the FreeRTOS APIs must be used within an ISR.
The ISR uses vTaskNotifyGiveFromISR() to alert the bottom-half task that data is ready for processing. If this call unblocks a higher-priority task, the ISR then calls portYIELD_FROM_ISR() to request a context switch. This ensures that the newly unblocked task can begin executing immediately after the ISR completes.
The FreeRTOS API provides FromISR() variants for several synchronization primitives, including semaphores, queues, task notifications, and event groups. Each of these mechanisms has its own trade-offs in terms of complexity, overhead, and use case suitability. Among them, task notifications are often the preferred choice for ISR-to-task signaling due to their minimal overhead and lower context switch latency. This helps reduce the time between an interrupt event and the corresponding software response, which is critical in real-time applications.
Example Bottom-Half Task
The bottom-half task is typically assigned a higher priority than other FreeRTOS tasks to ensure that incoming data is processed promptly. This is especially important in real-time systems, where the data received by the ISR may influence time-sensitive decisions in the application’s control logic.
The bottom-half task remains in a blocked state until it receives a notification from the ISR. Once notified, the task wakes up and begins processing the data. The specifics of how the data is processed depend heavily on the peripheral associated with the task, so those details have been intentionally omitted to keep the example focused on ISR structure and task synchronization.