The ARM Cortex-M processors contain a number of special purpose registers that are used to configure and control critical system operations. One important set of SP registers are related to managing the stack pointer and stack operations. Understanding these SP registers is key to effectively utilizing the Cortex-M stack.
Main Stack Pointer Register (MSP)
The Main Stack Pointer (MSP) register holds the current address of the top of the main stack. This stack is used to store local variables and function call linkage when executing code in Thread Mode. Some key points about MSP:
- MSP is banked per exception mode, so each mode has its own stack pointer.
- On reset, the MSP is initialized to the end of RAM memory.
- The processor switches to using MSP when entering Thread Mode.
- Thread Mode shares the MSP stack with Handler Mode.
- Modifying MSP updates the stack pointer for Thread and Handler Modes.
Process Stack Pointer Register (PSP)
The Process Stack Pointer (PSP) works similarly to MSP, but is used when executing code in Processor Modes. Key points about PSP:
- PSP is banked per exception mode, each Processor Mode has its own PSP.
- On reset, PSP is undefined and must be initialized before use.
- The processor switches to PSP when entering a Processor Mode.
- Modifying PSP only affects the stack pointer for the current Processor Mode.
The Cortex-M stack pointers should be aligned to 8 bytes to maintain performance when pushing and popping data. This means the stack pointer values stored in MSP and PSP should always be divisible by 8. If a stack pointer is not 8-byte aligned, the processor will generate a HardFault exception.
Stack Limit Registers
To help avoid stack overflow issues, the Cortex-M contains two Stack Limit registers:
- MSPLIM – Holds the lowest valid address for the MSP stack.
- PSPLIM – Holds the lowest valid address for the PSP stack.
These registers can be used to set a guard boundary and trigger a fault handler if the stack grows beyond the limit. They are optionally implemented, so may not be present on all Cortex-M variants.
Stack Alignment on Exception Entry
When an exception occurs, the processor automatically aligns the active stack pointer before executing the exception handler. This avoids penalties if the stack was unaligned when the exception was triggered. The alignment process is:
- Save unaligned stack pointer to the relevant stack limit register (MSPLIM or PSPLIM).
- Update the stack pointer to the next 8-byte aligned address.
- Push the remaining stack frame registers to the now aligned stack.
The original unaligned stack pointer is restored on exception return.
Stacking on Exception Entry
When an exception occurs, the Cortex-M processor automatically stacks certain registers to maintain context and allow nesting of exceptions. The stacking process depends on the type of exception:
Thread Mode Exceptions
For Thread Mode exceptions, the stacked registers are:
- LR (EXC_RETURN)
Processor Mode Exceptions
For Processor Mode exceptions, the stacked registers are:
- LR (EXC_RETURN)
The stacking process automatically uses the correct stack pointer (MSP or PSP) for the active mode and stacks the registers in descending address order.
Exception Return Behavior
When executing an exception return instruction, the Cortex-M processor automatically unstacks registers from the stack. The unstacked registers are:
- LR (EXC_RETURN)
This restores the context that was stacked on exception entry. The processor also handles switching back to the previous stack pointer (MSP or PSP).
Configuring the Thread Stack
Here is an overview of the steps required to configure the Cortex-M main thread stack:
- Define a stack memory region, aligned to 8 bytes.
- Initialize MSP to point to the top of the stack memory.
- Optionally initialize MSPLIM to the bottom of the stack.
- Enable the stack limit register if used, via CONTROL register.
- Store context values across context switches using PUSH and POP if using an RTOS.
Configuring the Process Stack
Key steps to configure the Cortex-M process stack:
- Define stack memory region, 8 byte aligned.
- Initialize PSP to point to the top of the stack.
- Initialize PSPLIM to the stack bottom if used.
- Enable PSPLIM via CONTROL if used.
- Use PUSH and POP to preserve PSP across context switches.
Tips for Stack Management
Here are some useful tips for managing Cortex-M stacks:
- Monitor stack usage to avoid overflow.
- Set stack limit registers and failsafe handlers.
- Reserve adequate stack space for interrupt needs.
- Keep ISR stack usage to a minimum.
- Use PSP for thread context switches instead of MSP.
- Initialize stacks to known values to help debug.
Cortex-M Stack Implementation
To utilize the Cortex-M stack features, the stacks must be properly implemented:
- Define stack memory regions in linker script or elsewhere.
- Enable the stack limit registers if desired.
- Initialize MSP and PSP registers during start up.
- Use stacking instructions to preserve context.
- Use unstacking during exception return.
- Monitor stack usage during development.
With robust stack implementation, many Cortex-M stack features help prevent issues and ease development.
There are several ways to optimize Cortex-M stack usage:
- Allocate only necessary stack space.
- Reduce stack usage in ISRs when possible.
- Use stack limit registers to detect overflow.
- Use PSP instead of MSP where applicable.
- Initialize unused stack to an unusual value.
- Analyze stack usage to fine tune stack size.
- Enable the CONTROL stack alignment features.
Optimized stack usage reduces memory requirements and helps avoid issues like overflow.
Cortex-M Stack Usage
The Cortex-M stack is used in the following main ways:
- Storing context on exception entry.
- Passing parameters at function calls.
- Allocating local variables.
- Saving context on RTOS context switches.
- Stacking layer specific registers on ISR entry.
Understanding these stack usage patterns allows tuning stack size and analyzing stack requirements.
Useful techniques for debugging Cortex-M stack issues:
- Initialize unused stack to a pattern like 0xDEADBEEF.
- Enable stack limit registers to detect overflow.
- Monitor stack pointers during execution.
- Check for unaligned accesses and hard faults.
- Perform stack usage analysis.
- Debug exceptions and check stacked context.
These debugging techniques can help identify stack issues and incorrect stack usage.
In summary, properly managing the Cortex-M stack pointers and understanding stack operation is key to building robust embedded applications. Utilizing stack limit registers, optimization techniques, and careful stack debugging all help create efficient and stable Cortex-M designs.