The Direct Answer: To perform an ESP32 check for core dump on bootup, use the esp_core_dump_image_check() function from the ESP-IDF API within your Arduino setup() block. This queries the flash or RTC memory partition to see if a previous crash log exists before your main application logic runs.

The Direct Answer: Checking ESP32 Core Dumps on Boot

When an ESP32 encounters a fatal exception, it doesn't just silently reboot. If configured correctly, the FreeRTOS kernel halts, captures the CPU registers, stack trace, and memory state, and writes it to a dedicated core dump partition. On the next boot, you can programmatically check for this dump, blink an external fault LED, and extract a summary of the crash before the partition is overwritten by the next fault.

This guide targets the ESP32-WROOM-32 (DevKit V1 30-pin) and the newer ESP32-S3-WROOM-1 running the Arduino-ESP32 core (v2.0.x or v3.0.x, which map to ESP-IDF v4.4 and v5.1 respectively). We will bypass the standard Python terminal scripts and handle the dump verification entirely in C++.

Difficulty: Intermediate | Time Required: 20 minutes | Soldering: Optional (breadboard friendly)

Hardware Requirements and Pin Mapping

You don't need much to capture and signal a crash. The built-in LED is fine for a heartbeat, but a dedicated external fault LED is critical for field-deployed nodes where you can't plug in a USB cable to read the serial monitor.

Spec Sheet & Pin Mapping Table
Component Specification / Variant ESP32 Pin Notes
Microcontroller ESP32-WROOM-32 DevKit V1 (30-pin) N/A Ensure it has at least 4MB Flash for the dump partition.
Status LED Built-in SMD LED GPIO 2 Used as a "clean boot" heartbeat indicator.
Fault LED 5mm Red LED + 330Ω Resistor GPIO 4 Turns on solid if esp_core_dump_image_check() returns true.
Power Supply 5V 2A USB-C / Micro-USB 5V / GND Brownouts cause false crashes; use a quality cable.

Exact Error Strings and Ranked Crash Causes

Before you can debug, you need to recognize the exact strings the ROM bootloader and FreeRTOS kernel spit out over UART at 115200 baud. Here are the most common fatal errors, ranked by how often I see them on the bench.

  1. Stack Overflow / Unhandled Exception:
    Guru Meditation Error: Core 1 panic'ed (Unhandled exception).
    Cause: You declared a massive local array (e.g., char buffer[8192];) inside a function, or you have an infinite recursive loop. The default FreeRTOS task stack is often just 4KB or 8KB.
  2. Watchdog Timer (WDT) Timeout:
    Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1).
    Cause: A blocking delay() or a tight while() loop in your loop() function starved the IDLE task, preventing the WDT from being fed.
  3. Memory Corruption / Null Pointer:
    abort() was called at PC 0x400d1234 on core 1
    Cause: Dereferencing a null pointer, writing past the bounds of an allocated array, or heap fragmentation causing malloc() to fail silently.
  4. Brownout Detector (Not a true core dump, but related):
    brownout detector was triggered
    Cause: The 3.3V LDO on the dev board overheated or the USB port couldn't supply enough current during a WiFi TX burst (which can spike to 500mA).

When any of the first three occur, the system halts and prints: Core dump written to flash. Restarting.... This is the trigger our bootup code will look for.

Complete Code: Read, Report, and Clear the Core Dump

The following code is fully compilable in the Arduino IDE. It checks for the dump, extracts the summary (including the crashed task name and program counter), and signals the fault LED. For full details on the underlying API, refer to the official Espressif Core Dump API Guide.

#include <Arduino.h>
#include "esp_core_dump.h"

// --- PIN DEFINITIONS ---
const int PIN_STATUS_LED = 2; // Built-in LED on DevKit V1
const int PIN_FAULT_LED  = 4; // External red LED for crash indication

void setup() {
  Serial.begin(115200);
  delay(1000); // Allow serial monitor to connect

  pinMode(PIN_STATUS_LED, OUTPUT);
  pinMode(PIN_FAULT_LED, OUTPUT);

  Serial.println("\n--- ESP32 BOOT SEQUENCE ---");
  Serial.println("Checking flash/RTC for previous crash logs...");

  // 1. Check if a core dump image exists
  if (esp_core_dump_image_check()) {
    Serial.println(">> CRASH DETECTED: Core dump found in storage!");
    digitalWrite(PIN_FAULT_LED, HIGH);

    // Blink status LED rapidly to indicate crash state visually
    for (int i = 0; i < 10; i++) {
      digitalWrite(PIN_STATUS_LED, HIGH);
      delay(100);
      digitalWrite(PIN_STATUS_LED, LOW);
      delay(100);
    }

    // 2. Retrieve the summary struct (Requires ESP-IDF 4.1+ / Arduino Core 2.x+)
    esp_core_dump_summary_t summary;
    esp_err_t err = esp_core_dump_get_summary(&summary);
    
    if (err == ESP_OK) {
      Serial.println("--- CRASH SUMMARY ---");
      Serial.printf("Crashed Task Name : %s\n", summary.exc_task);
      Serial.printf("Exception PC      : 0x%lx\n", summary.exc_pc);
      Serial.printf("Core ID           : %d\n", summary.exc_core);
      Serial.printf("Exception Cause   : %ld\n", summary.exc_cause);
      Serial.println("---------------------");
    } else {
      Serial.printf("Failed to parse core dump summary. Error: %d\n", err);
    }

    // 3. OPTIONAL: Erase the partition so we don't read it again on the next reboot.
    // Uncomment the line below if you want a clean slate after logging.
    // esp_core_dump_image_erase();
    // Serial.println("Core dump partition erased.");
    
  } else {
    Serial.println("No core dump found. Clean boot.");
    digitalWrite(PIN_STATUS_LED, HIGH); // Solid ON means healthy
  }

  Serial.println("Entering main application loop.\n");
}

