PSoC5LP RTOS
Lab 27 — Fault Handling (HardFault Diagnosis & Recovery)
FreeRTOS on PSoC 5LP · Lab Series Platform: CY8CKIT-059 (PSoC 5LP, Cortex-M3) · PSoC Creator 4.4 · FreeRTOS Kernel V11 Pattern: a fault traps into a handler that recovers the stacked frame, reads the fault-status registers, reports a post-mortem over UART, then decides on reset or graceful degradation Pairs with: Chapter 18 — Reliability & the Watchdog & Chapter 13 — System Architecture
1. Introduction & Objectives
Up to now, every safety net in the course has caught a known kind of mistake: configASSERT catches API misuse (Lab 23), the stack-overflow hook catches a task that outgrew its stack (Lab 13), the malloc-failed hook catches an exhausted heap (Lab 13). But none of them sees the classic C disaster — a wild pointer write, a call through a corrupted function pointer, a divide-by-zero, an unaligned access. On a Cortex-M3, those trap into a fault exception, and if you have not installed a handler, the board simply hangs in the default HardFault loop: dead, silent, no clue why.
This lab closes that last gap. The Cortex-M3 does most of the work for you: on a fault it automatically stacks a register snapshot (r0–r3, r12, LR, PC, xPSR) and vectors to your HardFault handler. Your handler recovers that frame — in particular the PC, the exact instruction that faulted — reads the fault-status registers (HFSR, CFSR, BFAR) to learn what and where, and prints a one-line post-mortem over UART. From "it hangs" you get to "fix the write on this source line." Then, because a fault handler is a branch point, not an end, you decide: log and reset (tying into the Lab 18 watchdog / reset-reason), or disable the broken subsystem and limp home.
After this lab you can:
- Explain the Cortex-M3 fault family (HardFault, BusFault, UsageFault, MemManage) and how configurable faults escalate to HardFault.
- Install a HardFault handler into the vector table that FreeRTOS_Init() built, with a small asm stub that picks MSP vs PSP.
- Recover the stacked exception frame and read HFSR / CFSR / BFAR to diagnose a fault's cause and address.
- Produce a post-mortem dump over UART that names the faulting PC.
- Decide recovery: controlled reset (Lab 18) vs graceful degradation, and know why a silent return is never an option.
- (Extension) Enable the configurable faults by name via SHCSR, and persist the dump across a reset.
2. Prerequisites
Hardware - CY8CKIT-059 PSoC 5LP Prototyping Kit - RobotSoC Kit: the UART-to-USB path from Lab 14 (the post-mortem comes out here); an LED to show "degraded but alive"
Software - PSoC Creator 4.4 with the FreeRTOS V11 BaseProject
Chapters (sync progress) - Required: Chapter 18 (Reliability & the Watchdog — reset and reset-reason) and Chapter 13 (System Architecture — where fault policy lives). - Related: Lab 18 (watchdog, CyResetStatus / reset reason), Lab 23 (configASSERT, run-time stats, the other hooks), Lab 13 (stack-overflow & malloc-failed hooks), Lab 12 (the vector table FreeRTOS_Init() builds, NVIC). - Cortex-M3 prerequisite: basic exception model (vector table, MSP/PSP, exception entry) — covered in the architecture chapter.
3. Reading Materials / References
- Chapter 18 slides — Reliability & the Watchdog (reset sources, reset-reason latches).
- ARMv7-M Architecture Reference Manual — exception entry & the stacked frame; the System Control Block fault registers: SHCSR, CFSR (= MMFSR | BFSR | UFSR), HFSR, BFAR, MMFAR.
- PSoC 5LP: CyResetStatus / CY_RESET_WD, CySoftwareReset(), CyIntSetSysVector() (installing a system exception vector into the RAM vector table).
- FreeRTOS: configASSERT, vApplicationStackOverflowHook, vApplicationMallocFailedHook (Lab 13 / Lab 23).
4. Background & Theory of Operation
The fault family and escalation. The Cortex-M3 has four fault exceptions. HardFault is always enabled and is the catch-all. The three configurable faults — UsageFault (divide-by-zero, undefined instruction, invalid state, optionally unaligned access), BusFault (a bad or aborted bus transfer, e.g. an access to an unmapped address), and MemManage (an MPU region violation — the PSoC 5LP has no MPU in this course, so this one does not arise) — are disabled at reset. While disabled, any such fault escalates into a HardFault, and HFSR.FORCED is set to say "I am standing in for a configurable fault." So out of the box, everything lands in your HardFault handler; enabling the configurable faults via SHCSR (the Exp03 extension) only changes which named vector you arrive at — the diagnosis is the same.
What the hardware gives you for free. On exception entry the CPU automatically pushes eight words onto the stack that was active when the fault hit: r0, r1, r2, r3, r12, LR, PC, xPSR (low address first). This is the register snapshot at the moment of the fault. The single most valuable word is the stacked PC — the address of the faulting instruction. Look that address up in the .lst/.map or the debugger's disassembly and you have the exact line of code to fix. The stacked LR is the faulting code's own return address (its caller), useful for context; note it is not the same as the EXC_RETURN value the CPU places in the live LR on entry.
Which stack? MSP vs PSP. Under FreeRTOS, tasks run on the Process Stack Pointer (PSP) and the kernel/ISRs run on the Main Stack Pointer (MSP). The frame is pushed onto whichever was active. On entry the CPU loads EXC_RETURN into LR; bit 2 tells you which stack to read: 0 → the frame is on MSP (the fault hit the kernel or an ISR), 1 → the frame is on PSP (the fault hit a task — the usual case). A tiny assembly stub is therefore the correct top-level handler: it tests LR bit 2, loads the right stack pointer into r0, and tail-calls a normal C function prvGetRegsFromStack(uint32_t *frame) that does the real work. (Writing the stub in asm avoids the C prologue disturbing the stack before you capture it.)
Reading the evidence — three registers, in order. 1. HFSR (HardFault Status). FORCED (bit 30) → a configurable fault escalated here, so go read CFSR. VECTTBL (bit 1) → a fault reading the vector table. 2. CFSR (Configurable Fault Status) — one 32-bit register packing UFSR | BFSR | MMFSR. Bits such as DIVBYZERO, UNALIGNED (usage), PRECISERR, IMPRECISERR, IBUSERR (bus) tell you the kind. BFARVALID says the address register is trustworthy. It is write-1-to-clear — clear it once you have read it so the next fault is unambiguous. 3. BFAR / MMFAR (Fault Address). The address that was accessed — valid only when BFARVALID / MMARVALID is set. An imprecise bus error (write buffer drained late) leaves no valid address; precise errors (a straight null-pointer dereference) do.
Together: HFSR says something escalated, CFSR says what kind, BFAR says at which address, and the stacked PC says from which instruction. Dump all of them over UART and the post-mortem writes itself.
The decision — a handler is a branch point. Once diagnosed, choose deliberately: - Log & reset. Write the dump somewhere that survives a reset (a reset-safe SRAM region, EEPROM, or just the UART log on the bench), then trigger a controlled reset — call CySoftwareReset(), or simply stop feeding the watchdog (Lab 18) and let it bite. On the next boot, CyResetStatus() & CY_RESET_WD tells you the last run died, and you can surface the saved dump. - Degrade gracefully. If the fault is isolable and the rest of the system state is sane, disable just the broken subsystem and keep the safe core running (limp-home). This is only justified when you can argue the remaining state is trustworthy — often it is not after memory corruption, so reset is the safer default. - Never silently return. Returning from a HardFault without fixing the cause just re-executes the faulting instruction and faults again — an infinite fault loop. A fault is not a warning; treat it as terminal to the current execution.
Where this sits in defence-in-depth. configASSERT + the two allocation hooks stop known misuse early, before corruption spreads. The fault handler is the last net — it catches the hardware traps the others never see, and converts a dead board into a readable diagnosis. A robust product has all four.
5. RTOS Task Architecture
A task commits a bad access and faults; the CPU auto-stacks the 8-word frame and vectors to the HardFault handler, whose asm stub picks MSP/PSP from EXC_RETURN and hands the frame to a C routine. That routine reads the evidence — the stacked PC, plus HFSR/CFSR/BFAR — and dumps a post-mortem over UART. Then comes the decision: log & reset (via the Lab 18 watchdog / soft reset), or degrade gracefully. The lower band shows the four nets of defence in depth and which failure each one catches.
6. Anatomy of a Fault
The stacked exception frame — where the CPU left the evidence:
The hardware pushes r0–r3, r12, LR, PC, xPSR onto the active stack; EXC_RETURN bit 2 says whether that is MSP or PSP. The stacked PC is the faulting instruction — the line to fix.
Decoding the fault — which register answers which question:
Configurable faults escalate to HardFault unless enabled. Inside the handler, read HFSR (forced?), then CFSR (which sub-fault?), then BFAR (which address?), and pair with the stacked PC.
The lesson both diagrams carry: the snapshot is already on the stack and in the SCB registers — a good handler just reads it out and prints it, turning a silent hang into an actionable bug report.
7. Schematic Circuit & Pin Configuration
- UART TX → the post-mortem output (reuse the Lab 14 UART-to-USB path); open a terminal at the project baud.
- LED → lit in the "degraded / limp-home" path so a bench observer sees the board survived in a reduced mode.
- Assign the pins in <project>.cydwr → Pins.
8. PSoC Creator Configuration
- A UART component for the dump (as in Lab 14); no new hardware is required for Exp01/Exp02.
- The HardFault vector is part of the table FreeRTOS_Init() builds. Install your handler with CyIntSetSysVector(CY_INT_HARD_FAULT_IRQN, (cyisraddress)HardFault_Handler); at startup, after FreeRTOS_Init() (the template does this). (Exp03: enable the configurable faults by setting the USGFAULTENA / BUSFAULTENA bits in SCB->SHCSR, and install BusFault_Handler / UsageFault_Handler the same way.)*
- FreeRTOSConfig.h (already set): configASSERT defined (Lab 23), configCHECK_FOR_STACK_OVERFLOW = 2, configUSE_MALLOC_FAILED_HOOK = 1 (Lab 13), heap_4, configUSE_TIME_SLICING = 1 (course default).
9. Provided Template
You are given Lab27_Template_main.c:
- Given: all includes (note #include "FreeRTOS_PSoC.h"), a report() UART-print helper sketch, the asm HardFault_Handler stub that selects MSP/PSP and tail-calls prvGetRegsFromStack, a complete main() that installs the handler after FreeRTOS_Init() and creates a "culprit" task plus a steady heartbeat task, and the SCB register names via CMSIS (core_cm3.h through project.h).
- You write: the body of prvGetRegsFromStack() (pull pc, lr, psr from the frame; read HFSR/CFSR/BFAR; print the post-mortem; then decide reset vs degrade). The Exp02 recovery policy and the Exp03 SHCSR-enable path are sketched in comments.
- Do not change the asm stub or the boot sequence in main().
/* top-level handler: asm stub picks the stack, then calls C */
__attribute__((naked)) void HardFault_Handler(void)
{
__asm volatile(
" tst lr, #4 \n" /* EXC_RETURN bit 2: 0=MSP, 1=PSP */
" ite eq \n"
" mrseq r0, msp \n"
" mrsne r0, psp \n"
" b prvGetRegsFromStack \n"
);
}
/* C side: frame[6] = faulting PC, frame[7] = xPSR */
void prvGetRegsFromStack(uint32_t *frame)
{
uint32_t pc = frame[6]; /* the instruction that faulted */
uint32_t cfsr = SCB->CFSR; /* which sub-fault + BFARVALID */
uint32_t bfar = SCB->BFAR; /* faulting address (if valid) */
report(pc, frame[5], cfsr, SCB->HFSR, bfar); /* post-mortem over UART */
/* decide: CySoftwareReset(); OR disable subsystem + continue */
for (;;) { } /* never silently return */
}
10. Lab Experiments
Exp01 — Basic: catch and diagnose a HardFault
Install the handler. From the "culprit" task, trigger a precise fault on command (e.g. a null-pointer write *(volatile uint32_t*)0 = 1;). Confirm the heartbeat task was running right up to the fault, then the handler fires and prints PC, LR, CFSR, HFSR, BFAR. Look the PC up in the .lst/map and confirm it points at your bad line. Expected: a one-line post-mortem that names the faulting instruction and (for the null write) BFARVALID with BFAR = 0.
Exp02 — Advanced: decide recovery (reset vs degrade)
Replace the for(;;) halt with a policy. (a) Log the dump to a reset-safe location and call CySoftwareReset(); on reboot, read CyResetStatus() and print "recovered from fault, last PC = …". (b) Alternatively, for an isolable fault, disable only the culprit subsystem, light the "degraded" LED, and keep the heartbeat running. Argue in your report which policy is right for your fault and why. Expected: a board that either restarts cleanly and reports the prior crash, or stays alive in a reduced mode — never an infinite fault loop.
Exp03 — Challenge: name the fault, and provoke each kind
Enable the configurable faults (SCB->SHCSR |= USGFAULTENA | BUSFAULTENA) and install UsageFault_Handler / BusFault_Handler. Provoke, one at a time: a divide-by-zero (also set SCB->CCR |= DIV_0_TRP), an unaligned access (UNALIGN_TRP), and a bus error (read a truly unmapped address). Show each now arrives at its named handler with the matching CFSR bit, instead of a bare forced HardFault. Expected: a table mapping each provoked fault to its vector and its CFSR bit, with the address shown where BFARVALID.
11. Testing & Verification
| # | Pass condition |
|---|---|
| Exp01 | A deliberate fault is caught; the dump names the faulting PC (verified against the map) and the correct CFSR bit. |
| Exp02 | Recovery is deliberate: a controlled reset that reports the prior crash, or a justified graceful degradation — no fault loop. |
| Exp03 | Configurable faults are enabled and each provoked fault reaches its named handler with the matching CFSR bit. |
Submit: your prvGetRegsFromStack() body, a captured post-mortem line with the PC resolved to a source line, your Exp02 recovery policy with justification, and the Exp03 fault→vector→CFSR table.
12. Q&A & Troubleshooting
- Board just hangs, handler never prints → your handler is not in the table; install it with CyIntSetSysVector(...) after FreeRTOS_Init(), which (re)builds the vector table.
- Handler fires but PC/registers look like garbage → you read the wrong stack; the asm stub must pick MSP vs PSP from EXC_RETURN bit 2. Don't add a C prologue before capturing the frame (keep the stub naked).
- BFAR shows a stale/wrong address → check BFARVALID first; imprecise bus errors leave no valid address. Also clear CFSR (write-1-to-clear) after reading so the next fault is clean.
- Everything arrives as a forced HardFault → that's normal until you enable the configurable faults in SHCSR (Exp03); HFSR.FORCED confirms the escalation.
- UART prints nothing in the handler → you're at an exception priority above your UART ISR, or using a blocking driver; use a polled/blocking-by-register UART write in the handler, never a *FromISR queue call.
- Fault loops forever → you returned from the handler with the same bad state; diagnose and reset or halt, don't retry blindly.
- Divide-by-zero doesn't fault → it's disabled by default; set SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk and enable UsageFault.
13. Analysis Questions & Lab Report
- Name the four Cortex-M3 fault exceptions. Which is always enabled, and what does HFSR.FORCED tell you about the other three?
- List the eight words the CPU auto-stacks on a fault. Which one is most valuable for debugging, and how do you turn it into a source line?
- How does the handler know whether the frame is on MSP or PSP, and why is the top-level handler written in assembly?
- Walk through HFSR → CFSR → BFAR: what question does each answer, and when is BFAR not to be trusted?
- Compare the four nets of defence in depth (configASSERT, stack-overflow hook, malloc-failed hook, fault handler): give one failure each one catches that the others do not.
- (Extension) Why is returning from a HardFault dangerous, and what are the two legitimate things a handler can do instead?
Your report should include your prvGetRegsFromStack(), a resolved post-mortem, your recovery policy with justification, the Exp03 fault table, and answers to the six questions.