ESP Serial Flasher is a portable C library for programming and interacting with Espressif SoCs from other host microcontrollers.
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.
- 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.
- 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)
| 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 | 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 headers: include/esp_loader.h, include/esp_loader_io.h, include/esp_loader_error.h, and include/esp_loader_efuse.h define the stable public API of this library.
- Examples and helpers: examples/common/ contains helper utilities used by the examples; not part of the library API, but can be used as a reference.
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.
To use ESP Serial Flasher, you need:
Different host platforms require specific setup procedures:
- ESP32 series: Works with ESP-IDF v5.5 or later
- STM32: Requires STM32 HAL libraries and ARM toolchain
- Zephyr: Integrates as Zephyr module with specific Kconfig options
- Raspberry Pi Pico: Uses Pico SDK
- Linux: For SBCs (Raspberry Pi, BeagleBone, …) and PCs using libgpiod or DTR/RTS
- Custom platforms: ESP Serial Flasher can be used as a git submodule with your own custom platform implementation
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.
#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;For complete implementation examples, see the examples directory:
- ESP32 Example - ESP32 family as host
- STM32 Example - STM32 setup guide
- Linux Example - Linux host (PC or SBC such as Raspberry Pi)
- Zephyr Example - Zephyr OS integration
- Raspberry Pi Pico Example - RP2040 or RP2350 as host
- ESF Demo - End-to-end demo flashing ESP targets from an embedded host (M5Stack Dial) over USB CDC ACM; includes SD card image selection and on-device progress UI
- 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
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).
The most common configuration options:
# Set custom retry count
cmake -DSERIAL_FLASHER_WRITE_BLOCK_RETRIES=5 ..For complete configuration reference, see Configuration Documentation.
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.
-ffunction-sections -fdata-sections # compiler
-Wl,--gc-sections # linkerThese 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.
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 |
- Skip the stub — call only
esp_loader_connect()and neveresp_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 publicesp_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_SDIOadditionally 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_MAClinks 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.cfiles, or thesrc/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);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.
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.
If you want to add support for a new host platform, see Supporting New Host Platforms Guide.
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.
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 = trueinesp_loader_flash_cfg_tfor 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.