Build and flash

Build release firmware with a size check, flash it, and pass extra arguments to cargo and espflash.

Four commands cover the build side: espx build, espx flash, espx run (build, flash and monitor in one step) and espx clean. A fifth, espx erase, wipes a board's flash. Each one runs from the project directory, or takes --dir to point at one.

Build

espx build

This runs cargo build --release with cargo's JSON messages turned on. cargo's progress lines reach your terminal unchanged, and espx prints each compiler message exactly as cargo would. On success it measures the ELF and prints one line:

=> image 64 KB of a 3.9 MB app partition (1%), 12 KB of static RAM

The image size counts what espflash writes to flash. Static RAM counts every allocated section outside flash except the stack, which esp-hal's linker gives all the DRAM that is left.

The partition check

espx compares the image with the app partition before anything reaches the board. A build whose image does not fit fails with image_too_large and tells you to shrink the firmware or give it a bigger partition. espx finds the partition size the way espflash 4.5 does:

  • A partition_table in the [idf_format_args] or [idf] table of espflash.toml (a CSV or .bin file). espx takes the target_app_partition named there, else the factory partition, else the first app partition.
  • Without a table, the default layout: the [flash] size from espflash.toml (4 MB when unset) minus 0x10000, capped at 16,000 KiB.

Build errors

A failed build exits 1 with cargo build failed with N errors. Fix them and build again. With --json, each compiler message is a diagnostic event with its level, code, file, line and column, and cargo's rendered text. See JSON events.

Flash

espx flash

flash builds through espx first, so the diagnostics and the partition check apply, then runs cargo espflash flash --release on the board espx picked. Boards and ports has the selection order. To name one:

espx flash --port /dev/cu.usbmodem101   # a port
espx flash --port 58:8C:81:53:AD:2C     # a board id
espx flash --port bench-c6              # a name from espx boards name

On a terminal espx draws one progress bar per flash region (bootloader, partition table, app), labelled with the region's offset:

0x10000   [====================>                   ] 2/4

espflash skips a region whose contents already match, so flashing the image a board already holds shows no bar at all.

Speed

--baud sets the bootloader connection speed. Without it espx takes [board] baud from the project's espx.toml, and without that espflash uses espflash.toml, ESPFLASH_BAUD or its own default.

When the bootloader does not answer

If espflash cannot get the bootloader to talk, espx tries once more at 115200 baud:

=> the bootloader did not answer; trying again at 115200 baud

When that fails too, or the board is not on the USB bus at all, espx prints what a person has to do above the error:

needs a person: espx could not get the bootloader to answer, also at 115200 baud. Ask a person to hold BOOT, tap RESET, release BOOT, and then flash again.

The two cases are download_mode (hold BOOT, tap RESET, release BOOT) and replug (plug the board in, or unplug and replug it, with a data cable). AI agents get the same diagnosis as needs_human in the tool result. A port that another program holds open is neither case. espx does not retry it or ask for a person; close the other program and flash again.

A board behind a WCH CH34x or CH9102 USB bridge on macOS's built-in serial driver (a /dev/cu.usbmodem… port instead of /dev/cu.wchusbserial…) corrupts large writes. Flashing fails with upload_corrupted, at any baud. Install WCH's driver, or use the board's native USB port. espx devices warns about such a port before you try.

Several boards

espx never asks which board to use. With several boards attached and none chosen, flash stops with port_required and lists them. Pass -i to let espflash ask instead; it needs a terminal.

Flash and monitor

espx flash --monitor opens the serial monitor after flashing. In a terminal it hands over to cargo espflash flash --monitor, with espflash's key handling. With --json, or when stdin is not a terminal, espx reads the port itself, the same as espx run below.

Run

espx run

run is build, flash and monitor. In a terminal with no other flags it builds through espx, then hands the terminal to cargo espflash flash --release --monitor. Ctrl+R resets the board and Ctrl+C exits.

With --until, --expect, --reject, --timeout or --json, or when stdin is not a terminal (espx run | grep panic), espx runs the steps itself:

  1. Build through espx, with the size check.
  2. Attach to the board, then release the port for cargo espflash flash.
  3. Flash, reporting flashing and flashed.
  4. Wait for the board to come back (reconnected), reset it so the boot log starts from the top (reset), and read its output.
  5. Test each line against the patterns, and stop on a match, a rejected line or the timeout.

The timeout clock starts when the flashed board boots, not when the build does. Scripts and CI covers the patterns and exit codes.

run takes no --baud. It flashes at [board] baud from espx.toml (or espflash's default) and monitors at 115200, or MONITOR_BAUD.

On this path the board boots twice: once when espflash resets it after writing, and again when espx resets it after reattaching, so the log you read starts at a clean boot.

Pass arguments through

Everything after -- goes, unchanged, to the one tool the command wraps:

CommandArguments after -- go to
espx buildcargo build
espx flashcargo espflash flash
espx runcargo espflash flash (the flash step only)
espx monitorespflash monitor
espx build -- --features wifi
espx flash -- --erase-parts otadata

espx validates none of it; cargo or espflash reports mistakes. With extra arguments, flash and run skip espx's own build, because those arguments are for cargo espflash, not cargo build. cargo espflash then builds on its own and espflash does its own size check.

Clean

espx clean

Runs cargo clean in the project.

Erase

espx erase
espx erase --port /dev/cu.usbmodem101

Runs espflash erase-flash, which wipes the board's whole flash and resets it. The board has no firmware afterwards. With one board attached espflash finds it; otherwise pass --port. If another espx process holds the board (a running monitor, the desktop app or espx mcp), erase borrows it through that process and gives it back.

Flashing a board that is being watched

A serial port can be open in only one process at a time. espx shares boards between its own processes instead, so you can leave espx monitor running in one terminal and run espx flash in another. The flash goes through the monitor's process, and the monitor shows the new boot. Monitor and replay explains the board hub.