| name | uart-driver-dev |
| description | Develop and integrate UART serial drivers on PolarFire SoC. Covers termios configuration, baud rates, framing, blocking/non-blocking modes, and userspace serial communication. Use when creating UART drivers, adding debug serial output, interfacing sensors via serial, or building telemetry streams. |
UART Driver Development
Structured approach for creating UART (serial) drivers on PolarFire SoC Discovery Kit using Linux termios interface.
Quick Start
1. Plan the UART Configuration
Gather from hardware/protocol datasheet:
- Port:
/dev/ttyS0, /dev/ttyUSB0, /dev/ttyAMA0, etc.
- Baud rate: 9600, 38400, 115200, 921600 (common)
- Data bits: Usually 8
- Stop bits: Usually 1
- Parity: None, even, or odd
- Flow control: None, RTS/CTS, or Xon/Xoff
- Protocol: Line-oriented (ASCII), binary frames, or custom
Example configurations:
- Debug console: 115200 8N1
- Sensor serial: 9600 8N1 + Xon/Xoff
- High-speed telemetry: 921600 8N1
2. Create UART Header (serial.h)
#ifndef SERIAL_H
#define SERIAL_H
#include <stdint.h>
#include <stddef.h>
typedef int uart_fd_t;
typedef struct {
const char *port;
uint32_t baud;
int data_bits;
int stop_bits;
int parity;
int flow_control;
} uart_config_t;
uart_fd_t uart_open(const uart_config_t *cfg);
void uart_close(uart_fd_t fd);
int uart_write(uart_fd_t fd, const uint8_t *data, size_t len);
int uart_read(uart_fd_t fd, uint8_t *buf, size_t len);
int uart_read_line(uart_fd_t fd, char *buf, size_t max_len);
#endif
3. Implement UART Control (serial.c)
Include required headers:
#include "serial.h"
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
Speed code mapping:
static uint32_t baud_to_code(uint32_t baud) {
switch (baud) {
case 9600: return B9600;
case 19200: return B19200;
case 38400: return B38400;
case 57600: return B57600;
case 115200: return B115200;
case 230400: return B230400;
case 460800: return B460800;
case 921600: return B921600;
default:
printf("[WARN] Unsupported baud %u, defaulting to B115200\n", baud);
return B115200;
}
}
Core initialization:
uart_fd_t uart_open(const uart_config_t *cfg) {
if (!cfg || !cfg->port) {
printf("[ERROR] Invalid UART config\n");
return -1;
}
int fd = open(cfg->port, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd < 0) {
printf("[ERROR] Failed to open %s: %s\n", cfg->port, strerror(errno));
return -1;
}
struct termios tty;
if (tcgetattr(fd, &tty) != 0) {
printf("[ERROR] tcgetattr failed: %s\n", strerror(errno));
close(fd);
return -1;
}
uint32_t baud_code = baud_to_code(cfg->baud);
cfsetispeed(&tty, baud_code);
cfsetospeed(&tty, baud_code);
tty.c_cflag = (tty.c_cflag & ~CSIZE) | (
cfg->data_bits == 5 ? CS5 :
cfg->data_bits == 6 ? CS6 :
cfg->data_bits == 7 ? CS7 : CS8
);
if (cfg->stop_bits == 2) {
tty.c_cflag |= CSTOPB;
} else {
tty.c_cflag &= ~CSTOPB;
}
if (cfg->parity == 'E') {
tty.c_cflag |= PARENB;
tty.c_cflag &= ~PARODD;
} else if (cfg->parity == 'O') {
tty.c_cflag |= PARENB | PARODD;
} {
tty.c_cflag &= ~(PARENB | PARODD);
}
(cfg->flow_control == ) {
tty.c_cflag |= CRTSCTS;
} (cfg->flow_control == ) {
tty.c_iflag |= IXON | IXOFF;
} {
tty.c_cflag &= ~CRTSCTS;
tty.c_iflag &= ~(IXON | IXOFF);
}
tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tty.c_oflag &= ~OPOST;
tty.c_cc[VTIME] = ;
tty.c_cc[VMIN] = ;
(tcsetattr(fd, TCSANOW, &tty) != ) {
(, strerror(errno));
close(fd);
;
}
(,
cfg->port, cfg->baud, cfg->data_bits, cfg->parity, cfg->stop_bits);
fd;
}
{
(fd >= ) {
close(fd);
}
}
4. Read/Write Operations
Write data:
int uart_write(uart_fd_t fd, const uint8_t *data, size_t len) {
if (fd < 0 || !data) return -1;
ssize_t n = write(fd, data, len);
if (n < 0) {
printf("[ERROR] UART write failed: %s\n", strerror(errno));
return -1;
}
if ((size_t)n != len) {
printf("[WARN] Partial write: %zd / %zu bytes\n", n, len);
}
return (int)n;
}
Read data (non-blocking):
int uart_read(uart_fd_t fd, uint8_t *buf, size_t len) {
if (fd < 0 || !buf) return -1;
ssize_t n = read(fd, buf, len);
if (n < 0) {
if (errno == EAGAIN) {
return 0;
}
printf("[ERROR] UART read failed: %s\n", strerror(errno));
return -1;
}
return (int)n;
}
Read line (ASCII protocol):
int uart_read_line(uart_fd_t fd, char *buf, size_t max_len) {
if (fd < 0 || !buf || max_len == 0) return -1;
size_t i = 0;
while (i < max_len - 1) {
uint8_t ch;
int n = uart_read(fd, &ch, 1);
if (n < 0) return -1;
if (n == 0) return 0;
buf[i++] = ch;
if (ch == '\n') {
buf[i] = '\0';
return (int)i;
}
}
return -1;
}
Hardware Setup
Identify UART Ports
ls -la /dev/tty*
ls -la /dev/ttyUSB*
dmesg | grep -i uart
Check Permissions
groups $USER
sudo usermod -aG dialout $USER
Common Configurations
Debug Console (115200 8N1)
uart_config_t cfg = {
.port = "/dev/ttyUSB0",
.baud = 115200,
.data_bits = 8,
.stop_bits = 1,
.parity = 'N',
.flow_control = 0
};
Sensor Serial (9600 8N1 with Xon/Xoff)
uart_config_t cfg = {
.port = "/dev/ttyUSB0",
.baud = 9600,
.data_bits = 8,
.stop_bits = 1,
.parity = 'N',
.flow_control = 2
};
High-Speed Telemetry (921600 8N1)
uart_config_t cfg = {
.port = "/dev/ttyUSB0",
.baud = 921600,
.data_bits = 8,
.stop_bits = 1,
.parity = 'N',
.flow_control = 1
};
Testing
Unit Test Template
#include "serial.h"
#include <unistd.h>
int main() {
uart_config_t cfg = {
.port = "/dev/ttyUSB0",
.baud = 115200,
.data_bits = 8,
.stop_bits = 1,
.parity = 'N',
.flow_control = 0
};
uart_fd_t fd = uart_open(&cfg);
if (fd < 0) {
printf("✗ Open failed\n");
return 1;
}
printf("✓ Open OK\n");
const char *msg = "Hello UART\r\n";
if (uart_write(fd, (uint8_t *)msg, strlen(msg)) < 0) {
printf("✗ Write failed\n");
return 1;
}
printf("✓ Write OK\n");
sleep(1);
uint8_t buf[256];
int n = uart_read(fd, buf, sizeof(buf));
if (n > 0) {
printf("✓ Read %d bytes: ", n);
fwrite(buf, 1, n, );
();
} {
();
}
uart_close(fd);
();
;
}
Integration into PAT
Place Files
firmware/
├── drivers/
│ └── uart/
│ ├── serial.h
│ ├── serial.c
│ └── README.md
└── include/
└── serial.h
Link in Build System (CMake)
add_library(uart_driver
${PROJECT_SOURCE_DIR}/drivers/uart/serial.c
)
target_include_directories(uart_driver PUBLIC
${PROJECT_SOURCE_DIR}/drivers/uart
)
Usage: Debug Logging
#include "serial.h"
static uart_fd_t debug_fd = -1;
void debug_init(void) {
uart_config_t cfg = {
.port = "/dev/ttyUSB0",
.baud = 115200,
.data_bits = 8,
.stop_bits = 1,
.parity = 'N',
.flow_control = 0
};
debug_fd = uart_open(&cfg);
}
void debug_printf(const char *fmt, ...) {
if (debug_fd < 0) return;
char buf[256];
va_list args;
va_start(args, fmt);
vsnprintf(buf, sizeof(buf), fmt, args);
va_end(args);
uart_write(debug_fd, (uint8_t *)buf, strlen(buf));
}
void debug_close(void) {
if (debug_fd >= 0) {
uart_close(debug_fd);
debug_fd = -1;
}
}
Usage: Sensor Input (Line-Based Protocol)
void sensor_read_loop(uart_fd_t sensor_fd) {
char line[128];
while (1) {
int n = uart_read_line(sensor_fd, line, sizeof(line));
if (n < 0) {
printf("Error reading sensor\n");
break;
}
if (n == 0) {
usleep(10000);
continue;
}
printf("Sensor: %s", line);
float value = atof(line);
process_sensor_reading(value);
}
}
Common Pitfalls
| Issue | Cause | Fix |
|---|
| "Permission denied" | User not in dialout group | Run with sudo or add user to dialout group |
| "Device not found" | Wrong port name | Check ls /dev/tty* and dmesg |
| "Data corrupted" | Baud rate mismatch | Verify both ends match (9600, 115200, etc.) |
| "Reads return empty" | Non-blocking mode + no data | Add timeout/sleep loop or switch to blocking mode |
| "Partial writes" | Buffer full | Retry write with remaining bytes |
| "No echo from device" | Flow control mismatch | Disable or enable RTS/CTS/Xon/Xoff |
Next Steps
- Add blocking mode with configurable timeouts
- Implement CRC/checksum for frame validation
- Add circular buffer for efficient streaming
- Create integration tests with real hardware