Linux下read()无法从USB串口设备立即返回字节的问题
问题描述
我通过FTDI串口转USB线连接了一个串口设备,该设备每10毫秒发送78字节数据。我每1毫秒调用一次read()函数(提供2KB缓冲区),预期每10次调用会返回78字节(可能分两次返回),中间8-9次调用返回0字节。但实际情况是read()每约63毫秒返回496字节,虽能获取全部数据,但无法实时获取,似乎数据被缓存至累计496字节才返回。
以下是我配置termios的代码片段,所有设置均为原始数据模式。由于VMIN和VTIME均设为0,我怀疑缓冲发生在Linux USB驱动中。请问如何实现数据的实时获取?是否可通过调整USB驱动设置解决?
char *portname = "/dev/ttyUSB0"; fd = open(portname, O_RDWR | O_NOCTTY | O_NDELAY); if (fd < 0) { printf("Failed to open %s. errno = %d (%s)\n", portname, errno, strerror(errno)); return -1; } printf("%s opened successfully\n\n", portname); struct termios settings; if (tcgetattr(fd, &settings) < 0) { printf("tcgettattr() failed (%i): %s\n", errno, strerror(errno)); return -1; } cfsetispeed(&settings, B460800); /* set baud rate */ cfsetospeed(&settings, B460800); settings.c_cflag &= ~PARENB; /* No parity */ settings.c_cflag &= ~CSTOPB; /* 1 stop bit */ settings.c_cflag &= ~CSIZE; /* Clears the mask for setting the data size */ settings.c_cflag |= CS8; /* Set the data bits = 8 */ settings.c_cflag &= ~CRTSCTS; /* No Hardware flow Control */ settings.c_cflag |= CREAD | CLOCAL; /* Enable receiver, Ignore Modem Control lines */ settings.c_iflag &= ~(IXON | IXOFF | IXANY); /* Disable XON/XOFF flow control both i/p and o/p */ settings.c_iflag &= ~(BRKINT | IGNBRK | ICRNL | INLCR | PARMRK | ISTRIP | IGNCR); settings.c_lflag &= ~(ICANON | ECHO | ECHOE | ECHONL | ISIG | IEXTEN); /* Non Cannonical mode */ settings.c_lflag |= NOFLSH; settings.c_oflag &= ~(OPOST | ONLCR); /* No Output Processing */ settings.c_cc[VMIN] = 0; settings.c_cc[VTIME] = 0; if ((tcsetattr(fd, TCSANOW, &settings)) != 0) /* Set the attributes to the termios structure */ { printf("Error setting attributes\n"); return -1; }
解决方案
1. 调整termios的VMIN和VTIME参数
当前VMIN=0、VTIME=0的非阻塞模式下,驱动可能倾向于攒够一定量数据再返回。你可以调整这两个参数来触发更及时的返回:
- 设置
VMIN=1、VTIME=0:让read()在有至少1字节数据时立即返回(配合O_NDELAY/O_NONBLOCK时,无数据会返回-1且errno=EAGAIN),避免驱动缓存过多数据。 - 如果需要严格的超时控制,可设置
VMIN=0、VTIME=1(VTIME单位为0.1秒,即超时100毫秒),此时read()会在有数据时立即返回,超时则返回0。
修改代码中对应部分:
settings.c_cc[VMIN] = 1; settings.c_cc[VTIME] = 0;
2. 修改FTDI USB驱动的延迟定时器
Linux下的ftdi_sio驱动默认会通过延迟定时器攒数据,你可以调整这个参数降低延迟:
- 临时生效:执行以下命令修改当前设备的延迟(单位为毫秒,最小1ms):
echo 1 > /sys/bus/usb-serial/devices/ttyUSB0/latency_timer - 永久生效:在
/etc/modprobe.d/ftdi.conf中添加一行配置,加载模块时自动设置:options ftdi_sio latency_timer=1
注意:设置过小的延迟会增加CPU使用率,需要在实时性和资源占用间做平衡。
3. 优化读取逻辑
避免每1ms主动调用read(),改用select()/poll()等待数据,减少无效系统调用的同时保证实时性:
fd_set readfds; struct timeval tv; while (1) { FD_ZERO(&readfds); FD_SET(fd, &readfds); // 设置1ms超时 tv.tv_sec = 0; tv.tv_usec = 1000; int ret = select(fd + 1, &readfds, NULL, NULL, &tv); if (ret > 0 && FD_ISSET(fd, &readfds)) { char buf[2048]; ssize_t n = read(fd, buf, sizeof(buf)); if (n > 0) { // 处理接收到的n字节数据 } } }
4. 关闭额外缓冲验证
确认系统层无额外串口缓冲:
- 确保
icanon模式已关闭(你代码中已通过settings.c_lflag &= ~ICANON完成) - 检查是否有第三方串口工具或服务在占用设备,避免额外缓冲叠加
内容的提问来源于stack exchange,提问作者gstemp
相关产品推荐
相关产品推荐

