# Complete ESP Flashing Flow: From Chip Detection to Hard Reset in esp_flasher

> Discover the complete ESP flashing flow in esp_flasher. Learn how to auto-detect your ESP chip, upload a stub, write firmware, and perform a hard reset to boot new firmware.

- Repository: [Jason2866/esp_flasher](https://github.com/jason2866/esp_flasher)
- Tags: how-to-guide
- Published: 2026-03-04

---

**The esp_flasher tool executes a 14-step sequence that auto-detects the ESP chip, uploads a flashing stub to RAM, writes the firmware image, and concludes with a hard reset via RTS/EN pin toggling to boot the new firmware.**

The `jason2866/esp_flasher` repository provides a streamlined Python utility for programming ESP32 and ESP8266 microcontrollers. Understanding the complete flashing flow from chip detection to hard reset helps developers diagnose connection failures and optimize upload speeds. This guide traces the exact execution path through the source code, from CLI parsing to the final hardware reset.

## CLI Argument Parsing and Port Selection

The process begins in [`esp_flasher/__main__.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/__main__.py) where the `parse_args()` function processes command-line flags including `--port`, chip-family selectors (`--esp32*`), and baud-rate options.

If the `--port` argument is omitted, the `select_port()` function auto-detects a single available USB/COM device. This ensures the tool can run without manual port specification when only one ESP device is connected.

## Chip Detection and Information Retrieval

The `detect_chip()` function in [`esp_flasher/common.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/common.py) establishes the initial connection. It either forces a specific ROM class via the `--esp32*` flags or queries the ESP's ROM directly through `ESPLoader.detect_chip` to identify the chip family.

After detection, the tool opens a **no-reset** connection to keep the device in bootloader mode. The `read_chip_info()` function then retrieves the MAC address, model name, core count, and flash capabilities from the chip.

## Stub Upload and Baud Rate Configuration

With the chip identified, `chip_run_stub()` in [`common.py`](https://github.com/jason2866/esp_flasher/blob/main/common.py) calls `chip.run_stub()` to upload the flashing stub into RAM. This stub provides higher-level flash commands and faster data transfer than the ROM bootloader.

If the user specified a custom upload speed via `--upload-baud-rate`, the `change_baud()` function in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py) attempts to raise the serial rate. If the chip rejects the requested rate, the connection automatically rebuilds at the default **115200 baud**.

## Flash Size Detection and Argument Preparation

The `detect_flash_size()` function reads the flash ID via the stub and maps it to a human-readable capacity. Meanwhile, `configure_write_flash_args()` in [`common.py`](https://github.com/jason2866/esp_flasher/blob/main/common.py) builds a `MockEsptoolArgs` object containing the binary data and flash-layout parameters.

For ESP32 targets, if the input is an ELF file rather than a binary, `esptool.elf2image()` converts it to flashable bin format before proceeding, as implemented in the `run_esp_flasher()` function in [`__main__.py`](https://github.com/jason2866/esp_flasher/blob/main/__main__.py).

## Flash Parameter Setup and Erasure

Before writing, `flash_set_parameters()` in [`own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/own_esptool.py) configures the stub with the correct flash size, mode, and frequency settings. These parameters ensure subsequent write commands use the proper timing and layout.

Unless the `--no-erase` flag is present, `erase_flash()` issues the `ESP_ERASE_FLASH` command through the stub to clear the chip's memory.

## Firmware Write and Verification

The `write_flash()` function in [`own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/own_esptool.py) streams the prepared binary blocks to the device. This function includes automatic retry logic for failed blocks, ensuring reliable transmission even over unstable serial connections.

## Hard Reset and Post-Flash Cleanup

After the write completes, `hard_reset()` in [`own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/own_esptool.py) toggles the RTS pin to pulse the EN line low then high. This sequence resets the ESP into normal execution mode, launching the newly flashed firmware.

If a high baud rate was used for the upload, the port returns to **115200 baud**, flushes remaining data, and optionally streams device logs via `show_logs()` in the final block of `run_esp_flasher()`.

## Practical Usage Examples

Flash a binary to an ESP32 with auto-detection and default erasing:

```bash
esp_flasher my_firmware.bin

```

Flash with a custom upload speed and automatic fallback:

```bash
esp_flasher --upload-baud-rate 3000000 my_firmware.bin

```

Flash an ELF bootloader for ESP32-S3 with partition tables:

```bash
esp_flasher \
    --esp32s3 \
    --bootloader bootloader.bin \
    --partitions partitions.csv \
    --otadata otadata.bin \
    --input bootloader.elf \
    factory_image.bin

```

## Summary

- The flashing flow starts with CLI parsing in [`__main__.py`](https://github.com/jason2866/esp_flasher/blob/main/__main__.py) and auto-detects the serial port via `select_port()` if not specified.
- `detect_chip()` in [`common.py`](https://github.com/jason2866/esp_flasher/blob/main/common.py) identifies the ESP family and opens a no-reset connection to the bootloader.
- `chip_run_stub()` uploads a RAM stub that enables high-speed flashing commands beyond the ROM's capabilities.
- The tool automatically detects flash size, converts ELF files to binary when necessary, and configures flash parameters via `flash_set_parameters()`.
- `erase_flash()` clears memory (unless skipped with `--no-erase`), followed by `write_flash()` streaming data with automatic retry logic.
- `hard_reset()` toggles the EN pin to boot the new firmware, after which the port returns to 115200 baud for log streaming.

## Frequently Asked Questions

### How does esp_flasher detect which ESP chip model is connected?

The `detect_chip()` function in [`esp_flasher/common.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/common.py) either uses the forced ROM class from `--esp32*` flags or queries the chip's ROM directly via `ESPLoader.detect_chip`. It maintains a no-reset connection to keep the device in bootloader mode during this process.

### What happens if the requested upload baud rate is not supported?

If `change_baud()` in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py) fails to negotiate the higher speed, the connection automatically rebuilds at the default **115200 baud** rate. The flashing continues at this safe speed rather than failing entirely.

### Why does esp_flasher upload a stub to RAM before flashing?

The `chip_run_stub()` function uploads a small program into the ESP's RAM because the ROM bootloader has limited functionality and slower transfer rates. The stub provides optimized flash commands and handles high-speed serial communication more efficiently than the built-in ROM routines.

### What triggers the hard reset at the end of the flashing process?

The `hard_reset()` function in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py) toggles the RTS control line, which pulses the EN (enable) pin low then high. This hardware reset sequence exits the bootloader and starts execution of the newly written firmware from address 0x0.