void loop() {
  // Simulate a crash for testing purposes.
  // Uncomment ONE of the blocks below, upload, and watch the serial monitor.
  
  /* 
  // TEST 1: Trigger Stack Overflow
  int massive_array[2000]; 
  massive_array[1999] = 1;
  */

  /* 
  // TEST 2: Trigger Watchdog Timeout
  while(true) { 
    // Blocking loop without yield() or vTaskDelay() 
  }
  */

  delay(1000);
}

The First Three Things to Check When It Fails

If your ESP32 is stuck in a boot loop and generating core dumps, don't just stare at the hex addresses. Run through this decision tree:

  1. Measure the 3.3V Rail Under Load: Grab your multimeter and probe the 3V3 and GND pins while the board is attempting to connect to WiFi. If the voltage dips below 2.8V, the brownout detector will trip. Fix: Solder a 100µF low-ESR electrolytic capacitor directly across the 5V and GND pins on the dev board, or use a dedicated buck converter instead of the onboard AMS1117 LDO.
  2. Audit Local Variables in Functions: If the summary shows a stack overflow, look at the function named in summary.exc_task. Move large arrays (anything over 1KB) out of local scope. Make them global, allocate them on the heap using malloc() / free(), or use std::vector if you are writing C++.
  3. Check for Blocking I2C/SPI Reads: A WDT panic usually means a sensor locked up the bus. If you are using Wire.requestFrom() without a timeout, a stuck SDA line will halt the CPU forever. Fix: Implement bus recovery routines or use FreeRTOS task delays (vTaskDelay(pdMS_TO_TICKS(10));) instead of Arduino delay() to yield to the IDLE task. See the FreeRTOS API docs for proper task yielding.

Extending and Simplifying the Build

How to Simplify: If you are building a commercial product and need to reclaim the 64KB+ of flash space used by the core dump partition, you can disable it. In the Arduino IDE, go to Tools > Core Debug Level and ensure it's set appropriately, but to truly remove the partition, you must use the ESP-IDF menuconfig (Component config > Core dump > Data destination) and set it to "None". Note that you will lose all post-mortem debugging capabilities.

How to Extend: For remote IoT nodes (like agricultural sensors or solar monitors), extend the setup() block to connect to WiFi immediately after detecting a dump. Read the raw dump partition using esp_partition_read(), base64 encode it, and POST it to an MQTT broker or HTTP endpoint. Only call esp_core_dump_image_erase() after the server acknowledges receipt. This gives you a complete remote crash-telemetry pipeline.

Frequently Asked Questions

How do I read the ESP32 core dump file without Python?

The raw core dump partition is formatted in ELF (Executable and Linkable Format). While the esp_core_dump_get_summary() function gives you the task name and Program Counter (PC) directly in C++, getting the full human-readable stack trace requires the espcoredump.py tool provided by Espressif. You must run this Python script on your PC, passing it the .elf file from your build folder and the raw binary dump extracted from the flash. There is currently no native way to print the full symbolized stack trace directly over UART from the ESP32 itself due to the lack of debug symbols stored on the device.

Why is my ESP32 boot loop showing a brownout detector error instead of a core dump?

The brownout detector is a hardware-level peripheral that triggers a system reset before the CPU can execute a software exception handler. Because the voltage drops too fast, the FreeRTOS kernel doesn't have the time or stable power required to write the core dump to flash. If you see brownout detector was triggered, it is strictly a power delivery issue, not a software bug. Check your USB cable resistance and the current capacity of your power supply.

Does saving core dumps to RTC memory survive a power loss?

No. If you configure the core dump destination to RTC Slow Memory (via ESP-IDF menuconfig), the dump will survive a soft reset, a watchdog reset, or a deep sleep wake-up. However, if the device loses main power entirely (the 3.3V rail drops to 0V), the RTC memory is wiped. For field devices that might suffer power cuts, you must configure the core dump destination to Flash. Flash storage survives total power loss, which is the default configuration in the Arduino-ESP32 core.