# `brawl:device/gpio`

## Overview

The `brawl:device/gpio` interface defines a minimal, **hardware-agnostic GPIO API** for WebAssembly (WASM) modules. It provides a consistent way to control and monitor digital and analog I/O across diverse embedded targets - ranging from microcontrollers to emulators - while remaining fully compatible with the WASM MVP specification.

Each function follows a simple convention: it returns a signed 32-bit integer, where negative values represent standardized error codes and non-negative values represent valid results or success indicators. This design keeps the ABI deterministic, low-overhead, and portable between environments, enabling WASM applications to interact directly with physical or virtual pins without callbacks, threads, or host-specific extensions.

## Implementation Notes

This section outlines the expected behavior and conventions used by the `brawl:device/gpio` interface. It provides guidance for both host implementers and guest developers to ensure consistent behavior across WASM targets.

#### GPIO Enumeration

`pinCount()` returns the total number of GPIO-capable pins available on the target. `pinList()` populates a memory buffer with a list of available pin identifiers (`uint8_t`) in the host environment’s canonical order.  Each pin identifier can be passed directly to `pinOpen()` to obtain a handle for further operations.

#### Interfacing With a GPIO Pin

Before reserving a pin, always call `pinCapabilities()` to verify that it supports the desired modes. Use `pinOpen()` to reserve the pin and obtain a handle; this handle is passed to all subsequent operations. When the pin is no longer needed, release it using `pinClose()`. Pins remain inactive and unpredictable until configured with either `pinModeIn()` or `pinModeOut()` to define their behavior and electrical characteristics.

#### Analog Measurements

`pinAnalogRead()` returns a value between `0` and `(2^resolution) - 1`, where the resolution is set using `pinAnalogReadResolution()`. `pinAnalogWrite()` accepts a value between `0` and `(2^resolution) - 1`, where the resolution is set using `pinAnalogWriteResolution()`. The returned value is proportional to the selected reference voltage (`pinAnalogReference()`).

#### Pulse Measurement

`pinPulseIn()` returns the duration of a pulse in microseconds (µs). Frequencies and PWM parameters are expressed in Hertz (Hz).

#### Interrupts (no callbacks)

When interrupts are available - they are polled, not event-driven. `pinInterruptState()` returns a `GPIO_EDGE` value when an edge has been detected and clears the latched state. Edges remain latched until `pinInterruptState()` is called. Changing the configured edge type using `pinAttachInterrupt()` automatically resets the latch.

#### Error Codes

All `brawl:device/gpio` functions return a signed 32-bit integer (`int32_t`), which encodes both result and error information:

| Value      |                                                                         |
|------------|-------------------------------------------------------------------------|
| `ret < 0`  | an error occurred.                                                      |
| `ret == 0` | operation completed successfully (or no value to return).               |
| `ret > 0`  | operation succeeded and returned a value (e.g., a pin level or handle). |

## Constants

#### GPIO Errors

All functions return a signed 32-bit integer (`int32_t`).  If the value is **negative**, it represents an error code from the table below:

| Constant                    |                                                                      |
|-----------------------------|----------------------------------------------------------------------|
| `GPIO_ERROR_BUSY`           | The GPIO pin is already in use or reserved by another handle.        |
| `GPIO_ERROR_UNSUPPORTED`    | The requested operation or capability is not supported on this pin.  |
| `GPIO_ERROR_TIMEOUT`        | The operation did not complete within the expected time.             |
| `GPIO_ERROR_INVALID_HANDLE` | The handle provided was invalid for the operation requested.         |
| `GPIO_ERROR_INVALID_ARG`    | The argument provided was invalid for the operation requested.       |

A return value **`>= 0`** indicates success - either `0` for non-returning operations or a positive value for operations that produce a result.

#### GPIO Capabilities

Each GPIO pin can support one or more capabilities, reported by `pinCapabilities()`. These are bit-flags and may be combined using bitwise OR.

