Skip to content

First QEMU Session

QEMU emulation gives you a virtual ESP32 that behaves like a real device over a serial connection. The Espressif QEMU fork emulates the chip’s ROM bootloader, flash memory, eFuses, and GPIO strapping — enough for esptool to connect, flash firmware, and read back results. No USB cable, no devkit, no wiring.

Every tool in mcesptool that accepts a port parameter works with QEMU socket URIs (socket://localhost:PORT) exactly as it works with physical serial ports.

When you start a QEMU instance, mcesptool launches the Espressif QEMU fork with a virtual flash image and exposes a TCP socket that speaks the ESP serial bootloader protocol. The returned socket://localhost:PORT URI is a drop-in replacement for /dev/ttyUSB0 in any tool invocation.

Two boot modes are available:

  • download — the virtual chip starts in serial bootloader mode (GPIO straps held), ready for esptool commands like flash, read, and chip detect
  • normal — the virtual chip boots from its flash image and runs whatever firmware is stored there

QEMU binaries ship as part of the ESP-IDF toolchain. Install them with:

Terminal window
python3 $IDF_PATH/tools/idf_tools.py install qemu-xtensa qemu-riscv32

After installation, activate the tools:

Terminal window
. $IDF_PATH/export.sh

mcesptool detects QEMU availability automatically at startup. If the binaries are on your PATH, QEMU tools are enabled with no additional configuration.

  1. Start a virtual device in download mode

    Create an ESP32 instance with a blank 4 MB flash image, ready for esptool interaction:

    esp_qemu_start(
    chip_type="esp32",
    flash_size_mb=4,
    boot_mode="download"
    )

    The response includes the socket URI and instance ID you will use for subsequent commands:

    {
    "success": true,
    "instance_id": "qemu-1",
    "chip_type": "esp32",
    "tcp_port": 5555,
    "socket_uri": "socket://localhost:5555",
    "flash_image": "/path/to/flash_esp32_5555.bin",
    "boot_mode": "download",
    "pid": 12345
    }
  2. Detect the virtual chip

    Use the socket URI with esp_detect_chip just as you would a USB port:

    esp_detect_chip(
    port="socket://localhost:5555",
    detailed=True
    )

    The virtual device reports as a real ESP32 with MAC address, flash size, and features. This confirms the QEMU instance is accepting esptool connections.

  3. Flash firmware to the virtual device

    Write a firmware binary using the same tool as physical hardware:

    esp_flash_firmware(
    firmware_path="/path/to/my_app.bin",
    port="socket://localhost:5555",
    address="0x10000"
    )

    For a complete firmware stack, use esp_flash_multi:

    esp_flash_multi(
    port="socket://localhost:5555",
    files=[
    {"address": "0x1000", "path": "bootloader.bin"},
    {"address": "0x8000", "path": "partitions.bin"},
    {"address": "0x10000", "path": "my_app.bin"}
    ]
    )
  4. Verify the flash contents

    Confirm the write was successful:

    esp_verify_flash(
    firmware_path="/path/to/my_app.bin",
    port="socket://localhost:5555",
    address="0x10000"
    )
  5. Stop the instance

    Shut down the download-mode instance. The flash image file is preserved on disk:

    esp_qemu_stop(instance_id="qemu-1")
  6. Restart in normal boot mode

    Boot the virtual device from flash to run your firmware. Point flash_image at the same file that was written to in the previous steps:

    esp_qemu_start(
    chip_type="esp32",
    flash_image="/path/to/flash_esp32_5555.bin",
    boot_mode="normal"
    )

    The device now boots from flash and executes the firmware you flashed in step 3.

  7. Clean up

    When you are finished, stop all running instances:

    esp_qemu_stop()

    Calling esp_qemu_stop with no instance_id stops every running QEMU instance. Flash image files remain on disk for future sessions.

ChipArchitectureQEMU MachineStatus
ESP32Xtensaesp32Supported
ESP32-S3Xtensaesp32s3Supported
ESP32-C3RISC-Vesp32c3Supported
ESP32-S2XtensaNot supported

mcesptool can run several QEMU instances in parallel, each on its own TCP port. Use esp_qemu_list to see all instances and their status:

esp_qemu_list()
{
"success": true,
"instances": [
{
"instance_id": "qemu-1",
"chip_type": "esp32",
"tcp_port": 5555,
"socket_uri": "socket://localhost:5555",
"running": true,
"uptime_seconds": 142.3
},
{
"instance_id": "qemu-2",
"chip_type": "esp32c3",
"tcp_port": 5556,
"socket_uri": "socket://localhost:5556",
"running": true,
"uptime_seconds": 87.1
}
],
"total": 2,
"running": 2,
"max_instances": 4
}

For detailed status on a specific instance including PID, flash image path, and boot mode:

esp_qemu_status(instance_id="qemu-1")