C语言串口通信异常:首次请求后无法正确读取仪器数据
串口通信后续请求数据偏移问题解决方案
问题背景
在Mac OSX M1 Max平台使用XCode开发C语言串口程序,向仪器发送d\r查询指令时,首次能收到正确响应 2.31<0D><0A>,但后续发送相同指令时,读取到的是偏移错误数据.31<0D><0A>。仪器串口参数为9600波特率、无校验、8位数据位、1位停止位,已通过SerialTools验证通信正常,且同类历史程序可稳定运行,怀疑问题与串口缓冲区或termios配置相关。
问题代码
#define TERMINAL "/dev/cu.usbserial-4013420" #define DISPLAY_STRING 1 #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <termios.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> int set_interface_attribs(int fd, int speed) { struct termios tty; if (tcgetattr(fd, &tty) < 0) { printf("Error from tcgetattr: %s\n", strerror(errno)); return -1; } cfsetospeed(&tty, (speed_t)speed); cfsetispeed(&tty, (speed_t)speed); tty.c_cflag |= (CLOCAL | CREAD); /* ignore modem controls */ tty.c_cflag &= ~CSIZE; tty.c_cflag |= CS8; /* 8-bit characters */ tty.c_cflag &= ~PARENB; /* no parity bit */ tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */ tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */ tty.c_lflag |= ICANON | ISIG; /* canonical input */ tty.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN); tty.c_lflag |= (ICANON | ECHO | ECHOE); tty.c_iflag &= ~IGNCR; /* preserve carriage return */ tty.c_iflag &= ~INPCK; tty.c_iflag &= ~(INLCR | ICRNL | IMAXBEL); tty.c_iflag &= ~(IXON | IXOFF | IXANY); /* no SW flowcontrol */ tty.c_oflag &= ~OPOST; tty.c_cc[VEOF] = 0x0A; if (tcsetattr(fd, TCSANOW, &tty) != 0) { printf("Error from tcsetattr: %s\n", strerror(errno)); return -1; } return 0; } int main(int argc, const char * argv[]) { char *portname = TERMINAL; int fd; size_t wlen; // use size_t for unsigned int int tester; char *xstr = "d\r"; size_t xlen = strlen(xstr); unsigned char buff[10]={0}; size_t rdlen2; fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC); if (fd < 0) { printf("Error opening %s: %s\n", portname, strerror(errno)); return -1; } /*baudrate 9600, 8 bits, no parity, 1 stop bit */ set_interface_attribs(fd, B9600); /* simple output write, then read and repeat...*/ printf("xstr is %s\n", xstr); wlen = write(fd, xstr, xlen); rdlen2 = read(fd, buff, sizeof(buff)); wlen = write(fd, xstr, xlen); rdlen2 = read(fd, buff, sizeof(buff)); wlen = write(fd, xstr, xlen); rdlen2 = read(fd, buff, sizeof(buff)); wlen = write(fd, xstr, xlen); rdlen2 = read(fd, buff, sizeof(buff)); wlen = write(fd, xstr, xlen); rdlen2 = read(fd, buff, sizeof(buff)); close(fd); }
问题分析
- 规范模式冲突:代码中重复设置
ICANON(规范模式)并启用ECHO,同时将VEOF设为换行符,导致系统对串口数据进行自动缓冲和字符转换,破坏了仪器返回数据的完整性。 - 读写逻辑不严谨:发送指令后直接读取,未确保数据完全发送完成,也未清空缓冲区残留数据,导致后续读取被历史数据干扰。
- 无超时与完整性校验:单次读取无法保证获取完整响应,也没有超时机制,容易出现数据截断。
解决方案
1. 修正termios配置(改用原始模式)
仪器返回固定格式的ASCII数据,适合用原始模式(非规范模式)处理,避免系统自动转换:
int set_interface_attribs(int fd, int speed) { struct termios tty; if (tcgetattr(fd, &tty) < 0) { printf("Error from tcgetattr: %s\n", strerror(errno)); return -1; } cfsetospeed(&tty, (speed_t)speed); cfsetispeed(&tty, (speed_t)speed); tty.c_cflag |= (CLOCAL | CREAD); /* 忽略modem控制 */ tty.c_cflag &= ~CSIZE; tty.c_cflag |= CS8; /* 8位数据位 */ tty.c_cflag &= ~PARENB; /* 无校验 */ tty.c_cflag &= ~CSTOPB; /* 1位停止位 */ tty.c_cflag &= ~CRTSCTS; /* 无硬件流控 */ /* 禁用规范模式,启用原始模式 */ tty.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOE | ECHONL | IEXTEN); /* 禁用输入转换,保留原始字符 */ tty.c_iflag &= ~(IGNCR | INPCK | INLCR | ICRNL | IMAXBEL | IXON | IXOFF | IXANY); /* 禁用输出转换,直接发送原始数据 */ tty.c_oflag &= ~OPOST; /* 设置读取超时:1秒超时,最少读取1个字符 */ tty.c_cc[VMIN] = 1; tty.c_cc[VTIME] = 10; if (tcsetattr(fd, TCSANOW, &tty) != 0) { printf("Error from tcsetattr: %s\n", strerror(errno)); return -1; } return 0; }
2. 完善读写逻辑
int main(int argc, const char * argv[]) { char *portname = TERMINAL; int fd; size_t wlen; char *xstr = "d\r"; size_t xlen = strlen(xstr); unsigned char buff[10]={0}; size_t rdlen; int i; fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC); if (fd < 0) { printf("Error opening %s: %s\n", portname, strerror(errno)); return -1; } if (set_interface_attribs(fd, B9600) < 0) { close(fd); return -1; } /* 初始化时清空输入缓冲区 */ tcflush(fd, TCIFLUSH); printf("xstr is %s\n", xstr); for (i = 0; i < 5; i++) { memset(buff, 0, sizeof(buff)); // 每次循环清空接收缓冲区 wlen = write(fd, xstr, xlen); if (wlen != xlen) { printf("Write error: wrote %zu bytes out of %zu\n", wlen, xlen); continue; } /* 等待所有数据发送到串口硬件 */ tcdrain(fd); /* 循环读取直到获取完整响应或超时 */ rdlen = 0; while (rdlen < sizeof(buff)-1) { ssize_t ret = read(fd, buff + rdlen, sizeof(buff)-1 - rdlen); if (ret < 0) { printf("Read error: %s\n", strerror(errno)); break; } else if (ret == 0) { printf("Timeout, read %zu bytes\n", rdlen); break; } rdlen += ret; /* 检查是否收到结束符<0D><0A> */ if (rdlen >=2 && buff[rdlen-2] == 0x0D && buff[rdlen-1] == 0x0A) { break; } } /* 打印原始字节和字符串形式的响应 */ printf("Received %zu bytes: ", rdlen); for (size_t j=0; j<rdlen; j++) { printf("0x%02X ", buff[j]); } printf("| %.*s\n", (int)rdlen, buff); } close(fd); return 0; }
关键修改说明
- 禁用规范模式,改用原始模式,避免系统对串口数据的自动处理
- 设置
VMIN=1和VTIME=10,确保读取时能及时获取数据且不会无限等待 - 每次循环前清空接收缓冲区,避免残留数据干扰
- 使用
tcdrain确保所有发送数据已输出到串口硬件 - 循环读取直到检测到结束符
<0D><0A>,保证获取完整响应
内容的提问来源于stack exchange,提问作者Markys
相关产品推荐
相关产品推荐

