armhub.dev
Cortex-M · 24 pages
Cortex-M

System Control Block (SCB)

ICSR, AIRCR, SCR, CCR, SHPR1-3, SHCSR, CFSR, HFSR, MMFAR, BFAR — every SCB register decoded. Reset, sleep, fault handling, exception priority, and unaligned-access trapping.

6 min read · 1,399 wordsupdated · May 13, 2026

The System Control Block is where ARM put everything that didn't fit naturally into the NVIC, the MPU, or the FPU. It controls system-wide behaviour: reset, sleep mode entry, exception priority for the architectural exceptions, fault enable/status, vector table relocation. About 60 bytes of MMIO at 0xE000_ED00, but density-of-meaning is extreme — every bit matters and most have surprising side effects.

This page walks the SCB register-by-register. The companion pages — NVIC registers and FPU — cover the adjacent register blocks.

SCB at a glance
Base address
0xE000_ED00 (inside the PPB)
Privileged access only
All SCB registers require privileged mode
Reset state
Most fields zero. CCR.STKALIGN has a 1-by-default architectural reset on v7-M
Key fault-related registers
CFSR, HFSR, MMFAR, BFAR, AFSR
Key control registers
ICSR, AIRCR, SCR, CCR, SHPRx, SHCSR
Critical bits to know
STKALIGN, BFHFNMIGN, DIV_0_TRP, UNALIGN_TRP, USERSETMPEND, VECTKEY

CPUID — read-only identification

CPUID at offset 0x00 returns the core's implementer, variant, architecture, part number, and revision. Useful for runtime feature detection.

uint32_t cpuid = SCB->CPUID;
uint32_t partno = (cpuid >> 4) & 0xFFF;     // 0xC60 = M0, 0xC20 = M0+, 0xC23 = M3, 0xC24 = M4, 0xC27 = M7, 0xD20 = M23, 0xD21 = M33, 0xD22 = M55, 0xD23 = M85
BitsNameDescription
31:24IMPLEMENTER0x41 = ARM
23:20VARIANTMajor revision (rN)
19:16ARCHITECTURE0xC for ARMv6-M / ARMv7-M, 0xF for ARMv8-M
15:4PARTNOPart number — see table above
3:0REVISIONMinor revision (pM)

ICSR — Interrupt Control and State Register

ICSR at offset 0x04. Mixes status (read-only fields) and control (write-1-to-set / write-1-to-clear). The mix-and-match is the source of many bugs.

BitsNameAccessDescription
31NMIPENDSETW1S / ROSet NMI pending
28PENDSVSETW1S / ROSet PendSV pending — the canonical RTOS context-switch trigger
27PENDSVCLRW1C / ROClear PendSV pending
26PENDSTSETW1S / ROSet SysTick pending
25PENDSTCLRW1C / ROClear SysTick pending
23ISRPREEMPTROAn IRQ will preempt at the next return
22ISRPENDINGROAn external IRQ is pending
21:12VECTPENDINGROHighest-priority pending exception number
11:0VECTACTIVEROCurrently-active exception number (0 = thread mode)

The classic RTOS pattern uses PENDSVSET to request a context switch from anywhere:

SCB->ICSR = SCB_ICSR_PENDSVSET_Msk;   /* trigger context switch via PendSV */

PendSV runs at the lowest priority (after every other pending IRQ has finished), making it the safe place to switch contexts.

VTOR — Vector Table Offset Register

Covered in detail on the vector table page. Offset 0x08.

AIRCR — Application Interrupt and Reset Control Register

AIRCR at offset 0x0C. Holds PRIGROUP, the endianness indicator, and the system-reset command.

BitsNameAccessDescription
31:16VECTKEYRW (with key)Must be 0x05FA on every write — guards against accidental resets
15ENDIANNESSRO0 = little-endian, 1 = big-endian (almost always 0)
10:8PRIGROUPRWPriority group split for NVIC priorities
2SYSRESETREQWOWrite 1 to request a full system reset
1VECTCLRACTIVEWODebug only — clear all active state
0VECTRESETWOCortex-M debug only — local reset

The VECTKEY requirement means every write must specify both the new bits and the magic value:

/* Set PRIGROUP = 3 (4 preempt bits / 4 sub bits on a 4-bit-prio part) */
SCB->AIRCR = (0x05FA << 16) | (3 << 8);
 
