1. 串口通信基础与核心概念
串口(Serial Port)作为计算机最古老的外设接口之一,至今仍在工业控制、嵌入式开发等领域发挥着不可替代的作用。UART(Universal Asynchronous Receiver/Transmitter)是实现串口通信的核心硬件模块,它负责将并行数据转换为串行比特流进行传输。
1.1 串口通信物理层特性
典型的串口连接使用RS-232标准,包含以下关键物理特性:
- 电压电平:±3V至±15V(负逻辑,+3V以下为逻辑1)
- 连接器:DB9(9针)或DB25(25针)
- 基本信号线:
- TXD(发送数据)
- RXD(接收数据)
- GND(信号地)
- 可选控制线:RTS/CTS, DSR/DTR等
现代嵌入式设备常使用TTL电平的UART(0V为逻辑0,3.3V/5V为逻辑1),直接通过GPIO引脚连接,省去了电平转换芯片。
1.2 串口通信协议层解析
异步串行通信的核心参数必须通信双方一致:
- 波特率(Baud Rate):300bps到3Mbps不等,常见9600/115200bps
- 数据位(Data Bits):5-9位,通常8位
- 停止位(Stop Bits):1、1.5或2位
- 校验位(Parity):无校验(None)、奇校验(Odd)、偶校验(Even)
数据帧格式示例(8N1):
code复制[Start Bit(0)] [D0] [D1] [D2] [D3] [D4] [D5] [D6] [D7] [Stop Bit(1)]
2. Linux下的串口设备管理
2.1 设备节点与权限管理
Linux系统将串口设备抽象为字符设备文件,通常位于/dev目录下:
- 传统串口:/dev/ttyS0, /dev/ttyS1...
- USB转串口:/dev/ttyUSB0, /dev/ttyUSB1...
- 蓝牙串口:/dev/rfcomm0
- 虚拟终端:/dev/ttyAMA0(树莓派)
查看当前系统串口设备:
bash复制dmesg | grep tty
ls -l /dev/ttyS*
设置普通用户串口访问权限(两种方式):
bash复制# 方式1:加入dialout组
sudo usermod -aG dialout $USER
# 方式2:直接修改设备权限
sudo chmod 666 /dev/ttyS0
2.2 串口工具链详解
Linux提供了丰富的串口调试工具:
- minicom - 功能齐全的终端程序
bash复制sudo apt install minicom
minicom -D /dev/ttyS0 -b 115200
常用快捷键:
- Ctrl+A Z:帮助菜单
- Ctrl+A O:配置菜单
- Ctrl+A Q:退出
- screen - 简易串口终端
bash复制screen /dev/ttyUSB0 115200
退出:Ctrl+A \
- picocom - 轻量级替代方案
bash复制picocom -b 115200 /dev/ttyACM0
退出:Ctrl+A Ctrl+X
- stty - 终端参数配置工具
查看当前设置:
bash复制stty -F /dev/ttyS0 -a
修改波特率:
bash复制stty -F /dev/ttyS0 115200
3. 串口编程实战:C语言实现
3.1 基本操作流程
典型的串口编程步骤:
- 打开设备文件
- 配置串口参数(波特率、数据位等)
- 设置读写模式(阻塞/非阻塞)
- 读写数据
- 关闭设备
3.2 完整示例代码
c复制#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int open_serial_port(const char *device, int baud_rate) {
int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
perror("open_port: Unable to open device");
return -1;
}
struct termios options;
tcgetattr(fd, &options);
// 设置波特率
cfsetispeed(&options, baud_rate);
cfsetospeed(&options, baud_rate);
// 8N1配置
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
// 启用接收
options.c_cflag |= (CLOCAL | CREAD);
// 原始输入模式
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// 原始输出模式
options.c_oflag &= ~OPOST;
// 无超时等待,立即返回
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 0;
if (tcsetattr(fd, TCSANOW, &options) != 0) {
perror("setup_port: Failed to set attributes");
close(fd);
return -1;
}
return fd;
}
int main() {
const char *device = "/dev/ttyUSB0";
int baud_rate = B115200;
int fd = open_serial_port(device, baud_rate);
if (fd < 0) {
return 1;
}
// 发送数据
char *msg = "Hello UART!\n";
write(fd, msg, strlen(msg));
// 接收数据
char buf[256];
while (1) {
int n = read(fd, buf, sizeof(buf));
if (n > 0) {
buf[n] = '\0';
printf("Received: %s", buf);
}
}
close(fd);
return 0;
}
编译命令:
bash复制gcc uart_example.c -o uart_test
3.3 关键参数详解
-
打开标志位:
- O_RDWR:读写模式
- O_NOCTTY:防止设备成为控制终端
- O_NDELAY:非阻塞模式(可省略使用fcntl设置)
-
termios结构体关键字段:
- c_cflag:控制模式标志
- CLOCAL:忽略调制解调器状态
- CREAD:启用接收
- c_lflag:本地模式标志
- ICANON:规范模式
- ISIG:启用信号
- c_cc:特殊控制字符
- VMIN:最小读取字符数
- VTIME:超时时间(十分之一秒)
- c_cflag:控制模式标志
-
波特率常量:
- B9600, B19200, B38400, B57600, B115200等
4. 高级应用与故障排查
4.1 流控制实现
硬件流控制(RTS/CTS)配置:
c复制options.c_cflag |= CRTSCTS; // 启用硬件流控
软件流控制(XON/XOFF)配置:
c复制options.c_iflag |= (IXON | IXOFF | IXANY); // 启用软件流控
4.2 多路复用与异步I/O
使用select实现多路复用:
c复制fd_set read_fds;
struct timeval timeout = {1, 0}; // 1秒超时
FD_ZERO(&read_fds);
FD_SET(fd, &read_fds);
int ret = select(fd+1, &read_fds, NULL, NULL, &timeout);
if (ret > 0 && FD_ISSET(fd, &read_fds)) {
// 有数据可读
}
4.3 常见问题排查指南
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 打开设备失败 | 权限不足/设备不存在 | 检查/dev下设备节点,确认用户组 |
| 发送数据无响应 | 波特率不匹配/线序错误 | 确认两端参数一致,检查TX/RX交叉 |
| 接收乱码 | 数据位/停止位设置错误 | 确认8N1等参数配置 |
| 数据丢失 | 无流控导致缓冲区溢出 | 启用硬件流控或降低波特率 |
| 随机断开 | USB转串口电源不稳 | 使用带外部供电的转换器 |
4.4 性能优化技巧
- 缓冲区设置:
c复制int buff_size = 1024;
ioctl(fd, FIONBIO, &buff_size); // 设置内核缓冲区大小
- 非规范模式超时:
c复制options.c_cc[VMIN] = 64; // 至少读取64字节
options.c_cc[VTIME] = 5; // 0.5秒超时
- 原始数据模式优化:
c复制options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP |
INLCR | IGNCR | ICRNL | IXON);
options.c_oflag &= ~OPOST;
options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
5. 现代替代方案与协议栈
5.1 内核串口框架
Linux内核提供了分层串口架构:
- TTY核心层:drivers/tty/tty_io.c
- 线路规程:drivers/tty/n_tty.c
- UART驱动:drivers/tty/serial/
查看已注册串口驱动:
bash复制cat /proc/tty/drivers/serial
5.2 设备树配置(嵌入式系统)
典型UART节点定义:
code复制uart0: serial@101f0000 {
compatible = "vendor,uart";
reg = <0x101f0000 0x1000>;
interrupts = <0 12 4>;
clocks = <&uart_clk>;
status = "okay";
};
5.3 协议封装方案
-
RFC2217 - 网络化串口协议
通过Telnet端口2217提供远程串口访问 -
ser2net实现:
bash复制ser2net -C "2217:raw:0:/dev/ttyS0:115200"
- Protocol Buffers over UART
定义protobuf消息格式,实现结构化数据传输
5.4 调试技巧与工具链
- 内核消息监控:
bash复制echo 8 > /proc/sys/kernel/printk
dmesg -wH
-
逻辑分析仪抓包:
- Saleae Logic Pro
- PulseView + FX2LA设备
-
Python快速测试脚本:
python复制import serial
ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=1)
ser.write(b'AT\r\n')
response = ser.read(100)
print(response.decode())
ser.close()
在实际项目中,我通常会先用Python脚本快速验证串口基本功能,确认物理连接正常后再进行C语言开发。对于工业级应用,务必注意添加重试机制和心跳检测,防止长时间运行后出现的异常断连情况。