The "Holy Bible" for embedded engineers
Understanding interrupt handling, interrupt service routines, and interrupt management in real-time operating systems with focus on FreeRTOS implementation and real-time interrupt principles
Interrupts are like emergency phone calls that can interrupt whatever the CPU is doing to handle urgent events immediately. Instead of constantly checking if something needs attention (polling), the system waits for important events to “call” it.
In embedded systems, timing is everything. A sensor reading that arrives 1ms late could mean the difference between a safe landing and a crash. Interrupts ensure critical events get immediate attention, making systems both responsive and efficient.
// Simple interrupt handler
void UART_IRQHandler(void) {
if (UART->SR & UART_SR_RXNE) { // Data received
uint8_t data = UART->DR; // Read data
// Signal task to process data
xSemaphoreGiveFromISR(uart_semaphore, NULL);
}
}
Interrupts transform reactive systems into proactive ones, ensuring critical events get immediate attention while maintaining system efficiency.
Interrupt handling is a critical component of real-time operating systems, enabling systems to respond quickly to external events and hardware signals. Understanding interrupt handling is essential for building embedded systems that can meet real-time requirements, handle multiple concurrent events, and provide predictable response times.
Interrupts are signals that temporarily halt normal program execution to handle urgent events. They provide a mechanism for hardware and software to communicate with the CPU, enabling systems to respond quickly to external events without continuous polling.
Interrupt Definition:
Interrupt Characteristics:
Interrupt vs Polling:
Basic Interrupt System:
┌─────────────────────────────────────────────────────────────┐
│ Interrupt Sources │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Timer │ │ UART │ │ GPIO │ │
│ │ Interrupt │ │ Interrupt │ │ Interrupt │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Interrupt Controller │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Priority Encoder │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┘
│ CPU │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Interrupt Handler │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Interrupt Processing Flow:
┌─────────────────────────────────────────────────────────────┐
│ Interrupt Processing │
├─────────────────────────────────────────────────────────────┤
│ 1. Interrupt occurs │
│ 2. CPU saves current context │
│ 3. CPU jumps to interrupt vector │
│ 4. Interrupt service routine executes │
│ 5. CPU restores context │
│ 6. Normal execution resumes │
└─────────────────────────────────────────────────────────────┘
Real-time vs Non-real-time Interrupt Handling:
┌─────────────────────────────────────────────────────────────┐
│ Non-Real-Time System │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Task A │ │ Task B │ │ Task C │ │
│ │ (10ms) │ │ (20ms) │ │ (15ms) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ Interrupt waits in queue │
│ (Response: 45ms later) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Real-Time System │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Task A │ │ Task B │ │ Task C │ │
│ │ (10ms) │ │ (20ms) │ │ (15ms) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ Interrupt preempts immediately │
│ (Response: <1ms) │
└─────────────────────────────────────────────────────────────┘
Effective interrupt handling is crucial for real-time systems because it directly affects system responsiveness, reliability, and ability to meet timing requirements. Proper interrupt design ensures that systems can respond quickly to critical events while maintaining predictable behavior.
Timing Constraints:
System Reliability:
Performance Requirements:
System Architecture:
Application Requirements:
Hardware Interrupts:
Software Interrupts:
Interrupt Sources by Priority:
Priority Levels:
Interrupt Nesting:
Priority Management:
Latency Components:
Timing Analysis:
Latency Optimization:
ISR Characteristics:
ISR Responsibilities:
ISR Design Patterns:
Basic ISR Structure:
// Basic interrupt service routine
void TIM2_IRQHandler(void) {
// Clear interrupt flag
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
// Handle timer interrupt
timer_interrupt_count++;
// Notify task if needed
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskNotifyFromISR(xTimerTask, timer_interrupt_count,
eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
// Yield if higher priority task was woken
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
// UART interrupt handler
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
// Read received data
uint8_t received_data = USART_ReceiveData(USART1);
// Buffer data for task processing
if (uart_rx_buffer_index < UART_RX_BUFFER_SIZE) {
uart_rx_buffer[uart_rx_buffer_index++] = received_data;
}
// Notify UART task
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskNotifyFromISR(xUARTTask, 1, eIncrement, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
Advanced ISR with Task Communication:
// Advanced ISR with multiple event handling
void EXTI15_10_IRQHandler(void) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Check which GPIO line generated interrupt
if (EXTI_GetITStatus(EXTI_Line15) != RESET) {
EXTI_ClearITPendingBit(EXTI_Line15);
// Handle GPIO interrupt
uint32_t gpio_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_15);
// Create event data
gpio_event_t event = {
.line = 15,
.state = gpio_state,
.timestamp = xTaskGetTickCountFromISR()
};
// Send event to task
xQueueSendFromISR(xGPIOEventQueue, &event, &xHigherPriorityTaskWoken);
}
if (EXTI_GetITStatus(EXTI_Line14) != RESET) {
EXTI_ClearITPendingBit(EXTI_Line14);
// Handle another GPIO line
uint32_t gpio_state = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_14);
gpio_event_t event = {
.line = 14,
.state = gpio_state,
.timestamp = xTaskGetTickCountFromISR()
};
xQueueSendFromISR(xGPIOEventQueue, &event, &xHigherPriorityTaskWoken);
}
// Yield if higher priority task was woken
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
Task Notification:
// ISR using task notification
void ADC1_IRQHandler(void) {
if (ADC_GetITStatus(ADC1, ADC_IT_EOC) != RESET) {
// Read ADC value
uint16_t adc_value = ADC_GetConversionValue(ADC1);
// Store value in buffer
if (adc_buffer_index < ADC_BUFFER_SIZE) {
adc_buffer[adc_buffer_index++] = adc_value;
}
// Notify ADC task
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xTaskNotifyFromISR(xADCTask, adc_value, eSetValueWithOverwrite,
&xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
// Task receiving notifications
void vADCTask(void *pvParameters) {
uint32_t notification_value;
while (1) {
// Wait for notification from ISR
if (xTaskNotifyWait(0, ULONG_MAX, ¬ification_value, portMAX_DELAY) == pdTRUE) {
// Process ADC value
printf("ADC Value: %lu\n", notification_value);
// Process buffered data
while (adc_buffer_index > 0) {
uint16_t value = adc_buffer[--adc_buffer_index];
process_adc_value(value);
}
}
}
}
Queue Communication:
// ISR using queue for communication
void DMA1_Channel1_IRQHandler(void) {
if (DMA_GetITStatus(DMA1_Channel1, DMA_IT_TC) != RESET) {
DMA_ClearITPendingBit(DMA1_Channel1, DMA_IT_TC);
// DMA transfer complete
dma_transfer_complete = true;
// Send completion event to task
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
dma_event_t event = {
.channel = 1,
.status = DMA_COMPLETE,
.timestamp = xTaskGetTickCountFromISR()
};
xQueueSendFromISR(xDMAEventQueue, &event, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
Hardware Priority Configuration:
// Configure interrupt priorities
void vConfigureInterruptPriorities(void) {
// Set priority grouping
NVIC_SetPriorityGrouping(NVIC_PriorityGroup_4);
// Configure specific interrupt priorities
NVIC_SetPriority(TIM2_IRQn, NVIC_EncodePriority(NVIC_PriorityGroup_4, 0, 0));
NVIC_SetPriority(USART1_IRQn, NVIC_EncodePriority(NVIC_PriorityGroup_4, 1, 0));
NVIC_SetPriority(EXTI15_10_IRQn, NVIC_EncodePriority(NVIC_PriorityGroup_4, 2, 0));
NVIC_SetPriority(ADC1_IRQn, NVIC_EncodePriority(NVIC_PriorityGroup_4, 3, 0));
// Enable interrupts
NVIC_EnableIRQ(TIM2_IRQn);
NVIC_EnableIRQ(USART1_IRQn);
NVIC_EnableIRQ(EXTI15_10_IRQn);
NVIC_EnableIRQ(ADC1_IRQn);
}
// Priority grouping explanation
void vExplainPriorityGrouping(void) {
printf("Priority Group 4: 4 bits for preemption, 0 bits for sub-priority\n");
printf("Priority Group 3: 3 bits for preemption, 1 bit for sub-priority\n");
printf("Priority Group 2: 2 bits for preemption, 2 bits for sub-priority\n");
printf("Priority Group 1: 1 bit for preemption, 3 bits for sub-priority\n");
printf("Priority Group 0: 0 bits for preemption, 4 bits for sub-priority\n");
}
Dynamic Priority Management:
// Dynamic interrupt priority management
void vDynamicPriorityManagement(void) {
// Store original priorities
uint32_t original_timer_priority = NVIC_GetPriority(TIM2_IRQn);
uint32_t original_uart_priority = NVIC_GetPriority(USART1_IRQn);
// Adjust priorities based on system state
if (system_under_high_load()) {
// Increase timer priority for better timing
NVIC_SetPriority(TIM2_IRQn,
NVIC_EncodePriority(NVIC_PriorityGroup_4, 0, 0));
// Decrease UART priority to reduce overhead
NVIC_SetPriority(USART1_IRQn,
NVIC_EncodePriority(NVIC_PriorityGroup_4, 3, 0));
} else {
// Restore original priorities
NVIC_SetPriority(TIM2_IRQn, original_timer_priority);
NVIC_SetPriority(USART1_IRQn, original_uart_priority);
}
}
Interrupt Priority Ceiling:
// Interrupt priority ceiling implementation
typedef struct {
uint32_t base_priority;
uint32_t ceiling_priority;
bool is_active;
} interrupt_priority_ceiling_t;
interrupt_priority_ceiling_t timer_ceiling = {
.base_priority = 1,
.ceiling_priority = 0,
.is_active = false
};
// Raise interrupt priority to ceiling
void vRaiseInterruptPriority(interrupt_priority_ceiling_t *ceiling) {
if (!ceiling->is_active) {
ceiling->is_active = true;
// Store current priority
uint32_t current_priority = NVIC_GetPriority(TIM2_IRQn);
// Raise to ceiling priority
NVIC_SetPriority(TIM2_IRQn, ceiling->ceiling_priority);
}
}
// Restore interrupt priority
void vRestoreInterruptPriority(interrupt_priority_ceiling_t *ceiling) {
if (ceiling->is_active) {
ceiling->is_active = false;
// Restore base priority
NVIC_SetPriority(TIM2_IRQn, ceiling->base_priority);
}
}
Interrupt Latency Measurement:
// Interrupt latency measurement using GPIO
volatile uint32_t interrupt_entry_time = 0;
volatile uint32_t interrupt_latency = 0;
void EXTI0_IRQHandler(void) {
// Record entry time
interrupt_entry_time = DWT->CYCCNT;
// Clear interrupt flag
EXTI_ClearITPendingBit(EXTI_Line0);
// Toggle GPIO for measurement
GPIO_SetBits(GPIOA, GPIO_Pin_0);
// Simulate ISR work
volatile uint32_t i;
for (i = 0; i < 1000; i++);
// Toggle GPIO back
GPIO_ResetBits(GPIOA, GPIO_Pin_0);
// Calculate latency
interrupt_latency = DWT->CYCCNT - interrupt_entry_time;
}
// Task to analyze interrupt latency
void vInterruptLatencyAnalyzer(void *pvParameters) {
uint32_t max_latency = 0;
uint32_t min_latency = UINT32_MAX;
uint32_t total_latency = 0;
uint32_t sample_count = 0;
while (1) {
if (interrupt_latency > 0) {
// Update statistics
if (interrupt_latency > max_latency) {
max_latency = interrupt_latency;
}
if (interrupt_latency < min_latency) {
min_latency = interrupt_latency;
}
total_latency += interrupt_latency;
sample_count++;
// Print statistics
printf("Latency - Max: %lu, Min: %lu, Avg: %lu, Samples: %lu\n",
max_latency, min_latency, total_latency / sample_count, sample_count);
// Reset for next measurement
interrupt_latency = 0;
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
Latency Budget Analysis:
// Interrupt latency budget analysis
typedef struct {
uint32_t max_allowed_latency;
uint32_t measured_latency;
uint32_t margin;
bool within_budget;
} latency_budget_t;
latency_budget_t timer_latency_budget = {
.max_allowed_latency = 1000, // 1000 cycles
.measured_latency = 0,
.margin = 0,
.within_budget = true
};
void vAnalyzeLatencyBudget(latency_budget_t *budget) {
// Calculate margin
budget->margin = budget->max_allowed_latency - budget->measured_latency;
// Check if within budget
budget->within_budget = (budget->measured_latency <= budget->max_allowed_latency);
// Print analysis
printf("Latency Budget Analysis:\n");
printf(" Max Allowed: %lu cycles\n", budget->max_allowed_latency);
printf(" Measured: %lu cycles\n", budget->measured_latency);
printf(" Margin: %lu cycles\n", budget->margin);
printf(" Within Budget: %s\n", budget->within_budget ? "Yes" : "No");
if (!budget->within_budget) {
printf(" WARNING: Latency exceeds budget!\n");
}
}
Interrupt Jitter Measurement:
// Interrupt jitter measurement
typedef struct {
uint32_t last_interrupt_time;
uint32_t jitter_samples[100];
uint8_t sample_index;
uint32_t total_jitter;
uint32_t max_jitter;
} jitter_measurement_t;
jitter_measurement_t timer_jitter = {0};
void TIM2_IRQHandler(void) {
uint32_t current_time = DWT->CYCCNT;
if (timer_jitter.last_interrupt_time > 0) {
// Calculate jitter
uint32_t expected_interval = 16000; // 1ms at 16MHz
uint32_t actual_interval = current_time - timer_jitter.last_interrupt_time;
uint32_t jitter = abs((int32_t)actual_interval - (int32_t)expected_interval);
// Store jitter sample
timer_jitter.jitter_samples[timer_jitter.sample_index] = jitter;
timer_jitter.sample_index = (timer_jitter.sample_index + 1) % 100;
// Update statistics
if (jitter > timer_jitter.max_jitter) {
timer_jitter.max_jitter = jitter;
}
timer_jitter.total_jitter += jitter;
}
timer_jitter.last_interrupt_time = current_time;
// Clear interrupt flag
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
// Task to analyze jitter
void vJitterAnalyzer(void *pvParameters) {
while (1) {
// Calculate average jitter
uint32_t total = 0;
for (int i = 0; i < 100; i++) {
total += timer_jitter.jitter_samples[i];
}
uint32_t avg_jitter = total / 100;
printf("Jitter Analysis - Max: %lu, Avg: %lu cycles\n",
timer_jitter.max_jitter, avg_jitter);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
Basic Configuration:
// FreeRTOS interrupt configuration
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191
#define configKERNEL_INTERRUPT_PRIORITY 255
#define configMAX_API_CALL_INTERRUPT_PRIORITY 191
// Interrupt-safe FreeRTOS functions
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configUSE_TICKLESS_IDLE 0
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ 16000000
#define configTICK_RATE_HZ 1000
#define configMAX_PRIORITIES 32
#define configMINIMAL_STACK_SIZE 128
#define configMAX_TASK_NAME_LEN 16
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 1
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_ALTERNATIVE_API 0
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_MALLOC_FAILED_HOOK 1
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_QUEUE_SETS 1
#define configUSE_TASK_NOTIFICATIONS 1
#define configSUPPORT_STATIC_ALLOCATION 1
#define configSUPPORT_DYNAMIC_ALLOCATION 1
Interrupt-Safe Functions:
// List of interrupt-safe FreeRTOS functions
void vListInterruptSafeFunctions(void) {
printf("Interrupt-Safe FreeRTOS Functions:\n");
printf(" - xTaskNotifyFromISR()\n");
printf(" - xTaskNotifyGiveFromISR()\n");
printf(" - xQueueSendFromISR()\n");
printf(" - xQueueReceiveFromISR()\n");
printf(" - xSemaphoreGiveFromISR()\n");
printf(" - xSemaphoreTakeFromISR()\n");
printf(" - xEventGroupSetBitsFromISR()\n");
printf(" - xTimerPendFunctionCallFromISR()\n");
printf(" - portYIELD_FROM_ISR()\n");
printf(" - xTaskGetTickCountFromISR()\n");
}
Interrupt Hook Functions:
// FreeRTOS interrupt hooks
void vApplicationTickHook(void) {
// Called every tick from interrupt context
static uint32_t tick_count = 0;
tick_count++;
// Perform periodic operations
if (tick_count % 1000 == 0) {
// Every 1000 ticks
system_heartbeat();
}
}
void vApplicationIdleHook(void) {
// Called when idle task runs
// Can be used for power management
if (system_can_sleep()) {
// Enter low power mode
__WFI(); // Wait for interrupt
}
}
void vApplicationMallocFailedHook(void) {
// Called when malloc fails
printf("Memory allocation failed in interrupt context!\n");
// Handle memory allocation failure
// Could restart system or free memory
}
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {
// Called when stack overflow detected
printf("Stack overflow in task: %s\n", pcTaskName);
// Handle stack overflow
// Could restart system or task
}
System Initialization:
// Complete interrupt system initialization
void vInitializeInterruptSystem(void) {
// Enable DWT for timing measurements
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
// Configure GPIO for interrupt generation
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// Configure external interrupt
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// Configure timer interrupt
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 15999; // 1ms at 16MHz
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// Enable timer interrupt
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// Configure interrupt priorities
vConfigureInterruptPriorities();
// Enable interrupts
__enable_irq();
// Start timer
TIM_Cmd(TIM2, ENABLE);
}
// Main function
int main(void) {
// Hardware initialization
SystemInit();
HAL_Init();
// Initialize peripherals
MX_GPIO_Init();
MX_TIM2_Init();
// Initialize interrupt system
vInitializeInterruptSystem();
// Create FreeRTOS tasks
xTaskCreate(vInterruptLatencyAnalyzer, "Latency", 256, NULL, 2, NULL);
xTaskCreate(vJitterAnalyzer, "Jitter", 256, NULL, 1, NULL);
// Start scheduler
vTaskStartScheduler();
// Should never reach here
while (1) {
// Error handling
}
}
Common Problems:
Solutions:
Timing Problems:
Solutions:
Memory Problems:
Solutions:
ISR Design:
Priority Management:
Latency Optimization:
Resource Management:
Objective: Set up a simple GPIO interrupt system Steps:
Expected Outcome: LED toggles within microseconds of button press
Objective: Understand interrupt priority and nesting Steps:
Expected Outcome: High-priority interrupt can preempt low-priority one
Objective: Learn proper communication between ISRs and tasks Steps:
Expected Outcome: Data processed within predictable time bounds
This enhanced Interrupt Handling document now provides a comprehensive balance of conceptual explanations, practical insights, and technical implementation details that embedded engineers can use to understand and implement robust interrupt handling systems in RTOS environments.