/* System reset — board-level reset, retains debug connection on most parts */
SCB->AIRCR = (0x05FA << 16) | SCB_AIRCR_SYSRESETREQ_Msk;
while (1) { }   /* wait for reset to take effect */

SYSRESETREQ resets the core and all peripherals except the SWD/JTAG debug logic, RTC backup domain, and (on some vendors) the watchdog. It's the standard programmatic reset path.

SCR — System Control Register

SCR at offset 0x10. Configures sleep behaviour.

BitsNameAccessDescription
4SEVONPENDRWSend an event when an IRQ pends (for WFE wakeup) even if it's disabled or masked
2SLEEPDEEPRWWhen 1, WFI/WFE enters the deep-sleep mode (vendor-defined behavior)
1SLEEPONEXITRWWhen 1, the core sleeps automatically on returning to thread mode

SLEEPDEEP is the bridge from "WFI" (which is otherwise just "clock-gate the core until next IRQ") to the vendor's deep sleep mode (Stop, Standby, whatever the marketing name is). The peripheral-specific configuration — which clocks to keep on, which to gate off — is in vendor MMIO outside the SCB. See low-power modes for the full taxonomy.

SLEEPONEXIT is for interrupt-driven applications. The thread mode does nothing useful — only ISRs do work. Set SLEEPONEXIT once, then never run any code outside ISRs.

SCB->SCR |= SCB_SCR_SLEEPONEXIT_Msk;
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;    /* if you want deep sleep on every WFI */

CCR — Configuration and Control Register

CCR at offset 0x14. A bag of orthogonal control bits with high impact.

BitsNameDescription
18BPBranch prediction enable (M7, M85 only)
17ICI-cache enable (M7, M55, M85)
16DCD-cache enable (M7, M55, M85)
10STKALIGNForce 8-byte stack alignment on exception entry. Reset = 1 on v7-M, fixed = 1 on v8-M
9BFHFNMIGNBus faults in HardFault, NMI, FAULTMASK-asserted handlers are ignored (e.g., for fault-tolerant probe drivers)
8DIV_0_TRPTrap on integer divide by zero (UsageFault). Off by default — divide returns 0.
4UNALIGN_TRPTrap on unaligned word/halfword access (UsageFault). Off by default on v7-M.
3USERSETMPENDAllow unprivileged code to write STIR
1USERSETMPEND - alt naming(same)
0NONBASETHRDENAAllow thread-mode code to be entered with an empty exception stack

The big ones to know:

  • DIV_0_TRP — turn this on. Silent divide-by-zero returning 0 hides bugs; trapping makes them visible.
  • UNALIGN_TRP — controversial. On v7-M, halfword and word accesses to unaligned addresses are allowed by default but slow. Trapping forces clean code, but may break legacy drivers that hand-roll unaligned access. Turn it on for new code.
  • BFHFNMIGN — fault-tolerance bit. With BFHFNMIGN = 1, a bus fault inside HardFault doesn't escalate to a lockup. Useful for fault-handler code that probes possibly-bad memory.
SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk
         |  SCB_CCR_UNALIGN_TRP_Msk;
__DSB();

SHPR1, SHPR2, SHPR3 — System Handler Priority

Three registers at offsets 0x18, 0x1C, 0x20. They hold the 8-bit priority bytes for the architectural exceptions 4..15.

RegisterBytes (low to high)
SHPR1MemMgmt (4), BusFault (5), UsageFault (6), SecureFault (7, v8-M)
SHPR2reserved, reserved, reserved, SVCall (11)
SHPR3DebugMon (12), reserved, PendSV (14), SysTick (15)

The priority encoding is the same as IPR — upper __NVIC_PRIO_BITS bits implemented, lower bits zero. CMSIS gives NVIC_SetPriority() overloads that accept the negative IRQ numbers (-1 = SysTick, -2 = PendSV, etc.) and route to SHPR.

The standard RTOS pattern:

NVIC_SetPriority(PendSV_IRQn, 0xFF);  /* lowest urgency — context switch */
NVIC_SetPriority(SysTick_IRQn, 0xFF); /* lowest urgency — RTOS tick */
NVIC_SetPriority(SVCall_IRQn, 0);     /* highest urgency — kernel entry */

