![]() |
HOME
–
BLOG
–
CONTACT
–
ABOUT
|
HOME / BRAWL
brawl:device/uart
The brawl:device/uart
interface defines a portable, minimal Universal Serial Receiver/Transmitter (UART) API for WebAssembly (WASM) modules. It provides a consistent way to control and monitor UARTs 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 UART devices without callbacks, threads, or host-specific extensions.
This section outlines the expected behavior and conventions used by the brawl:device/uart
interface. It provides guidance for both host implementers and guest developers to ensure consistent behavior across WASM targets.
uartCount()
returns the total number of UARTs available on the target. uartList()
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 uartOpen()
to obtain a handle for further operations.
Before utilizing a UART, always call uartCapabilities()
to verify that it supports the desired modes. Use uartOpen()
to reserve the UART and obtain a handle; this handle is passed to all subsequent operations. When the UART is no longer needed, release it using uartClose()
. UARTs remain inactive until configured with calls to uartSetBaud()
, uartSetFormat()
, uartSetFlow()
, uartSetTimeouts()
, uartSetBuffers()
, uartSetLoopback()
and optionally uartSetRS485()
- to define the UART behavior and electrical characteristics.
A software UART, TX/RX can be synthesized on GPIO pins by toggling and sampling at fixed baud timing. The host reserves the pins and implements 8N1 framing in software. Maximum reliable baud depends on timing jitter of the host. Once opened, the software UART handle behaves the same as a hardware handle for I/O and configuration calls.
To detect if the host supports a software implementation, call uartModuleCapabilities()
check for the UART_CAPS_SW_PORT
capability, a UART can then be created using the uartOpenOnPins()
function.
All brawl:device/uart
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., UART data or handle). |
All memory access operations are bounds-checked by the host; going outside will return a UART_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 | |
---|---|
UART_ERROR_BUSY |
The UART is already in use or reserved by another handle. |
UART_ERROR_UNSUPPORTED |
The requested operation or capability is not supported on this UART. |
UART_ERROR_TIMEOUT |
The operation did not complete within the expected time. |
UART_ERROR_INVALID_HANDLE |
The handle provided was invalid for the operation requested. |
UART_ERROR_INVALID_ARG |
The argument provided was invalid for the operation requested. |
UART_ERROR_MEMORY_OOB |
The requested operation would result in a MEMORY_OOB Error. |
A return value >= 0
indicates success - either 0
for non-returning operations or a positive value for operations that produce a result.
Each UART can support one or more capabilities, reported by uartModuleCapabilities()
and uartCapabilities()
. These are bit-flags and may be combined using bitwise OR.
Constant (Flag) | |
---|---|
UART_CAPS_BAUD_CUSTOM |
Supports arbitrary baud rates (not just presets). |
UART_CAPS_DATABITS_5 |
Supports 5 data bits. |
UART_CAPS_DATABITS_6 |
Supports 6 data bits. |
UART_CAPS_DATABITS_7 |
Supports 7 data bits. |
UART_CAPS_DATABITS_8 |
Supports 8 data bits. |
UART_CAPS_DATABITS_9 |
Supports 9 data bits. |
UART_CAPS_PARITY_NONE |
Supports no parity. |
UART_CAPS_PARITY_EVEN |
Supports even parity. |
UART_CAPS_PARITY_ODD |
Supports odd parity. |
UART_CAPS_STOPBITS_1 |
Supports 1 stop bit. |
UART_CAPS_STOPBITS_2 |
Supports 2 stop bits. |
UART_CAPS_FLOW_RTSCTS |
Supports hardware flow control. |
UART_CAPS_FLOW_XONXOFF |
Supports software flow control. |
UART_CAPS_MODEM_LINES |
Supports DTR/DSR/DCD/RI signals. |
UART_CAPS_RS485 |
Supports RS-485 protocol |
UART_CAPS_DMA |
Supports DMA / large buffer transfers. |
UART_CAPS_LOOPBACK |
Supports internal loopback. |
UART_CAPS_SW_PORT |
Supports creating a UART on arbitrary GPIO pins. |
Combine capability flags with the bitwise OR operator (|
) to describe a UART that supports multiple features.
Represents the parity of the UART.
Constant | |
---|---|
UART_PARITY_NONE |
No parity. |
UART_PARITY_EVEN |
Even parity. |
UART_PARITY_ODD |
Odd parity. |
Configures the stop bits of the UART.
Constant | |
---|---|
UART_STOPBITS_1 |
1 stop bit. |
UART_STOPBITS_2 |
2 stop bits. |
Configures the flow control of the UART.
Constant | |
---|---|
UART_FLOW_OFF |
No flow control. |
UART_FLOW_RTSCTS |
Hardware flow control. |
UART_FLOW_XONXOFF |
Software flow control. |
Flush the UART.
Constant | |
---|---|
UART_FLUSH_RX |
Flush only RX buffer. |
UART_FLUSH_TX |
Flush only TX buffer. |
UART_FLUSH_BOTH |
Flush Both RX/TX buffers. |
Specifies the line status of the UART, these are bit-flags and may be combined using bitwise OR.
Constant (Flag) | |
---|---|
UART_STATUS_OVERRUN |
Receiver overrun (data lost). |
UART_STATUS_PARITY |
Parity error. |
UART_STATUS_FRAMING |
Framing error (bad stop bit). |
UART_STATUS_BREAK |
Break condition being detected. |
Specifies the modem signals of the UART, these are bit-flags and may be combined using bitwise OR.
Constant (Flag) | |
---|---|
UART_SIGNAL_CTS |
Clear to send. |
UART_SIGNAL_DSR |
Data set ready. |
UART_SIGNAL_DCD |
Data carrier detect. |
UART_SIGNAL_RI |
Ring indicator. |
UART_SIGNAL_RTS |
Ready to send. |
UART_SIGNAL_DTR |
Data terminal ready. |
//--------------------------------------------------------------------------
// brawl:device/uart
// UART errors
enum UART_ERROR {
// >= 0 no error
UART_ERROR_BUSY = -1, // port already open / TX in progress (where exclusive)
UART_ERROR_UNSUPPORTED = -2, // feature not supported on this UART
UART_ERROR_TIMEOUT = -3, // RX/TX timeout
UART_ERROR_INVALID_HANDLE = -4, // invalid handle
UART_ERROR_INVALID_ARG = -5, // invalid argument
UART_ERROR_MEMORY_OOB = -6 // memory OOB
};
// UART capabilities (flags)
enum UART_CAPS {
UART_CAPS_BAUD_CUSTOM = (1u << 0), // arbitrary baud rates (not just presets)
UART_CAPS_DATABITS_5 = (1u << 1),
UART_CAPS_DATABITS_6 = (1u << 2),
UART_CAPS_DATABITS_7 = (1u << 3),
UART_CAPS_DATABITS_8 = (1u << 4),
UART_CAPS_DATABITS_9 = (1u << 5),
UART_CAPS_PARITY_NONE = (1u << 6),
UART_CAPS_PARITY_EVEN = (1u << 7),
UART_CAPS_PARITY_ODD = (1u << 8),
UART_CAPS_STOPBITS_1 = (1u << 9),
UART_CAPS_STOPBITS_2 = (1u << 10),
UART_CAPS_FLOW_RTSCTS = (1u << 11), // hardware flow control
UART_CAPS_FLOW_XONXOFF = (1u << 12), // software flow control
UART_CAPS_MODEM_LINES = (1u << 13), // DTR/DSR/DCD/RI signals
UART_CAPS_RS485 = (1u << 14), // RS-485 driver enable support
UART_CAPS_DMA = (1u << 15), // DMA / large buffer transfers
UART_CAPS_LOOPBACK = (1u << 16), // internal loopback
UART_CAPS_SW_PORT = (1u << 17) // create a UART on arbitrary GPIO pins
};
// UART parity
enum UART_PARITY {
UART_PARITY_NONE = 0,
UART_PARITY_EVEN = 1,
UART_PARITY_ODD = 2
};
// UART stopbits
enum UART_STOPBITS {
UART_STOPBITS_1 = 0,
UART_STOPBITS_2 = 1
};
// UART flow control
enum UART_FLOW {
UART_FLOW_OFF = 0,
UART_FLOW_RTSCTS = 1,
UART_FLOW_XONXOFF = 2
};
// UART flush
enum UART_FLUSH {
UART_FLUSH_RX = 1,
UART_FLUSH_TX = 2,
UART_FLUSH_BOTH = 3
};
// UART status
enum UART_STATUS {
UART_STATUS_OVERRUN = (1u << 0),
UART_STATUS_PARITY = (1u << 1),
UART_STATUS_FRAMING = (1u << 2),
UART_STATUS_BREAK = (1u << 3)
};
// UART signal
enum UART_SIGNAL {
// get
UART_SIGNAL_CTS = (1u << 0),
UART_SIGNAL_DSR = (1u << 1),
UART_SIGNAL_DCD = (1u << 2),
UART_SIGNAL_RI = (1u << 3),
// set/clear
UART_SIGNAL_RTS = (1u << 8),
UART_SIGNAL_DTR = (1u << 9)
};
int32_t // UART_ERROR|UART_CAPS
uartModuleCapabilities(void);
int32_t // UART_ERROR|int32_t
uartCount(void);
int32_t // UART_ERROR|int32_t
uartList(int32_t mem_ptr, int32_t max_items);
int32_t // UART_ERROR|UART_CAPS
uartCapabilities(int32_t uartBus);
int32_t // UART_ERROR|int32_t
uartOpen(int32_t uartBus);
int32_t // UART_ERROR|int32_t
uartOpenOnPins(int32_t tx_pin, int32_t rx_pin);
int32_t // UART_ERROR|void
uartSetBaud(int32_t handle, int32_t baud);
int32_t // UART_ERROR|void
uartSetFormat(int32_t handle, int32_t databits, enum UART_PARITY parity, enum UART_STOPBITS stopbits);
int32_t // UART_ERROR|void
uartSetFlow(int32_t handle, enum UART_FLOW flow);
int32_t // UART_ERROR|void
uartSetTimeouts(int32_t handle, int32_t rx_timeout_us, int32_t tx_timeout_us);
int32_t // UART_ERROR|void
uartSetBuffers(int32_t handle, int32_t rx_bytes, int32_t tx_bytes);
int32_t // UART_ERROR|void
uartSetLoopback(int32_t handle, int32_t enable);
int32_t // UART_ERROR|void
uartSetRS485(int32_t handle, int32_t enable, int32_t rts_active_high);
int32_t // UART_ERROR|void
uartSetRS485Timing(int32_t handle, int32_t pre_us, int32_t post_us);
int32_t // UART_ERROR|void
uartClose(int32_t handle);
int32_t // UART_ERROR|int32_t
uartWrite(int32_t handle, int32_t tx_ptr, int32_t len, int32_t timeout_us);
int32_t // UART_ERROR|int32_t
uartRead(int32_t handle, int32_t rx_ptr, int32_t len, int32_t timeout_us);
int32_t // UART_ERROR|int32_t
uartAvailable(int32_t handle);
int32_t // UART_ERROR|int32_t
uartTxSpace(int32_t handle);
int32_t // UART_ERROR|int32_t
uartFlush(int32_t handle, enum UART_FLUSH which);
int32_t // UART_ERROR|void
uartSendBreak(int32_t handle, int32_t duration_us);
int32_t // UART_ERROR|UART_STATUS
uartGetLineStatus(int32_t handle);
int32_t // UART_ERROR|UART_SIGNAL
uartGetSignals(int32_t handle);
int32_t // UART_ERROR|void
uartSetSignals(int32_t handle, int32_t set_mask, int32_t clear_mask);
#include <stdint.h>
#include <stdio.h>
#include "brawl-device-uart.h"
static void uart_err(int32_t rc, const char *what) {
if (rc < 0) fprintf(stderr, "%s failed: %d\n", what, rc);
}
#define MAX_UARTS 8
int main(void) {
int32_t uart_h, rc, n;
uint8_t buses[MAX_UARTS];
const char *tx = "hello, uart!\r\n";
uint8_t rx[64];
// enumerate UARTs (WASM MVP: caller-allocated buffer)
n = uartCount();
if (n < 0) { uart_err(n, "uartCount"); return 1; }
if (n > MAX_UARTS) n = MAX_UARTS;
rc = uartList((int32_t)(uintptr_t)buses, n);
if (rc < 0) { uart_err(rc, "uartList"); return 1; }
// do we have any UART's?
if (rc > 0) {
// open first UART
uart_h = uartOpen(buses[0]);
if (uart_h < 0) { uart_err(uart_h, "uartOpen"); return 1; }
// configure: 115200 baud, 8N1, no flow; set sane timeouts/buffers
rc = uartSetBaud(uart_h, 115200);
if (rc < 0) { uart_err(rc, "uartSetBaud"); goto done; }
rc = uartSetFormat(uart_h, 8, UART_PARITY_NONE, UART_STOPBITS_1);
if (rc < 0) { uart_err(rc, "uartSetFormat"); goto done; }
rc = uartSetFlow(uart_h, UART_FLOW_OFF);
if (rc < 0) { uart_err(rc, "uartSetFlow"); goto done; }
// configure per-transfer timeout default and buffer sizes
rc = uartSetTimeouts(uart_h, 20000, 20000); // 20 ms RX/TX
if (rc < 0) { uart_err(rc, "uartSetTimeouts"); goto done; }
rc = uartSetBuffers(uart_h, 1024, 1024);
if (rc < 0) { uart_err(rc, "uartSetBuffers"); goto done; }
// write a line
rc = uartWrite(uart_h, (int32_t)(uintptr_t)tx, (int32_t)strlen(tx), 20000);
if (rc < 0) { uart_err(rc, "uartWrite"); goto done; }
// try to read any echo/response (up to 64 bytes, 50 ms timeout)
rc = uartRead(uart_h, (int32_t)(uintptr_t)rx, (int32_t)sizeof(rx), 50000);
if (rc < 0) { uart_err(rc, "uartRead"); }
// check sticky line status (clears on read)
rc = uartGetLineStatus(uart_h);
if (rc < 0) { uart_err(rc, "uartGetLineStatus"); }
done:
// close
rc = uartClose(uart_h);
if (rc < 0) { uart_err(rc, "uartClose"); return 1; }
}
return 0;
}
2025-10 (draft)