Step 07 of 13
What a build actually does
Five steps from your Ada to a flashable image, and two small host tools — both written in Ada — that replace esptool entirely.
The five steps
build.sh sets the profile and heap size, then the shared
bare_build.sh:
- Regenerates or selects the runtime crate for the chosen profile. Cached after the first time.
- Compiles your Ada against that pinned runtime with
gprbuild, producing a relocatableada_app.o(withMainrenamed to_ada_main). - Compiles the shared bare boot —
start.S, the interrupt vectors, the heap, and a freestanding libc — plus the example's C console glue. - Links it all against the linker scripts into
app.elf. - Packages
app.bin: image header plus segments.
Then flash.sh writes three images: the 2nd-stage bootloader, the
partition table, and app.bin.
esp_elf2image — ELF to flash image
Turning app.elf into app.bin is more than a copy.
The on-flash format the ROM and our bootloader expect has:
- Segments: the ELF's allocated
PROGBITSsections, merged where contiguous, each padded to a multiple of four. - A 24-byte header: the 8-byte common part (magic
16#E9#, segment count, the flash-mode byte, the entry point) plus a 16-byte extended part. - Flash segments aligned to 64 KB, with the RAM segments written interleaved as the alignment padding — the exact scheme the bootloader's MMU mapping relies on.
- An XOR checksum (seed
16#EF#) as the last byte of a 16-aligned block, and a SHA-256 of the whole image appended.
esp_flash — writing it to the chip
The flasher speaks the chip's ROM serial bootloader protocol directly. It is
100 % Ada: the OS serial interface (termios raw mode, the
TIOCM* DTR/RTS modem lines, poll/read/write) is bound to libc
through Interfaces.C, with no C source. The sequence, with no RAM
stub and no compression:
- Reset into download mode — the USB-JTAG DTR/RTS pulse.
- SYNC — the handshake, repeated until the ROM answers.
- SPI attach and set-params (flash geometry).
- Per file, flash_begin (erase) then flash_data in 1 KB blocks, each carrying the ROM's XOR checksum.
- flash_end, then a hard reset to run.
A typical invocation writes the three images at their flash offsets:
esp_flash -p /dev/ttyACM0 --flash-size 2MB \
0x0 bootloader.bin \
0x8000 partition-table.bin \
0x10000 app.bin
A ~225 KB image flashes in about three seconds. That is ROM speed — no stub, no compression. Note that only flash is written; the external PSRAM is mapped at runtime by the 2nd-stage bootloader.
Everyday build tips
| Goal | Command |
|---|---|
| Rebuild after editing Ada | ./build.sh again, then ./flash.sh |
| Force the Ada to rebuild | rm -f main/app_main.o |
| Force the runtime to regenerate | rm -rf crates/esp32s3_rts/*-esp32s3 |
| Use esptool instead (optional fallback) | ESP_USE_ESPTOOL=1 ./build.sh (and ./flash.sh) |