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

> Learn how esp_flasher reads MAC address and CPU frequency by interacting with the ESP ROM bootloader using a custom esptool.py module.

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

---

**esp_flasher retrieves MAC addresses and CPU frequencies by communicating directly with the ESP ROM bootloader through a custom [`own_esptool.py`](https://github.com/jason2866/esp_flasher/blob/main/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`](https://github.com/jason2866/esp_flasher/blob/main/own_esptool.py)), business logic ([`common.py`](https://github.com/jason2866/esp_flasher/blob/main/common.py)), and user interface ([`__main__.py`](https://github.com/jason2866/esp_flasher/blob/main/__main__.py)).

## Step 1: Detecting the Chip and Opening Serial Connection

The entry point begins in [`esp_flasher/__main__.py`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/__main__.py) with the `detect_chip()` function. This helper instantiates the appropriate ROM loader class based on the detected chip family.

```python
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`](https://github.com/jason2866/esp_flasher/blob/main/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`](https://github.com/jason2866/esp_flasher/blob/main/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.

```python

# 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`](https://github.com/jason2866/esp_flasher/blob/main/own_esptool.py).

The ROM returns feature strings such as `"80MHz"`, `"160MHz"`, or `"240MHz"`. The `read_chip_info()` function in [`common.py`](https://github.com/jason2866/esp_flasher/blob/main/common.py) parses this list against an ordered frequency hierarchy:

```python

# 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`](https://github.com/jason2866/esp_flasher/blob/main/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`).

```python
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`](https://github.com/jason2866/esp_flasher/blob/main/esp_flasher/__main__.py) (lines 33-48) simply prints these fields:

```python
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:

```python
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:

```bash

# 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`](https://github.com/jason2866/esp_flasher/blob/main/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`](https://github.com/jason2866/esp_flasher/blob/main/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`](https://github.com/jason2866/esp_flasher/blob/main/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`](https://github.com/jason2866/esp_flasher/blob/main/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`](https://github.com/jason2866/esp_flasher/blob/main/common.py).
- The `read_chip_info()` function in [`common.py`](https://github.com/jason2866/esp_flasher/blob/main/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`](https://github.com/jason2866/esp_flasher/blob/main/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`](https://github.com/jason2866/esp_flasher/blob/main/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.