Linux Termios串口设备跳过隔条消息问题排查
串口自定义帧发送异常问题分析与解决
问题描述
以下是操作Linux串口发送自定义帧的代码(帧起始标识0xAA、0xBB,结束标识0x77):
#include <stdio.h> #include <string.h> #include <fcntl.h> #include <errno.h> #include <termios.h> #include <unistd.h> #include <stdint.h> #include <stdlib.h> // 补充EXIT_SUCCESS的头文件 int main() { int port_fd; // 补充变量声明 // 打开串口 port_fd = open("/dev/ttyUSB0", O_RDWR); if (port_fd < 0) { printf("Error %i while opening serial port: %s\n", errno, strerror(errno)); return EXIT_FAILURE; // 错误时退出 } // 获取串口配置 struct termios tty; if (tcgetattr(port_fd, &tty) != 0) { printf("Error %i from tcgetattr: %s\n", errno, strerror(errno)); close(port_fd); return EXIT_FAILURE; } // 修改串口配置 tty.c_cflag &= ~PARENB; tty.c_cflag &= ~CSTOPB; tty.c_cflag |= CS8; tty.c_cflag &= ~CRTSCTS; tty.c_cflag |= CREAD | CLOCAL; tty.c_lflag &= ~ICANON; tty.c_lflag &= ~ECHO; tty.c_lflag &= ~ECHOE; tty.c_lflag &= ~ECHONL; tty.c_lflag &= ~ISIG; tty.c_iflag &= ~(IXON | IXOFF | IXANY); tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); tty.c_oflag &= ~OPOST; tty.c_oflag &= ~ONLCR; tty.c_cc[VTIME] = 10; tty.c_cc[VMIN] = 0; cfsetispeed(&tty, B115200); cfsetospeed(&tty, B115200); // 设置串口配置 if (tcsetattr(port_fd, TCSANOW, &tty) != 0) { printf("Error %i from tcsetattr: %s\n", errno, strerror(errno)); close(port_fd); return EXIT_FAILURE; } uint8_t testMsg[] = {0xAA, 0xBB, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x77}; write(port_fd, testMsg, sizeof(testMsg)); close(port_fd); // 关闭串口 return EXIT_SUCCESS; }
运行时出现两个异常:
- 调用
write()时传入sizeof(testMsg)作为长度参数,串口设备无响应;改为sizeof(testMsg)+1时,设备响应正常。 - 执行以下循环发送代码时,设备仅对隔条消息做出响应:
for(int i = 0; i < 100; i++) { write(port_fd, testMsg, sizeof(testMsg)); sleep(10); }
串口设备配置:波特率115200、数据位8、无校验位、停止位1。
异常原因分析
核心问题出在Linux串口的输出缓冲机制:
- 内核默认会为串口维护输出缓冲,当写入的数据量远小于缓冲阈值(通常为4096字节)时,不会立即将数据发送到硬件,而是等待更多数据或超时触发发送。
- 传入
sizeof(testMsg)(9字节)时,数据量太小,未达到缓冲发送条件,数据滞留在内核缓冲中,设备未收到完整帧,因此无响应。 - 传入
sizeof(testMsg)+1时,额外的无效字节触发了缓冲发送逻辑,设备才收到完整帧并响应。
- 传入
- 循环发送时,前一次写入的数据滞留在缓冲中,第二次写入后,两次数据被拼接成连续字节流发送给设备。设备的帧解析逻辑无法区分连续的起始标识,仅能正确解析其中一帧,因此隔条响应。
原代码还存在细节隐患:
- 未声明
port_fd变量类型,属于语法漏洞。 - 未检查
write()返回值,无法确认数据是否全部写入缓冲。 - 未关闭串口,可能导致资源泄漏。
解决办法
1. 强制等待数据发送完成
在write()后调用tcdrain()函数,该函数会阻塞直到所有输出数据都发送到硬件,确保帧被完整发送:
uint8_t testMsg[] = {0xAA, 0xBB, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x77}; ssize_t bytes_written = write(port_fd, testMsg, sizeof(testMsg)); if (bytes_written != sizeof(testMsg)) { printf("Write error: only %zd bytes written\n", bytes_written); } // 等待所有数据发送到硬件 tcdrain(port_fd);
2. 禁用串口输出缓冲(可选)
通过修改termios配置,禁用输出缓冲,让数据立即发送:
// 在修改串口配置时添加以下代码 tty.c_oflag |= OPOST; tty.c_oflag &= ~ONLCR; // 设置最小发送字节数为1 tty.c_cc[VEOF] = 1;
注意:此方法可能降低串口发送效率,适合小帧频繁发送的场景。
3. 完善错误处理
- 每次系统调用(
open、tcgetattr、tcsetattr、write)后都要检查返回值,出现错误时及时关闭串口并退出。 - 程序结束前必须调用
close(port_fd)释放串口资源。
优化后的循环发送代码
for(int i = 0; i < 100; i++) { ssize_t bytes_written = write(port_fd, testMsg, sizeof(testMsg)); if (bytes_written != sizeof(testMsg)) { printf("Loop %d write failed: %zd bytes written\n", i, bytes_written); break; } tcdrain(port_fd); // 确保当前帧发送完成 sleep(10); }
内容的提问来源于stack exchange,提问作者user20278411
相关产品推荐
相关产品推荐

