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

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 and esp_flasher/own_esptool.py.

High-Level Detection Wrapper

The entry point for chip identification resides in 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:


# 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.

Low-Level Hardware Probing

The ESPLoader.detect_chip() static method in 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.


# 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:

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:

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:

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 exposes these capabilities:


# 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 handles user overrides before delegating to the low-level probe in 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 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 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 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 and the entry point esp_flasher/__main__.py.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →