The stack limits in Arm Cortex-M series microcontrollers refer to the maximum stack size available for each software thread or task. The stack is a region of memory used for temporary data storage during function calls and interrupt handling. Each thread or task running on the Cortex-M CPU has its own dedicated stack space. The stack size limit determines the maximum depth of nested function calls and interrupt nesting supported per thread before overflowing.
Stack Organization in Cortex-M
In Cortex-M microcontrollers, the stack is located in the on-chip SRAM memory and grows downwards from the high address towards the low address. The stack pointer (SP) register points to the top of the stack, and is decremented when pushing data onto the stack. The stack space for each thread is allocated during compile time and runtime initialization. Cortex-M devices may support multiple concurrent threads each with its own stack.
Main Stack
The main stack is allocated for the initial purpose or main thread executed after reset. The main stack stores the return addresses and local variables for main thread function calls. The main stack size is set at link time based on the application requirements. The SP starts at the top of the main stack after reset.
Process Stack
The process stack refers to stack space allocated for exception handlers and interrupt service routines. This stores the return context when exceptions and interrupts occur. The processor automatically switches to using the process stack when entering exception handlers. The SP is banked on exception entry and restored on exception return.
MSP and PSP
Cortex-M3 and above devices maintain two stack pointers – the main stack pointer (MSP) and process stack pointer (PSP). The MSP is used for the main thread while PSP is used when servicing interrupts and exceptions. The PSP allows interrupting low priority threads without corrupting the main thread’s stack.
Factors Affecting Stack Size
Several factors affect the required stack size for a Cortex-M application:
Nested Function Calls
Stack must accommodate return addresses and local variables for nested function calls. More deeply nested calls require larger stack for call context storage.
Interrupt Nesting
The stack must handle the maximum interrupt and exception nesting supported by the application. More nested interrupts require more stack space.
ISR Requirements
Interrupt service routines (ISRs) may use the stack for temporary data and call other functions, requiring adequate stack allocation per interrupt.
Task Stack Requirements
In multithreaded apps, separate stack allocation is required for each concurrent thread or task. Larger tasks need more stack space.
Use of Local Variables
Functions that declare large stack-allocated arrays or structures consume more stack space.
Compiler Settings
The compiler may pad the stack to align data. Compiler optimization settings can also impact stack usage.
Configuring Stack Limits
The linker command file controls the stack sizes and locations for Cortex-M apps. The stack sizes are set based on the application requirements.
Main Stack Size
The main stack size is configured via the STACK_SIZE linker directive. This sets the size of the stack used by the main thread. Typically a few KB is sufficient for many applications.
Process Stack Size
The process stack size is set via the PROCESS_STACK_SIZE linker directive. This configures the stack for exception handlers. A few hundred bytes is usually adequate.
Thread Stack Size
Additional thread stack allocations use the STACK_SIZE directive within the .stack section. This defines per-thread stack sizes in a multi-threaded system.
Linker Overflow Checks
The linker can also check stack overflow via the STACK_GUARD directive. This places a known value at the stack end to detect overflows.
Runtime Checks
Software can check for stack overflows during runtime by examining the current stack pointer value against the configured stack limits.
Optimizing Stack Size
To optimize stack usage on Cortex-M:
- Allocate only necessary stack space for the main and process stacks.
- Carefully determine stack requirements for threads and ISRs based on their usage.
- Reduce unnecessary nested function calls and interrupt nesting.
- Declare large data objects as global/static rather than local variables.
- Use compiler optimizations to reduce function call overhead.
- Use stack overflow protection mechanisms to detect errors.
Exceeding the Stack Limits
If the stack limits are exceeded due to too deep nesting or exhausting the allocated space, several outcomes can occur:
- The stack pointer (SP) will reach the base address and get corrupted as decrements wrap around.
- Further pushes onto the stack will overwrite other memory areas, causing erratic behavior.
- An exception may occur if the SP decrements beyond a protected region.
- The overflow can lead to a hard fault or system crash if not handled.
To recover from stack overflows, resetting the microcontroller or watching for SP corruption is required. Carefully tuning the stack sizes during development can avoid these issues.
Conclusion
The Cortex-M stack limits determine the maximum stack usage per thread and ISR. Configuring the limits appropriately as per the application requirements helps prevent both memory wastage and hard-to-debug stack overflows. Keeping stack usage optimized is key for both efficiency and robustness on the memory-constrained Cortex-M platforms.