Symptom
The serial log shows a task watchdog message naming IDLE (or IDLE0/IDLE1) with a backtrace, or the board resets, when the code waits for something in a tight loop or does a long computation.
Why
The ESP32 runs FreeRTOS, even under Arduino. The Task Watchdog Timer (TWDT) by default watches the idle task of each CPU. The idle task only runs when nothing else wants the CPU. Code like this never gives it a chance:
while (!Serial.available()) { } // busy-wait, never yields
while (digitalRead(PIN) == HIGH) { } // same
for (uint32_t i = 0; i < 50000000; i++) { /* long computation */ }
Default behavior on timeout: print a warning and a backtrace and keep running. With CONFIG_ESP_TASK_WDT_PANIC enabled the chip panics and resets. Which one you get depends on your configuration and framework build.
Fixes
- Yield while waiting: add
delay(1)(Arduino) orvTaskDelay(1)inside wait loops, or better, use blocking APIs with timeouts (queues, semaphores, event groups). - Split long computations into chunks and yield between them, or move them to a separate task with lower priority.
- Use interrupts or events instead of polling pins and peripherals.
- Your own long-running tasks: subscribe them to the TWDT and call
esp_task_wdt_reset()in their main loop, so a hung task is detected, not just a starved idle task. - Don't "fix" it by disabling the watchdog or setting a huge timeout. The watchdog is what turns a silent hang into a recoverable reset (and makes OTA rollback work).
Set CONFIG_ESP_TASK_WDT_TIMEOUT_S to cover your longest legitimate stretch without yielding, not longer.