| Constant (Flag)         |                                                                     |
|-------------------------|---------------------------------------------------------------------|
| `GPIO_CAPS_DIGITAL_IN`  | Supports digital input.                                             |
| `GPIO_CAPS_DIGITAL_OUT` | Supports digital output.                                            |
| `GPIO_CAPS_ANALOG_IN`   | Supports analog input.                                              |
| `GPIO_CAPS_ANALOG_OUT`  | Supports analog output.                                             |
| `GPIO_CAPS_PULL_UP`     | Internal pull-up resistor available (input mode).                   |
| `GPIO_CAPS_PULL_DOWN`   | Internal pull-down resistor available (input mode).                 |
| `GPIO_CAPS_PUSH_PULL`   | Push-pull output driver (actively drives both HIGH and LOW).        |
| `GPIO_CAPS_OPEN_DRAIN`  | Open-drain output (drives LOW, requires external pull-up for HIGH). |
| `GPIO_CAPS_PWM`         | Supports pulse-width modulation or tone generation.                 |
| `GPIO_CAPS_IRQ_RISING`  | Can trigger an interrupt on a LOW to HIGH edge.                     |
| `GPIO_CAPS_IRQ_FALLING` | Can trigger an interrupt on a HIGH to LOW edge.                     |
| `GPIO_CAPS_IRQ_CHANGE`  | Can trigger an interrupt on any change (both edges).                |
| `GPIO_CAPS_LED`         | Pin is connected to an onboard or indicator LED.                    |

Combine capability flags with the bitwise OR operator (`|`) to describe a pin that supports multiple features. Pins marked as `GPIO_CAPS_LED` typically drive an onboard indicator.

#### Digital State

Represents the logical level of a digital pin.

| Constant          |                               |
|-------------------|-------------------------------|
| `GPIO_STATE_LOW`  | Logic LOW / GND.              |
| `GPIO_STATE_HIGH` | Logic HIGH / VCC (3V3 or 5V). |

#### Digital Pull (Input)

Configures the internal resistor behavior when a pin is set as an input.

| Constant         |                                             |
|------------------|---------------------------------------------|
| `GPIO_PULL_NONE` | No internal pull resistor (floating input). |
| `GPIO_PULL_UP`   | Enables a weak internal pull-up resistor.   |
| `GPIO_PULL_DOWN` | Enables a weak internal pull-down resistor. |

#### Digital Drive (Output)

Selects how a digital output drives its line.

| Constant                |                                                                    |
|-------------------------|--------------------------------------------------------------------|
| `GPIO_DRIVE_DEFAULT`    | Use the platform’s default drive mode (typically push-pull).       |
| `GPIO_DRIVE_PUSH_PULL`  | Actively drives the line HIGH or LOW (standard output).            |
| `GPIO_DRIVE_OPEN_DRAIN` | Only drives LOW; releases the line for external pull-up when HIGH. |

When using `GPIO_DRIVE_OPEN_DRAIN`, writing `LOW` drives the line low, while writing `HIGH` releases it (requires a pull-up resistor).

#### Digital Interrupt Edge (Input)

Defines which signal transitions generate an interrupt event.

| Constant            |                                          |
|---------------------|------------------------------------------|
| `GPIO_EDGE_NONE`    | No interrupt triggered.                  |
| `GPIO_EDGE_RISING`  | Trigger on LOW to HIGH transition.       |
| `GPIO_EDGE_FALLING` | Trigger on HIGH to LOW transition.       |
| `GPIO_EDGE_CHANGE`  | Trigger on any change (both directions). |

Interrupt edges are polled via `pinInterruptState()`, which returns a `GPIO_EDGE` and clears the latched state.

#### Bit Order

Specifies the bit transmission order for `pinShiftIn()` and `pinShiftOut()` operations.

| Constant        |                              |
|-----------------|------------------------------|
| `GPIO_MSBFIRST` | Most-significant bit first.  |
| `GPIO_LSBFIRST` | Least-significant bit first. |

## Application Programming Interface (API)

