When an interrupt occurs on a Cortex-M processor, the processor pushes registers onto the stack to save the current state before jumping to the interrupt handler. Understanding the stack frame layout during interrupts is important for debugging and optimizing interrupt performance.
What Happens When an Interrupt Occurs
On a Cortex-M processor, when an interrupt occurs:
- The processor finishes executing the current instruction.
- The processor saves the Program Counter (PC) onto the stack. This saves the return address so the processor can resume where it left off after handling the interrupt.
- The processor enters the exception handler and pushes the CPU registers onto the stack to save their state. This is called the stack frame.
- The processor looks up the interrupt vector and jumps to the corresponding interrupt handler function.
- The interrupt handler runs and performs whatever tasks required for that interrupt.
- At the end of the handler, the handler executes code to pop the stack frame and restore the saved registers.
- The handler executes a return instruction to pop the saved PC from the stack and resume normal execution.
Stack Frame Contents
The stack frame pushed by the processor contains the following information:
- xPSR – Saved copy of the processor’s program status register
- PC – Program counter containing the return address
- LR – Link register containing the exception return address
- R12-R0 – R12 to R0 (if configured to be pushed)
On Cortex-M3 and M4, the stack frame follows the same layout described above. On Cortex-M0 and M0+, the frame is simplified to contain only the xPSR and PC.
xPSR Register
The xPSR (eXtended Program Status Register) contains information like:
- Interrupt enable/mask bits
- Execution state bits (Handler vs Thread mode)
- Stack alignment control bit
- Overflow flag
Saving xPSR preserves the processor state so it can be restored properly at the end of the interrupt.
Program Counter (PC)
The PC register contains the current program address. Saving this provides the return address needed to resume execution after handling the interrupt.
Link Register (LR)
LR is used by exception return instructions to restore the PC as well. LR gets populated with the appropriate return address when entering the exception handler.
General Purpose Registers (R0-R12)
These registers (R0-R12) contain context data that needs preserved across the interrupt. The processor can be configured via the CONTROL register to determine which registers get pushed to the stack.
Stack Pointer Behavior
As each piece of the stack frame is pushed, the Stack Pointer (SP) is decremented. For example, on a Cortex-M3/M4 pushing R4-R11, xPSR, PC, LR, and R0-R3 would decrement SP by 56 bytes total (4 bytes per register x 14 registers).
The SP gets aligned to a 32-bit boundary before exception entry. At the end of the handler, the stack frame is popped by looping and incrementing SP to restore SP to its pre-interrupt value.
Stack Overflow Considerations
The processor stack space is limited in size, often just a few KB. If too many nested interrupts occur without popping stack frames, the stack can overflow and cause a hard fault or reset. A few tips to avoid stack overflow:
- Minimize the number of automatically saved registers if not needed
- Process interrupts quickly before re-enabling them
- Avoid nested interrupts if possible
- Allocate sufficient stack space for your application
- Use an MPU to protect the stack from corruption
Context Switching
During context switching between threads, a similar stack frame layout is used to save one thread’s context before switching to another thread. The same registers (xPSR, PC, LR, R4-R11) are pushed to save the thread’s CPU status.
When resuming a thread, its stack frame can be popped to restore the thread’s context and resume where it left off.
Use Cases
Understanding the stack frame layout is useful for several cases:
- Debugging: Examining stack contents when halted can reveal register values
- Stack overflow detection: Check for stack collisions by examining SP
- Interrupt latency optimization: Reduce pushed registers to improve interrupt response
- Manual context switching: Manipulate stack frames when switching between threads
Debugging the Stack Contents
When halted after an interrupt, we can examine the stack to view the stored register values. For example, using gdb: (gdb) x $sp 0x20002f80: 0x02020000 0x08003f92 0xaaaaaaaa 0xbbbbbbbb 0x20002f90: 0xcccccccc 0xdddddddd 0xeeeeeeee 0xffffffff
This shows xPSR at 0x20002f80, PC return address at 0x20002f84, and R0-R3 values in the following addresses. This register data provides valuable context when debugging interrupt issues.
Optimizing Interrupt Handling
The number of registers automatically pushed to the stack during interrupt handling can be configured using the CONTROL register settings. For example, setting the STKALIGN bit will disable pushing R4-R11 which may help optimize interrupt latency in some cases.
However, fewer saved registers means less context available during debug. A balance is needed based on priority between interrupt latency, debug visibility, and ease of context restore.
Manual Context Switching
In a real-time OS or advanced application, the stack frame contents can be manipulated to perform context switches between threads or tasks. This requires:
- Manually pushing registers to stack to save context of current thread
- Loading stack frame of next thread from its stack
- Resuming execution of next thread
This is an advanced technique but can allow precise control over context switching behavior.
Summary
Key points about the Cortex-M interrupt stack frame layout:
- xPSR and PC registers are always pushed to stack
- LR contains exception return address
- CONTROL configures which R registers are pushed
- SP decreases as frame is pushed
- Understanding contents helps debugging and optimization
- Frame can be manipulated for low-level context switching
Knowing the precise layout of the stack frame provides valuable low-level insight into ARM Cortex-M interrupt and context switching behavior.