Step 06 of 13
What happens between reset and Main
Your blink worked. Here is every layer it went through, from the chip's mask ROM to the first line of your Ada — and what an RTOS would normally be doing that nothing here does.
The layers, top to bottom
| Layer | What it is |
|---|---|
| Your application | Ada packages and a Main procedure. |
| The HAL | libs/esp32s3_hal: hand-written driver packages
(ESP32S3.*) over a generated register layer
(ESP32S3_Registers.*). Optional — a program may
poke registers itself — but it is where the device knowledge
lives. |
| The binder output | gnatbind generates b__main, which
elaborates every unit in dependency order and then calls
Main. |
| The Ada runtime | The System.* and Ada.* bodies: secondary
stack, exceptions, text I/O over the USB-serial console, and the
tasking kernel — tasks, protected objects, delays,
scheduling. |
| Board support | The silicon-specific bottom of the runtime: the Xtensa windowed
context switch, interrupt entry/exit vectors, the clock tick, SMP
scheduling and the inter-core interrupt. Ada plus a little assembly
(start.S, highint5.S,
context_switch.S). This is the layer an
off-the-shelf RTOS would otherwise provide. |
| 2nd-stage bootloader | A minimal loader of our own that maps the flash cache/MMU, brings up the octal PSRAM, and jumps to the application image. It replaces the vendor's stock second-stage bootloader. |
| Mask ROM | On-chip, immutable. Loads the 2nd-stage bootloader from flash at reset. |
The boot path
ROM ─> 2nd-stage bootloader ─> start.S ─> adainit ─> Main
cache/MMU, PSRAM PLL, vectors, elaboration
release core 1
start.S selects the 240 MHz PLL, sets up the stack and the
interrupt vectors, and on the SMP examples releases the second core. Then the
runtime's startup calls adainit, which runs every package's
elaboration — and that is where library-level tasks like the blinker come
alive, before Main is entered. Finally
Main runs.
There is no vendor startup shim and no third-party scheduler in that chain. The Ada runtime is the only scheduler and it is linked from source.
Why the blink example's Main does nothing
Look at examples/esp32s3_gpio0_blink/src/main.adb and you find
this:
with Ada.Real_Time; use Ada.Real_Time;
-- The GPIO0 blink driver + its task live in package GPIO; withing it pulls the
-- task into the program so it elaborates and runs.
with GPIO;
pragma Unreferenced (GPIO);
procedure Main is
begin
loop
delay until Clock + Seconds (3600);
end loop;
end Main;
The work is done by a library-level task inside package GPIO,
which starts at elaboration. Main becomes the environment task,
which here just parks forever. Withing a package you never call is how you pull
its tasks into the link closure; pragma Unreferenced tells the
compiler that is deliberate.
A minimal application
If you would rather do the work in Main itself, this is a
complete program:
with Ada.Real_Time; use Ada.Real_Time;
with ESP32S3.GPIO;
procedure Main is
Led : constant ESP32S3.GPIO.Pin_Id := 2;
begin
ESP32S3.GPIO.Configure (Led, Mode => ESP32S3.GPIO.Output);
loop
ESP32S3.GPIO.Toggle (Led);
delay until Clock + Milliseconds (250);
end loop;
end Main;
delay until is served by the board-support tick;
ESP32S3.GPIO is the HAL; everything links against the runtime
crate.