Interrupts and exceptions: stopping what you are doing and coming back
When an event occurs, the CPU saves its state, jumps to a handler and returns. How that happens on Cortex-M.
Polling, where you keep checking whether a button was pressed, wastes the CPU and responds late. An interrupt is the hardware calling the CPU to say "something has happened".
What happens on Cortex-M
- The peripheral raises an interrupt signal
- The NVIC checks the priority and notifies the CPU
- The CPU automatically saves r0 to r3, r12, lr, pc and xPSR on the stack
- It looks up the handler address in the vector table and jumps there
- When the handler ends, the saved registers are restored and execution returns to where it was
Because register saving is done automatically in hardware, a Cortex-M interrupt function is written just like an ordinary C function.
Exceptions
An interrupt is one kind of exception. Reset, NMI, HardFault, SysTick and SVCall are all exceptions and have slots at the front of the vector table. A problem such as an access to a bad address or a division by zero enters the HardFault exception.
Rules for writing handlers
- Short. Set a flag or put data in a buffer and finish.
- Variables shared with main code are
volatile, and multi-byte values are protected so they are read atomically. - If you do not clear the flag that caused the interrupt, the function is entered again the moment it ends.
What happens in one interrupt
On Cortex-M the following sequence is done automatically in hardware.
- The instruction in progress is completed.
- Eight registers (R0 to R3, R12, LR, PC, xPSR) are saved on the stack.
- The handler address is read from the vector table and execution jumps there.
- When the handler ends, the saved values are popped and the interrupted work continues.
Because the hardware saves the registers, a handler can be written as a plain C function. From request to the first handler instruction takes 12 cycles on a Cortex-M3/M4.
3D labRun what you have read so far→Short handlers, work in main
volatile uint8_t btn_flag;
volatile uint32_t btn_tick;
void EXTI0_IRQHandler(void) {
EXTI->PR = EXTI_PR_PR0; // 1) clear the flag first
btn_tick = sys_tick; // 2) just record the time
btn_flag = 1; // 3) just raise the flag
}
int main(void) {
while (1) {
if (btn_flag) { btn_flag = 0; handle_button(btn_tick); }
}
}
Call printf or run a delay function inside a handler and every other interrupt of the same or lower priority is held up meanwhile.
Problems that come up in practice
| Symptom | Cause |
|---|---|
| The handler is called again endlessly | The interrupt flag is not cleared |
| The main loop seems to have stopped | The handler is too long or fires too often |
| A value is occasionally corrupted | Main and the handler modify the same variable without protection |
| Fails only in release builds | volatile missing on a shared variable |
| Never called at all | The handler name differs from the name in the vector table (the weak symbol is called instead) |
The last case is the one beginners spend longest on. If the name differs by even one character from the one in the startup file, the linker silently connects the default handler (an infinite loop).
Check yourself
- In a control loop that needs constant interrupt latency, what goes wrong if main code keeps interrupts off for a long time?
- If two interrupts of the same priority arrive at once, which is handled first?