How do you debug a hard fault on ARM Cortex-M? Walk through the process from the moment the fault triggers.
When a hard fault occurs on Cortex-M, the processor stacks eight registers onto the current stack (R0-R3, R12, LR, PC, xPSR) and vectors to the HardFault_Handler. The first step is to write a fault handler that captures this stacked frame. The handler must determine whether the MSP or PSP was active at the time of the fault by examining bit 2 of the EXC_RETURN value in the LR register: if bit 2 is 0, the main stack (MSP) was used; if 1, the process stack (PSP) was used. This distinction matters in RTOS environments where tasks use PSP.
Once you have the stacked frame, the stacked PC is the most critical value — it points to the instruction that was executing (or about to execute) when the fault occurred. Look up this address in your map file or disassembly to identify the exact function and line of code. Next, read the fault status registers: CFSR (Configurable Fault Status Register, at 0xE000ED28) combines three sub-registers — MemManage, BusFault, and UsageFault status bits. HFSR (HardFault Status Register, at 0xE000ED2C) tells you whether the hard fault was caused by a failed escalation from a lower-priority fault (FORCED bit) or a vector table read error. If the CFSR shows a MemManage or BusFault with the MMAR/BFAR valid bit set, read the corresponding address register to find the exact address that caused the fault.
Common root causes and their CFSR signatures: IMPRECISERR (BusFault) means a buffered write failed — the stacked PC will not point to the offending instruction because the write buffer deferred the access. Disable write buffering temporarily (set DISDEFWRT in the Auxiliary Control Register) to make it precise and reproducible. INVSTATE (UsageFault) means the processor tried to execute an instruction with the Thumb bit cleared — usually caused by a corrupted function pointer or calling a function through a zero-initialized pointer. UNALIGNED (UsageFault) means an unaligned multi-byte access on a Cortex-M0 that does not support unaligned access, or with the UNALIGN_TRP bit enabled on M3/M4. A systematic approach is to always have a permanent fault handler in your firmware that logs the stacked PC, CFSR, and HFSR to a dedicated RAM region that survives reset, so you can diagnose field faults from crash logs.
Source: Debugging & Testing Q&A
