What Happens When You Flash
Flashing firmware to an ESP32 is a three-phase process. First, the host computer signals the ESP32 to enter download mode by asserting the DTR and RTS lines on the USB-to-serial chip in a specific sequence that grounds GPIO 0 while pulsing the EN (reset) pin. Second, esptool.py communicates with the ROM bootloader over UART, negotiating from the bootloader’s fixed 115200 baud start to a higher transfer speed. Third, the binary data is written to the SPI flash chip in 4 KB sector-sized chunks with CRC verification after each write.
Understanding this pipeline helps when diagnosing failures: if the process stalls at “Connecting…”, the boot mode negotiation failed; if it fails partway through with a CRC error, the flash write is being corrupted; if the board does not run after a successful flash, the binary addresses are wrong.
Method 1: esptool.py — The Universal Command-Line Tool
esptool.py is the open-source Python tool that Arduino IDE, PlatformIO, and ESP-IDF all use internally. You can use it directly for tasks the IDEs do not expose through their GUIs. Install it with pip:
pip install esptool
Verify installation:
esptool.py version
Detect Flash Information
Always start with flash_id to confirm esptool can communicate with the device:
esptool.py --port COM3 flash_id
Expected output:
Connecting...
Detecting chip type... ESP32
Chip is ESP32-D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse
Crystal is 40MHz
Detecting flash size... 4MB
Manufacturer: c8
Device: 4016
The “Manufacturer: c8” and “Device: 4016” are the flash chip’s ID codes. This output confirms: the chip type, flash size, and crystal frequency. If you see “Failed to connect” here, the issue is hardware (cable, driver, boot mode) not the flash itself.
Erase All Flash
esptool.py --port COM3 erase_flash
This erases all 4 MB of flash, including the application, bootloader, partition table, NVS, and any file system. After this the ESP32 will not boot until you flash a new firmware. Use this when: resetting a corrupted device, clearing factory-burned firmware, or ensuring a completely clean starting point.
Flash a Complete Firmware
For a full firmware flash (bootloader + partition table + application):
esptool.py --port COM3 --baud 921600 write_flash
0x1000 bootloader.bin
0x8000 partitions.bin
0xe000 ota_data_initial.bin
0x10000 firmware.bin
The four binaries and their addresses are the standard layout for a default ESP32 OTA partition scheme. You can find these files in the Arduino build directory (enable verbose output to see the exact path) or in the ESP-IDF build/ subdirectory of your project.
Flash Only the Application (Fast Update)
esptool.py --port COM3 --baud 921600 write_flash 0x10000 firmware.bin
Writing only the application binary (at 0x10000) leaves the bootloader, partition table, NVS, and file system intact. This is the fastest way to update firmware without disturbing user data — the same operation Arduino IDE performs when you click Upload.
Merge All Binaries into One File
For distributing firmware to end users or for web-based flashing:
esptool.py merge_bin
--flash_size 4MB
-o merged-firmware.bin
0x1000 bootloader.bin
0x8000 partitions.bin
0xe000 ota_data_initial.bin
0x10000 firmware.bin
The merged file contains padding to maintain correct addresses, so it can be flashed with a single command at address 0x0 using the web-based ESP Tool or Flash Download Tool without specifying individual files.
Method 2: Espressif Flash Download Tool (Windows GUI)
The Flash Download Tool is Espressif’s official GUI application for Windows. Download it from the Espressif website. It is useful for: batch programming multiple boards simultaneously, flashing pre-built binary packages without command-line knowledge, and production programming environments.
Launch the tool and select “ESP32” in the chip dropdown. You will see a grid of file/address pairs. For a standard firmware:
- Check the first row, click “…” to browse to bootloader.bin, enter 0x1000 in the address field
- Check the second row for partitions.bin at 0x8000
- Check the third row for ota_data_initial.bin at 0xe000
- Check the fourth row for firmware.bin at 0x10000
- Set COM port (dropdown on right) and baud rate (921600)
- Click START
The tool supports up to eight simultaneous serial port connections — useful for programming assembly lines where multiple boards are flashed in parallel.
Method 3: Web Serial (Browser-Based Flashing)
Tools built on the Web Serial API allow flashing directly from a web browser on Chrome or Edge without installing any software. Navigate to esp.huhn.me or a similar tool, connect your ESP32 via USB, and select the merged .bin file. The browser handles all communication through the Web Serial API.
This method is ideal for: sharing pre-built firmware with end users who should not need to install development tools; flashing firmware for IoT projects where the end user configures the device via a browser after flashing; and development environments where installing desktop software is restricted.
Recovery: Unbricking an ESP32
An ESP32 that does not boot (crash loop, blank serial output, or completely silent) is almost never truly bricked. The ROM bootloader is stored in the chip’s internal ROM, which cannot be overwritten — it is always available to accept a new firmware download.
Recovery procedure:
- Put the board in download mode: hold BOOT, press EN, release EN, release BOOT
- Verify connection:
esptool.py --port COM3 flash_id— you should see chip information - Erase all flash:
esptool.py --port COM3 erase_flash - Reflash firmware:
esptool.py --port COM3 write_flash 0x1000 bootloader.bin 0x8000 partitions.bin 0x10000 firmware.bin
If step 2 fails (“Failed to connect”), the board is not entering download mode. Check: GPIO 0 is grounded (BOOT button), EN is pulsing (RST button), the USB cable has data lines, and the driver is installed. An oscilloscope on the TX pin can confirm whether the ROM bootloader is outputting any serial data at all.
Flash Read-Back (Backup)
You can read the existing firmware from an ESP32 flash chip before overwriting it — useful for creating backups or extracting firmware from a working device:
esptool.py --port COM3 read_flash 0x0 0x400000 flash_backup.bin
This reads the full 4 MB of flash (0x400000 bytes) into flash_backup.bin. The resulting file is a raw image of the flash at the time of reading. You can restore it with write_flash 0x0 flash_backup.bin to return the device to exactly the state it was in when backed up. Note: if Flash Encryption is enabled on the device, the read-back data is encrypted and cannot be easily interpreted or transferred to another device.