You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 20:50:54