What is Fault Generation & Analysis-Embedded Systems Course for Freshers in Hyderabad

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Example 12 — STM32F407 Discovery

Fault Generation & Analysis

Understand UsageFault, BusFault, MemManageFault, and HardFault on Cortex-M4. Learn to enable individual fault handlers, analyze fault status registers, and dump the exception stack frame for post-mortem debugging.

============================================================

1. Fault Hierarchy on Cortex-M4

Cortex-M4 defines a layered fault system. Three configurable fault handlers can be individually enabled. When a fault occurs but its dedicated handler is disabled (or when a fault happens inside a fault handler), it escalates to HardFault.

Fault hierarchy and escalation to HardFault
FAULT HIERARCHY
MemManageFault
(MPU violations)
if disabled or fault in handler
BusFault
(bus errors, bad addr)
if disabled
UsageFault
(INVSTATE, div/0, etc)
if disabled
HardFault
SVCall / PendSV / SysTick — fixed priority, no escalation
NMI — cannot be masked or disabled

Fault Exception Numbers

Exception NumberNamePriorityConfigurableVector Offset
3HardFault-1 (fixed)No0x0C
4MemManageFaultProgrammableYes (SHCSR bit16)0x10
5BusFaultProgrammableYes (SHCSR bit17)0x14
6UsageFaultProgrammableYes (SHCSR bit18)0x18
============================================================

2. SHCSR — System Handler Control and State Register (0xE000ED24)

SHCSR enables the three configurable fault handlers and shows their active/pending state. Address: 0xE000ED24, part of the SCB (System Control Block).

BitNameFunction
[18]USGFAULTENA1 = UsageFault handler enabled. 0 = UsageFaults escalate to HardFault.
[17]BUSFAULTENA1 = BusFault handler enabled. 0 = BusFaults escalate to HardFault.
[16]MEMFAULTENA1 = MemManageFault handler enabled. 0 = MemFaults escalate to HardFault.
[15]SVCALLPENDEDSVCall is pending
[14]BUSFAULTPENDEDBusFault is pending (pre-empted by higher priority)
[13]MEMFAULTPENDEDMemFault is pending
[12]USGFAULTPENDEDUsageFault is pending
[11]SYSTICKACTSysTick is active
[10]PENDSVACTPendSV is active
[8]MONITORACTDebug monitor is active
[7]SVCALLACTSVCall is active
[3]USGFAULTACTUsageFault is active
[1]BUSFAULTACTBusFault is active
[0]MEMFAULTACTMemManageFault is active
/* Enable all three configurable fault handlers */
void enable_all_fault_handlers(void)
{
    uint32_t *pSHCSR = (uint32_t*)0xE000ED24;

    *pSHCSR |= (1 << 16); /* Enable MemManageFault */
    *pSHCSR |= (1 << 17); /* Enable BusFault      */
    *pSHCSR |= (1 << 18); /* Enable UsageFault    */

    /* Without these, all three fault types route to HardFault_Handler */
}
============================================================

3. Fault Status Registers — Full Bit Reference

CFSR — Configurable Fault Status Register (0xE000ED28)

CFSR is a 32-bit register containing all three configurable fault status sub-registers concatenated:

Configurable Fault Status Register (CFSR) layout
Bit: [31..16][15..8][7..0]
UFSR
(bits 31:16)
BFSR
(15:8)
MMFSR
(7:0)
Addr: 0xE000ED2A (half)0xE000ED29(byte)0xE000ED28(byte)

UFSR — UsageFault Status Register (bits [31:16] of CFSR, addr 0xE000ED2A)

Bit (UFSR)Bit (CFSR)NameCause
[9][25]DIVBYZEROInteger divide by zero when CCR.DIV_0_TRP=1
[8][24]UNALIGNEDUnaligned word/halfword access when CCR.UNALIGN_TRP=1
[3][19]NOCPCoprocessor instruction when CP not present/disabled
[2][18]INVPCInvalid PC loaded on exception return (bad EXC_RETURN)
[1][17]INVSTATEInvalid state — T-bit=0 or illegal EPSR combo
[0][16]UNDEFINSTRUndefined instruction executed

BFSR — BusFault Status Register (bits [15:8] of CFSR, addr 0xE000ED29)

