← board

ESP32 peripheral callback API (timer / GPIO / ADC) — the user-facing "interrupt"

Why

From a user's perspective a timer / ADC / GPIO callback is "an interrupt", even though at the ISA level it is a plain C callback dispatched by FreeRTOS (which already did the window spill / context save before calling it). Users want:

Timer1.OnElapsed := @MyHandler;        // or  Gpio.OnEdge := @h;  /  Adc.OnReady := @h;

They do not want to write esp_intr_alloc(source, flags, @h, arg, @handle) + cdecl; iram; boilerplate, and they must not reach for the interrupt; keyword — that is bare-metal raw-vector plumbing (see feature-esp32-isr-iram; on IDF it actively breaks, because registering a raw-vector proc with esp_intr_alloc double-saves context and rfes instead of rets → crash).

This ticket builds the high-level layer that hides the SDK call and the iram; detail behind an event-style surface.

Layering (keep the dark CPU magic hidden)

Layer Who Surface
App user "run my code when the peripheral fires" Timer1.OnElapsed := @h
This library wraps the SDK esp_intr_alloc / esp_timer + callback trampoline marked iram;
Compiler (Track A) plumbing iram;, @isr, extern C — all done

Scope

Non-goals

Open questions

Acceptance (slice 1 — timer)

Log

Moved to blocked/ (2026-07-20, Track B sweep)

Slice 1 (esptimer + demo) is written but unverified, and the acceptance for every remaining slice is "it runs on the device". Neither ESP32 hardware nor a qemu/IDF runner is available to this lane, so nothing here can be honestly completed or honestly closed — it would be code nobody has ever executed.

Blocked on an external constraint, not on another ticket, which is what blocked/ is for (same category as [[feature-port-macos]]). What would unblock it: a C3/S3 board or a working qemu-esp32 harness. A host-side compile-only smoke (riscv32 .o + readelf -sW import assertions) is possible without hardware and would be worth doing, but it proves linkage, not behaviour — do not mistake it for acceptance.

Back to blocked/ 2026-07-31 (Track B sweep) — still no way to RUN it

Re-checked rather than assumed: this box has no qemu-system-riscv32 and no qemu-system-xtensa, IDF_PATH is unset, and there is no board. An ESP-IDF checkout exists at ~/esp/esp-idf, which is enough to LINK and nothing more.

Every remaining item here — the qemu acceptance run, dropping the interim iram;, and the GPIO and ADC slices — has "it runs on the target" as its acceptance. Writing more of it would add code nobody has ever executed, which is the one thing this ticket already says not to do.

Tagged for later testing. When a C3/S3 board or a working qemu-IDF harness appears, the first action is the slice-1 acceptance run that was never done, not more slices. The existing compile-only smoke in lib-test (riscv32 .o + readelf -sW import assertions) stays where it is — it proves linkage, and it must not be read as acceptance. Nothing further from this ticket goes into the regression suite until something can execute it.