Step 01 of 56
What you need (and what you don't)
One board, one USB cable, and one package manager. No ESP-IDF, no idf.py, no esptool, no Python anywhere in the build or flash path.
Most ESP32 tutorials open by telling you to install a 400 MB SDK. This one does not. The whole toolchain here is Alire, the Ada package manager, which fetches a cross-compiler for you. Everything else — packaging the image, writing it to flash, reading the console — is done by tools that live in this repository and are themselves written in Ada.
Hardware
- An ESP32-S3 devkit, using its native USB
port — the one wired to the chip's built-in USB-Serial-JTAG
controller. On most boards it is labelled
USB, and the other one is labelledUART. Pick the native one: it carries the console and the debugger over a single cable. - A data-carrying USB cable. A charge-only cable will enumerate nothing and waste an hour of your life.
That is the minimum. An LED and a resistor on GPIO0 make the first example visible, but the console output alone proves it works.
Software
- Alire (
alr) — fetches three toolchains: thegnat_xtensa_esp32_elfcross-compiler for the chip, a nativegnat_nativefor the host tools, andgprbuild. - git — optional. You can unzip a release archive instead (step 3); there are no submodules to fetch either way.
- A host C compiler — used exactly once, to build the
xtensa-dynconfigcore-config plugin the toolchain needs.
What you do not install: ESP-IDF,
idf.py, esptool, or Python. The build path uses none of them.
(esptool remains an optional fallback if you happen to have it and
prefer it — see what a build does.)
The big picture
Two commands drive everything. Here is what they set in motion:
your Ada code ─┐
Ada RTS ─┤ ./build.sh ─> gprbuild (Alire xtensa GNAT) ─> app_main.o
(generated) │ ─> link (bare boot + vendored Xtensa support)
bare boot (Ada) ┘ ─> esp_elf2image (Ada) ─> app.bin
─> our 2nd-stage bootloader ─> bootloader.bin
./flash.sh ─> esp_flash (Ada, over USB ROM) ─> board runs it
0x0 bootloader | 0x8000 partitions | 0x10000 app
The Ada runtime is generated on the first build and cached; you never
build it by hand. The two host tools (esp_elf2image and
esp_flash) are compiled once, also on the first build. That is why
the first build is slow and every one after it is fast.
Ours, and what is vendored
The 2nd-stage bootloader is this project's own, not the
vendor's. It is built from examples/common/bare/bootloader/ —
Ada (boot_main, boot_psram, boot_glue)
plus an assembly prologue and linker scripts — into its own
bootloader.bin, flashed at offset 0x0. It is a separate
image, not something linked into your application.
What is vendored sits in examples/common/bare/vendor/,
originates from ESP-IDF v5.4.4, and is committed in-tree rather than fetched:
the Xtensa support (context save/restore, the vector table, interrupt tables),
compiled here from that IDF source and verified instruction-identical to IDF's
own build; the linker scripts and mask-ROM symbol addresses; a trivial
single-app partition table; and two genuine opaque blobs that cannot be built
from this tree — libxt_hal.a, the Cadence/Tensilica Xtensa
HAL, and libgcc.a for the 64-bit divide helpers.
Those vendored files are the source of truth here and need no ESP-IDF to build — nothing is required from an IDF install at build time. The only reason to go back to ESP-IDF is a maintainer re-vendoring against a different release.
Time and platform
Budget about 15 minutes, most of it the one-time toolchain download. The commands below are shown for Linux (Ubuntu/Debian). macOS is similar but untested; on Windows, use WSL2.