Bit (BFSR)NameCause
[7]BFARVALIDBFAR register contains valid fault address
[5]LSPERRFault during lazy FP state preserve
[4]STKERRBus fault during exception stacking
[3]UNSTKERRBus fault during exception unstacking
[2]IMPRECISERRImprecise bus error (instruction already retired)
[1]PRECISERRPrecise bus error, BFAR valid
[0]IBUSERRInstruction bus error (prefetch fault)

MMFSR — MemManage Fault Status Register (bits [7:0] of CFSR, addr 0xE000ED28)

BitNameCause
[7]MMARVALIDMMFAR contains valid fault address
[5]MLSPERRMemFault during lazy FP state preserve
[4]MSTKERRMemFault during exception stacking
[3]MUNSTKERRMemFault during exception unstacking
[1]DACCVIOLData access violation (MPU or XN region)
[0]IACCVIOLInstruction access violation (fetch from no-execute region)

HFSR — HardFault Status Register (0xE000ED2C)

BitNameMeaning
[31]DEBUGEVTHardFault caused by a debug event (breakpoint with no debugger)
[30]FORCEDHardFault caused by escalation from a configurable fault. Check CFSR for the original fault.
[1]VECTTBLHardFault during vector table read on exception entry
HFSR.FORCED = 1 means a configurable fault (MemManage/Bus/Usage) occurred but its handler was disabled or the fault occurred inside a fault handler, causing escalation. Always read CFSR afterwards to find the root cause.
============================================================

4. CCR — Configuration and Control Register (0xE000ED14)

BitNameDescription
[9]STKALIGN1 (default) = 8-byte stack alignment on exception. 0 = 4-byte.
[8]BFHFNMIGN1 = BusFaults ignored in NMI/HardFault handlers. Used for fault probing.
[4]DIV_0_TRP1 = Generate UsageFault on integer divide-by-zero. Default=0 (silent result=0).
[3]UNALIGN_TRP1 = Trap all unaligned accesses (not just atomic ones). Default=0.
[1]USERSETMPEND1 = Unprivileged code can write to STIR (software interrupt trigger).
[0]NONBASETHRDENA1 = Allow Thread mode when exception number is non-zero (rare use).
/* Enable divide-by-zero trap */
void enable_div0_trap(void)
{
    uint32_t *pCCR = (uint32_t*)0xE000ED14;
    *pCCR |= (1 << 4); /* DIV_0_TRP = 1 */
    /* Now integer divide by zero → UsageFault with UFSR.DIVBYZERO=1 */
}

int risky_divide(int x, int y)
{
    return x / y; /* if y==0 and DIV_0_TRP=1 → UsageFault fires */
}
============================================================

5. Scenario A — INVSTATE UsageFault (Undefined Instruction)

This example demonstrates what happens when you jump to an address containing 0xFFFFFFFF (an undefined instruction). The function pointer has LSB=1 (correct Thumb mode), but the instruction at the address is invalid.

/* ================================================================
 * FAULT SCENARIO A: Undefined Instruction → UsageFault INVSTATE
 * ================================================================
 *
 * Memory layout:
 *   0x20010000: [0xFFFFFFFF]  ← invalid/undefined instruction word
 *   Function pointer: 0x20010001  ← LSB=1 ensures Thumb mode entry
 *
 * What happens:
 *   1. CPU enters at 0x20010000 in Thumb state
 *   2. Fetches 0xFFFF — undefined Thumb opcode
 *   3. UFSR.INVSTATE = 1 (or UFSR.UNDEFINSTR = 1)
 *   4. UsageFault_Handler fires (if enabled via SHCSR)
 *   5. If not enabled → HardFault fires instead
 * ================================================================ */

#include <stdint.h>
#include <stdio.h>

void enable_fault_handlers(void)
{
    uint32_t *pSHCSR = (uint32_t*)0xE000ED24;
    *pSHCSR |= (1 << 16); /* MemManageFault */
    *pSHCSR |= (1 << 17); /* BusFault       */
    *pSHCSR |= (1 << 18); /* UsageFault     */
}

int main(void)
{
    enable_fault_handlers();

    /* Write 0xFFFFFFFF (two undefined 16-bit Thumb opcodes) to SRAM */
    uint32_t *pSRAM = (uint32_t*)0x20010000;
    *pSRAM = 0xFFFFFFFF;

    /* Create function pointer: address with LSB=1 (Thumb mode indicator)
       Actual execution starts at 0x20010000 (LSB stripped by processor) */
    void (*fn)(void) = (void*)0x20010001;
    fn(); /* → INVSTATE or UNDEFINSTR UsageFault */

    for(;;);
}

