Interrupts & ISRs in C

How interrupt handlers are written in C, the rules they must follow, and how to share data with main safely.

An interrupt stops the main program, runs a handler (interrupt service routine, ISR), then resumes exactly where it left off. On Cortex-M an ISR is an ordinary C function with no arguments and no return value; its address sits in the vector table.

Writing an ISR

The handler's name must match the vector table entry (EXTI15_10_IRQHandler for pins 10–15 on STM32F4).

Clearing the pending flag is mandatory; otherwise the interrupt fires again the moment the handler returns.

The ISR only records the event; the slow work runs in main.

irq_save/irq_restore form a critical section that is safe even if interrupts were already off.

Shared data and atomicity

void USART2_IRQHandler(void) { char c = USART2->DR; printf("%c", c); }

Put the byte into a ring buffer; print from main printf in an ISR is slow and usually non-reentrant — it can corrupt output or deadlock if main was printing.

void EXTI0_IRQHandler(void) { handle(); } // flag never cleared

Write 1 to EXTI->PR for that line inside the handler An uncleared pending flag re-triggers the interrupt immediately; the CPU never gets back to main.

Why must a variable shared between an ISR and main be volatile?

So the compiler does not keep it in a register: main must re-read it from memory to see the ISR's changes, and the ISR's writes must actually reach memory.

What is a critical section and how long should it be?

A code region during which interrupts (or the scheduler) are disabled so a multi-step update cannot be interrupted. As short as possible — it delays every interrupt.

Is reading a 32-bit variable atomic on Cortex-M? On an 8-bit AVR?

On Cortex-M an aligned 32-bit read is a single instruction, so yes. On an 8-bit AVR it takes four byte reads, so an ISR can change it halfway — protect it.