In this lesson, you will learn how to design interrupt-safe data pipelines that move information from high-priority ISRs to background tasks without causing system-wide jitter.
The Challenge of ISR-to-Task Communication
When an Interrupt Service Routine (ISR) receives data from hardware, it cannot perform blocking operations (like waiting for a mutex) because it must exit immediately to avoid violating real-time scheduling constraints. If an ISR attempts to update a shared variable or queue that a task is currently accessing, it risks data corruption or race conditions.
To maintain determinism, we decouple the ISR from the processing task using a non-blocking communication buffer. The goal is to keep the ISR execution time constant and minimal, moving the "heavy lifting" (processing) to a low-priority task.
Designing the Safe Pipeline
We use a lock-free structure or an RTOS-native "ISR-safe" queue to pass data. An ISR-safe primitive disables interrupts or uses atomic instructions to ensure that the task-level code cannot preempt the buffer update halfway through.
Consider this pattern for a producer (ISR) and consumer (Task):
// ISR: High Priority, must be fast
void Hardware_ISR(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Add data to a thread-safe queue
// The "FromISR" suffix indicates an atomic, non-blocking operation
xQueueSendFromISR(xDataQueue, &incomingData, &xHigherPriorityTaskWoken);
// If the queue was full or a task was waiting, notify the scheduler
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
By using xQueueSendFromISR, the kernel manages the transition atomically. The xHigherPriorityTaskWoken flag is critical: it tells the scheduler whether the newly arrived data has made a high-priority task ready to run, allowing the kernel to trigger an immediate context switch only after the ISR completes.
This approach ensures that your ISR never waits for a resource, keeping its Worst-Case Execution Time (WCET) predictable. The task handles the data processing, effectively moving the computational burden out of the interrupt context and into the scheduled domain.
Key Takeaway: Always use non-blocking, interrupt-safe primitives to pass data from ISRs to tasks, ensuring the ISR remains short and deterministic by offloading processing to the task scheduler.