/* ── Naked entry: capture MSP before any push/pop ── */
__attribute__((naked)) void UsageFault_Handler(void)
{
    /* MSP points to the exception stack frame pushed by hardware */
    __asm("MRS R0, MSP");
    __asm("B   UsageFault_Handler_c");
}

void UsageFault_Handler_c(uint32_t *frame)
{
    uint32_t *pUFSR = (uint32_t*)0xE000ED2A; /* UFSR at CFSR byte [31:16] */
    uint32_t ufsr   = (*pUFSR) & 0xFFFF;

    printf("=== UsageFault Handler ===\n");
    printf("UFSR  = 0x%04lX\n", ufsr);

    /* Decode UFSR bits */
    if (ufsr & (1 << 1))  printf("  INVSTATE    : Invalid state / bad T-bit\n");
    if (ufsr & (1 << 0))  printf("  UNDEFINSTR  : Undefined instruction\n");
    if (ufsr & (1 << 9))  printf("  DIVBYZERO   : Integer divide by zero\n");
    if (ufsr & (1 << 8))  printf("  UNALIGNED   : Unaligned memory access\n");
    if (ufsr & (1 << 3))  printf("  NOCP        : No coprocessor\n");
    if (ufsr & (1 << 2))  printf("  INVPC       : Invalid PC on exception return\n");

    printf("\n=== Exception Stack Frame ===\n");
    printf("R0   = 0x%08lX\n", frame[0]);
    printf("R1   = 0x%08lX\n", frame[1]);
    printf("R2   = 0x%08lX\n", frame[2]);
    printf("R3   = 0x%08lX\n", frame[3]);
    printf("R12  = 0x%08lX\n", frame[4]);
    printf("LR   = 0x%08lX\n", frame[5]);
    printf("PC   = 0x%08lX\n", frame[6]); /* address of faulting instruction */
    printf("xPSR = 0x%08lX\n", frame[7]);

    /* PC should be 0x20010000 — the address that caused the fault */

    while(1);
}
============================================================

6. Scenario B — Divide by Zero Trap

/* ================================================================
 * FAULT SCENARIO B: Divide by Zero → UsageFault DIVBYZERO
 * CCR.DIV_0_TRP must be set first (default = 0 = silent)
 * ================================================================ */

int safe_divide(int x, int y)
{
    return x / y;
}

int main(void)
{
    uint32_t *pSHCSR = (uint32_t*)0xE000ED24;
    *pSHCSR |= (1 << 16) | (1 << 17) | (1 << 18);

    /* Enable divide-by-zero trap in CCR */
    uint32_t *pCCR = (uint32_t*)0xE000ED14;
    *pCCR |= (1 << 4); /* DIV_0_TRP = 1 */

    /* This will trigger UsageFault with UFSR.DIVBYZERO = 1 */
    volatile int result = safe_divide(10, 0);
    (void)result;

    for(;;);
}

__attribute__((naked)) void UsageFault_Handler(void)
{
    __asm("MRS R0, MSP");
    __asm("B   UsageFault_Handler_c");
}

void UsageFault_Handler_c(uint32_t *frame)
{
    uint32_t ufsr = *((uint32_t*)0xE000ED2A) & 0xFFFF;
    printf("UsageFault! UFSR=0x%04lX\n", ufsr);
    /* UFSR bit9=DIVBYZERO will be set */
    /* frame[6]=PC will point into safe_divide() at the SDIV instruction */
    printf("Fault at PC = 0x%08lX\n", frame[6]);
    while(1);
}
============================================================

7. Fault Status Register Reading Utilities

/* ── Complete fault analysis utility ───────────────────────────── */

#define CFSR_ADDR     0xE000ED28
#define HFSR_ADDR     0xE000ED2C
#define BFAR_ADDR     0xE000ED38
#define MMFAR_ADDR    0xE000ED34

