Skip to content




STM32 »

Fault Handlers

A fault is an exception generated by the processor to indicate an error. If an associated exception is enabled, the exception handler will be called to report, resolve, or recover the system from the fault.

Last update: 2022-06-29


STM32-Tutorials F411RE_Fault_Handlers.zip

Fault exception#

A fault is an exception generated by the processor to indicate an error.

When there is something violates the design rules of the processor, a fault is triggered.

Whenever a fault happens, internal processor registers will be updated to record the type of fault, the address of instruction at which fault happened, and if an associated exception is enabled, the exception handler will be called.

The fault handler can report, resolve, or recover the system from the fault.

For example:

Dividing a number by zero causes DIVBYZERO fault, which will invoke Usage Fault Handler in which you can get rid of the problem such as closing the problematic task.

The Vector Interrupt Table defines different types with priority of handling order as below:

Exception Number IRQ Number Exception Type Priority Function
3 -13 Hard Fault -1 All faults that hang the processor
4 -12 Memory Fault Configurable Memory issue
5 -11 Bus Fault Configurable Data Bus issue
6 -10 Usage Fault Configurable Instruction/State/Access issue

By default, not all exceptions and interrupts are enabled to be handled.

Exception Default state Handling behavior
Hard Fault Always enabled, can be masked -
Memory Fault Disabled by default Synchronous
Bus Fault Disabled by default Synchronous
Usage Fault Disabled by default Synchronous

The Vector Interrupt Table is implemented in assembly code in the startup file of MCU startup_*.s.

g_pfnVectors:
  .word _estack                 /* MSP value */
  .word Reset_Handler           /* Reset routine */
  .word NMI_Handler             /* No-Maskable Interrupt */
  .word HardFault_Handler       /* System faults */
  .word MemManage_Handler       /* Memory access issues */
  .word BusFault_Handler        /* Bus access issues */
  .word UsageFault_Handler      /* Instruction/State issues */
  ...

Fault types#

Fault Handler Bit name Fault status register
Bus error on a vector read HardFault_Handler VECTTBL Hard fault status register (HFSR)
Fault escalated to a hard fault FORCED
MPU or default memory map mismatch: MemManage_Handler - Memory management fault address register (MMFSR, MMFAR)
– on instruction access IACCVIOL
– on data access DACCVIOL
– during exception stacking MSTKERR
– during exception unstacking MUNSKERR
– during lazy floating-point state preservation MLSPERR
Bus error BusFault_Handler - Bus fault address register (BFSR, BFAR)
– During exception stacking STKERR
– During exception unstacking UNSTKERR
– During instruction prefetch IBUSERR
– During lazy floating-point state preservation LSPERR
Precise data bus error PRECISERR
Imprecise data bus error IMPRECISERR
Attempt to access a coprocessor Usage fault NOCP Configurable fault status register (CFSR ; UFSR+BFSR+MMFSR)
Undefined instruction UNDEFINSTR
Attempt to enter an invalid instruction set state INVSTATE
Invalid EXC_RETURN value INVPC
Illegal unaligned load or store UNALIGNED
Divide By 0 DIVBYZERO


Fault escalation and hard faults

All faults exceptions except for hard fault have configurable exception priority, as described in System handler priority registers (SHPRx). Software can disable execution of the handlers for these faults.

Usually, the exception priority, together with the values of the exception mask registers, determines whether the processor enters the fault handler, and whether a fault handler can preempt another fault handler.

In some situations, a fault with configurable priority is treated as a hard fault. This is called priority escalation, and the fault is described as escalated to hard fault. Escalation to hard fault occurs when:

  • A fault handler causes the same kind of fault as the one it is servicing. This escalation to hard fault occurs when a fault handler cannot preempt itself because it must have the same priority as the current priority level.
  • A fault handler causes a fault with the same or lower priority as the fault it is servicing. This is because the handler for the new fault cannot preempt the currently executing fault handler.
  • An exception handler causes a fault for which the priority is the same as or lower than the currently executing exception.
  • A fault occurs and the handler for that fault is not enabled.

If a bus fault occurs during a stack push when entering a bus fault handler, the bus fault does not escalate to a hard fault. This means that if a corrupted stack causes a fault, the fault handler executes even though the stack push for the handler failed. The fault handler operates, but the stack contents are corrupted.

Only Reset and NMI can preempt the fixed priority hard fault. A hard fault can preempt any exception other than Reset, NMI, or another hard fault.


Lockup state

The processor enters a lockup state if a hard fault occurs when executing the NMI or hard fault handlers. When the processor is in lockup state it does not execute any instructions.

The processor remains in lockup state until either:

  • It is reset
  • An NMI occurs
  • It is halted by a debugger

If lockup state occurs from the NMI handler a subsequent NMI does not cause the processor to leave lockup state.

Example#

This example enables all configurable fault exceptions, implement fault exceptions handlers, and trigger faults by following methods:

  • Execute an undefined instruction
  • Divide by Zero
  • Execute instruction from peripheral region
  • Execute SVC inside the SVC Handler
  • Execute SVC inside an interrupt handler whose priority is same or less than SVC priority


Step 0: Create a new project

You should create a bare-metal project which just has a few files including a linker and a main.


Step 1: Enable all fault exceptions

In the document PM0214: STM32 Cortex®-M4 MCUs and MPUs programming manual, look at the below section:

