Projects

Create an esp-hal project with espx new, pick its chip, board and options, and read its dependencies.

espx new creates a bare-metal Rust project with esp-generate, the esp-rs project generator, and adds the files espx and coding agents read. Everything else espx does works on any directory that holds a Cargo.toml, so projects you made another way work too.

Create a project

espx new blinky

This creates ./blinky for the ESP32-C6. Underneath it runs:

esp-generate --chip esp32c6 --skip-update-check --option log --headless --output-path . blinky

Pass --dir to create the project somewhere else. espx creates that folder if it is missing and refuses to touch a project folder that already exists.

espx new blinky --dir ~/code      # creates ~/code/blinky

The name must work as a cargo package name: ASCII letters, digits, - and _, starting with a letter, at most 64 characters. espx also refuses Rust keywords (fn, Self), crates built into Rust (std, core, alloc, test), cargo's own folder names (build, deps, examples, incremental) and Windows device names (con, nul, com1). If esp-generate fails, espx removes the half-made folder.

Pick a chip

espx new blinky --chip esp32c3

The default is esp32c6. espx chips lists every id. The RISC-V chips build with the stable toolchain and the targets espx setup added. An Xtensa chip (esp32, esp32s2, esp32s3) needs Espressif's Rust fork first; without it espx new stops with xtensa_toolchain_missing and names the espx setup --chip command to run. See Xtensa chips.

Pick a dev board

espx new blinky --board xiao-esp32c6

--board takes an id from espx boards. It selects the board's chip, so you do not pass --chip. Passing a --chip that differs from the board's chip is an error (board_chip_mismatch).

The board id is recorded as a Board: line in the generated README.md and AGENTS.md, together with the board's LED, BOOT button and UART pins and a link to its documentation. espx does not generate pin code from it. The facts are there for you and for agents to read before they drive a GPIO. espx board xiao-esp32c6 prints the same facts. Chips and boards lists every board espx knows.

esp-generate options

-o (or --option) passes an option straight to esp-generate. Repeat it for several:

espx new sensor -o embassy -o unstable-hal -o alloc
espx new radio -o wifi
espx new crashy -o esp-backtrace

espx turns on log by default. -o defmt replaces it, because esp-generate refuses the two together. espx validates none of the options; esp-generate reports a name it does not know. Some options worth knowing:

OptionWhat it adds
esp-backtraceA panic handler that prints the panic message and a backtrace. Without it a panic prints no addresses, so espx cannot resolve it to source lines
defmtCompact log frames that espx decodes on the host against the release binary
embassyAsync tasks on the embassy executor; pair it with unstable-hal
allocA global allocator from esp-alloc, for Vec, String and Box
wifiesp-generate's Wi-Fi setup
probe-rsesp-generate's probe-rs setup

To pick options in esp-generate's own terminal UI, pass -i. It needs a terminal; without one espx stops with not_a_tty.

Bind a port or a board

Two flags tie a new project to the hardware on your desk:

espx new blinky --port /dev/cu.usbmodem101
espx new blinky --board-id 58:8C:81:53:AD:2C
FlagWritesEffect
--port Pespflash_ports.toml with [connection] serial = "P"espx and espflash use that port for this project
--board-id IDespx.toml with [board] id = "ID"flash, run and monitor use that physical board, and AI agents may flash it

A board id is the board's USB serial number (on native-USB chips that is its MAC address), or the port path when the board reports no serial number. --board-id also takes a port or a name from ~/.espx/boards.toml. espx devices prints the ids. Boards and ports explains how espx picks a board and why binding one matters.

What espx new writes

esp-generate writes the cargo project: Cargo.toml, .cargo/config.toml with the target and the espflash runner, rust-toolchain.toml, and the firmware under src/ (the entry point is src/bin/main.rs). It also runs git init. espx then adds:

FileContents
README.mdThe espx commands for this project, how to pin a port, and how to use plain cargo run --release. With --board, the Board: line
AGENTS.mdThe espx loop for coding agents, the chip and target, the board's pins, the bound board, the esp-hal version and the examples link. See Agent context
espx.tomlOnly with --board-id: the [board] section
espflash_ports.tomlOnly with --port: the pinned serial port

What the project depends on

espx deps

deps reads Cargo.lock (or the lockfile of an enclosing workspace) and prints every resolved version of esp-hal, esp-radio, esp-rtos, esp-println, esp-backtrace, esp-alloc, embassy-executor and defmt. A crate that resolves to two versions is listed twice. --json adds each crate's source. A fresh project has no Cargo.lock until the first build, so run espx build first.

espx context prints the same versions together with the chip, the board's pins and the link to the esp-hal examples of the exact release in Cargo.lock. It is the text espx new writes to AGENTS.md, plus the boards attached right now.

espx context
espx context --board xiao-esp32c6   # when the project does not record its board

Projects from elsewhere

A project is a directory with a Cargo.toml. espx reads the target from .cargo/config.toml, the toolchain channel from rust-toolchain.toml and the binary name from Cargo.toml, and derives the chip from the target. cargo stays the authority on how to build it. Pass --dir to any project command to point at it without changing directory.