void analyse_fault_registers(void)
{
    uint32_t cfsr  = *(volatile uint32_t*)CFSR_ADDR;
    uint32_t hfsr  = *(volatile uint32_t*)HFSR_ADDR;
    uint32_t mmfsr = cfsr & 0xFF;
    uint32_t bfsr  = (cfsr >> 8)  & 0xFF;
    uint32_t ufsr  = (cfsr >> 16) & 0xFFFF;

    printf("── Fault Status ──\n");
    printf("CFSR  = 0x%08lX\n", cfsr);
    printf("HFSR  = 0x%08lX\n", hfsr);
    printf("MMFSR = 0x%02lX\n",  mmfsr);
    printf("BFSR  = 0x%02lX\n",  bfsr);
    printf("UFSR  = 0x%04lX\n",  ufsr);

    if (hfsr & (1u << 30))
        printf("  HardFault: FORCED (escalation from configurable fault)\n");

    if (ufsr & (1 << 9))  printf("  UsageFault: DIVBYZERO\n");
    if (ufsr & (1 << 1))  printf("  UsageFault: INVSTATE\n");
    if (ufsr & (1 << 0))  printf("  UsageFault: UNDEFINSTR\n");
    if (ufsr & (1 << 3))  printf("  UsageFault: NOCP (no coprocessor)\n");

    if (bfsr & (1 << 1)) {
        printf("  BusFault: PRECISERR at BFAR = 0x%08lX\n",
               *(volatile uint32_t*)BFAR_ADDR);
    }
    if (bfsr & (1 << 2))  printf("  BusFault: IMPRECISERR\n");

    if (mmfsr & (1 << 1)) {
        printf("  MemFault: DACCVIOL at MMFAR = 0x%08lX\n",
               *(volatile uint32_t*)MMFAR_ADDR);
    }
    if (mmfsr & (1 << 0))  printf("  MemFault: IACCVIOL\n");
}
============================================================

8. HardFault Handler — Universal Safety Net

/* Universal HardFault handler for any Cortex-M4 project */
__attribute__((naked)) void HardFault_Handler(void)
{
    /* Determine which stack was active: PSP or MSP
       If bit 2 of LR (EXC_RETURN) is set → PSP was active in Thread
       else MSP was active                                           */
    __asm volatile(
        "TST   LR, #4      \n"   /* test bit 2 of EXC_RETURN */
        "ITE   EQ          \n"   /* if EQ (bit2=0) */
        "MRSEQ R0, MSP     \n"   /* then R0 = MSP */
        "MRSNE R0, PSP     \n"   /* else R0 = PSP */
        "B     HardFault_Handler_c \n"
    );
}

void HardFault_Handler_c(uint32_t *frame)
{
    printf("=== HARD FAULT ===\n");

    /* Check if it was an escalation */
    uint32_t hfsr = *(volatile uint32_t*)0xE000ED2C;
    if (hfsr & (1u << 30)) {
        printf("FORCED (configurable fault escalated)\n");
        analyse_fault_registers(); /* print CFSR details */
    }
    if (hfsr & (1u << 1))
        printf("VECTTBL (vector table read error)\n");

    printf("Faulting PC  = 0x%08lX\n", frame[6]);
    printf("LR at fault  = 0x%08lX\n", frame[5]);
    printf("R0           = 0x%08lX\n", frame[0]);
    printf("xPSR         = 0x%08lX\n", frame[7]);

    while(1); /* halt */
}
============================================================

9. Interview Questions & Answers

