UART (Serial / RS-232 / RS-485)¶
Overview¶
Universal Asynchronous Receiver/Transmitter (UART) provides serial communication over RS-232/RS-485 physical layers. This implementation uses Linux termios for direct device file configuration with support for all standard baud rates up to 4 Mbaud.
Status: Implemented
Full implementation with configurable baud, parity, flow control, non-blocking I/O, and modem line control.
Protocol Specification¶
Frame Format¶
┌───────┬────────────────────────────────┬────────┬──────────┐
│ Start │ Data Bits (5-8) │ Parity │ Stop Bits│
│ (1) │ [D0] [D1] [D2] ... [D7] │ (0-1) │ (1-2) │
└───────┴────────────────────────────────┴────────┴──────────┘
LOW LSB first HIGH
Idle state: HIGH (mark)
Start bit: LOW (space) signals beginning of frame
Data bits: 5, 6, 7, or 8 bits, LSB first
Parity: None, Odd, or Even (optional error detection)
Stop bits: 1 or 2 HIGH bits signals end of frame
Timing (115200 baud, 8N1)¶
1 bit = 8.68 µs
┌───────────────────────────────┐
│ │
─────┐ ┌───┐ ┌───┐ ┌───┬───┬─────
│ │ │ │ │ │ │ │
└───┘ └───┘ └───┘ │ │
START D0 D1 D2 ... D7 STOP IDLE
Total frame time (10 bits @ 115200): 86.8 µs
API Reference¶
Configuration Types¶
typedef enum {
UART_PARITY_NONE = 0,
UART_PARITY_ODD = 1,
UART_PARITY_EVEN = 2
} uart_parity_t;
typedef enum {
UART_FLOW_NONE = 0,
UART_FLOW_RTSCTS = 1, /* Hardware flow control */
UART_FLOW_XONOFF = 2 /* Software flow control */
} uart_flow_t;
typedef struct {
uint32_t baud_rate; /* Baud rate (e.g., 115200) */
uint8_t data_bits; /* 5, 6, 7, or 8 */
uint8_t stop_bits; /* 1 or 2 */
uart_parity_t parity;
uart_flow_t flow_control;
uint32_t read_timeout_ms; /* VTIME-based read timeout */
} uart_config_t;
typedef struct {
int fd; /* File descriptor */
char device[64]; /* Device path */
uart_config_t config; /* Active configuration */
struct termios orig_termios; /* Original settings (for restore) */
bool is_open;
} uart_t;
Supported Baud Rates¶
| Standard | High Speed | Very High Speed |
|---|---|---|
| 300 | 115200 | 1000000 |
| 1200 | 230400 | 1500000 |
| 2400 | 460800 | 2000000 |
| 4800 | 500000 | 2500000 |
| 9600 | 576000 | 3000000 |
| 19200 | 921600 | 3500000 |
| 38400 | 4000000 | |
| 57600 |
Lifecycle¶
uart_default_config¶
Returns the standard 115200 8N1 configuration (no flow control, 100ms read timeout).
uart_open¶
Open a UART device and apply configuration. Sets raw mode (no echo, no canonical processing, no signal generation).
| Parameter | Type | Description |
|---|---|---|
uart | uart_t * | Handle to initialize |
device | const char * | Device path (e.g., "/dev/ttyS0", "/dev/ttyUSB0") |
config | const uart_config_t * | Baud, parity, flow control settings |
Returns: 0 on success, -errno on failure.
uart_close¶
Drain pending output, restore original terminal settings, and close the device.
Read / Write¶
uart_write¶
Non-blocking write. Returns bytes written or -errno.
uart_write_all¶
Blocking write with timeout. Uses poll() internally to wait for TX readiness.
uart_read¶
Non-blocking read. Returns bytes read, 0 if no data available, or -errno.
uart_read_timeout¶
Read with explicit timeout using poll().
Line Control¶
uart_bytes_available¶
Query bytes waiting in the kernel receive buffer (FIONREAD).
uart_flush¶
Discard pending input/output data.
uart_drain¶
Block until all pending output has been physically transmitted.
uart_send_break¶
Send a break signal (line held LOW for ~250ms).
uart_set_dtr / uart_set_rts¶
Manually control DTR/RTS modem lines.
Usage Examples¶
Basic Loopback Test (PTY)¶
#include "uart.h"
#include <stdio.h>
int main(void)
{
uart_t uart;
uart_config_t cfg = uart_default_config();
cfg.baud_rate = 115200;
/* Use virtual serial port from socat */
int rc = uart_open(&uart, "/dev/pts/3", &cfg);
if (rc < 0) {
fprintf(stderr, "Open failed: %s\n", strerror(-rc));
return 1;
}
/* Write test data */
const char *msg = "Hello UART!";
ssize_t written = uart_write_all(&uart, msg, strlen(msg), 1000);
printf("Wrote %zd bytes\n", written);
/* Read back (loopback) */
char buf[64];
ssize_t n = uart_read_timeout(&uart, buf, sizeof(buf) - 1, 500);
if (n > 0) {
buf[n] = '\0';
printf("Received: '%s'\n", buf);
}
uart_close(&uart);
return 0;
}
RS-485 Half-Duplex with RTS Direction Control¶
#include "uart.h"
/* RS-485: Assert RTS before TX, deassert after TX complete */
ssize_t rs485_send(uart_t *uart, const void *data, size_t len)
{
uart_set_rts(uart, true); /* Enable TX driver */
ssize_t n = uart_write_all(uart, data, len, 1000);
uart_drain(uart); /* Wait for physical TX */
uart_set_rts(uart, false); /* Release bus for RX */
return n;
}
Virtual Serial Port Setup (Testing)¶
# Create a virtual serial port pair
socat -d -d pty,raw,echo=0 pty,raw,echo=0
# Output:
# 2024/01/01 12:00:00 socat[1234] N PTY is /dev/pts/3
# 2024/01/01 12:00:00 socat[1234] N PTY is /dev/pts/4
# Connect your application to /dev/pts/3
# Connect a terminal (minicom/picocom) to /dev/pts/4
Build & Run¶
# Compile
gcc -Wall -Wextra -Werror -O3 -std=c11 -o uart_demo uart.c
# Add user to dialout group (one-time setup)
sudo usermod -aG dialout $USER
# Run
./uart_demo
Prerequisites¶
Device Access
User must be in the dialout group:
Flow Control¶
No flow control. Sender transmits at will. Risk of data loss if receiver can't keep up.
Test Output¶
$ ./build/uart_demo
[uart] Opened /dev/pts/3 (115200 8N1, no flow control)
[uart] Write: 11 bytes → "Hello UART!"
[uart] Read: 11 bytes ← "Hello UART!" (loopback verified)
[uart] Flush test: input buffer cleared
[uart] Break signal sent
[uart] DTR: asserted, RTS: asserted
[PASS] All uart tests passed