The "Holy Bible" for embedded engineers
Mastering External Interrupt Handling for Responsive Embedded Systems
Learn to implement edge/level triggered interrupts, debouncing techniques, and interrupt-driven designs
External interrupts allow embedded systems to respond immediately to external events without polling. They are essential for real-time applications, user interfaces, and efficient system design.
Choose edges to capture transitions; levels to detect sustained conditions. Always clear the source appropriately and consider masking during long handlers.
Edge-Triggered Interrupts
Input Signal
^
β βββββββββββββββββββ
β β β
β β β
β β β
+ββββββββββββββββββββββββββ-> Time
β² βΌ
Rising Falling
Edge Edge
Interrupt Triggers
^
β β β
β β β
β β β
+ββββββββββββββββββββββββββ-> Time
β<->β Interruptβ<->β Interrupt
β Trigger β β Trigger
Level-Triggered Interrupts
Input Signal
^
β βββββββββββββββββββ
β β β
β β β
β β β
+ββββββββββββββββββββββββββ-> Time
β<->β High Level β<->β Low Level
β Trigger β β Trigger
Interrupt Processing Pipeline
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β External Event β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Hardware βββββΆβ Interrupt βββββΆβ CPU Core β β
β β Detection β β Controller β β Response β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β β β
β βΌ βΌ βΌ β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Signal β β Priority β β Context β β
β β Condition β β Resolution β β Switching β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β β β
β βΌ βΌ βΌ β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β ISR β β Return β β Resume β β
β β Execution β β to ISR β β Main β β
β β β β β β Program β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Interrupt Priority Levels
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Priority Hierarchy β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β High β β Medium β β Low β β
β β Priority β β Priority β β Priority β β
β β (Level 0) β β (Level 1) β β (Level 2) β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β β β β
β βΌ βΌ βΌ β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
β β Can β β Can β β Cannot β β
β β Interrupt β β Interrupt β β Interrupt β β
β β All β β Lower β β Higher β β
β β Levels β β Levels β β Levels β β
β βββββββββββββββ βββββββββββββββ βββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
External interrupts represent a fundamental shift from polling-based to event-driven system design. Instead of continuously checking for external conditions, the system waits for events and responds immediately when they occur.
Key Characteristics:
External interrupts are essential for modern embedded systems:
Designing effective interrupt systems involves balancing multiple competing requirements:
Triggered when signal transitions from one state to another.
// Edge-triggered interrupt configuration
typedef enum {
RISING_EDGE, // 0 β 1 transition
FALLING_EDGE, // 1 β 0 transition
BOTH_EDGES // Both transitions
} edge_type_t;
typedef struct {
uint8_t pin;
edge_type_t edge;
uint8_t priority;
void (*handler)(void);
} external_interrupt_config_t;
Triggered when signal remains at a specific level.
// Level-triggered interrupt configuration
typedef enum {
HIGH_LEVEL, // Trigger when high
LOW_LEVEL // Trigger when low
} level_type_t;
typedef struct {
uint8_t pin;
level_type_t level;
uint8_t priority;
void (*handler)(void);
} level_interrupt_config_t;
// Configure GPIO for external interrupt
void configure_external_interrupt(uint8_t pin, edge_type_t edge) {
// Enable GPIO clock
RCC->AHB1ENR |= (1 << GPIO_PORT(pin));
// Configure GPIO as input with pull-up
GPIO_TypeDef *port = GPIO_BASE(pin);
uint8_t pin_num = GPIO_PIN_NUM(pin);
// Set as input
port->MODER &= ~(3 << (pin_num * 2));
// Configure pull resistors per board design (pull-up OR pull-down)
port->PUPDR &= ~(3 << (pin_num * 2));
if (edge == FALLING_EDGE) {
port->PUPDR |= (1 << (pin_num * 2)); // pull-up
} else if (edge == RISING_EDGE) {
port->PUPDR |= (2 << (pin_num * 2)); // pull-down
}
// Configure interrupt trigger
configure_interrupt_trigger(pin, edge);
// Enable interrupt in NVIC with appropriate priority
enable_nvic_interrupt(EXTI_IRQn);
}
// Configure interrupt trigger type
void configure_interrupt_trigger(uint8_t pin, edge_type_t edge) {
uint8_t pin_num = GPIO_PIN_NUM(pin);
uint8_t exti_line = pin_num;
// Enable SYSCFG clock
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
// Connect GPIO to EXTI line
SYSCFG->EXTICR[exti_line / 4] &= ~(0xF << ((exti_line % 4) * 4));
SYSCFG->EXTICR[exti_line / 4] |= (GPIO_PORT(pin) << ((exti_line % 4) * 4));
// Configure trigger type
switch (edge) {
case RISING_EDGE:
EXTI->RTSR |= (1 << exti_line);
EXTI->FTSR &= ~(1 << exti_line);
break;
case FALLING_EDGE:
EXTI->FTSR |= (1 << exti_line);
EXTI->RTSR &= ~(1 << exti_line);
break;
case BOTH_EDGES:
EXTI->RTSR |= (1 << exti_line);
EXTI->FTSR |= (1 << exti_line);
break;
}
// Enable interrupt
EXTI->IMR |= (1 << exti_line);
}
// Software debouncing with timer
typedef struct {
uint8_t pin;
uint32_t last_trigger_time;
uint32_t debounce_delay;
bool last_state;
} debounce_config_t;
debounce_config_t debounce_configs[MAX_INTERRUPTS];
// Debounced interrupt handler
void debounced_interrupt_handler(uint8_t pin) {
uint32_t current_time = get_system_tick();
debounce_config_t *config = &debounce_configs[pin];
// Check if enough time has passed since last trigger
if (current_time - config->last_trigger_time < config->debounce_delay) {
return; // Ignore this trigger
}
// Read current pin state
bool current_state = read_gpio_pin(pin);
// Only process if state actually changed
if (current_state != config->last_state) {
config->last_state = current_state;
config->last_trigger_time = current_time;
// Process the actual interrupt
process_interrupt_event(pin, current_state);
}
}
// Hardware debouncing circuit analysis
/*
Hardware debouncing options:
1. RC Filter:
Switch --[R]--+--[C]-- GND
|
Input Pin
2. Schmitt Trigger:
Switch --[R]-- Schmitt Trigger -- Input Pin
3. Bounce Suppression IC:
Switch -- Bounce Suppression IC -- Input Pin
*/
// Calculate RC values for debouncing
void calculate_debounce_values(uint32_t bounce_time_ms, uint32_t *r_value, uint32_t *c_value) {
// Typical bounce time: 1-50ms
// RC time constant should be > bounce_time/3
uint32_t rc_time = bounce_time_ms * 3; // 3x bounce time for safety
// Choose standard values
*c_value = 0.1; // 0.1ΞΌF
*r_value = (rc_time * 1000) / (*c_value * 1000); // R = t/(C*1000) for ΞΌF
}
// State machine debouncing
typedef enum {
DEBOUNCE_IDLE,
DEBOUNCE_WAIT,
DEBOUNCE_CONFIRMED
} debounce_state_t;
typedef struct {
debounce_state_t state;
uint32_t start_time;
uint32_t debounce_time;
bool stable_state;
} debounce_state_machine_t;
void debounce_state_machine(debounce_state_machine_t *sm, bool current_input) {
uint32_t current_time = get_system_tick();
switch (sm->state) {
case DEBOUNCE_IDLE:
if (current_input != sm->stable_state) {
sm->state = DEBOUNCE_WAIT;
sm->start_time = current_time;
}
break;
case DEBOUNCE_WAIT:
if (current_time - sm->start_time >= sm->debounce_time) {
if (current_input != sm->stable_state) {
sm->stable_state = current_input;
sm->state = DEBOUNCE_CONFIRMED;
// Process state change
process_state_change(sm->stable_state);
} else {
sm->state = DEBOUNCE_IDLE;
}
}
break;
case DEBOUNCE_CONFIRMED:
sm->state = DEBOUNCE_IDLE;
break;
}
}
// External interrupt service routine
void EXTI0_IRQHandler(void) {
// Check if interrupt is pending
if (EXTI->PR & (1 << 0)) {
// Clear pending bit
EXTI->PR = (1 << 0);
// Process interrupt
process_external_interrupt(0);
}
}
// Generic external interrupt handler
void process_external_interrupt(uint8_t pin) {
// Read pin state
bool pin_state = read_gpio_pin(pin);
// Process based on application
switch (pin) {
case BUTTON_PIN:
handle_button_press(pin_state);
break;
case SENSOR_PIN:
handle_sensor_interrupt(pin_state);
break;
default:
// Unknown pin
break;
}
}
// ISR with minimal processing
void EXTI15_10_IRQHandler(void) {
// Check which pins triggered
uint16_t pending = EXTI->PR & 0xFC00; // Pins 10-15
if (pending) {
// Clear pending bits
EXTI->PR = pending;
// Set flag for main loop processing
for (int i = 10; i <= 15; i++) {
if (pending & (1 << i)) {
set_interrupt_flag(i);
}
}
}
}
// Main loop processes interrupt flags
void main_loop(void) {
while (1) {
// Check for pending interrupts
for (int i = 0; i < MAX_INTERRUPTS; i++) {
if (check_interrupt_flag(i)) {
clear_interrupt_flag(i);
process_interrupt_event(i);
}
}
// Other main loop tasks
process_system_tasks();
}
}
// Interrupt latency analysis
typedef struct {
uint32_t hardware_latency; // Time to enter ISR
uint32_t software_latency; // Time in ISR
uint32_t context_switch; // Time to save/restore context
uint32_t total_latency; // Total response time
} interrupt_latency_t;
// Measure interrupt latency
void measure_interrupt_latency(void) {
uint32_t start_time, end_time;
// Configure test interrupt
configure_test_interrupt();
// Start measurement
start_time = get_high_resolution_timer();
// Trigger interrupt
trigger_test_interrupt();
// Measure in ISR
end_time = get_high_resolution_timer();
// Calculate latency
uint32_t latency = end_time - start_time;
// Report results
printf("Interrupt latency: %lu cycles\n", latency);
}
// Optimized ISR for minimal latency
__attribute__((interrupt)) void optimized_isr(void) {
// Use registers directly (no function calls)
// Minimize stack usage
// Avoid complex operations
// Quick state check
if (GPIOA->IDR & (1 << 0)) {
// Set flag immediately
interrupt_flags |= (1 << 0);
}
// Clear interrupt
EXTI->PR = (1 << 0);
}
// ISR design checklist
/*
β‘ Keep ISRs short and fast
β‘ Avoid function calls when possible
β‘ Use volatile for shared variables
β‘ Clear interrupt flags early
β‘ Don't use blocking operations
β‘ Avoid floating-point operations
β‘ Use appropriate interrupt priorities
β‘ Test interrupt timing
β‘ Handle interrupt nesting properly
β‘ Document interrupt dependencies
*/
// Good ISR example
volatile uint32_t interrupt_counter = 0;
void good_isr_example(void) {
// Clear interrupt immediately
EXTI->PR = (1 << 0);
// Simple operation
interrupt_counter++;
// Set flag for main loop
interrupt_pending = true;
}
// Configure interrupt priorities
void configure_interrupt_priorities(void) {
// Set priority grouping
NVIC_SetPriorityGrouping(3); // 4 bits for preemption, 0 for sub
// Configure priorities (lower number = higher priority)
NVIC_SetPriority(EXTI0_IRQn, 1); // High priority
NVIC_SetPriority(EXTI1_IRQn, 2); // Medium priority
NVIC_SetPriority(EXTI2_IRQn, 3); // Low priority
// Enable interrupts
NVIC_EnableIRQ(EXTI0_IRQn);
NVIC_EnableIRQ(EXTI1_IRQn);
NVIC_EnableIRQ(EXTI2_IRQn);
}
// WRONG: Missing interrupt clear
void bad_isr_example(void) {
// Process interrupt
process_interrupt();
// Missing: EXTI->PR = (1 << pin);
}
// CORRECT: Clear interrupt flag
void good_isr_example(void) {
// Clear interrupt flag first
EXTI->PR = (1 << 0);
// Process interrupt
process_interrupt();
}
// WRONG: Long operations in ISR
void bad_long_isr(void) {
EXTI->PR = (1 << 0);
// Don't do this in ISR!
for (int i = 0; i < 1000; i++) {
process_data();
}
}
// CORRECT: Set flag and return
void good_short_isr(void) {
EXTI->PR = (1 << 0);
// Set flag for main loop
data_processing_pending = true;
}
// WRONG: Race condition with shared variable
volatile bool button_pressed = false;
void isr_with_race(void) {
EXTI->PR = (1 << 0);
button_pressed = true; // Race condition possible
}
// CORRECT: Atomic operation
volatile uint32_t button_flags = 0;
void isr_without_race(void) {
EXTI->PR = (1 << 0);
__atomic_or_fetch(&button_flags, 1, __ATOMIC_RELEASE);
}
// Button interrupt implementation
#define BUTTON_PIN 0
#define DEBOUNCE_MS 50
volatile bool button_pressed = false;
volatile uint32_t last_button_time = 0;
void EXTI0_IRQHandler(void) {
if (EXTI->PR & (1 << BUTTON_PIN)) {
EXTI->PR = (1 << BUTTON_PIN);
uint32_t current_time = get_system_tick();
// Software debouncing
if (current_time - last_button_time > DEBOUNCE_MS) {
button_pressed = true;
last_button_time = current_time;
}
}
}
// Main loop processes button press
void main_loop(void) {
while (1) {
if (button_pressed) {
button_pressed = false;
handle_button_action();
}
// Other tasks
process_system_tasks();
}
}
// Sensor interrupt with edge detection
#define SENSOR_PIN 1
#define SENSOR_RISING 1
#define SENSOR_FALLING 0
volatile uint32_t sensor_rising_count = 0;
volatile uint32_t sensor_falling_count = 0;
void EXTI1_IRQHandler(void) {
if (EXTI->PR & (1 << SENSOR_PIN)) {
EXTI->PR = (1 << SENSOR_PIN);
// Read current pin state
bool current_state = (GPIOA->IDR & (1 << SENSOR_PIN)) ? 1 : 0;
if (current_state == SENSOR_RISING) {
sensor_rising_count++;
} else {
sensor_falling_count++;
}
}
}
// Multiple interrupt sources with priority
#define INT_PIN_1 0
#define INT_PIN_2 1
#define INT_PIN_3 2
volatile uint32_t interrupt_flags = 0;
void EXTI0_IRQHandler(void) {
if (EXTI->PR & (1 << INT_PIN_1)) {
EXTI->PR = (1 << INT_PIN_1);
interrupt_flags |= (1 << INT_PIN_1);
}
}
void EXTI1_IRQHandler(void) {
if (EXTI->PR & (1 << INT_PIN_2)) {
EXTI->PR = (1 << INT_PIN_2);
interrupt_flags |= (1 << INT_PIN_2);
}
}
void EXTI2_IRQHandler(void) {
if (EXTI->PR & (1 << INT_PIN_3)) {
EXTI->PR = (1 << INT_PIN_3);
interrupt_flags |= (1 << INT_PIN_3);
}
}
// Process interrupts in priority order
void process_interrupts(void) {
if (interrupt_flags & (1 << INT_PIN_1)) {
interrupt_flags &= ~(1 << INT_PIN_1);
process_high_priority_interrupt();
}
if (interrupt_flags & (1 << INT_PIN_2)) {
interrupt_flags &= ~(1 << INT_PIN_2);
process_medium_priority_interrupt();
}
if (interrupt_flags & (1 << INT_PIN_3)) {
interrupt_flags &= ~(1 << INT_PIN_3);
process_low_priority_interrupt();
}
}
// Configure button interrupt
void configure_button_interrupt(void) {
// GPIO as input with pull-up
GPIO_InitTypeDef gpio_init = {0};
gpio_init.Pin = BUTTON_PIN;
gpio_init.Mode = GPIO_MODE_IT_FALLING;
gpio_init.Pull = GPIO_PULLUP;
HAL_GPIO_Init(BUTTON_PORT, &gpio_init);
// Enable interrupt
HAL_NVIC_SetPriority(BUTTON_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(BUTTON_IRQn);
}
volatile uint32_t edge_count = 0;
void sensor_isr(void) {
if (EXTI->PR & (1 << SENSOR_PIN)) {
EXTI->PR = (1 << SENSOR_PIN);
edge_count++;
}
}
1) Debouncing comparison
2) Edge vs level triggering
Hardware_Fundamentals/Interrupts_Exceptions.md
for interrupt handlingHardware_Fundamentals/Digital_IO_Programming.md
for pin configurationNext Topic: Watchdog Timers β Interrupts and Exceptions