Crash dumps and the watchdog
The board reboots on its own and prints a wall of hex before it goes. That wall is not noise - its first line is a diagnosis, and the four common ones point at completely different problems.
Four messages, four directions
xtensa-esp32-elf-addr2line -pfiaC -e your.elf 0x400d1a2f. Without that step you have an address; with it you have a filename and a line.Three watchdogs, and which one bit you
- Task watchdog — a task ran for over five seconds without yielding. Names
the task. Usually a blocking call or a loop with no
vTaskDelay. - Interrupt watchdog — interrupts were disabled too long. Almost always an ISR doing far too much.
- RTC watchdog — the last resort, during boot. Usually a bootloader or partition problem rather than your code.
Log the reason, always
Two lines in setup() that print the reset reason and a boot counter turn "it
crashes sometimes" into a fact you can act on. Keep the counters in
RTC_DATA_ATTR so they survive the reboot they are counting.
The code
Print the reset reason at every boot and keep a counter in RTC memory. A board that reboots once a day in the field is invisible without this and obvious with it.
#include <esp_system.h>
RTC_DATA_ATTR int boots = 0;
RTC_DATA_ATTR int panics = 0;
void setup() {
Serial.begin(115200);
delay(100);
esp_reset_reason_t r = esp_reset_reason();
boots++;
if (r == ESP_RST_PANIC || r == ESP_RST_TASK_WDT ||
r == ESP_RST_INT_WDT) panics++;
const char *why =
r == ESP_RST_POWERON ? "power on" :
r == ESP_RST_SW ? "software restart" :
r == ESP_RST_PANIC ? "PANIC - a bug" :
r == ESP_RST_TASK_WDT ? "task watchdog - something blocked" :
r == ESP_RST_BROWNOUT ? "BROWNOUT - the supply sagged" :
r == ESP_RST_DEEPSLEEP ? "woke from deep sleep" : "other";
Serial.printf("boot %d (%d bad): %s\n", boots, panics, why);
}
void loop() {}A panic is a bug in your code. A brownout is a bug in the power supply. Treating one as the other is the most common wasted week on this chip.
MicroPython gives you the reset cause and a traceback on the REPL. Writing the traceback to a file is what turns an unattended crash into something you can read afterwards.
import machine, sys, time
causes = {machine.PWRON_RESET: 'power on',
machine.HARD_RESET: 'hard reset',
machine.WDT_RESET: 'watchdog',
machine.DEEPSLEEP_RESET: 'deep sleep',
machine.SOFT_RESET: 'soft reset'}
print('reset:', causes.get(machine.reset_cause(), 'unknown'))
try:
import app
app.run()
except Exception as e:
with open('crash.log', 'a') as f:
f.write('{}\n'.format(time.time()))
sys.print_exception(e, f)
time.sleep(5)
machine.reset()Wrap the whole of main.py in a try. Without it, a crash drops to the REPL and an unattended board simply sits there doing nothing, which looks like a hang.
When it does not work
Decode it. Paste it into the Arduino IDE Exception Decoder, or run xtensa-esp32-elf-addr2line -pfiaC -e your.elf followed by the addresses. That turns hex into filenames and line numbers.
Brownout, not a bug. The message says so on its first line. Better cable, better supply, 100 µF on the rail.
It does not have to be stuck - just busy for more than five seconds without yielding. A long while loop or a blocking network call is enough.
It made it silent. The board is still spending five seconds somewhere, and now nothing recovers it when that becomes five minutes.
It stays up. The rest of the chapter is the peripherals that only some boards have: audio, camera, and MIPI screens.
I2S audio →Edit this page — content/esp32/crash-dumps-and-the-watchdog.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.