4.4 System control block (SCB The System control block (SCB) provides system implementation information, and system control. This includes configuration, control, and reporting of the system exceptions.

The System handler control and state register (SHCSR) is at the address 0xE000ED24

4.4.7 Configuration and control register (CCR)

The CCR controls entry to Thread mode and enables:

  • The handlers for NMI, hard fault and faults escalated by FAULTMASK to ignore bus faults
  • Trapping of divide by zero and unaligned accesses
  • Access to the STIR by unprivileged software

Configuration and control register (CCR)

4.4.9 System handler control and state register (SHCSR)

The SHCSR enables the system handlers, and indicates:

  • The pending status of the bus fault, memory management fault, and SVC exceptions
  • The active status of the system handlers

If you disable a system handler and the corresponding fault occurs, the processor treats the fault as a hard fault.

System handler control and state register (SHCSR)

We can use direct memory access method to configure this SHCSR register:

// enable handlers
uint32_t* pSHCSR = (uint32_t *)0xE000ED24;
*pSHCSR |= (1 << 16); // Memory Fault
*pSHCSR |= (1 << 17); // Bus Fault
*pSHCSR |= (1 << 18); // Usage Fault

// enable Divide by Zero Trap
uint32_t* pCCR = (uint32_t*)0xE000ED14;
*pCCR |= (1 << 4); // Div by Zero

Step 2: Implement Fault Handlers

The names of handlers are defined in the Interrupt Vector Table:

g_pfnVectors:
  .word _estack                 /* MSP value */
  .word Reset_Handler           /* Reset routine */
  .word NMI_Handler             /* No-Maskable Interrupt */
  .word HardFault_Handler       /* System faults */
  .word MemManage_Handler       /* Memory access issues */
  .word BusFault_Handler        /* Bus access issues */
  .word UsageFault_Handler      /* Instruction/State issues */

We can directly override those functions in the main file, for example:

void UsageFault_Handler() {
  printf("Exception: Usage Fault\n");
  while(1);
}

Step 3: Trigger Usage Fault

We will try to call a function at a location where there is invalid instruction:

  /* Fill a meaningless value */
  *(uint32_t*)0x20010000 = 0xFFFFFFFF;

  /* Set PC with LSB being 1 to indicate Thumb State */
  void (*pFunc)(void) = (void*)0x20010001;

  /* call function */
  pFunc();

Compile and run the program, you will get Usage Fault exception:

Usage Fault: Undefined Instruction

To find out which line of code caused the exception, you can refer to the Fault Analyzer tool. Note that this tool dumps all saved registers during Stacking of Context Switching.

Note that the LR register save the address of the next instruction of what was being executed.

In our example, if LR contains 0x80004b7, we can find the address 0x80004b7 or 0x80004b6 in the disassembly file. The previous instruction of the found instruction at 0x80004b6 mostly the hot spot which caused the fault.

Use Fault Analyzer to find the executing instruction

Exercise

  1. Try to cause Divide by Zero exception
  2. If disable Usage Fault in SHCSR register, which Fault Exception will be raised?

Fault Handler#

We can not plug a debugger all the time to catch the state of system after a Fault happened. A good method is to capture the system state to file or memory for later analysis.

We know that when exception occurs, CPU automatically saves some context registers before jumping to a Fault Handler. We can implement a way to dump those saved registers.


Naked function to capture Stack Frame

A normal function call always has Prologue and Epilogue sequences added by compiler. In the Prologue, some line of code is added to prepare the stack for the function. However, that action will change the Stack Pointer value. Therefore, a naked function should be used to keep the Stack Pointer value.

__attribute__ ((naked)) void UsageFault_Handler(void) {
    // get current Stack Pointer
    __asm volatile("MRS R0, MSP");
    __asm volatile("B UsageFault_Handler_main");
}

This naked function will save the MSP register to R0, and pass R0 to the actual handler:

void UsageFault_Handler_main(uint32_t* pMSP) {
  printf("Exception: Usage Fault\n");
  DumpExceptionRegister(pMSP);

  uint32_t* pUFSR = (uint32_t*)0xE000ED2A;
  printf("UFSR = 0x%lx\n", *pUFSR & 0xFFFF);

  while(1);
}

Helper function to dump Stack Frame

We can write a general dumper to print out the Stack Frame:

void DumpExceptionRegister(uint32_t* pMSP)
{
  printf(" MSP = %p\n", pMSP);
  printf("  R0 = 0x%lx\n", pMSP[0]);  // May have argument of function
  printf("  R1 = 0x%lx\n", pMSP[1]);  // May have argument of function
  printf("  R2 = 0x%lx\n", pMSP[2]);  // May have argument of function
  printf("  R3 = 0x%lx\n", pMSP[3]);  // May have argument of function
  printf(" R12 = 0x%lx\n", pMSP[4]);  // IP holds an intermediate value of a calculation
  printf("  LR = 0x%lx\n", pMSP[5]);  // Address of the next instruction before the exception
  printf("  PC = 0x%lx\n", pMSP[6]);  // CPU was executing the instruction at PC
  printf("xPSR = 0x%lx\n", pMSP[7]);  // Status of system before execution at PC completes
}

You can use any technique to redirect the Standard IO from printf to UART terminal, or SWD Terminal.

Dump saved Stack Frame to SWD

Comments