General-Purpose Registers
These are used to store values during program execution.
๐น Low Registers (R0โR7)
- Commonly used for function arguments, return values, and temporary storage.
- Can be accessed quickly and directly.
R0, R1, R2, R3, R4, R5, R6, R7
๐ธ High Registers (R8โR12)
- Also general-purpose but used less frequently.
- Needed more in complex calculations or interrupt handling.
R8, R9, R10, R11, R12
โ๏ธ Special-Purpose Registers
๐ต R13 โ Stack Pointer (SP)
- Points to the top of the stack memory.
- Cortex-M4 has two stack pointers:
- MSP (Main Stack Pointer) โ Default after reset.
- PSP (Process Stack Pointer) โ Used in OS tasks or threads.
๐ R14 โ Link Register (LR)
- Holds the return address when a function is called.
- When the function ends, CPU uses LR to return.
๐ด R15 โ Program Counter (PC)
- Points to the next instruction the CPU will execute.
๐งพ Status Register
โซ xPSR โ Program Status Register
Includes:
- APSR (Application PSR) โ condition flags (N, Z, C, V)
- IPSR (Interrupt PSR) โ current exception number
- EPSR (Execution PSR) โ Thumb state, IT state
๐จ Special Control Registers
These are used to manage CPU privileges, interrupt masking, and protection levels.
Register | Description |
---|---|
PRIMASK | Disables all interrupts except NMI when set. Used for critical sections. |
FAULTMASK | Disables all exceptions including faults, except NMI. |
BASEPRI | Sets a priority threshold โ blocks interrupts of lower priority. |
CONTROL | Controls stack pointer selection (MSP/PSP) and privilege level (Privileged/Unprivileged). |
๐ CONTROL Register (Bits)
- Bit 0 โ nPRIV:
0
: Privileged1
: Unprivileged - Bit 1 โ SPSEL:
0
: Use MSP1
: Use PSP - Bit 2 โ FPCA (If FPU is enabled):
Floating-point context active
๐ง Summary Diagram
+------------------------+
| R0 - R12 | General-purpose registers
| R13 (SP) | Stack Pointer (MSP or PSP)
| R14 (LR) | Link Register (return address)
| R15 (PC) | Program Counter
| xPSR | Program status flags
| MSP / PSP | Stack pointers (Main / Process)
| PRIMASK | Mask all interrupts except NMI
| FAULTMASK | Mask all faults & interrupts except NMI
| BASEPRI | Priority threshold for interrupts
| CONTROL | Privilege + SP selection
+------------------------+
Use Case Example
void my_function(int a, int b) {
int c = a + b;
}
While executing:
a
,b
, andc
will likely be in R0, R1, R2- LR (R14) holds return address
- SP (R13) holds the stack
- PC (R15) tracks next instruction
- xPSR gets updated based on the result
Letโs break down the Stack Pointer (SP), R13, and the banked versions of the Stack PointerโMSP (Main Stack Pointer) and PSP (Process Stack Pointer)โin a detailed, beginner-friendly way, especially with relevance to ARM Cortex-M architectures.
๐น 1. What is R13 / SP?
- R13 is one of the 16 general-purpose registers in the ARM architecture.
- However, R13 is reserved as the Stack Pointer (SP).
- So, when we say SP, it’s just an alias for R13.
๐ก The stack is a region of memory used for function calls, storing local variables, return addresses, etc. The SP (R13) keeps track of the top of the stack.
๐น 2. Stack Pointer Behavior
In general:
- The stack grows downward (from higher memory to lower memory).
- Each time you push (store) data, SP decreases.
- Each time you pop (load) data, SP increases.
๐น 3. Banked Stack Pointers: MSP and PSP
โ Why two stack pointers?
ARM Cortex-M processors support two stack pointers for separating privileged and unprivileged stack operations:
Stack Pointer | Name | Purpose |
---|---|---|
MSP | Main Stack Pointer | Used by the OS, system handlers, and in privileged mode by default |
PSP | Process Stack Pointer | Used by user/application-level code (can run in unprivileged mode) |
๐ง Key Idea:
- Only one stack pointer is active at a time.
- The CONTROL register decides whether SP (R13) refers to MSP or PSP.
๐น 4. CONTROL Register and SP Selection
CONTROL register (in Cortex-M):
Bit | Name | Meaning |
---|---|---|
0 | nPRIV | 0 = privileged, 1 = unprivileged |
1 | SPSEL | 0 = use MSP, 1 = use PSP |
So, if:
CONTROL.SPSEL = 0
: SP uses MSPCONTROL.SPSEL = 1
: SP uses PSP
Example in C (CMSIS-style):
__set_CONTROL(__get_CONTROL() | (1 << 1)); // Select PSP
๐น 5. Initialization and Usage Flow
Boot time:
- MSP is used by default on reset.
- The value of MSP is set from the first word of the vector table.
RTOS context:
- RTOS usually:
- Keeps MSP for the kernel and system calls
- Assigns PSP to individual threads/tasks
Example scenario:
Upon reset:
SP = MSP (default)
OS starts
OS creates thread
OS assigns PSP to thread
Switch CONTROL register SPSEL to use PSP
Thread runs with PSP (user mode)
Interrupt occurs:
Switch to MSP (automatically)
Handle interrupt in privileged mode
Return from interrupt: restore PSP
๐น 6. How to Read/Write SP, MSP, PSP (Assembly or CMSIS)
Read current SP (R13):
MOV R0, SP ; Get current SP (could be MSP or PSP based on CONTROL)
Set MSP/PSP directly:
__set_MSP(0x20001000); // Set Main Stack Pointer
__set_PSP(0x20002000); // Set Process Stack Pointer
๐น 7. Summary Table
Term | Register | Function |
---|---|---|
SP | R13 | General name for stack pointer (MSP or PSP depending on context) |
MSP | Banked R13 | Used by default, for kernel/system |
PSP | Banked R13 | Used for threads/user mode |
CONTROL.SPSEL | Bit 1 | Selects between MSP (0) and PSP (1) |
๐น 8. Visualization
+---------------------+
| Stack Memory |
|---------------------|
| High Address |
| ... |
| PSP (User Stack) | --> Used by user tasks
| ... |
| MSP (Main Stack) | --> Used by system/IRQ
| ... |
| Low Address |
+---------------------+
๐น 9. Use Cases
- RTOS-based systems: Each task/thread uses a PSP, while the kernel uses the MSP.
- Security: Keeps user and kernel stacks separate.
- Interrupt handling: Always uses MSP for consistency and security.
๐ Conclusion
R13
is the generic register for the stack pointer.- In Cortex-M, it’s banked into MSP and PSP.
- The CONTROL register selects which one is active.
- Separation of MSP and PSP improves security and supports multitasking (RTOS).
You want to:
- Use MSP by default after reset (standard behavior).
- Set up a new stack region for a user task.
- Switch to PSP before running that task.
- Ensure CONTROL register uses PSP.
Prerequisites
Assume:
- The user task stack is at
0x20002000
(adjust as per your RAM size). - Using CMSIS or direct ARM assembly.
Method 1: C (CMSIS-style)
#include "stm32f4xx.h" // or the CMSIS header for your chip
void switch_to_psp(void) {
uint32_t psp_stack = 0x20002000; // Example PSP location (top of task stack)
__set_PSP(psp_stack); // Set the PSP
__set_CONTROL(__get_CONTROL() | (1 << 1)); // Set CONTROL.SPSEL = 1 -> Use PSP
__ISB(); // Instruction Synchronization Barrier
}
int main(void) {
switch_to_psp(); // Switch to Process Stack Pointer
while (1) {
// Code running with PSP
}
}
Method 2: Pure ARM Assembly (Thumb mode)
LDR R0, =0x20002000 ; Load new PSP address
MSR PSP, R0 ; Move to PSP register
MRS R0, CONTROL ; Read CONTROL register
ORR R0, R0, #2 ; Set bit 1 to use PSP
MSR CONTROL, R0 ; Write back to CONTROL
ISB ; Instruction Synchronization Barrier
You can embed this inline in a C function using __asm__
.
Explanation of Each Step
Step | Explanation |
---|---|
__set_PSP() / MSR PSP, R0 | Load new PSP stack address |
__get_CONTROL() / MRS CONTROL | Read CONTROL register |
Set bit 1 of CONTROL | Tell CPU to use PSP instead of MSP |
__ISB() / ISB | Ensures pipeline flush to apply changes |
๐งช How to Verify?
You can check current SP using:
uint32_t sp_val;
__asm volatile ("MOV %0, SP" : "=r" (sp_val));
You can also check:
uint32_t psp = __get_PSP();
uint32_t msp = __get_MSP();
uint32_t ctrl = __get_CONTROL();
โ ๏ธ Tips
- Be careful not to overwrite the MSP unless you’re booting custom firmware.
- Make sure
0x20002000
is a valid region in your RAM (check your linker script). - This switch is especially useful in RTOS, privilege separation, and secure context management.
Hereโs a beginner-friendly tutorial on the Link Register (R14) in ARM Cortex-M4:
๐ What is the Link Register (R14) in Cortex-M4?
The Link Register (LR) is R14, one of the special-purpose registers in the ARM Cortex-M4 processor.
It is used to store the return address when a function or an interrupt is called โ so the CPU knows where to go back after executing that function.
๐ง Simple Analogy:
Think of it like this:
You go from Room A to Room B but want to return back to Room A later. So, before leaving, you write down “Room A” on a sticky note (that’s the Link Register storing the return address).
๐ฆ Role of LR (R14) in Function Calls:
๐ Function Call (e.g., BL func
)
- The
BL
(Branch with Link) instruction:- Jumps to the function.
- Stores the return address (next instruction after
BL
) into R14 (LR).
๐ Function Return
- The
BX LR
instruction is used at the end of the function:- It reads the address in LR and jumps back to it.
main:
BL my_function ; Call function, save return addr in LR (R14)
; Continue here after my_function returns
my_function:
; Do something
BX LR ; Return to main (address stored in LR)
๐ฅ What If LR is Lost?
If you accidentally overwrite LR before using BX LR
, the processor wonโt know where to return โ this causes a crash or unpredictable behavior.
To prevent this, compilers or you (in assembly) save LR on the stack at the start of a function and restore it before returning.
๐งฐ How LR Works with Stack:
Typical function prologue and epilogue:
; Prologue (start of function)
PUSH {LR} ; Save LR on stack
; ... function body ...
; Epilogue (end of function)
POP {LR} ; Restore LR
BX LR ; Return
๐จ LR in Interrupts and Exceptions:
In interrupts, the processor automatically saves LR (and other registers) on the stack.
But it sets LR to a special EXC_RETURN value โ not a typical return address.
This tells the processor how to return from the exception (e.g., using BX LR
with this value).
๐ ๏ธ Summary:
Feature | Details |
---|---|
Register Name | R14 (Link Register / LR) |
Purpose | Holds return address of functions or exceptions |
Used by | BL , BX LR , and exception return |
Should be saved? | Yes, in nested calls or interrupts |
Important in | Function calls, RTOS task switching, ISRs |
โ Tips for Beginners:
- Think of LR as a “bookmark” for the CPU.
- Always preserve LR if you’re writing low-level assembly functions.
- Learn how the stack and LR work together.
- Try writing simple ARM assembly examples to observe LR behavior.
๐ง Understanding PC (R15) in Cortex-M4: A Beginner-Friendly Guide
In ARM Cortex-M4 processors, R15 is a special-purpose register known as the Program Counter (PC). It’s one of the most important registers in any processor because it tells the CPU where to fetch the next instruction from.
๐ท๏ธ What is R15 / Program Counter (PC)?
- The Program Counter (PC) is register R15 in the Cortex-M4 register set.
- It holds the address of the next instruction to execute.
- As each instruction is executed, the PC is automatically updated to point to the next instruction.
๐ Where is PC (R15) used?
Every instruction cycle involves the PC:
- Fetch: CPU fetches the instruction at the address in PC.
- Execute: The instruction is decoded and executed.
- Update: PC is incremented to point to the next instruction.
For example:
MOV R0, #1 ; PC = 0x08000000
ADD R0, R0, #1 ; PC = 0x08000004
The PC increases by 4 bytes after each 32-bit instruction in Thumb-2.
๐ How is PC (R15) different from other registers?
Register | Purpose |
---|---|
R0โR12 | General-purpose |
R13 | Stack Pointer (SP) |
R14 | Link Register (LR) |
R15 | Program Counter (PC) |
Unlike general-purpose registers (R0โR12), you should not manipulate the PC arbitrarily unless you are doing branching, calling functions, or returning.
๐ Branching and PC
Whenever we jump to a different part of the code, the PC is changed manually:
Example:
B label ; Branch to 'label', PC is updated
BL func ; Branch with Link (calls a function), PC is updated
BX LR ; Return from function, PC = LR
๐งช Fun Fact: You Can Read the PC
You can even read the PC value in code:
uint32_t current_pc;
__asm volatile("mov %0, pc" : "=r" (current_pc));
This will store the current program counter into a variable.
โ ๏ธ Caution: Writing to PC Directly
Directly writing to PC can change the control flow:
LDR PC, =0x08000400 ; Jump to 0x08000400
Use this only when you’re intentionally jumping to a specific memory location (e.g., starting a bootloader or handling an exception).
๐งฉ In Debugging
When you debug Cortex-M4 code:
- The PC register helps you see what instruction is being executed.
- You can change PC to skip over code or re-run a part.
๐ ๏ธ In Exception Handling
When an interrupt or exception occurs:
- The hardware pushes the current PC value onto the stack.
- This is how the processor knows where to return after handling the interrupt.
Leave a Reply