linux 串行端口读取延迟(和字节丢失)

nukf8bse  于 12个月前  发布在  Linux
关注(0)|答案(1)|浏览(159)

我有一个Rasperry Pi和一个Raspberry Pi皮科通过串行通信在一起。我在两个上面都使用了USB引脚,在通信方面一切都很好,除了一些环境很吵,所以我切换到了USB CDC(USB使用差分对.)
在皮科方面,我不得不对我的代码做一些修改,但现在看起来没问题,在添加了以下内容之后:

stdio_set_translate_crlf(&stdio_usb, false);

字符串
(My协议是二进制的,所以翻译是二进制的)
现在在“Regular Pi”上,事情有点不对劲。使用USB,所有读取都是非缓冲/瞬时的。现在我使用USB,似乎我必须等待相当多的数据才能将任何数据传输到我的程序。请注意,其他程序(如minicom)没有这个问题。我似乎也错过了相当多的字节。
我做了一个特殊的程序在皮科上运行,它连续输出从0x 00到0xFF的字节。如果我把皮科连接到Windows主机,运行Putty并记录会话,我就会得到所有的字节,没有问题。
我自己的Raspberry Pi软件丢失了很多字节。
我也试过这个命令:

(stty raw; cat > received.log) < /dev/ttyACM0


大多数情况下,0x 0A和0x 16之间的字节丢失(我假设0x 0A触发与CR/LF相关的东西,但我不确定为什么直到0x 16(不包括)的字符被跳过。0x 7 E和0x 7 F也经常丢失。偶尔,完整的256字节似乎被原始捕获。我在皮科Pi上的循环中添加了10 ms的睡眠,上面的命令可以正确地捕获数据。(原始的循环应该运行在9600-115200 bps,没有更高的,我的循环当前输出>200 kbps,所以这对我的需要来说已经足够了)。
下面是初始化代码:

uart0_fd = ::open("/dev/ttyACM0", O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);       //Open in non blocking read/write mode

struct termios options;
tcgetattr(uart0_fd, &options);
cfmakeraw(&options);
options.c_cflag = B115200 | CS8 | CLOCAL | CSTOPB | CREAD;      //<Set baud rate
options.c_iflag = IGNPAR | IGNBRK;
options.c_oflag = 0;
options.c_lflag = ICANON | NOFLSH;
options.c_cc[VTIME] = 10;
tcflush(uart0_fd, TCIFLUSH);
tcsetattr(uart0_fd, TCSANOW, &options);

struct termios tty;
memset (&tty, 0, sizeof tty);
if (tcgetattr (uart0_fd, &tty) != 0)
{
  printf ("error %d from tggetattr\r\n", errno);
  return;
}

tty.c_cc[VMIN]  = 0;
tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

if (tcsetattr (uart0_fd, TCSANOW, &tty) != 0) { ... }


下面是阅读代码:

void printHex(const uint8_t *data, const size_t size)
{
  for (uint32_t index = 0; index < size; ++index)
  {
    printf("%.2X",data[index]);
  }
}

void execute()
{
  printf("UART thread started\r\n");

  sendOverUart(COMMAND_RESEND_ALL, 0xFFFF);

  int local_uart0_fd = uart0_fd;

  unsigned char buffer[100];
  memset(buffer, 0, sizeof(buffer));

  while (!shouldFinish)
  {
    int bytesRead = read(local_uart0_fd, &buffer, sizeof(buffer) - 1);
    buffer[bytesRead + 1] = '\0';
    if (-1 == bytesRead)
    {
      if (errno != EAGAIN)
      {
        printf("UART... error reading %d\r\n", errno);
      }
    }
    else
    {
      printHex(buffer, bytesRead);
      fflush(stdout);
      continue;
    }
    if (EFAULT == errno)
    {

    }
  }
  ::close(local_uart0_fd);
  printf("UART thread finished\r\n");
}


正如我所说的,它在我的Pi上的/dev/serial 0引脚上工作得很好,我错过了什么吗?我需要因为USB而做一些不同的事情吗?我需要它“快速”(即能够在没有缓冲的情况下获得字节)和可靠(即不会错过接收到的部分字节)。
我的程序在Raspberry Pi上的当前输出始终是00-0A,然后是DE-FF,始终捕获256个字节中的88个[我想,我没有时间,也许它捕获了512个字节中的88个或更多.]。

33qvvth1

33qvvth11#

就像我说的,它可以很好地使用USB引脚(我的Pi上的/dev/serial 0),我错过了什么吗?我需要因为USB而做一些不同的事情吗?
在Linux中,你的程序访问的是一个串行终端,而不是一个USB或USB CDC。你对那个串行终端的初始化写得很差,也不可靠。参见Setting Terminal Modes ProperlySerial Programming Guide for POSIX Operating Systems
我怀疑您发布的代码是否真的使用正则表达式传递了二进制数据。在调用**cfmakeraw()**配置非规范模式之后,您的代码继续进行多余的硬termios赋值,包括灾难性的

options.c_lflag = ICANON | NOFLSH;

字符串
这就是为什么ASCII控制字符没有出现在读缓冲区中的简单解释。
有关正确使用**cfmakeraw()**的示例,请参阅此答案。
即使指定了定时读取,

tty.c_cc[VMIN]  = 0;
tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout


此termios规范被uart0_fd文件描述符的非阻塞模式覆盖。该模式在打开串行终端时(并注解)被(冗余地)指定:

uart0_fd = ::open("/dev/ttyACM0", O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK);       //Open in non blocking read/write mode


更多细节请参考这个答案。注意EAGAIN errno的提及,它在你的代码中得到了正确的处理。
如果你对这个优先级持怀疑态度,那么简单地尝试VMIN=0和VTIME=250进行25秒的定时读取。**read()系统调用的实际持续时间应该是相当明显的,并指示termios或文件描述符模式是否具有优先级。
我的意思是类似于“我想在数据到达后不到100毫秒的时间内获得数据”。
阅读串行终端时的延迟可能会有问题。在一个现已删除的论坛上有一个很好的讨论,其中一个开发人员将他的Linux SBC调整到只有几毫秒的延迟。时间是通过示波器捕获线上的数据和GPIO翻转来测量的,当
read()完成时。关键的调整IIRC使用了ASYNC_LOW_LATENCY标志和TIOC[GS]SERIAL ioctl,以及增加串行驱动程序的小任务或工作队列的优先级。
.当缓冲区中有数据时,获取所有数据。系统缓冲区太小?
termios缓冲区通常为4K字节。
对于非规范的termios读取,您必须在高效读取(即每次系统调用获取尽可能多的字节)和低延迟之间进行折衷。您可以通过指定VMIN=1和VTIME>0,在不到0.1秒的时间内让
read()**返回(带一些数据)。

相关问题