Home | Projects | Notes > ARM Cortex-M3/M4 Processor > Stack Memory
Stack memory is part of the main memory (internal RAM or external RAM) reserved for the temporary storage of data (transient data)
Mainly used during function, interrupt/exception handling
Stack memory is accessed in Last-In-First-Out (LIFO) fashion
Stack can be accessed using push
and pop
instructions or using any memory manipulation instructions (ld
, str
)
Stack is traced using stack pointer (sp
orr13
) register. push
and pop
instructions affect (decrement or increment) the stack pointer register.
Stack can expand and shrink dynamically within its boundary during the run-time. (Crossing the boundary will cause errors referred to as "Stack overflow", or "Stack underflow" and your software must have a way to prevent/handle these errors.)
The temporary storage of processor register values
The temporary storage of local variables of the function
During system exception or interrupt, stack memory will be used to save the context (status of general-purpose registers, processor status register, return address, etc.) of the currently executing code
STM32F407 microcontroller has 2 SRAMs (SRAM1 + SRAM2). They are collectively called as SRAM and its size is 128 KB.
Boundaries of each section can be set by programmers using tool chains or linker scripts according to the project's requirement.
Stack operation model is determined at the processor design time. This is not configurable by the programmer.
Full Ascending (FA)
Full Descending (FD) - ARM Cortex Mx processors
Stack pointer is initialized to the higher memory address
push
decrements, and pop
increments the stack pointer
Stack pointer points to the last pushed item (or next item to be popped)
Empty Ascending (FA)
Empty Descending (E)
In which portion of RAM would you place the stack?
In our applications, the second model will be used. (Stack pointer is initialized to the highest address of RAM.)
This is generally set by the linker script of the program, or the configuration files of the tool chain.
ARM Cortex Mx processors have 3 for stack pointers:
SP(R13) - Current Stack Pointer
MSP - Main Stack Pointer
PSP - Process Stack Pointer (Generally used for application tasks in embedded systems and embedded OS's)
After processor reset, by default, MSP will be selected as current stack pointer. This means that the SP will copy the contents of MSP. (Upon power-up, the processor automatically initializes the MSP by reading the first location of the vector table.)
Thread mode can change the current stack pointer (which is, by default, MSP) to PSP by configuring the CONTROL register's SPSEL bit.
When SPSEL = 0 (default), [SP]
When SPSEL = 1, [SP]
Handler mode code execution will ALWAYS use MSP as the current stack pointer. Changing the value of SPSEL bit while in the handler mode doesn't make any sense. The write will be ignored.
MSP will be initialized automatically by the processor after reset by reading the content of the address 0x00000000
If you want to use the PSP as your current stack pointer then make sure that you initialize the PSP to valid stack address in your code first, and then change the SPSEL bit of the CONTROL register to 1.
As shown in the snapshot below, MSP will be used as the current stack pointer by default.
One way we can design our system is that
When the program runs in thread mode, it will use the PSP space as its stack space
When the program runs in handler mode, it will use the MSP space as its stack space
This technique is widely used in the real-time OS design. For example, MSP will be used for kernel tasks, PSP will be used for user tasks. (OS for consumer-level products will not have to manage stack pointer this way.)
Before reaching main()
Generally done by the processor reset sequence reading the first location of the vector table. It must be ensured that the first location of the vector table holds a valid value for the stack pointer. This is normally taken care of by the startup code.
After reaching main()
For example, you may want to shift the stack to some other memory location (e.g., SRAM3, or external RAM connected to the microcontroller) after reaching main()
. This can't be done before reaching main()
.
Evaluate your targeted application. Decide the amount of stack that would be needed for the worst-case scenario of your application run-time.
Know your processor's stack consumption model (FD, FA, ED, EA)
Decide stack placement in the RAM (middle, end, external memory). This can be adjusted using the linker script.
In many applications, there may be second stage stack init. For example, if you want to allocate stack in an external SDRAM then
First, start with the the internal RAM.
Second, initialize (configure) the SDRAM in the main or startup code and then change the stack pointer to point to that SDRAM.
If you are using the ARM cortex Mx processor, make sure that the first location of the vector table contains the initial stack address (MSP). The startup code of the project usually does this.
You may also use the linker script to decide the stack, heap and other RAM area boundaries. Startup code usually fetches boundary information from linker scripts.
In an RTOS scenario, the kernel code may use MSP to trace its own stack and configure PSP for user task's stack.
To view where the stack pointer gets initialized, check the startup code and linker script of your project.
xxxxxxxxxx
201/* startup_stm32f407vgtx.s */
2...
3/******************************************************************************
4*
5* The STM32F407VGTx vector table. Note that the proper constructs
6* must be placed on this to ensure that it ends up at physical address
7* 0x0000.0000.
8*
9******************************************************************************/
10 .section .isr_vector,"a",%progbits
11 .type g_pfnVectors, %object
12 .size g_pfnVectors, .-g_pfnVectors
13
14g_pfnVectors:
15 .word _estack @ a linker symbol which denotes the end of RAM as beginning of stack
16 .word Reset_Handler
17 .word NMI_Handler
18 .word HardFault_Handler
19 .word MemManage_Handler
20 ...
xxxxxxxxxx
111/* LinkerScript.ld */
2
3/* Entry Point */
4ENTRY(Reset_Handler)
5
6/* Highest address of the user mode stack */
7_estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */
8
9_Min_Heap_Size = 0x200; /* required amount of heap */
10_Min_Stack_Size = 0x400; /* required amount of stack */
11...
Initialized SP points to 0x20020000 (0x20000000 + (128 * 1024)).
This value is calculated according to the linker script, and then used in the vector table defined in the startup code. This is how SP gets initialized.
Changing SP between MSP and PSP:
To access MSP and PSP in assembly code, you can use the msr
and mrs
instructions.
In a C program, you can write a naked function (C like assembly function which doesn't have epilogue and prologue sequences) to change the currently selected stack pointer.
xxxxxxxxxx
641
2
3
4
5
6
7
8int fun_add(int a, int b, int c, int d)
9{
10 return a+b+c+d;
11}
12
13__attribute__((naked)) void change_sp_to_psp(void)
14{
15 // set up CONSTANTS in assembly (C-style macro is not compatible with assembly code)
16 __asm volatile(".equ SRAM_END, (0x20000000 + (128 * 1024))");
17 __asm volatile(".equ PSP_START, (SRAM_END - 512)");
18
19 // initialize PSP (MUST DO THIS before using it)
20 __asm volatile("ldr r0, =PSP_START");
21 __asm volatile("msr psp, r0");
22
23 // SP: MSP -> PSP
24 // set bit 1 of the CONTROL register (SPSEL = 1)
25 __asm volatile("mov r0, #0x02");
26 __asm volatile("msr CONTROL, r0"); // after this code PSP value will be copied into SP
27
28 __asm volatile("bx lr"); // return to where this routine was called
29 // [pc] <- [lr]
30}
31
32void generate_exception()
33{
34 __asm volatile("SVC #0x2"); // call SVC_Handler
35 // 'SVC' instruction can be invoked by the code running in thread mode
36 // in order to get some services from the kernel level code
37}
38
39int main(void)
40{
41 change_sp_to_psp();
42
43 int ret;
44 ret = fun_add(1, 4, 5, 6);
45
46 printf("result = %d\n", ret);
47
48 generate_exception();
49
50 /* Loop forever */
51 for(;;);
52}
53
54// Handler name can be found in the vector table of the startup code.
55// As soon as the program jumps to this handler,
56// - MSP will be copied into SP
57// - CONTROL register bit 1 will be set (1)
58void SVC_Handler(void)
59{
60 printf("SVC_Handler called\n");
61}
62// As soon as this handler returns and branches back to the main function,
63// - PSP will be copied into SP
64// - CONTROL register bit 1 will be reset (0)
Nayak, K. (2022). Embedded Systems Programming on ARM Cortex-M3/M4 Processor [Video file]. Retrieved from https://www.udemy.com/course/embedded-system-programming-on-arm-cortex-m3m4/