# How esp_flasher Automatically Configures Flash Mode, Frequency, and Size Parameters

> Discover how esp_flasher automatically configures flash mode, frequency, and size by querying the ESP chip and parsing firmware headers saving you manual setup time.

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

---

**esp_flasher queries the ESP chip's flash ID to determine capacity, parses the firmware header to extract mode and frequency, and selects a matching bootloader binary—all without manual intervention unless overridden via CLI flags.**

When flashing ESP8266 and ESP32 devices, correctly configuring the **flash mode** (QIO/DIO), **frequency** (40MHz/80MHz), and **size** parameters is critical for successful boot. The `jason2866/esp_flasher` tool automates this detection through a multi-stage pipeline that inspects hardware IDs, parses binary headers, and conditionally rewrites configuration bytes before transmission.

## Detecting Flash Size from Hardware IDs

When you omit the `--flash_size` argument, esp_flasher interrogates the target chip directly to determine storage capacity. In [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py), the `detect_flash_size()` function calls `esp.flash_id()` to retrieve the raw flash identification number.

```python
def detect_flash_size(esp, args):
    if args.flash_size == 'detect':
        if esp.secure_download_mode:
            raise FatalError("Detecting flash size …")
        flash_id = esp.flash_id()
        size_id = flash_id >> 16
        args.flash_size = DETECTED_FLASH_SIZES.get(size_id)   # → e.g. '4MB'

```

The function right-shifts the flash ID by 16 bits to isolate the size nibble, then maps that value to a human-readable string (e.g., `4MB`, `8MB`) using the `DETECTED_FLASH_SIZES` dictionary. This detected value propagates through the flashing workflow, ensuring the partition table and bootloader align with physical hardware limits.

## Parsing Flash Mode and Frequency from Firmware Headers

Before transmission, esp_flasher inspects the firmware binary to extract the intended **flash mode** and **frequency** settings. The `read_firmware_info()` function in [`esp_flasher/common.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/common.py) performs this analysis by examining the 4-byte ESP image header.

### Inspecting the Safeboot Offset

The tool first seeks to offset `0x10000` to check for a safeboot (factory) image header:

```python
def read_firmware_info(firmware):
    firmware.seek(0x10000)                     # safeboot image?

    header = firmware.read(4)
    magic, _, flash_mode_raw, flash_size_freq = struct.unpack("BBBB", header)
    if magic == esptool.ESPLoader.ESP_IMAGE_MAGIC:
        flash_freq_raw = flash_size_freq & 0x0F
        flash_mode = {0: "qio", 1: "qout", 2: "dio", 3: "dout"}.get(flash_mode_raw)
        flash_freq = {0: "40m", 1: "26m", 2: "20m", 0xF: "80m"}.get(flash_freq_raw)
        flag_factory = True
        return flash_mode, flash_freq, flag_factory
    # … second try at offset 0 for normal images

```

The header bytes decode as follows: byte 2 contains the raw flash mode (0-3 mapping to qio/qout/dio/dout), while byte 3 contains the size/frequency composite. The lower nibble (masked with `0x0F`) represents frequency, and the upper nibble represents size.

### Factory Image Detection

If the magic byte matches `ESP_IMAGE_MAGIC` at offset `0x10000`, the function sets `flag_factory = True`. This boolean flag signals to downstream logic that the image is factory-calibrated and its flash parameters should remain untouched to preserve manufacturer optimizations.

## Rewriting Flash Parameters Before Flashing

For non-factory images—or when CLI overrides are specified—esp_flasher modifies the binary's flash-parameter bytes immediately before transmission. The `_update_image_flash_params()` function in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py) handles this mutation.

```python
def _update_image_flash_params(esp, address, args, image):
    # … sanity checks …

    magic, _, flash_mode, flash_size_freq = struct.unpack("BBBB", image[:4])
    if args.flash_mode != 'keep':
        flash_mode = {'qio': 0, 'qout': 1, 'dio': 2, 'dout': 3}[args.flash_mode]
    flash_freq = flash_size_freq & 0x0F
    if args.flash_freq != 'keep':
        flash_freq = esp.parse_flash_freq_arg(args.flash_freq)
    flash_size = flash_size_freq & 0xF0
    if args.flash_size != 'keep':
        flash_size = esp.parse_flash_size_arg(args.flash_size)

    flash_params = struct.pack(b'BB', flash_mode, flash_size + flash_freq)
    if flash_params != image[2:4]:
        print('Flash params set to 0x%04x' % struct.unpack(">H", flash_params))
        image = image[0:2] + flash_params + image[4:]
    return image

```

The function uses `struct.pack('BB', ...)` to reassemble the two-byte parameter field (bytes 2-3 of the header). If the packed value differs from the original header, the tool prints a diagnostic message and returns the modified image buffer. This ensures the ESP chip receives configuration bytes matching either the auto-detected hardware capabilities or the user's explicit CLI instructions.

## Selecting the Matching Bootloader Binary

After determining the flash mode and frequency, esp_flasher must load a compatible bootloader stub. In [`esp_flasher/common.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/common.py), the `configure_write_flash_args()` function constructs the bootloader filename dynamically:

