![]() |
HOME
–
BLOG
–
CONTACT
–
ABOUT
|
HOME / BRAWL
brawl:device/gpio
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.
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.
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.
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.
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()
).
pinPulseIn()
returns the duration of a pulse in microseconds (µs). Frequencies and PWM parameters are expressed in Hertz (Hz).
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.
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). |
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.
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.
Represents the logical level of a digital pin.
Constant | |
---|---|
GPIO_STATE_LOW |
Logic LOW / GND. |
GPIO_STATE_HIGH |
Logic HIGH / VCC (3V3 or 5V). |
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. |
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).
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.
Specifies the bit transmission order for pinShiftIn()
and pinShiftOut()
operations.
Constant | |
---|---|
GPIO_MSBFIRST |
Most-significant bit first. |
GPIO_LSBFIRST |
Least-significant bit first. |
//--------------------------------------------------------------------------
// 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
};
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);
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);
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);
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);
#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;
}
2025-10 (draft)