Hardware Events

One mechanism: GPIO interrupts. A pin event lowers to gpio_pin_interrupt_configure_dt plus a callback registered the Zephyr way, with the edge or level mode expressed as Zephyr’s own GPIO.INT_* tokens.

For sequential “wait for a signal” logic, interrupts are the wrong tool — poll inside an async function with Time.sleep and let the task yield between checks. Both patterns are below.


Attaching an Interrupt

import { GPIO } from '@typecad/hal';
import { LED, BUTTON } from '@typecad/board';

const led = new GPIO(LED, GPIO.OUTPUT);
const button = new GPIO(BUTTON, GPIO.INPUT | GPIO.PULL_UP);

button.onInterrupt(GPIO.INT_EDGE_FALLING, () => {
  led.toggle();
});

The mode is one of these tokens — Zephyr’s names, covering edges and levels:

TokenFires when
GPIO.INT_EDGE_RISINGthe pin goes low → high
GPIO.INT_EDGE_FALLINGthe pin goes high → low
GPIO.INT_EDGE_BOTHeither transition
GPIO.INT_LEVEL_LOWthe pin is held low
GPIO.INT_LEVEL_HIGHthe pin is held high

A misspelled token is an editor-visible member error; an invalid mode is a build error naming the valid spellings.

Detaching

button.offInterrupt();   // disable and remove the callback

ISR Safety

The handler runs in interrupt context. The transpiler checks handler bodies: setting variables and driving pins is fine, but calls that would block — delays, bus transactions, UART writes — are flagged at build time with the offending call named.

The pattern that always works: set a flag in the handler, act on it in the main flow.

let pressed = false;
button.onInterrupt(GPIO.INT_EDGE_FALLING, () => { pressed = true; });

while (true) {
  if (pressed) {
    pressed = false;
    UART0.writeLine('pressed');   // safe here
  }
  Time.sleep(10);
}

Interrupts or Polling?

InterruptPoll in an async task
Latencymicrosecondsthe poll period (typically milliseconds)
ContextISR constraints applynone — ordinary code
Best atevents that must not be missedsequential logic, debouncing
Many inputsone callback per pinone task can watch many conditions

Debouncing is the classic deciding case: an interrupt fires on every bounce, while a polling task reads a settled level. while (button.get()) { await Time.sleep(20); } debounces by construction.


API Reference

MemberDescription
onInterrupt(intFlags, handler)Attach with a GPIO.INT_* token.
offInterrupt()Detach.