```
//--------------------------------------------------------------------------
// brawl:device/gpio

// GPIO errors
enum GPIO_ERROR {
                         // >= 0              no error
  GPIO_ERROR_BUSY           = -1,          // GPIO pin is in use
  GPIO_ERROR_UNSUPPORTED    = -2,          // GPIO pin action unsupported
  GPIO_ERROR_TIMEOUT        = -3,          // GPIO pin operation timed out
  GPIO_ERROR_INVALID_HANDLE = -4,          // invalid handle
  GPIO_ERROR_INVALID_ARG    = -5           // invalid argument
};

// GPIO capabilities (flags)
enum GPIO_CAPS {
  GPIO_CAPS_DIGITAL_IN      = (1UL << 0),  // supports digitalRead()
  GPIO_CAPS_DIGITAL_OUT     = (1UL << 1),  // supports digitalWrite()
  GPIO_CAPS_ANALOG_IN       = (1UL << 2),  // supports analogRead()
  GPIO_CAPS_ANALOG_OUT      = (1UL << 3),  // supports analogWrite()
  GPIO_CAPS_PULL_UP         = (1UL << 4),  // DIGITAL IN: pull up
  GPIO_CAPS_PULL_DOWN       = (1UL << 5),  // DIGITAL IN: pull down
  GPIO_CAPS_PUSH_PULL       = (1UL << 6),  // DIGITAL OUT: push/pull between GND and VCC
  GPIO_CAPS_OPEN_DRAIN      = (1UL << 7),  // DIGITAL OUT: drives LOW, external resistor drives HIGH
  GPIO_CAPS_PWM             = (1UL << 8),  // supports pulse width modulation
  GPIO_CAPS_IRQ_RISING      = (1UL << 9),  // irq: on LOW   -> HIGH transition
  GPIO_CAPS_IRQ_FALLING     = (1UL << 10), // irq: on HIGH  -> LOW  transition
  GPIO_CAPS_IRQ_CHANGE      = (1UL << 11), // irq: on HIGH <-> LOW  transition
  GPIO_CAPS_LED             = (1UL << 12)  // pin is connected to an LED
};

// digital state
enum GPIO_STATE {
  GPIO_STATE_LOW            = 0,           // GND
  GPIO_STATE_HIGH           = 1            // VCC (3v3 or 5V)
};

// digital pull (input)
enum GPIO_PULL {
  GPIO_PULL_NONE            = 0,
  GPIO_PULL_UP              = 1,           // internal pull up
  GPIO_PULL_DOWN            = 2            // internal pull down
};

// digital drive (output)
enum GPIO_DRIVE {
  GPIO_DRIVE_DEFAULT        = 0,
  GPIO_DRIVE_PUSH_PULL      = 1,           // connected to either (GND or VCC)
  GPIO_DRIVE_OPEN_DRAIN     = 2            // drives LOW, external resistor drives HIGH
};

// digital interrupt edge (input)
enum GPIO_EDGE {
  GPIO_EDGE_NONE            = 0,
  GPIO_EDGE_RISING          = 1,           // trigger interrupt on change from LOW  -> HIGH
  GPIO_EDGE_FALLING         = 2,           // trigger interrupt on change from HIGH -> LOW
  GPIO_EDGE_CHANGE          = 3            // trigger interrupt on any change
};

// bit order
enum GPIO_BITORDER {
  GPIO_MSBFIRST             = 0,           // most significant bit first
  GPIO_LSBFIRST             = 1            // least significant bit first
};
```

#### Discovery and Mode

```
int32_t // GPIO_ERROR|int32_t
pinCount(void);

int32_t // GPIO_ERROR|int32_t
pinList(int32_t mem_ptr, int32_t max_items);

int32_t // GPIO_ERROR|GPIO_CAPS
pinCapabilities(int32_t pinNum);

int32_t // GPIO_ERROR|int32_t
pinOpen(int32_t pinNum);

int32_t // GPIO_ERROR|void
pinModeIn(int32_t handle, enum GPIO_PULL pull);

int32_t // GPIO_ERROR|void
pinModeOut(int32_t handle, enum GPIO_DRIVE drive);

int32_t // GPIO_ERROR|void
pinClose(int32_t handle);
```

#### Analog

```
int32_t // GPIO_ERROR|void
pinAnalogReadResolution(int32_t handle, int32_t bits);

int32_t // GPIO_ERROR|void
pinAnalogWriteResolution(int32_t handle, int32_t bits);

int32_t // GPIO_ERROR|int32_t
pinAnalogRead(int32_t handle);

int32_t // GPIO_ERROR|int32_t
pinAnalogReference(int32_t handle, int32_t mV);

int32_t // GPIO_ERROR|void
pinAnalogWrite(int32_t handle, int32_t value);
```

