Hardware Events

Sometimes you need your board to react the moment a pin changes — a button press, a sensor pulse, a signal going high. Cuttlefish gives you two ways to do that: External Interrupts, which fire instantly with very low latency (how quickly it responds), and Asynchronous Edge Detection, which lets you write the reaction as plain, readable async/await code.


External Interrupts

Interrupts allow the microcontroller to respond to a physical event (like a button press or sensor trigger) immediately, pausing the main loop to execute a handler function.

Basic Interrupts

Use .onRising(), .onFalling(), or .onChange() to attach an interrupt handler. These watch for an edge on the pin — rising (LOW→HIGH) or falling (HIGH→LOW).

import { D2, D13 } from '@typecad/board';

const led = D13.asOutput();
const button = D2.asInput();

// Toggle LED whenever D2 goes from LOW to HIGH
button.onRising(() => {
  led.toggle();
});

Detaching Handlers

You can remove all interrupts from a pin using .offAll().

button.offAll();

Important Constraints (ISR Safety)

Interrupt handlers — ISRs (interrupt service routines), the code that runs the moment a pin changes — run in a special hardware context. To ensure system stability:

  1. Keep it short: Execute the minimum amount of logic necessary.
  2. Avoid blocking: Never use delay() or long-running loops inside an interrupt.
  3. No Serial/I2C: Avoid complex communication inside an ISR as they often rely on interrupts themselves.

Asynchronous Edge Detection

For many applications, interrupts are unnecessary and can make code logic difficult to follow. Cuttlefish provides async methods that allow you to “wait” for a hardware event in a clean, sequential style.

Waiting for an Edge

The waitForRising and waitForFalling methods return a Promise that resolves when the event occurs. Cuttlefish handles the timing for you, so it doesn’t block the rest of your program.

import { D2, delay } from '@typecad/board';

const sensor = D2.asInput();

async function wait_for_trigger() {
  while (true) {
    console.log("Waiting for trigger...");
    
    // Execution pauses here (non-blocking) until D2 goes HIGH
    await sensor.waitForRising();
    
    console.log("Event detected!");
    
    // Resolution: handle the event
    delay(2000); 
  }
}

wait_for_trigger();

Timeouts

You can provide an optional timeout (in milliseconds). If the event doesn’t occur within the timeout, the promise resolves.

// Wait up to 5 seconds for a response
await sensor.waitForRising(5000);

Comparison: Interrupts vs. Async

FeatureInterrupts (onRising)Async (waitForRising)
LatencyExtremely low (nanoseconds)Moderate (microsecond polling)
Code StyleEvent-driven (Callbacks)Sequential (async/await)
ComplexityHigh (requires ISR-safe logic)Low (standard TypeScript style)
Best ForHigh-speed encoders, safety stopsButtons, slow sensor pulses, UI

API Reference

Interrupt Methods

Available on pins with hardware interrupt capabilities (narrowed via .asInput()).

MethodParametersDescription
onRising(fn)fn: () => voidExecute fn when pin goes from LOW to HIGH.
onFalling(fn)fn: () => voidExecute fn when pin goes from HIGH to LOW.
onChange(fn)fn: () => voidExecute fn on any state change.
offAll()Remove all interrupt handlers from the pin.

Async Methods

Available on all digital input pins.

MethodParametersDescription
waitForRising(to?)to: number (opt)Pauses execution until a rising edge (or timeout).
waitForFalling(to?)to: number (opt)Pauses execution until a falling edge (or timeout).

Advanced Example: Manual Pulse Measurement

While the Pulse utility is optimized for high speed, you can use async methods for long-duration pulses.

import { D2, millis } from '@typecad/board';

const pin = D2.asInput();

async function measureLongPulse() {
  console.log("Waiting for start of pulse...");
  await pin.waitForRising();
  const start = millis();
  
  await pin.waitForFalling();
  const end = millis();
  
  console.log(`Pulse lasted ${end - start} ms`);
}