Q1. What is the difference between UsageFault, BusFault, MemManageFault, and HardFault?
UsageFault: Software-level errors — divide by zero, undefined instruction, invalid state (T-bit=0), unaligned access, no coprocessor.
BusFault: AHB bus errors during memory access — accessing non-existent memory regions, or bus slave returning an error response.
MemManageFault: MPU access violations — code accessing a region it doesn’t have permission to, or executing from a no-execute region.
HardFault: Catch-all — fires when any configurable fault is disabled, or when a fault occurs inside a fault handler (double fault).
Q2. How do you enable UsageFault, BusFault, and MemManageFault handlers?
Write to the SHCSR register (0xE000ED24): bit16=MemManageFault enable, bit17=BusFault enable, bit18=UsageFault enable. If these bits are 0, all faults route to HardFault_Handler. This is why you must set these bits early in your embedded application for proper fault isolation.
Q3. Why must UsageFault_Handler be declared as __attribute__((naked))?
The naked attribute prevents the compiler from generating prologue/epilogue code (PUSH/POP). This is critical because we need to capture the exact stack pointer value (MSP) before anything modifies the stack. A regular function would push registers first, corrupting the SP value we need to read the exception frame. The naked handler reads MSP then calls a normal C function, passing the frame pointer as argument.
Q4. What does UFSR.INVSTATE mean and how is it triggered?
INVSTATE (Invalid State) is set when the processor attempts to execute an instruction in an invalid mode. The most common cause on Cortex-M4 is calling a function via a pointer where the LSB is 0 — this clears the T-bit (Thumb mode indicator) in EPSR, and when the next instruction is fetched in ARM mode (which Cortex-M4 doesn’t support), INVSTATE fires. Always ensure function pointers have LSB=1 on Cortex-M.
Q5. How do you find the address of the instruction that caused a fault?
The stacked PC (pFrame[6], word index 6 in the exception frame) holds the address of the next instruction to execute when the exception returns. For most faults, this is the faulting instruction itself (precise faults). For imprecise BusFaults, the PC may be a few instructions ahead. You can also check BFAR (0xE000ED38) for the faulting data address if BFSR.BFARVALID=1.
Q6. What is HFSR.FORCED and how do you find the root cause?
HFSR.FORCED=1 means a configurable fault (MemManage/Bus/Usage) occurred but was escalated to HardFault because: (a) the specific handler was not enabled via SHCSR, or (b) the fault occurred inside that specific fault handler (which already has fixed priority). When FORCED=1, read CFSR (0xE000ED28) to determine which sub-fault actually fired.
Q7. By default, does integer divide by zero cause a fault on Cortex-M4?
No! By default, CCR.DIV_0_TRP=0, which means integer divide by zero silently produces an undefined/zero result without any fault. To trap divide-by-zero, set CCR bit4 (DIV_0_TRP=1). After that, any SDIV or UDIV with divisor=0 triggers UsageFault with UFSR.DIVBYZERO=1.
Q8. What is the purpose of reading both MSP and PSP in HardFault_Handler?
The fault could have occurred in either Thread mode (using PSP if CONTROL.SPSEL=1) or Handler mode (using MSP). The EXC_RETURN value in LR tells us: if bit2=1, the thread was using PSP and the frame is on PSP. If bit2=0, MSP held the stack and the frame is on MSP. A robust HardFault handler checks LR bit2 and reads the appropriate SP to find the exception frame.
Q9. What is the difference between PRECISERR and IMPRECISERR in BusFault?
PRECISERR: The exact instruction that caused the bus fault is known. BFAR contains the faulting address. The processor stalls until it knows the error occurred.
IMPRECISERR: The bus error is detected late — the write buffer has completed and the faulting instruction has already retired. The PC in the stack frame is a few instructions past the actual fault, and BFAR is not valid. Imprecise errors are harder to debug.
Q10. What happens when a fault occurs inside a fault handler?
This is a double-fault. Cortex-M4 immediately escalates to HardFault (unless the fault occurs in NMI or HardFault handler, which escalates to LockUp — processor stops responding). This is why fault handlers must be written carefully to avoid accessing bad memory. HFSR.FORCED will be set in this case.
Q11. What is processor LockUp state?
LockUp occurs when a fault fires inside the NMI handler or HardFault handler, or when returning from a HardFault causes another HardFault. In LockUp, the processor drives a special bus signal and effectively halts. A debugger can recover the system; in production, a watchdog timer must reset it. LockUp is visible as the WFI instruction not completing and the DHCSR.S_LOCKUP bit being set.
Q12. How do you implement a production-quality fault handler for shipping firmware?
1. Enable all configurable fault handlers via SHCSR.
2. Use naked fault handlers to capture the exact frame pointer.
3. Read and decode CFSR, HFSR, BFAR, MMFAR.
4. Save fault info to non-volatile memory (e.g., backup SRAM, RTC backup registers, or flash).
5. Print or log all stacked registers (R0-R3, R12, LR, PC, xPSR).
6. Trigger a system reset via AIRCR.SYSRESETREQ (0xE000ED0C, write key 0x05FA0004).
7. On next boot, check the saved fault data to diagnose the issue.

STM32F407 Deep Dive Series — Example 12: Fault Generation & Analysis

ARM Cortex-M4 TRM · ARMv7-M Architecture Reference Manual

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *