How esp_flasher Reads and Displays Chip Properties: MAC Address and CPU Frequency

esp_flasher retrieves MAC addresses and CPU frequencies by communicating directly with the ESP ROM bootloader through a custom own_esptool.py module, then aggregates these properties into human-readable formats via the read_chip_info() helper.

The jason2866/esp_flasher repository provides a Python-based tool for flashing ESP8266 and ESP32 devices. Understanding how esp_flasher reads and displays chip properties like MAC address and CPU frequency requires examining its three-layer architecture: serial detection, ROM communication, and data presentation.

Understanding the Detection Architecture

The process follows a clear pipeline from hardware connection to console output. First, esp_flasher establishes a serial connection and identifies the specific chip family. Next, it queries the ROM bootloader for raw hardware registers and feature flags. Finally, it transforms these low-level values into structured data classes and formatted console output.

This architecture separates concerns between transport layer (own_esptool.py), business logic (common.py), and user interface (__main__.py).

Step 1: Detecting the Chip and Opening Serial Connection

The entry point begins in esp_flasher/__main__.py with the detect_chip() function. This helper instantiates the appropriate ROM loader class based on the detected chip family.

from esp_flasher import common

# Detect creates ESP32ROM, ESP8266ROM, etc. based on chip family

chip = common.detect_chip("/dev/ttyUSB0")

The function returns a chip-specific object (such as ESP32ROM or ESP8266ROM) defined in esp_flasher/own_esptool.py. These classes inherit from base ROM handlers that manage the serial protocol and register access.

Step 2: Reading Raw Properties from the ROM

Once the ROM loader is active, esp_flasher queries specific hardware attributes through low-level register reads and ROM function calls.

Retrieving the MAC Address

The MAC address is read via the read_mac() method implemented in own_esptool.py. For ESP8266, this method resides at lines 1507-1520; for ESP32-family chips, it appears around lines 1815-1830.

The method reads OTP registers (ESP_OTP_MAC0, ESP_OTP_MAC1, ESP_OTP_MAC3) to extract the Organizationally Unique Identifier (OUI) and device-specific bytes. It returns a 6-byte tuple representing the hardware MAC.


# Inside ESP32ROM or ESP8266ROM class

mac_bytes = chip.read_mac()  # Returns (0x30, 0xAE, 0xA4, 0x12, 0x34, 0x56)

Determining CPU Frequency

The CPU frequency is not stored in a single register. Instead, esp_flasher queries the ROM for a list of chip features via get_chip_features(). For ESP32ROM, this method appears around line 1482 in own_esptool.py.

The ROM returns feature strings such as "80MHz", "160MHz", or "240MHz". The read_chip_info() function in common.py parses this list against an ordered frequency hierarchy:


# From esp_flasher/common.py

FREQUENCIES = ("400MHz", "240MHz", "160MHz", "120MHz", "80MHz")

The function selects the first matching frequency from this tuple, ensuring the highest supported frequency is reported as the maximum CPU frequency.

Step 3: Aggregating and Displaying the Data

The read_chip_info() function in esp_flasher/common.py (lines 29-64) serves as the aggregation layer. It accepts a chip object and returns a structured data class (ESP32ChipInfo or ESP8266ChipInfo).

from esp_flasher.common import read_chip_info

info = read_chip_info(chip)
print(f"MAC: {info.mac}")                    # Formatted as 30:AE:A4:12:34:56

print(f"CPU Frequency: {info.cpu_frequency}") # e.g., 240MHz

The function converts the raw MAC tuple into a colon-separated hexadecimal string using ":".join(f"{x:02X}" for x in mac). It bundles the parsed CPU frequency along with other attributes like core count, Bluetooth support, and flash configuration into the info object.

The CLI entry point in esp_flasher/__main__.py (lines 33-48) simply prints these fields:

print(f" - MAC Address: {info.mac}")
print(f" - Max CPU Frequency: {info.cpu_frequency}")

Practical Code Examples

Programmatic Usage in Python

You can integrate esp_flasher's chip detection into your own Python scripts to retrieve hardware properties before flashing:

from esp_flasher import common
from esp_flasher.common import read_chip_info

def get_hardware_info(port="/dev/ttyUSB0"):
    # Step 1: Detect chip type and open connection

    chip = common.detect_chip(port)
    
    # Step 2: Read aggregated properties

    info = read_chip_info(chip)
    
    # Step 3: Access structured data

    return {
        "mac": info.mac,
        "cpu_frequency": info.cpu_frequency,
        "cores": info.num_cores,
        "chip_model": info.chip_model
    }

# Usage

properties = get_hardware_info()
print(f"Device MAC: {properties['mac']}")

Command-Line Interface

For manual inspection or scripting in shell environments, use the CLI to display chip properties before flashing firmware:


# Display chip information without flashing

$ python -m esp_flasher -p /dev/ttyUSB0 --chip-info

Chip Info:
 - Chip Family: ESP32
 - Chip Model: ESP32-C6 (revision v1.0)
 - Number of Cores: 2
 - Max CPU Frequency: 240MHz
 - Has Bluetooth: YES
 - MAC Address: 30:AE:A4:12:34:56

Key Source Files and Their Roles

File Role
esp_flasher/own_esptool.py Low-level ROM loader implementation; defines read_mac() and get_chip_features() for each chip family (ESP8266, ESP32, etc.).
esp_flasher/common.py High-level aggregation layer containing read_chip_info() (lines 29-64), detect_chip(), and data class definitions (ESP32ChipInfo, ESP8266ChipInfo).
esp_flasher/__main__.py Command-line entry point; orchestrates detection and prints formatted output using the aggregated chip information.

Summary

  • esp_flasher queries chip properties through a custom own_esptool.py module that communicates directly with the ESP ROM bootloader.
  • The MAC address is read from OTP registers via read_mac() methods specific to each chip family (ESP8266 at lines 1507-1520, ESP32 at lines 1815-1830).
  • CPU frequency is determined by parsing feature strings from get_chip_features() against a prioritized frequency tuple in common.py.
  • The read_chip_info() function in common.py (lines 29-64) aggregates raw data into structured objects and formats the MAC as a colon-separated hexadecimal string.
  • Both programmatic Python APIs and CLI interfaces expose these properties for integration into flashing workflows.

Frequently Asked Questions

How does esp_flasher determine which chip family is connected?

The detect_chip() function in esp_flasher/common.py establishes a serial connection and attempts to synchronize with the ROM bootloader. Based on the response and magic numbers returned by the chip, it instantiates the appropriate ROM class (such as ESP32ROM, ESP8266ROM, or ESP32C3ROM) from own_esptool.py to handle family-specific register layouts and commands.

Why does CPU frequency detection rely on feature strings instead of a direct register read?

The ESP ROM bootloader does not expose a single register containing the CPU frequency value. Instead, it provides a list of supported capabilities via get_chip_features(), which includes strings like "240MHz" or "80MHz". The read_chip_info() function parses this list and matches it against an ordered tuple of frequencies to determine the maximum supported clock speed, ensuring compatibility across different ESP32 variants and ESP8266 devices.

Can I retrieve chip properties without flashing firmware using esp_flasher?

Yes, you can use the Python API to query hardware information independently of the flashing process. Import detect_chip and read_chip_info from esp_flasher.common, open a connection to the serial port, and call these functions to retrieve the MAC address, CPU frequency, core count, and other properties without writing any binary to the device flash.

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 →