Skip to content

Repository files navigation

ESP Serial Flasher

pre-commit.ci status Component Registry License

ESP Serial Flasher is a portable C library for programming and interacting with Espressif SoCs from other host microcontrollers.

Overview

This library enables you to program Espressif SoCs from various host platforms using different communication interfaces. It provides a unified API that abstracts the underlying communication protocol, making it easy to integrate ESP device programming into your projects. In this context, the host (flashing/programming device running this library) controls the target (the ESP-series SoC being programmed). It serves a similar purpose to esptool, but is designed for embedded hosts without a PC or Python runtime or on less powerful single board computers.

  • Connection and identification: Connect to targets, autodetect chip family, read MAC address, retrieve security info.
  • Flash operations: Write, read, erase, detect flash size, and verify data integrity via MD5.
  • RAM download and execution: Load binaries to RAM and run them.
  • Registers and control: Read/write registers, change transmission rate, reset the target.

Supported Communication Interfaces

  • UART - Universal asynchronous communication
  • USB CDC ACM - USB virtual serial port
  • SPI - Serial Peripheral Interface (RAM download only)
  • SDIO - Secure Digital Input/Output (experimental)

Note

SDIO interface is experimental and currently supports only ESP32-C5 and ESP32-C6 as target. It uses esp-flasher-stub and supports the full stub command protocol over SDIO.

Supported Host Platforms (device running this library and performing flashing)

  • STM32 microcontrollers
  • ESP32 series microcontrollers
  • Zephyr OS compatible devices
  • Raspberry Pi Pico (RP2040) and Raspberry Pi Pico 2 (RP2350)
  • Linux (via UART or USB serial port, with optional GPIO control)

Supported Target Devices (ESP device being flashed)

Target UART SPI SDIO USB CDC ACM
ESP8266
ESP32 🚧
ESP32-S2
ESP32-S3
ESP32-C2
ESP32-C3
ESP32-H2
ESP32-H21 🚧
ESP32-C6
ESP32-C5
ESP32-P4 🚧
ESP32-C61 🚧
ESP32-S31

Legend: ✅ Supported | ❌ Not supported | 🚧 Under development

Feature Support by Interface

Feature UART USB CDC ACM SPI SDIO
Connect (ROM bootloader)
Connect with stub
Secure Download Mode
Flash write
Compressed flash write (deflate) 🔶 🔶
Flash read (fast) 🔶 🔶
Flash read (slow)
Flash erase (chip)
Flash erase (region)
Flash MD5 verify
RAM download
Get security info
Change baud / clock rate
eFuse

Legend: ✅ Supported | ❌ Not supported | 🔶 Requires connecting with stub (esp_loader_connect_with_stub())

eFuse read and burn are supported on all target devices above except ESP8266 and ESP32-S31, which is still under development. See the eFuse Guide for the per-chip capability matrix and the staged-write model.

Tip

Connecting with stub (esp_loader_connect_with_stub()) is recommended over the plain ROM bootloader connection when flash size on the host is not a limiting constraint. The stub unlocks faster flashing speeds (higher baud rates), flash sizes larger than 2 MB, compressed writes (deflate), and fast flash read. SDIO connects through the stub automatically. All supported chips now have a bundled stub. See Flash Size Footprint for the flash overhead introduced by the bundled stubs.

Public API

Versioning and Compatibility

This library follows Semantic Versioning. The public API defined in include/ folder maintains backward compatibility within the same major version — no breaking changes are introduced in minor or patch releases.

Port implementations (under port/) are reference implementations that depend on the SDK of the target platform. They are tested against the specific SDK versions listed in the Platform Setup Guide and do not carry their own semver guarantee. Ports are maintained on a best-effort basis — breaking changes to them are minimized, but are sometimes necessary to stay up to date with upstream platform SDKs. When such an update is needed, it is released as a minor or patch version of this library.

Important

Upgrading from v1? v2 introduces breaking changes to the public API and the port layer. See the Migration Guide for a complete list of changes and step-by-step upgrade instructions.

Getting Started

Prerequisites

