Linux上的低延迟串行通信

时间:2012-10-29 16:53:12

标签: c++ linux serial-port low-latency throughput

我正在Linux上通过串口实现协议。该协议基于请求应答方案,因此吞吐量受到将数据包发送到设备并获得答案所需的时间的限制。这些设备大多是基于arm的,并且运行Linux> = 3.0。我遇到麻烦,将往返时间减少到10ms以下(115200波特,8个数据位,无奇偶校验,每个消息7个字节)。

哪些IO接口会给我最低的延迟:使用ioctl手动选择,轮询,epoll或轮询?阻塞或非阻塞IO是否会影响延迟?

我尝试使用setserial设置low_latency标志。但它似乎没有效果。

我还可以尝试减少延迟吗?由于我控制所有设备,甚至可以修补内核,但是首选不要。

----编辑----

串口控制器使用的是16550A。

7 个答案:

答案 0 :(得分:11)

请求/应答方案往往效率低下,并且在串行端口上很快显示出来。如果您对吞吐量感兴趣,请查看窗口协议,如kermit文件发送协议。

现在,如果你想坚持你的协议并减少延迟,选择,轮询,阅读都会给你大致相同的延迟,因为正如Andy Ross指出的那样,真正的延迟是在硬件fifo处理中。

如果运气好,可以在不修补的情况下调整驱动程序行为,但仍需要查看驱动程序代码。但是,让ARM处理10 kHz中断率肯定不会对整体系统性能有利......

另一种选择是填充数据包,以便每次都达到fifo阈值。它还将确认是否存在fifo阈值问题。

10毫秒@ 115200足以传输100个字节(假设为8N1),所以你看到的可能是因为没有设置low_latency标志。尝试

setserial /dev/<tty_name> low_latency

它将设置low_latency标志,当在tty层中向上移动数据时,该标志用于内核:

void tty_flip_buffer_push(struct tty_struct *tty)
{
         unsigned long flags;
         spin_lock_irqsave(&tty->buf.lock, flags);
         if (tty->buf.tail != NULL)
                 tty->buf.tail->commit = tty->buf.tail->used;
         spin_unlock_irqrestore(&tty->buf.lock, flags);

         if (tty->low_latency)
                 flush_to_ldisc(&tty->buf.work);
         else
                 schedule_work(&tty->buf.work);
}

schedule_work调用可能导致您观察到的10毫秒延迟。

答案 1 :(得分:6)

与更多的工程师讨论了这个话题,我得出结论,这个问题在用户空间是无法解决的。由于我们需要跨越桥接到内核域,我们计划实现一个内核模块,该模块可以跟踪我们的协议并提供延迟&lt; 1毫秒。

---编辑---

原来我完全错了。所有必要的是增加内核滴答率。默认的100个滴答添加了10毫秒的延迟。 1000Hz和串行过程的负值很好,这给了我想达到的时间行为。

答案 2 :(得分:6)

Linux上的串行端口被“包装”成unix风格的终端结构,它以1个滴答延迟命中,即10ms。尝试stty -F /dev/ttySx raw low_latency有帮助,但不保证。

在PC上,你可以直接硬核并与标准串口通信,发出setserial /dev/ttySx uart none从串口hw取消绑定linux驱动程序,并通过inb/outb控制端口到端口寄存器。我试过了,效果很好。

缺点是,当数据到达并且您必须轮询寄存器时,不会中断。常。

你应该能够在手臂设备方面做同样的事情,在异国情调的串口hw上可能会更难。

答案 3 :(得分:1)

这些系统调用都不会影响延迟。如果你想从用户空间尽快读取和写入一个字节,你真的不会比一个简单的read()/write()对更好。尝试使用来自其他用户空间进程的套接字替换串行流,并查看延迟是否有所改进。如果他们不这样做,那么你的问题是CPU速度和硬件限制。

您确定您的硬件可以执行此操作吗?发现具有缓冲设计的UART并引入许多字节延迟的情况并不罕见。

答案 4 :(得分:1)

这是setserial在端口文件描述符上设置低延迟的内容:

ioctl(fd, TIOCGSERIAL, &serial);
serial.flags |= ASYNC_LOW_LATENCY;
ioctl(fd, TIOCSSERIAL, &serial);

答案 5 :(得分:1)

简而言之:使用USB适配器和ASYNC_LOW_LATENCY。

我在Modbus上使用了基于FT232RL的USB适配器,速度为115.2 kbs。

我通过ASYNC_LOW_LATENCY获得大约20 mS的5笔交易(4台设备)。这包括两个到慢速设备的事务(4 mS响应时间)。

没有ASYNC_LOW_LATENCY,总时间约为60 mS。

使用FTDI USB适配器ASYNC_LOW_LATENCY将芯片本身的字符间定时器设置为1 mS(而不是默认的16 mS)。

我目前正在使用家用USB适配器,我可以将适配器本身的延迟设置为我想要的任何值。将其设置为200μS会使另一个mS偏离20 mS。

答案 6 :(得分:0)

在这些线路速度下,无论您如何检查准备情况,都不应该看到很大的延迟。

您需要确保串行端口处于原始模式(因此您执行“非规范读取”)并且正确设置了VMIN和VTIME。你想确保VTIME为零,这样一个字符间的计时器就不会开始了。我可能会先将VMIN设置为1并从那里开始调整。

与线路上的时间相比,系统调用开销无关紧要,因此select()与poll()等不太可能产生影响。