Skip to content

Connecting and flashing an ESP-01

The ESP-01 is an ESP8266 module on an eight-pin header. Getting a serial console and flashing new firmware are both straightforward once you know which adapter you have — most of the pain comes from cheap USB dongles that omit the auto-reset wiring, so every tutorial written for a NodeMCU board quietly fails.

Pinout

Held with the antenna at the top, pins facing you:

      ┌───────────────┐
      │   [antenna]   │
      │               │
  GND │ ●           ● │ TX
GPIO2 │ ●           ● │ CH_PD (EN)
GPIO0 │ ●           ● │ RST
   RX │ ●           ● │ VCC
      └───────────────┘

The pairs across the two rows, in order, are fixed by the module: GND/TX, GPIO2/CH_PD, GPIO0/RST, RX/VCC. That ordering never changes, which makes it a reliable way to identify an unlabelled adapter socket: find GND with a continuity meter (it beeps against the USB shell), and everything else follows. VCC is diagonally opposite GND.

Two pins matter beyond power and serial:

  • CH_PD (EN) must be pulled high. Floating means the chip sits in permanent reset — powered, silent, and completely unresponsive.
  • GPIO0 selects boot mode, sampled only at reset.

The auto-reset problem

Plain CH340 dongles for the ESP-01 typically wire only TX, RX, VCC and GND. DTR and RTS go nowhere. Consequences:

  • esptool cannot reset the board, so it cannot enter flash mode by itself and --before default-reset always fails
  • You can never catch the power-on boot banner, because the ESP boots the instant USB power arrives — long before the kernel creates /dev/ttyUSB0

Adapters with a PROG/FLASH switch, or a CP2102/FTDI with DTR → GPIO0 and RTS → RST, avoid all of this. Wiring those two lines yourself is the single highest-value modification.

If the tool reports Failed to connect ... No serial data received for every combination of settings, suspect wiring rather than software. A quick check: bridge the adapter's TX to RX with the module removed and confirm bytes echo back. That proves the USB side end to end and halves the search space.

Reading the boot banner

The ROM bootloader always prints at 74880 baud — an unusual rate, but the chip's own, and it works regardless of what firmware is installed.

The trick that makes this practical without auto-reset: hold the serial port open, then briefly touch RST to GND. The chip resets while something is already listening, so the banner lands in an open port. Chasing it by replugging is a race you cannot win.

import serial, time
s = serial.Serial('/dev/ttyUSB0', 74880, timeout=0.2)
t0 = time.time()
while time.time() - t0 < 30:          # tap RST to GND during this window
    d = s.read(4096)
    if d:
        print(d.decode('utf-8', 'replace'), end='')

A healthy boot looks like:

ets Jan  8 2013,rst cause:2, boot mode:(3,6)
load 0x40100000, len 1856, room 16
...
2nd boot version : 1.5
  SPI Flash Size & Map: 8Mbit(512KB+512KB)
jump to run user1 @ 1000

Read it as:

Field Meaning
rst cause:1 power-on
rst cause:2 external reset (your RST tap)
rst cause:4 watchdog — often a crash loop
boot mode:(3,x) GPIO0 high — normal boot
boot mode:(1,x) GPIO0 low — bootloader, waiting for esptool
jump to run user1 firmware loaded and started

RST must be momentary. Holding it at GND keeps the chip halted — silent, with the blue TX LED dark — which looks exactly like a dead board.

Entering flash mode

GPIO0 is sampled only as the chip leaves reset, so:

  1. Hold GPIO0 to GND
  2. Tap RST to GND, release RST
  3. Release GPIO0

Shorting GPIO0 on an already-running chip does nothing at all.

Once latched, the bootloader waits indefinitely — there is no timeout to race. Confirm with boot mode:(1,x) and the absence of jump to run user1.

esptool

pipx install esptool          # or: uv tool install esptool
sudo usermod -aG dialout $USER   # then log out and back in

Group membership does not reach already-running sessions. Until you log out, wrap commands in sg dialout -c "...".

With no auto-reset, tell esptool not to try:

esptool --port /dev/ttyUSB0 --chip esp8266 \
        --before no-reset --after no-reset --no-stub chip-id

Why --no-stub

esptool normally uploads a helper program ("stub") into RAM for speed. Over long jumper wires that bulk transfer often fails with Packet content transfer stopped. --no-stub drives the ROM loader directly: slower, far more tolerant.

The catch:

  • write-flash works without the stub — the ROM supports flash writes
  • read-flash does not. It fails with ESP8266 ROM does not support function read_flash_slow. Dumping flash requires the stub, so on a flaky link you may be able to write firmware but never back up what was there first.

Flashing

esptool --port /dev/ttyUSB0 --chip esp8266 \
        --before no-reset --after no-reset --no-stub \
        --baud 57600 --flash-size 1MB --flash-mode qio \
        write-flash 0x0 firmware.bin

Drop the baud rate if a write dies partway. At 115200 a ~290 KB write may fail around 10–15%; the same write at 57600 completes reliably. It is roughly twice as slow and worth every second — flash is erased before writing, so a failed write leaves the module unbootable until you retry.

A failed write is never fatal: flash mode is selected by GPIO0 in hardware, independent of flash contents, so the board is always recoverable.

Brownout

The ESP-01 draws ~300 mA peaks when the radio starts. Symptom: it boots, prints its banner, begins associating, and resets — over and over.

  • Seat the module directly in its adapter rather than running it through jumper wires; the bulk capacitor is then millimetres away instead of at the end of a resistive chain
  • Add 470 µF + 100 nF across VCC/GND right at the module
  • Better still, use a separate 3.3 V supply rated for the peaks
  • In firmware, WiFi.setOutputPower(12.0) cuts the current spike

Never feed it 5 V — neither VCC nor the GPIOs are 5 V tolerant.

PlatformIO

[env:esp01_1m]
platform = espressif8266
board = esp01_1m
framework = arduino
board_build.flash_mode = qio
monitor_speed = 115200

pio run -t upload cannot work on an adapter without auto-reset — build with pio run, then flash manually as above.

Quick diagnosis

Symptom Likely cause
Nothing at any baud, ever TX not contacting; module not fully seated
Red LED on, blue LED never lights Chip held in reset — RST tied low, or CH_PD floating
Banner appears, but commands get no reply RX leg not contacting
Boots, then resets during Wi-Fi Brownout
boot mode:(3,x) when you wanted flash mode GPIO0 was not low at reset
Garbled text Wrong baud — the ROM talks at 74880, not 115200