To use ESP Serial Flasher, you need:

  • CMake 3.22 or later - Build system
  • Git - For cloning the repository with submodules

Platform Setup

Different host platforms require specific setup procedures:

For detailed setup instructions, see Platform Setup Guide.

For implementing custom platform support, see Supporting New Platforms Guide, particularly the sections on using ESP Serial Flasher as an external library and implementation steps.

Basic Usage

#include "esp_loader.h"
#include "esp32_port.h"  // replace with the port header for your platform

esp_loader_error_t err;

// 1. Fill in the port struct with hardware parameters
esp32_port_t port = {
    .port.ops          = &esp32_uart_ops,
    .baud_rate         = 115200,
    .uart_port         = UART_NUM_1,
    .uart_rx_pin       = GPIO_NUM_5,
    .uart_tx_pin       = GPIO_NUM_4,
    .reset_pin = GPIO_NUM_25,
    .boot_pin  = GPIO_NUM_26,
};

// 2. Initialize the loader context — binds the protocol and port vtable
esp_loader_t loader;
err = esp_loader_init_serial(&loader, &port.port);
if (err != ESP_LOADER_SUCCESS) return err;

// 3. Connect to the target chip
esp_loader_connect_args_t connect_args = ESP_LOADER_CONNECT_DEFAULT();
err = esp_loader_connect(&loader, &connect_args);
if (err != ESP_LOADER_SUCCESS) return err;

// 4. Flash a binary (example: 64KB at 0x10000)
// Variable holding your binary image. Typical sources:
// - Read from storage (SD card, filesystem, flash)
// - Received over a link (UART/SPI/USB/Wi-Fi) into a RAM buffer
// - Compiled-in C array generated from a .bin
const uint8_t *data = /* pointer to your firmware image buffer */;

esp_loader_flash_cfg_t flash_cfg = {
    .offset     = 0x10000,
    .image_size = 65536,
    .block_size = 4096,
};
err = esp_loader_flash_start(&loader, &flash_cfg);
if (err != ESP_LOADER_SUCCESS) return err;

size_t offset = 0;
while (offset < flash_cfg.image_size) {
    size_t chunk = MIN(flash_cfg.block_size, flash_cfg.image_size - offset);
    err = esp_loader_flash_write(&loader, &flash_cfg, (void *)(data + offset), chunk);
    if (err != ESP_LOADER_SUCCESS) return err;
    offset += chunk;
}

err = esp_loader_flash_finish(&loader, &flash_cfg);
if (err != ESP_LOADER_SUCCESS) return err;

Examples

For complete implementation examples, see the examples directory:

Educational Resources

  • esptool documentation - Contains most of the information on how the communication with the chip works, what is and is not possible etc.
  • YouTube Tutorial published 9th September 2024 - Comprehensive guide covering library usage, internals, and custom port implementation

Configuration

ESP Serial Flasher provides several configuration options to customize its behavior. These options are set as CMake cache variables (plain CMake builds) or Kconfig options (ESP-IDF / Zephyr builds).

Basic Configuration

The most common configuration options:

# Set custom retry count
cmake -DSERIAL_FLASHER_WRITE_BLOCK_RETRIES=5 ..

For complete configuration reference, see Configuration Documentation.

Flash Size Footprint

The library carries data for every supported chip — bundled stub binaries, eFuse field tables — but it is written so that you only pay for what you call. Data is split per chip and per feature into separate translation units and sections, so the linker keeps only what your application actually references.

Build With Section Garbage Collection

-ffunction-sections -fdata-sections      # compiler
-Wl,--gc-sections                        # linker

These flags are recommended for every build. ESP-IDF and Zephyr enable them by default; for plain CMake builds add them yourself. Without them the linker's granularity drops to whole object files, and unused per-chip data can be linked into your image even though nothing calls it.

What Costs What

Rough orders of magnitude, to decide what is worth caring about. These move with upstream chip support, so measure your own build (size -A, or nm --print-size --size-sort on the final ELF) rather than budgeting against the numbers here:

