Fix ESP32 “rst:0x8 (TG1WDT_SYS_RESET)” reset loop
A hardware timer watchdog reset the ESP32 — interrupts were blocked too long. Shorten ISRs, avoid Serial/delay in interrupts, and check bit-banged LED libraries.
Community draft — report a mistake
What you see
ets Jul 29 2019 12:21:46
rst:0x8 (TG1WDT_SYS_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0030,len:4888
load:0x40078000,len:16516
load:0x40080400,len:4
load:0x40080404,len:3476
entry 0x400805b4
Started
ets Jul 29 2019 12:21:46
rst:0x8 (TG1WDT_SYS_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)ets Jul 29 2019 12:21:46 rst:0x8 (TG1WDT_SYS_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) configsip: 0, SPIWP:0xee clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00 mode:DIO, clock div:1 load:0x3fff0030,len:4888 load:0x40078000,len:16516 load:0x40080400,len:4 load:0x40080404,len:3476 entry 0x400805b4 Started ets Jul 29 2019 12:21:46 rst:0x8 (TG1WDT_SYS_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
The ESP32 has hardware watchdogs in two timer groups. Timer Group 1 backs up the interrupt watchdog: if interrupts stay blocked so long that even the panic handler can’t run, it resets the whole system, and the next boot prints rst:0x8 TG1WDT_SYS_RESET.
That almost always points at your interrupt code or a library that turns interrupts off for too long. See also Interrupt wdt timeout on CPU1.
Quick fix — try in order
- 1
Keep interrupt handlers tiny
In an attachInterrupt() handler only set a
volatileflag or counter. Move Serial.print, delay(), I2C, Wi-Fi and display code to loop(). - 2
Mark ISRs IRAM_ATTR
void IRAM_ATTR onPulse() { ... }. Without it, an interrupt arriving while flash is busy can stall and trip the watchdog. - 3
Look for noInterrupts() and long critical sections
Code that disables interrupts and then loops (bit-banged NeoPixels with hundreds of LEDs, old DHT or OneWire versions, portENTER_CRITICAL around slow code) starves the watchdog. Update the library or use RMT-based drivers.
- 4
Check for a noisy interrupt pin
A floating input or a bouncing switch on a CHANGE interrupt can fire thousands of times a second. Add INPUT_PULLUP and debounce in software.
- 5
Upload the reset-reason sketch to confirm
It prints the last reset reason. INT_WDT confirms an interrupt-watchdog problem in your code.
Diagnostic sketch
// FlashReady diagnostic: why did my ESP32 reset?
// Prints the reset reason, chip, flash and PSRAM details once at boot, then a heartbeat
// every 5 s. If the uptime keeps going back to 0, the board is rebooting - and the
// "Reset reason" line tells you which kind of reset it was.
// Works on ESP32, ESP32-C3 and ESP32-S3 (on C3/S3 native USB set Tools -> USB CDC On Boot -> Enabled).
#include <esp_system.h>
#include <esp_flash.h>
const char *reasonText(esp_reset_reason_t r) {
switch (r) {
case ESP_RST_POWERON: return "POWERON - power applied or EN/RST pressed";
case ESP_RST_EXT: return "EXT - external reset pin";
case ESP_RST_SW: return "SW - ESP.restart() or a library called esp_restart()";
case ESP_RST_PANIC: return "PANIC - crash (Guru Meditation); scroll up for the backtrace";
case ESP_RST_INT_WDT: return "INT_WDT - interrupt watchdog (an ISR or critical section ran too long)";
case ESP_RST_TASK_WDT: return "TASK_WDT - task watchdog (loop() blocked too long)";
case ESP_RST_WDT: return "WDT - other watchdog (RTC or timer group)";
case ESP_RST_DEEPSLEEP: return "DEEPSLEEP - woke from deep sleep";
case ESP_RST_BROWNOUT: return "BROWNOUT - supply dipped below ~2.4 V; fix the power";
case ESP_RST_SDIO: return "SDIO - reset over SDIO";
default: return "other / unknown";
}
}
void setup() {
Serial.begin(115200);
delay(1500); // give native-USB boards time to enumerate before printing
esp_reset_reason_t r = esp_reset_reason();
Serial.println();
Serial.println("=== FlashReady reset-reason report ===");
Serial.printf("Reset reason : %d (%s)\n", (int)r, reasonText(r));
Serial.printf("Chip : %s rev %d, %d core(s), %lu MHz\n", ESP.getChipModel(), ESP.getChipRevision(),
ESP.getChipCores(), (unsigned long)ESP.getCpuFreqMHz());
uint32_t physical = 0;
esp_flash_get_physical_size(esp_flash_default_chip, &physical);
uint32_t header = ESP.getFlashChipSize();
Serial.printf("Flash (chip) : %lu KB\n", (unsigned long)(physical / 1024));
Serial.printf("Flash (IDE) : %lu KB at %lu MHz\n", (unsigned long)(header / 1024),
(unsigned long)(ESP.getFlashChipSpeed() / 1000000));
if (physical && header > physical) {
Serial.println("WARNING : Tools -> Flash Size is larger than the real chip. Lower it.");
}
Serial.printf("PSRAM : %lu KB%s\n", (unsigned long)(ESP.getPsramSize() / 1024),
ESP.getPsramSize() ? "" : " (none found or PSRAM disabled in Tools)");
Serial.printf("Sketch : %lu KB used, %lu KB free in app partition\n",
(unsigned long)(ESP.getSketchSize() / 1024), (unsigned long)(ESP.getFreeSketchSpace() / 1024));
Serial.printf("Free heap : %lu bytes\n", (unsigned long)ESP.getFreeHeap());
Serial.println("======================================");
}
void loop() {
static uint32_t last = 0;
if (millis() - last >= 5000) {
last = millis();
Serial.printf("alive, uptime %lu s, free heap %lu\n", (unsigned long)(millis() / 1000),
(unsigned long)ESP.getFreeHeap());
}
}// FlashReady diagnostic: why did my ESP32 reset?
// Prints the reset reason, chip, flash and PSRAM details once at boot, then a heartbeat
// every 5 s. If the uptime keeps going back to 0, the board is rebooting - and the
// "Reset reason" line tells you which kind of reset it was.
// Works on ESP32, ESP32-C3 and ESP32-S3 (on C3/S3 native USB set Tools -> USB CDC On Boot -> Enabled).
#include <esp_system.h>
#include <esp_flash.h>
const char *reasonText(esp_reset_reason_t r) {
switch (r) {
case ESP_RST_POWERON: return "POWERON - power applied or EN/RST pressed";
case ESP_RST_EXT: return "EXT - external reset pin";
case ESP_RST_SW: return "SW - ESP.restart() or a library called esp_restart()";
case ESP_RST_PANIC: return "PANIC - crash (Guru Meditation); scroll up for the backtrace";
case ESP_RST_INT_WDT: return "INT_WDT - interrupt watchdog (an ISR or critical section ran too long)";
case ESP_RST_TASK_WDT: return "TASK_WDT - task watchdog (loop() blocked too long)";
case ESP_RST_WDT: return "WDT - other watchdog (RTC or timer group)";
case ESP_RST_DEEPSLEEP: return "DEEPSLEEP - woke from deep sleep";
case ESP_RST_BROWNOUT: return "BROWNOUT - supply dipped below ~2.4 V; fix the power";
case ESP_RST_SDIO: return "SDIO - reset over SDIO";
default: return "other / unknown";
}
}
void setup() {
Serial.begin(115200);
delay(1500); // give native-USB boards time to enumerate before printing
esp_reset_reason_t r = esp_reset_reason();
Serial.println();
Serial.println("=== FlashReady reset-reason report ===");
Serial.printf("Reset reason : %d (%s)\n", (int)r, reasonText(r));
Serial.printf("Chip : %s rev %d, %d core(s), %lu MHz\n", ESP.getChipModel(), ESP.getChipRevision(),
ESP.getChipCores(), (unsigned long)ESP.getCpuFreqMHz());
uint32_t physical = 0;
esp_flash_get_physical_size(esp_flash_default_chip, &physical);
uint32_t header = ESP.getFlashChipSize();
Serial.printf("Flash (chip) : %lu KB\n", (unsigned long)(physical / 1024));
Serial.printf("Flash (IDE) : %lu KB at %lu MHz\n", (unsigned long)(header / 1024),
(unsigned long)(ESP.getFlashChipSpeed() / 1000000));
if (physical && header > physical) {
Serial.println("WARNING : Tools -> Flash Size is larger than the real chip. Lower it.");
}
Serial.printf("PSRAM : %lu KB%s\n", (unsigned long)(ESP.getPsramSize() / 1024),
ESP.getPsramSize() ? "" : " (none found or PSRAM disabled in Tools)");
Serial.printf("Sketch : %lu KB used, %lu KB free in app partition\n",
(unsigned long)(ESP.getSketchSize() / 1024), (unsigned long)(ESP.getFreeSketchSpace() / 1024));
Serial.printf("Free heap : %lu bytes\n", (unsigned long)ESP.getFreeHeap());
Serial.println("======================================");
}
void loop() {
static uint32_t last = 0;
if (millis() - last >= 5000) {
last = millis();
Serial.printf("alive, uptime %lu s, free heap %lu\n", (unsigned long)(millis() / 1000),
(unsigned long)ESP.getFreeHeap());
}
}Why it happens
TG1 and TG0 — what's the difference?
Timer Group 1 runs the interrupt watchdog; Timer Group 0 runs the task watchdog and the bootloader's watchdog. rst:0x8 points at interrupts being blocked; rst:0x7 (TG0WDT_SYS_RESET) during boot points at flash or bootloader trouble instead.
Why no Guru Meditation message?
Usually the interrupt watchdog prints “Interrupt wdt timeout on CPU1” first. If the CPU is so stuck it can't even print, the hardware watchdog resets it directly and you only see rst:0x8.
Board-specific notes
ESP32 with WS2812B: Use a library that drives LEDs through the RMT peripheral (FastLED and Adafruit NeoPixel both do on current cores) instead of bit-banging with interrupts off.
Did this fix it?
Thanks — that helps us keep this page right.