# How esp_flasher Auto-Detects the Connected ESP Chip Model and Family

> Discover how esp_flasher auto-detects your ESP chip model. It uses a smart two-stage routine, reading chip IDs and matching magic values for seamless identification.

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

---

**esp_flasher determines the attached ESP chip by executing a two-stage detection routine that first attempts to read the chip ID via the Security-Info command, then falls back to magic-value register matching for legacy devices.**

The jason2866/esp_flasher project implements a robust auto-detection mechanism that eliminates the need for manual chip specification when flashing ESP8266, ESP32, and newer C-series devices. This Python-based tool embeds a customized version of esptool to probe hardware identifiers through a cascading detection strategy implemented across [`esp_flasher/common.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/common.py) and [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py).

## High-Level Detection Wrapper

The entry point for chip identification resides in [`esp_flasher/common.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/common.py), where the `detect_chip()` function serves as the primary interface between user input and hardware probing.

### User-Forced Chip Selection

When operators specify a chip family via command-line flags such as `--esp32`, `--esp8266`, `--esp32s2`, or `--esp32c3`, the wrapper bypasses auto-detection entirely. It instantiates the corresponding ROM loader class directly from the bundled esptool implementation without executing the probe sequence.

### Delegation to ESPLoader

If no force flag is provided, the function delegates to the low-level detector:

```python

# esp_flasher/common.py – detect_chip() (lines 388-429)

if force_esp8266 or force_esp32 or force_esp32s2 or force_esp32s3 or force_esp32c3 or force_esp32c6:
    # Forced path – instantiate specific ROM class immediately

else:
    chip = esptool.ESPLoader.detect_chip(port)  # Auto-detection path

```

This call triggers the hardware probing sequence implemented in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py).

## Low-Level Hardware Probing

The `ESPLoader.detect_chip()` static method in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py) (lines 69–124) executes a two-step probe sequence to identify both modern and legacy ESP silicon.

### Step 1: Security-Info Command for C-Series

For newer ESP32-C3 and ESP32-C6 family devices, the routine sends the `get_chip_id()` command, which retrieves a 32-bit hardware identifier via the Security-Info protocol.

```python

# esp_flasher/own_esptool.py – detect_chip()

chip_id = detect_port.get_chip_id()
chip_name = chip_map.get(chip_id)
if chip_name:
    # Instantiate appropriate ROM class (e.g., ESP32C3ROM)

```

The `chip_map` dictionary dynamically maps `IMAGE_CHIP_ID` constants to their respective chip names and ROM loader classes, enabling direct instantiation without register probing.

### Step 2: Magic-Value Register Fallback

If the Security-Info command fails—indicating an older chip that lacks the command—the detector reads the magic-value register at `CHIP_DETECT_MAGIC_REG_ADDR`:

```python
chip_magic_value = detect_port.read_reg(ESPLoader.CHIP_DETECT_MAGIC_REG_ADDR)
for cls in [ESP8266ROM, ESP32ROM, ESP32S2ROM]:
    if chip_magic_value in cls.CHIP_DETECT_MAGIC_VALUE:
        # Instantiate legacy ROM class based on magic constant match

```

This register-based matching identifies ESP8266, ESP32, and ESP32-S2 devices through hardware-specific magic constants defined in their respective ROM classes.

### Stub Loader Integration

During both detection steps, the routine checks `detect_port.sync_stub_detected` to determine if a stub loader has already been uploaded. When detected, the tool wraps the ROM class instance with the stub class, enabling accelerated communication for subsequent flashing operations.

## Practical Implementation Examples

### Detecting Chips Programmatically

To leverage auto-detection in Python applications:

```python
from esp_flasher.common import detect_chip

# Auto-detect on default serial port

chip = detect_chip(port=None)
print(f"Detected: {chip.CHIP_NAME}")  # Output: "ESP32-C3" or "ESP32", etc.

```

### Bypassing Detection with Force Flags

For scenarios requiring explicit chip selection to skip the probe delay:

```python
from esp_flasher.common import detect_chip

# Force ESP32-S2 identification without probing

chip = detect_chip(port=None, force_esp32s2=True)
print(chip.CHIP_NAME)  # Guaranteed: "ESP32-S2"

```

### Command-Line Interface Usage

The CLI entry point in [`esp_flasher/__main__.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/__main__.py) exposes these capabilities:

```bash

# Automatic detection and flash

esp_flasher --port /dev/ttyUSB0 write_flash 0x1000 firmware.bin

# Force specific chip family when auto-detection fails

esp_flasher --port /dev/ttyUSB0 --esp32c3 write_flash 0x1000 firmware.bin

```

## Summary

- **Two-stage detection**: The system first tries the Security-Info command for C-series chips, then falls back to magic-value register reading for legacy ESP8266/ESP32/ESP32-S2 devices.
- **High-level wrapper**: `detect_chip()` in [`esp_flasher/common.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/common.py) handles user overrides before delegating to the low-level probe in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py).
- **Low-level probing**: `ESPLoader.detect_chip()` implements the actual hardware interrogation using `get_chip_id()` for modern chips and `CHIP_DETECT_MAGIC_REG_ADDR` for legacy ones.
- **Chip mapping**: Modern devices use `chip_map` lookups against `IMAGE_CHIP_ID` constants, while legacy chips match against `CHIP_DETECT_MAGIC_VALUE` tables in their ROM classes.
- **Stub awareness**: The detector checks `sync_stub_detected` to wrap ROM classes with stub loaders when available, optimizing flash performance.

## Frequently Asked Questions

### What happens if auto-detection fails to identify the chip?

If neither the Security-Info command nor the magic-value register yields a match, `ESPLoader.detect_chip()` raises a `FatalError`, requiring the user to specify the chip manually using flags like `--esp32` or `--esp8266` to force a specific ROM class.

### How does esp_flasher distinguish between ESP32-C3 and ESP32-C6?

Both chips respond to the `get_chip_id()` command with unique `IMAGE_CHIP_ID` values. The `chip_map` dictionary in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py) translates these 32-bit hardware identifiers into the appropriate `ESP32C3ROM` or `ESP32C6ROM` class instances.

### Can I force a specific chip family to skip the detection delay?

Yes. Passing boolean flags such as `force_esp32=True` or `force_esp8266=True` to `detect_chip()` in [`esp_flasher/common.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/common.py) immediately instantiates the specified ROM class without executing the probe sequence, reducing connection latency when the chip family is already known.

### Where does the actual detection logic reside versus the CLI interface?

The hardware detection algorithm lives in [`esp_flasher/own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/own_esptool.py) within the `ESPLoader.detect_chip()` method (lines 69–124), while the user-facing orchestration—including command-line argument parsing and force-flag handling—resides in [`esp_flasher/common.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/common.py) and the entry point [`esp_flasher/__main__.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/__main__.py).