![]() |
HOME
–
BLOG
–
CONTACT
–
ABOUT
|
HOME / BRAWL
brawl:device/i2c
The brawl:device/i2c
interface defines a portable, minimal I2C (Wire) API for WebAssembly (WASM) modules. It provides a consistent way to control and monitor I2C buses 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 I2C devices without callbacks, threads, or host-specific extensions.
This section outlines the expected behavior and conventions used by the brawl:device/i2c
interface. It provides guidance for both host implementers and guest developers to ensure consistent behavior across WASM targets.
i2cCount()
returns the total number of I2C buses available on the target. i2cList()
populates a memory buffer with a list of available bus identifiers (uint8_t
) in the host environment’s canonical order. Each bus identifier can be passed directly to i2cOpen()
to obtain a handle for further operations.
Before utilizing an I2C bus, always call i2cCapabilities()
to verify that it supports the desired modes. Use i2cOpen()
to reserve the I2C bus and obtain a handle; this handle is passed to all subsequent operations. When the I2C bus is no longer needed, release it using i2cClose()
. I2C buses remain inactive until configured with calls to i2cSetFrequency()
, i2cSetTimeout()
and i2cSet10Bit()
- to define the I2C bus behavior and electrical characteristics.
I2C can be implemented on any two GPIO pins by emulating open-drain signalling: the host drives lines low or releases them to pull-ups. Clock-stretching and 10-bit addressing are supported only if the host declares them in capabilities. Existing read/write and write-then-read calls operate transparently on the software bus handle. Pull-ups may be external or, if available, enabled by the host internally.
To detect if the host supports a software implementation, call i2cModuleCapabilities()
check for the I2C_CAPS_SW_PORT
capability, a I2C bus can then be created using the i2cOpenOnPins()
function.
All brawl:device/i2c
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., I2C bus data or handle). |
All memory access operations are bounds-checked by the host; going outside will return a I2C_ERROR_MEMORY_OOB
error.
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 | |
---|---|
I2C_ERROR_BUSY |
The I2C bus is already in use or reserved by another handle. |
I2C_ERROR_UNSUPPORTED |
The requested operation or capability is not supported on this I2C bus. |
I2C_ERROR_TIMEOUT |
The operation did not complete within the expected time. |
I2C_ERROR_INVALID_HANDLE |
The handle provided was invalid for the operation requested. |
I2C_ERROR_INVALID_ARG |
The argument provided was invalid for the operation requested. |
I2C_ERROR_MEMORY_OOB |
The requested operation would result in a MEMORY_OOB Error. |
I2C_ERROR_NACK |
The operation resulted in the address or data being NACK'd. |
I2C_ERROR_ARB_LOST |
The operation arbitation was lost. |
I2C_ERROR_BUS_ERROR |
The operation incurred an illegal START/STOP or line error. |
A return value >= 0
indicates success - either 0
for non-returning operations or a positive value for operations that produce a result.
Each I2C bus can support one or more capabilities, reported by i2cModuleCapabilities()
and i2cCapabilities()
. These are bit-flags and may be combined using bitwise OR.
Constant (Flag) | |
---|---|
I2C_CAPS_CONTROLLER |
Supports master/controller mode. |
I2C_CAPS_DEVICE |
Supports device/slave mode (optional, future). |
I2C_CAPS_ADDR_10BIT |
Supports 10-bit addressing. |
I2C_CAPS_GENERAL_CALL |
Supports 0x00 general call. |
I2C_CAPS_CLOCK_STRETCH |
Supports clock stretching. |
I2C_CAPS_SMBUS |
Supports SMBus timing/transactions. |
I2C_CAPS_SMBUS_PEC |
Supports SMBus Packet Error Code. |
I2C_CAPS_SPEED_STD |
Supports 100Hz. |
I2C_CAPS_SPEED_FAST |
Supports 400Hz. |
I2C_CAPS_SPEED_FASTP |
Supports 1MHz (Fast Mode Plus). |
I2C_CAPS_SPEED_HIGH |
Supports 3.4MHz (High Speed). |
I2C_CAPS_DMA |
Supports DMA or large-buffer transfers. |
I2C_CAPS_RECOVER_BUS |
Can attempt bus recovery (toggle SCL etc). |
I2C_CAPS_SW_PORT |
Supports creating a I2C bus on arbitrary GPIO pins. |
Combine capability flags with the bitwise OR operator (|
) to describe an I2C bus that supports multiple features.
//--------------------------------------------------------------------------
// brawl:device/i2c
// I2C errors
enum I2C_ERROR {
// >= 0 no error
I2C_ERROR_BUSY = -1, // bus/controller busy
I2C_ERROR_UNSUPPORTED = -2, // feature not supported
I2C_ERROR_TIMEOUT = -3, // transfer timed out
I2C_ERROR_INVALID_HANDLE = -4, // invalid handle
I2C_ERROR_INVALID_ARG = -5, // invalid argument
I2C_ERROR_MEMORY_OOB = -6, // memory OOB
I2C_ERROR_NACK = -7, // address or data NACKed
I2C_ERROR_ARB_LOST = -8, // arbitration lost
I2C_ERROR_BUS_ERROR = -9 // illegal START/STOP or line error
};
// I2C capabilities (flags)
enum I2C_CAPS {
I2C_CAPS_CONTROLLER = (1u << 0), // master role
I2C_CAPS_DEVICE = (1u << 1), // (optional future) target/slave role
I2C_CAPS_ADDR_10BIT = (1u << 2), // supports 10-bit addressing
I2C_CAPS_GENERAL_CALL = (1u << 3), // supports 0x00 general call
I2C_CAPS_CLOCK_STRETCH = (1u << 4), // handles clock stretching
I2C_CAPS_SMBUS = (1u << 5), // SMBus timing/transactions
I2C_CAPS_SMBUS_PEC = (1u << 6), // SMBus Packet Error Code
I2C_CAPS_SPEED_STD = (1u << 7), // 100 kHz
I2C_CAPS_SPEED_FAST = (1u << 8), // 400 kHz
I2C_CAPS_SPEED_FASTP = (1u << 9), // 1 MHz (Fast Mode Plus)
I2C_CAPS_SPEED_HIGH = (1u << 10), // 3.4 MHz (High Speed)
I2C_CAPS_DMA = (1u << 11), // large/DMA-friendly transfers
I2C_CAPS_RECOVER_BUS = (1u << 12), // can attempt bus recovery (toggle SCL, etc.)
I2C_CAPS_SW_PORT = (1u << 13) // supports creating a I2C bus on an arbitrary GPIO pin
};
int32_t // I2C_ERROR|I2C_CAPS
i2cModuleCapabilities(void);
int32_t // I2C_ERROR|int32_t
i2cCount(void);
int32_t // I2C_ERROR|int32_t
i2cList(int32_t mem_ptr, int32_t max_items);
int32_t // I2C_ERROR|I2C_CAPS
i2cCapabilities(int32_t i2cBus);
int32_t // I2C_ERROR|int32_t
i2cOpen(int32_t i2cBus);
int32_t // I2C_ERROR|int32_t
i2cOpenOnPins(int32_t sda_pin, int32_t scl_pin);
int32_t // I2C_ERROR|void
i2cSetFrequency(int32_t handle, int32_t hz);
int32_t // I2C_ERROR|void
i2cSetTimeout(int32_t handle, int32_t timeout_us);
int32_t // I2C_ERROR|void
i2cSet10Bit(int32_t handle, int32_t enable);
int32_t // I2C_ERROR|void
i2cClose(int32_t handle);
int32_t // I2C_ERROR|void
i2cWrite(int32_t handle, int32_t addr, int32_t tx_ptr, int32_t len, int32_t stop, int32_t timeout_us);
int32_t // I2C_ERROR|void
i2cRead(int32_t handle, int32_t addr, int32_t rx_ptr, int32_t len, int32_t stop, int32_t timeout_us);
int32_t // I2C_ERROR|void
i2cWriteRead(int32_t handle, int32_t addr, int32_t tx_ptr, int32_t tx_len, int32_rx_ptr, int32_t rx_len, int32_t timeout_us);
int32_t // I2C_ERROR|void
i2cScan(int32_t handle, int32_t dst_ptr, int32_t max_items);
int32_t // I2C_ERROR|void
i2cRecoverBus(int32_t handle);
#include <stdint.h>
#include <stdio.h>
#include "brawl-device-i2c.h"
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 i2c_h, rc, n;
uint8_t buses[8];
uint8_t reg, val;
// enumerate i2c buses (WASM MVP: caller-allocated buffer)
n = i2cCount();
if (n < 0) { gpio_err(rc, "i2cCount"); return 1; }
if (n > 8) n = 8;
rc = i2cList((int32_t)(uintptr_t)buses, n);
if (rc < 0) { gpio_err(rc, "i2cList"); return 1; }
// do we have any I2C buses?
if (rc > 0) {
// open first available I2C bus
i2c_h = i2cOpen(buses[0]);
if (i2c_h < 0) { gpio_err(rc, "i2cOpen"); return 1; }
// configure i2c bus for 400kHz and 7-bit
rc = i2cSetFrequency(h, 400000);
if (rc < 0) { gpio_err(rc, "i2cSetFrequency"); i2cClose(i2c_h); return 1; }
rc = i2cSet10Bit(h, false);
if (rc < 0) { gpio_err(rc, "i2cSet10Bit"); i2cClose(i2c_h); return 1; }
// configure per-transfer timeout default
rc = i2cSetTimeout(h, 2000);
if (rc < 0) { gpio_err(rc, "i2cSetTimeout"); i2cClose(i2c_h); return 1; }
// read WHOAMI register (0x0F) from device 0x1C
reg = 0x0F;
val = 0;
rc = i2cWriteRead(i2c_h, 0x1C, (int32_t)(uintptr_t)®, 1, (int32_t)(uintptr_t)&val, 1, 2000);
if (rc < 0) { gpio_err(rc, "i2cWriteRead"); i2cClose(i2c_h); return 1; }
i2cClose(h);
if (rc < 0) { gpio_err(rc, "i2cClose"); return 1; }
}
return 0;
}
2025-10 (draft)