```python
flash_mode, flash_freq, flag_factory = read_firmware_info(firmware)
…
bootloaderstring = "bootloader_" + flash_mode + "_" + flash_freq + ".elf"

```

This string concatenation produces filenames such as `bootloader_dio_40m.elf` or `bootloader_qio_80m.elf`, ensuring the stub loader matches the target flash configuration. The tool ships with pre-compiled bootloader binaries for all valid mode/frequency combinations in the repository's `bootloader/` directory tree.

## Command-Line Overrides and the 'keep' Option

While esp_flasher automatically configures flash parameters, you retain full manual control via three CLI arguments:

- **`--flash_mode`**: Accepts `qio`, `qout`, `dio`, `dout`, or `keep`
- **`--flash_freq`**: Accepts `40m`, `26m`, `20m`, `80m`, or `keep`
- **`--flash_size`**: Accepts specific sizes (e.g., `4MB`, `8MB`), `detect`, or `keep`

When any parameter is set to `keep`, the tool preserves the value extracted from the firmware header. For example, to force DIO mode while auto-detecting size and preserving the original frequency:

```bash
esp_flasher -p /dev/ttyUSB0 -b 921600 --flash_mode dio firmware.bin

```

To flash a binary verbatim without modifying header bytes:

```bash
esp_flasher -p /dev/ttyUSB0 -b 921600 \
  --flash_mode keep --flash_freq keep --flash_size keep firmware.bin

```

## Summary

- **Flash size** is auto-detected by querying the chip's flash ID via `detect_flash_size()` in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py) unless overridden with `--flash_size`.
- **Flash mode and frequency** are extracted from the firmware header at offset `0x10000` (safeboot) or offset `0` by `read_firmware_info()` in [`esp_flasher/common.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/common.py).
- **Factory images** (detected by magic byte) bypass parameter rewriting to preserve calibration data via the `flag_factory` boolean.
- **Parameter rewriting** occurs in `_update_image_flash_params()`, which uses `struct.pack('BB', ...)` to inject the final 2-byte configuration into the binary header.
- **Bootloader selection** is determined by concatenating the detected mode and frequency into a filename pattern (`bootloader_<mode>_<freq>.elf`) within `configure_write_flash_args()`.

## Frequently Asked Questions

### How does esp_flasher detect the flash size if I don't specify it?

According to the `jason2866/esp_flasher` source code, the tool calls `esp.flash_id()` to retrieve the hardware identification number, then isolates the size nibble by shifting right 16 bits (`flash_id >> 16`). This value maps to a capacity string (e.g., `4MB`, `8MB`) in the `DETECTED_FLASH_SIZES` dictionary defined in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py).

### What happens if the firmware is a factory-calibrated image?

When `read_firmware_info()` in [`esp_flasher/common.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/common.py) detects the `ESP_IMAGE_MAGIC` byte at offset `0x10000`, it returns `flag_factory = True`. This signals the flashing pipeline to skip the `_update_image_flash_params()` rewriting step, preserving the manufacturer's original flash settings to maintain calibration integrity.

### Can I override the flash mode while keeping auto-detected size and frequency?

Yes. Pass `--flash_mode dio` (or `qio`, `qout`, `dout`) while omitting `--flash_size` and `--flash_freq`. The tool will auto-detect the size from hardware, parse the frequency from the firmware header, and only override the mode field in the final 2-byte parameter block.

### Where does esp_flasher get the bootloader binary from?

The tool constructs the filename by concatenating the detected flash mode and frequency (e.g., `bootloader_dio_40m.elf`) in `configure_write_flash_args()` within [`esp_flasher/common.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/common.py). It then loads this pre-compiled ELF file from the `bootloader/` directory tree bundled with the repository, ensuring the stub matches the target flash configuration.