Fix: ESP32 does nothing after “Hard resetting via RTS pin...”
Upload finished, but the sketch doesn't run until you press EN — or never runs. Check GPIO0/GPIO2/GPIO12, the Serial Monitor baud rate and USB CDC settings.
Community draft — report a mistake
What you see
Writing at 0x0003f2a8... (100 %)
Wrote 271904 bytes (151346 compressed) at 0x00010000 in 2.4 seconds (effective 905.8 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting via RTS pin...
(Serial Monitor stays empty; the sketch only starts after pressing EN)Writing at 0x0003f2a8... (100 %) Wrote 271904 bytes (151346 compressed) at 0x00010000 in 2.4 seconds (effective 905.8 kbit/s)... Hash of data verified. Leaving... Hard resetting via RTS pin... (Serial Monitor stays empty; the sketch only starts after pressing EN)
“Hard resetting via RTS pin…” is the last line of a successful upload. esptool has written and verified your code and is now asking the USB-serial chip to reset the board so the sketch starts.
If nothing seems to happen, the board either restarted into download mode (strapping pins or a jumper), or it is running but its output is going somewhere you are not looking. The diagnostic sketch below tells the two apart.
Quick fix — try in order
- 1
Press EN/RESET once and watch Serial Monitor
Set the monitor to 115200. If text appears after pressing EN, the upload is fine and only the automatic reset did not happen.
- 2
Remove the ESP32-CAM IO0 jumper
If IO0 is still tied to GND, every reset goes straight back into download mode. Remove it, then press RST.
- 3
Free the strapping pins
GPIO0, GPIO2, GPIO12 and GPIO15 must be at their default levels at reset. A module that pulls GPIO2 high or GPIO12 high can stop a normal boot. The pin checker marks them.
- 4
ESP32-C3/S3: enable USB CDC On Boot
On native-USB boards your Serial output goes nowhere unless Tools → USB CDC On Boot → Enabled. The sketch is running; you just cannot see it.
- 5
Upload the reset-reason sketch below
It prints why the chip last reset and a heartbeat every 5 s, so you can tell “not running” from “running but silent”.
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
Why does it need EN pressed?
esptool pulses RTS to reset the chip. On some clone boards the auto-reset transistors and capacitor leave GPIO0 low a little too long, so the chip restarts into download mode instead of your sketch.
Serial Monitor holds it in reset
Some terminals assert DTR/RTS when they open, which holds EN low. Arduino IDE 2's monitor handles this; PuTTY and older tools may not — turn off flow control.
The board really is crashing
If you see a boot message repeating, read it: brownout, Guru Meditation and watchdog resets each have their own page in the list.
Board-specific notes
ESP32-CAM: Most “not running after upload” reports are the IO0 jumper still in place.
NodeMCU ESP8266: The same message appears on ESP8266. If nothing runs, check GPIO15 (D8) is low and GPIO0 (D3)/GPIO2 (D4) are high at reset.
Did this fix it?
Thanks — that helps us keep this page right.