Chapter 4: What Is a Register?

September 17, 2026 · View on GitHub

Introduction

Registers are the fastest storage in the processor — small, 32-bit holding cells built directly into the CPU silicon. Every computation happens in registers: loading values from memory, performing arithmetic, testing conditions, and storing results back. This chapter maps out the complete ARM Cortex-M33 register file and shows exactly which registers our blink driver uses.

The ARM Cortex-M33 Register File

The Cortex-M33 provides 16 general-purpose registers plus several special-purpose registers:

RegisterABI NamePurpose
r0a1Argument 1 / return value / scratch
r1a2Argument 2 / scratch
r2a3Argument 3 / scratch
r3a4Argument 4 / scratch
r4v1Callee-saved variable
r5v2Callee-saved variable
r6v3Callee-saved variable
r7v4Callee-saved variable
r8v5Callee-saved variable
r9v6Callee-saved variable (platform-specific)
r10v7Callee-saved variable
r11v8Callee-saved variable (frame pointer)
r12IPIntra-procedure scratch
r13SPStack Pointer
r14LRLink Register
r15PCProgram Counter

Registers r0-r3: Arguments and Scratch

The first four registers pass function arguments and return values. They are caller-saved: a called function is free to overwrite them without restoring their previous values.

In our firmware:

  ldr   r0, =PADS_BANK0_GPIO16_OFFSET            // argument 1: pad offset
  ldr   r1, =IO_BANK0_GPIO16_CTRL_OFFSET         // argument 2: ctrl offset
  ldr   r2, =16                                  // argument 3: GPIO number
  bl    GPIO_Config                              // call GPIO_Config

Three arguments are loaded into r0, r1, r2 before the call.

Registers r4-r11: Callee-Saved

These registers must be preserved across function calls. If a function uses them, it must save them on entry and restore them on exit:

  push  {r4-r12, lr}                             // save callee-saved registers
  // ... use r4, r5 freely ...
  pop   {r4-r12, lr}                             // restore callee-saved registers

Our GPIO_Config function uses r4 and r5 as working registers for address calculations and read-modify-write operations.

Register r12 (IP): Intra-Procedure Scratch

r12 is designated as the Intra-Procedure call scratch register. The linker may use it for long-range branch veneers. Our firmware saves and restores it as part of the {r4-r12, lr} push/pop block.

Register r13 (SP): Stack Pointer

The Stack Pointer holds the address of the top of the stack. ARM Cortex-M33 actually has two stack pointers:

  • MSP (Main Stack Pointer) — used in handler mode (interrupts) and by default in thread mode
  • PSP (Process Stack Pointer) — optionally used in thread mode

Our Init_Stack function initializes both:

  ldr   r0, =STACK_TOP                           // load stack top
  msr   PSP, r0                                  // set PSP
  msr   MSP, r0                                  // set MSP

When a bl (branch-with-link) instruction calls a function, the processor stores the return address in LR. The function returns by branching to this address:

  bl    GPIO_Set                                 // LR = address of next instruction
  // ... GPIO_Set runs ...
  bx    lr                                       // return to caller

For nested calls, LR must be saved on the stack because the inner call will overwrite it.

Register r15 (PC): Program Counter

The PC holds the address of the current instruction being fetched. You rarely write to it directly — branches, calls, and returns update it implicitly.

On Cortex-M33, the PC always contains the address of the current instruction plus 4 (due to the pipeline).

Special Registers

Beyond the general-purpose file, the Cortex-M33 has special registers accessible only through the msr and mrs instructions:

RegisterPurpose
MSPMain Stack Pointer
PSPProcess Stack Pointer
MSPLIMMSP limit (stack overflow detection)
PSPLIMPSP limit (stack overflow detection)
PRIMASKInterrupt mask (1 = all interrupts disabled)
CONTROLStack pointer selection, privilege level
xPSRCombined program status register

Our Init_Stack function uses four of these:

  msr   PSP, r0                                  // set Process Stack Pointer
  msr   MSPLIM, r0                               // set MSP lower limit
  msr   PSPLIM, r0                               // set PSP lower limit
  msr   MSP, r0                                  // set Main Stack Pointer

The Program Status Register (xPSR)

The xPSR is actually three registers combined:

Sub-registerBitsContents
APSR31:28Condition flags: N (negative), Z (zero), C (carry), V (overflow)
IPSR8:0Exception number (0 = thread mode, nonzero = handler)
EPSR24T bit (always 1 for Thumb mode)

The condition flags in APSR are set by instructions with the s suffix (like subs, tst, cmp) and tested by conditional branches (beq, bne, ble, etc.).

In our delay loop:

  subs  r5, r5, #1                               // decrement counter (sets Z flag)
  bne   .Delay_MS_Loop                           // branch if Z=0 (not zero)

The subs instruction updates the Z flag. When r5 reaches zero, Z is set to 1, and bne falls through to end the loop.

Register Usage in Our Firmware

RegisterWhere UsedPurpose
r0EverywhereAddresses, arguments, GPIO pin numbers
r1EverywhereRegister values, second arguments
r2main.sGPIO number argument
r4gpio.s, delay.sWorking register for address math
r5gpio.s, delay.sWorking register for values
SPImplicitlyStack operations (push/pop)
LRImplicitlyReturn addresses
PCImplicitlyInstruction fetch

Summary

  • The Cortex-M33 has 16 general-purpose registers (r0–r15) plus special registers.
  • r0–r3 are argument/scratch registers; r4–r11 are callee-saved.
  • SP (r13) points to the top of the stack; LR (r14) holds the return address.
  • The xPSR contains condition flags (N, Z, C, V) set by arithmetic instructions.
  • Special registers (MSP, PSP, MSPLIM, PSPLIM) are accessed via msr/mrs.
  • Our firmware uses r0–r2 for function arguments and r4–r5 for internal computation.