Component Order of magnitude Pulled in by
Library code a few KB of .text always
All bundled flasher stubs ~87 KB of rodata esp_loader_connect_with_stub()
Flasher stub, per chip ~6–12 KiB of rodata a provider returning one public esp_stub_<chip> descriptor
SDIO stubs ~20 KB of rodata esp_loader_init_sdio() — the ESP32-C5 and ESP32-C6 stubs
eFuse field table, per chip a few KB of rodata referencing a named field of that chip
eFuse field tables roughly stub-sized in total esp_loader_efuse_get_field_info() — every chip's table at once

Keeping It Small

  • Skip the stub — call only esp_loader_connect() and never esp_loader_connect_with_stub(). The bundled provider lives in a separate object, so a static-library link does not pull it or the stub data into the application. Note that the stub is otherwise recommended: it unlocks higher baud rates, flash larger than 2 MB, compressed writes, and fast flash read.
  • Use one bundled stub — call esp_loader_connect_with_stub_provider() with a provider that returns the required public esp_stub_<chip> descriptor. Linker garbage collection can then retain only that chip's stub object.
  • Load a custom stub from external storage — call esp_loader_connect_with_stub_provider() with a provider that loads and returns the stub for the detected chip. Because this API references no bundled stub symbols, the built-in stub data is not linked.
  • Do not initialise SDIO if you do not use it — the SDIO path references the ESP32-C5 and ESP32-C6 stubs directly, so those are linked once esp_loader_init_sdio() is reachable. (On ESP-IDF, CONFIG_SERIAL_FLASHER_PORT_SDIO additionally controls whether the ESP32 SDIO port is compiled; it does not gate the stub data, and it does not exist in plain CMake builds.)
  • Reference eFuse fields for the chips you actually talk to — each chip's field table is its own translation unit, so using e.g. ESP32C6_EFUSE_MAC links the ESP32-C6 table only. The raw-read, bit, key, and burn paths address eFuses by block and bit position and reference no field table at all.
  • Avoid esp_loader_efuse_get_field_info() unless you need it — resolving a chip and revision to its full field metadata names every chip's table and links all of them. It lives in its own translation unit so applications that never call it pay nothing.
  • If you cannot use --gc-sections — exclude what you do not need at the CMake level: drop the stub .c files, or the src/efuse/<chip>/ tables, from the sources list when integrating the library as a subdirectory or submodule.

For example, a provider can select one bundled stub without pulling in the others:

#include "esp_loader_stubs.h"

static const esp_stub_t *esp32s3_stub(esp_loader_t *loader, target_chip_t chip, void *ctx)
{
    (void)loader;
    (void)ctx;
    return chip == ESP32S3_CHIP ? &esp_stub_esp32s3 : NULL;
}

esp_loader_connect_args_t args = ESP_LOADER_CONNECT_DEFAULT();
esp_loader_connect_with_stub_provider(&loader, &args, esp32s3_stub, NULL);

Hardware Connections

Each communication interface has specific hardware connection requirements and pin configurations. For complete wiring diagrams, pin assignments, and interface-specific setup instructions, see Hardware Connections Guide.

Contributing

We welcome contributions! Before starting work on new features or significant changes, please open an issue to discuss your proposal.

For detailed contribution guidelines, see CONTRIBUTING.md.

Adding New Platform Support

If you want to add support for a new host platform, see Supporting New Host Platforms Guide.

License

This project is licensed under the Apache 2.0 License - see the LICENSE file for details.

This repository includes precompiled stub binaries from esp-flasher-stub, which are licensed under the Apache 2.0 OR MIT license.

Known Limitations

The following limitations are currently known:

  • Binary image size must be known before flashing
  • ESP8266 targets do not support the MD5 verify command without stub; set skip_verify = true in esp_loader_flash_cfg_t for ESP8266 targets
  • SPI interface only supports RAM download operations
  • SDIO interface is experimental with limited platform support
  • Only one target can be flashed at a time

For additional limitations and current issues, see the GitHub Issues page.

About

Library for flashing Espressif SoCs from other MCUs.

Resources

Contributing

Stars

560 stars

Watchers

21 watching

Forks

Releases

Packages

Used by

Contributors

Languages