What are the risks of calling printf from an ISR?
Calling printf() from an ISR is one of the most common and dangerous mistakes in embedded development, yet it is tempting because developers want debug output from interrupt context. The problems are multiple and severe. First, printf() is not reentrant — it uses internal static buffers and state. If the main loop is mid-printf when an ISR calls printf, the internal state (buffer pointers, format parsing position) is corrupted. The result is garbled output at best, a heap corruption or HardFault at worst. This is not theoretical — it is one of the most frequently encountered crash causes in embedded systems during development.
Second, printf() typically calls malloc() internally (for buffer management in many C library implementations) and malloc() is not reentrant either. If the main loop is inside a malloc call when the ISR triggers and calls printf, the heap's linked-list metadata is corrupted, causing either an immediate crash or a delayed, seemingly unrelated crash the next time any code touches the heap. Third, the underlying output mechanism (UART transmit, semihosting, SWO) involves blocking I/O. UART printf waits for the transmit buffer to drain, which can take milliseconds at 115200 baud for a long format string — an eternity in ISR context. During this time, all equal-and-lower-priority interrupts are blocked, latency guarantees are destroyed, and the system may miss time-critical events.
Safe alternatives for ISR debugging: (1) Toggle a GPIO pin and measure timing with an oscilloscope or logic analyzer — zero overhead, zero risk. (2) Write to a RAM-based circular log buffer in the ISR (just a pointer increment and a memcpy), then drain the buffer to UART in the main loop. (3) Use ITM/SWO trace output via the debug port, which is designed for this purpose and has minimal latency. (4) Set a volatile flag or error code that the main loop prints. The bottom line: printf in an ISR violates every ISR safety rule simultaneously — it is non-reentrant, uses the heap, blocks, and takes an unpredictable amount of time. Never use it, not even "temporarily" for debugging.
Source: Interrupts & Priorities Q&A
