What Is the Stub Chip Loader and Why Does ESP Flasher Use It for High Baud Rates?

The stub chip loader is a small firmware binary that ESP Flasher uploads into RAM to replace the ROM bootloader, enabling UART baud rates up to 921600 and beyond while overcoming the fixed 115200 limitation of the built-in bootloader.

The jason2866/esp_flasher project relies on this technique to dramatically reduce firmware upload times on ESP32 and other ESP chips. By temporarily replacing the chip's stock bootloader with a lightweight stub, the tool gains access to advanced commands like ESP_CHANGE_BAUDRATE that the ROM bootloader does not support.

What Is the Stub Chip Loader?

The stub chip loader is a minimal firmware image originally generated from stub_loader.c in the upstream esptool project. In ESP Flasher, this binary is embedded as the STUB_CODE constant inside esp_flasher/own_esptool.py and copied into the chip's RAM at runtime.

Key characteristics of the stub loader include:

  • RAM Execution: Unlike the ROM bootloader stored in read-only memory, the stub is loaded into volatile RAM using the mem_block command and activated via run_stub.
  • Extended Command Set: The stub implements its own protocol commands, including ESP_CHANGE_BAUDRATE, accelerated flash-write routines, and flash-erase helpers.
  • Identity Flag: When ESP Flasher instantiates a stub loader, it sets the class attribute IS_STUB = True. For example, the ESP32StubLoader class inherits from ESP32ROM but marks itself as a stub implementation in esp_flasher/own_esptool.py【link:/esp_flasher/own_esptool.py#L1557-L1562】.

Why ESP Flasher Uses the Stub Chip Loader for Higher Baud Rates

The ROM bootloader burned into every ESP chip operates at a fixed 115200 baud and cannot negotiate higher speeds. This limitation becomes a bottleneck when flashing large firmware images, as the upload process takes significantly longer at lower baud rates.

ESP Flasher overcomes this by leveraging the stub's change_baud method, which sends the ESP_CHANGE_BAUDRATE command to the running stub and synchronizes the host-side UART port. As implemented in esp_flasher/own_esptool.py (lines 1064-1071), this method updates both the chip and the host to the new rate simultaneously【link:/esp_flasher/own_esptool.py#L1064-L1071】.

Once the stub acknowledges the new baud rate—commonly 921600 for modern ESP32 devices—all subsequent flash operations execute at this higher speed, reducing upload time by a factor of eight compared to the ROM bootloader's fixed rate.

How the Stub Loading Process Works in ESP Flasher

The ESP Flasher workflow follows a strict sequence to safely transition from the ROM bootloader to high-speed stub operation:

  1. Chip Detection: The tool calls detect_chip() to identify the specific ESP variant and instantiate the appropriate ROM loader class.

  2. Stub Injection: Through chip_run_stub(), ESP Flasher uploads the STUB_CODE binary into RAM and transfers execution from the ROM bootloader to the stub loader.

  3. Baud Rate Negotiation: If the user specifies --upload-baud-rate greater than 115200 (as handled in esp_flasher/__main__.py lines 153-156), the tool invokes stub_chip.change_baud(args.upload_baud_rate)【link:/esp_flasher/main.py#L153-L156】.

  4. Fallback Handling: If the stub rejects the requested baud rate or the command fails, ESP Flasher catches the exception and falls back to the default 115200 baud, ensuring the flash operation continues reliably rather than failing silently.

Implementation Details in the Source Code

The stub functionality is concentrated in esp_flasher/own_esptool.py, where the change_baud method constructs the baudrate command packet and updates the serial port:

def change_baud(self, baud):
    # Send ESP_CHANGE_BAUDRATE command to the stub

    self.command(self.ESP_CHANGE_BAUDRATE, struct.pack('<I', baud))
    # Synchronize host-side port to match

    self._set_port_baudrate(baud)

The CLI layer in esp_flasher/__main__.py determines when to activate high-speed mode based on the --upload-baud-rate argument, ensuring the stub is running before attempting to exceed the ROM's 115200 limit.

Practical Usage Examples

To flash firmware at 921600 baud using the stub loader from the command line:

esp_flasher \
    --port /dev/ttyUSB0 \
    --upload-baud-rate 921600 \
    --chip esp32 \
    factory_image.bin

When using ESP Flasher as a Python library, the sequence appears as follows:

import esp_flasher.own_esptool as esptool

# Initialize connection at default ROM baud rate

chip = esptool.ESPLoader.detect_chip(port='/dev/ttyUSB0', baud=115200)

# Load and run the stub loader

stub = chip.run_stub()

# Attempt high-speed upload

try:
    stub.change_baud(921600)
except esptool.FatalError:
    # Revert to safe speed if negotiation fails

    stub._port.baudrate = 115200

# Proceed with flashing at the selected baud rate

Summary

  • The stub chip loader is a small RAM-resident firmware that replaces the ROM bootloader to provide extended functionality.
  • ESP Flasher embeds this stub as STUB_CODE in esp_flasher/own_esptool.py and loads it via mem_block and run_stub commands.
  • High baud rates (e.g., 921600) require the stub because the ROM bootloader is hardcoded to 115200 baud and lacks the ESP_CHANGE_BAUDRATE command.
  • The change_baud method in the stub class updates both the ESP chip and the host PC's serial port to maintain synchronization.
  • If baud rate negotiation fails, ESP Flasher automatically falls back to 115200 baud to ensure reliable flashing.

Frequently Asked Questions

What is the maximum baud rate supported by the stub chip loader?

Most ESP32 devices support 921600 baud reliably through the stub loader, with some chips and USB-to-serial adapters capable of reaching 1500000 baud or higher. The actual limit depends on the specific ESP chip variant, cable quality, and USB bridge hardware. ESP Flasher attempts the requested rate and falls back to 115200 if the stub reports an error.

Can I flash ESP devices without using the stub chip loader?

Yes, ESP Flasher can communicate directly with the ROM bootloader at 115200 baud without loading the stub. However, you will be limited to the ROM's basic command set and fixed baud rate, resulting in significantly slower upload speeds. The stub is only required when you need baud rates above 115200 or advanced features like compressed flashing.

Why does the stub chip loader run from RAM instead of flash?

The stub runs from RAM because the flash memory may need to be erased or rewritten during the flashing process. By executing from RAM, the stub avoids any interference with the flash chip's contents and ensures the code remains accessible even when the flash is being modified. This also allows the stub to be small and fast, loading in milliseconds without permanent modification to the chip.

What happens if the baud rate change fails during stub execution?

If the stub rejects the baud rate change—either because the rate is unsupported or the serial link becomes unstable—ESP Flasher catches the FatalError exception and reverts both the host and the chip back to 115200 baud. As implemented in the CLI logic, this fallback ensures the flashing process continues safely rather than leaving the connection in an undefined state【link:/esp_flasher/main.py#L153-L156】.

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 →