Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

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

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

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.