#### Digital

```
int32_t // GPIO_ERROR|GPIO_STATE
pinDigitalRead(int32_t handle);

int32_t // GPIO_ERROR|void
pinDigitalWrite(int32_t handle, enum GPIO_STATE state);

int32_t // GPIO_ERROR|void
pinAttachInterrupt(int32_t handle, enum GPIO_EDGE edge);

int32_t // GPIO_ERROR|void
pinDetachInterrupt(int32_t handle);

int32_t // GPIO_ERROR|GPIO_EDGE
pinInterruptState(int32_t handle);
```

#### PWM / bit-banging

```
int32_t // GPIO_ERROR|void
pinToneStart(int32_t handle, int32_t freq, int32_t duration);

int32_t // GPIO_ERROR|void
pinToneStop(int32_t handle);

int32_t // GPIO_ERROR|int32_t
pinPulseIn(int32_t handle, enum GPIO_STATE state, int32_t timeout_us);

int32_t // GPIO_ERROR|int32_t
pinShiftIn(int32_t handle_data, int32_t handle_clock, int32_t bit_count, enum GPIO_BITORDER bit_order);

int32_t // GPIO_ERROR|void
pinShiftOut(int32_t handle_data, int32_t handle_clock, int32_t bit_count, enum GPIO_BITORDER bit_order, int32_t value);
```

## Example - LED Blink (C)

```
#include <stdint.h>
#include <stdio.h>

#include "brawl-device-gpio.h"

#define MAX_PINS 32

static void gpio_err(int32_t rc, const char *what) {
    if (rc < 0) fprintf(stderr, "%s failed: %d\n", what, rc);
}

int main(void) {

    int32_t n, rc;
    uint8_t ids[MAX_PINS];
    int32_t led_pin, led_h;
    int32_t caps;
    enum GPIO_DRIVE drive;

    // enumerate pins (WASM MVP: caller-allocated buffer)
    n = pinCount();
    if (n < 0) { gpio_err(n, "pinCount"); return 1; }
    if (n > MAX_PINS) n = MAX_PINS;

    rc = pinList((int32_t)(uintptr_t)ids, n);
    if (rc < 0) { gpio_err(rc, "pinList"); return 1; }

    // find a pin that supports LED
    led_pin = -1;
    for (int i = 0; i < rc; ++i) {
        caps = pinCapabilities(ids[i]);
        if (caps < 0) continue;

        // check the capabilities, break on first LED found
        if (caps & GPIO_CAPS_LED) { led_pin = ids[i]; break; }
    }

    // did we find an LED?
    if (led_pin >= 0) {

        // open and configure drive mode (prefer open drain if advertised)
        led_h = pinOpen(led_pin);
        if (led_h < 0) { gpio_err(led_h, "pinOpen"); return 1; }

        caps = pinCapabilities(led_pin);
        drive = (caps & GPIO_CAPS_OPEN_DRAIN) ? GPIO_DRIVE_OPEN_DRAIN : GPIO_DRIVE_PUSH_PULL;

        rc = pinModeOut(h, drive);
        if (rc < 0) { gpio_err(rc, "pinModeOut"); pinClose(led_h); return 1; }

        // this is where we do the business, toggle the pin repeatedly
        while (1)
        {
            // toggle the pin safely with a delay
            rc = pinDigitalWrite(led_h, GPIO_STATE_HIGH);
            if (rc < 0) gpio_err(rc, "pinDigitalWrite(GPIO_STATE_HIGH)");
            delay(250);

            // toggle the pin safely with a delay
            rc = pinDigitalWrite(led_h, GPIO_STATE_LOW);
            if (rc < 0) gpio_err(rc, "pinDigitalWrite(GPIO_STATE_LOW)");
            delay(250);
        }

        // close access to the pin (shouldn't get here)
        rc = pinClose(led_h);
        if (rc < 0) gpio_err(rc, "pinClose");
    }

    // if we get here, we couldn't find an LED
    return 1;
}
```

#### Revision

2025-10 (draft)
