Course contentsShow
← Rust
Lesson 1817 of 1,91016. Specialized Runtime TargetsFree lesson

Panic Handling in Embedded Systems

Design custom panic handlers that safely report failure states when standard logging mechanisms are unavailable.

In this lesson, you will learn how to design robust, custom panic handlers that communicate critical failure states when you lack a standard operating system or logging infrastructure.

In an embedded context, a panic! typically halts execution. Because we don't have a stderr to pipe logs into, the default behavior (often an infinite loop) is a "silent death." For production-grade firmware, you must design a handler that communicates why the system failed, even when standard facilities are unavailable.

Designing the "Hard Failure" Handler

When the runtime encounters a panic, it calls a function marked with the #[panic_handler] attribute. To ensure safety and diagnostic utility, your custom implementation must avoid any code that might itself trigger a panic or rely on heap allocation (which could be the cause of your failure).

Consider this pattern for communicating failure through minimal hardware interfaces:

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    // 1. Disable interrupts to prevent race conditions during reporting
    cortex_m::interrupt::disable();

    // 2. Write to a persistent storage or a low-level peripheral
    // We avoid standard logging and write directly to registers
    let mut uart = unsafe { &mut *UART0_BASE };
    
    // We manually transmit a failure code or diagnostic string
    // without relying on complex string formatting macros
    write_panic_signal(&mut uart, b"CRITICAL_FAILURE");

    // 3. Enter a deterministic loop or trigger a hardware reset
    loop {
        cortex_m::asm::nop();
    }
}

Why This Matters

When debugging hardware, you cannot rely on high-level abstractions to report a crash. If your panic handler is too complex—for example, if it tries to allocate memory to format a string—you risk a "recursive panic," where the error handler crashes itself. By utilizing low-level memory-mapped I/O (MMIO) and avoiding core::fmt where memory safety is at risk, you create a "last mile" diagnostic tool that works even when the heap is corrupted and interrupts are firing uncontrollably.

Your goal is to transition from a system that silently stops to a system that leaves a breadcrumb trail in a hardware register or external flash, enabling post-mortem analysis.

Key Takeaway: Design your panic handler to be self-contained and dependency-free, ensuring it can report failure states using only low-level peripheral access when all other runtime abstractions have collapsed.