SHCSR — System Handler Control and State Register

SHCSR at offset 0x24. Enables individual fault handlers and reports their state.

BitsNameDescription
18USGFAULTENAEnable UsageFault as a separate exception (else escalates to HardFault)
17BUSFAULTENAEnable BusFault as separate exception
16MEMFAULTENAEnable MemManage as separate exception
15SVCALLPENDEDSVCall pending
14BUSFAULTPENDEDBusFault pending
...etc.(other pending/active bits)
7SVCALLACTSVCall active
4MONITORACTDebugMonitor active
3SVCALLACT(already listed; this is BusFault active by spec)
1MEMFAULTACTMemManage active
0MEMFAULTACT(same — encoding varies; see ARM ARM)

The most important bits are MEMFAULTENA, BUSFAULTENA, USGFAULTENA. By default, all fault classes escalate to HardFault, which makes triage hard ("HardFault — was it bus, mem, usage, or fault-on-fault?"). Enable each fault separately to get distinct handlers:

SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk
           |  SCB_SHCSR_BUSFAULTENA_Msk
           |  SCB_SHCSR_USGFAULTENA_Msk;

Now MemMgmt faults call MemManage_Handler, BusFault calls BusFault_Handler, UsageFault calls UsageFault_Handler. The handler can read CFSR for the per-class detail.

CFSR — Configurable Fault Status Register

CFSR at offset 0x28. Three 8-bit subfields packed into a word:

BitsSubfieldSource
7:0MMFSRMemManage fault status
15:8BFSRBusFault status
31:16UFSRUsageFault status

MMFSR bits:

BitNameMeaning
0IACCVIOLInstruction-access violation
1DACCVIOLData-access violation
3MUNSTKERRFault during exception unstacking
4MSTKERRFault during exception stacking
5MLSPERRLazy FP stacking fault
7MMARVALIDMMFAR holds a valid address

BFSR bits:

BitNameMeaning
8IBUSERRInstruction bus error
9PRECISERRPrecise data bus error
10IMPRECISERRImprecise data bus error
11UNSTKERRBus fault during unstacking
12STKERRBus fault during stacking
13LSPERRLazy FP stacking bus error
15BFARVALIDBFAR holds a valid address

UFSR bits:

BitNameMeaning
16UNDEFINSTRUndefined instruction
17INVSTATEInvalid execution state (e.g., entered ARM mode)
18INVPCInvalid PC load (bad EXC_RETURN)
19NOCPNo-coprocessor (FPU access without enabling)
24UNALIGNEDUnaligned access (only if CCR.UNALIGN_TRP = 1)
25DIVBYZERODivide by zero (only if CCR.DIV_0_TRP = 1)

A fault handler should read CFSR, then write the bits back (CFSR is W1C) to clear them:

void HardFault_Handler_C(uint32_t *stack) {
    uint32_t cfsr = SCB->CFSR;
    uint32_t hfsr = SCB->HFSR;
    uint32_t mmfar = SCB->MMFAR;
    uint32_t bfar  = SCB->BFAR;
    /* log everything, then... */
    SCB->CFSR = cfsr;   /* clear by writing 1s back */
    SCB->HFSR = hfsr;
}

HFSR — HardFault Status Register

HFSR at offset 0x2C. Why a HardFault triggered.

BitsNameMeaning
1VECTTBLVector table fetch failed
30FORCEDA configurable fault was escalated to HardFault (because its enable was off or fault-on-fault)
31DEBUGEVTDebug event

If FORCED = 1, read CFSR to find the original fault class. If VECTTBL = 1, the vector fetch faulted — usually a bad VTOR.

MMFAR / BFAR — Fault Address Registers

MMFAR at offset 0x34, BFAR at offset 0x38. The faulting address for MemManage and BusFault respectively. Valid only if the corresponding MMARVALID / BFARVALID bit in CFSR is set.

if (SCB->CFSR & SCB_CFSR_MMARVALID_Msk) {
    printf("MemMgmt fault address: 0x%08X\n", SCB->MMFAR);
}

AFSR — Auxiliary Fault Status Register

AFSR at offset 0x3C. Implementation-defined. Vendors use it to expose bus-matrix-level fault info (which AHB master, which slave). Read the silicon's TRM for the encoding.

Cross-references