Linux上的低延迟串行通信

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

什么IO接口将给我最低的延迟:手动select,轮询,epoll或轮询与ioctl? 阻塞或非阻塞IO影响延迟?

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

还有什么我可以尝试减less延迟? 由于我控制所有设备,所以甚至可以修补内核,但是最好不要。

—-编辑—-

串行控制器使用的是一个16550A。

Solutions Collecting From Web of "Linux上的低延迟串行通信"

请求/回答方案往往效率低下,并且在串口上显示得很快。 如果你有兴趣在吞吐量,看看窗口的协议,如kermit文件发送协议。

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

如果幸运的话,您可以在不修补的情况下调整驱动程序行为,但是您仍然需要查看驱动程序代码。 但是,如果ARM处理10 kHz的中断率,肯定不利于整个系统的性能。

另一种选择是填充你的数据包,以便每次都达到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毫秒延迟。

Linux上的串行端口被“包装”成unix风格的终端结构,以1 tick的时间间隔(即10ms)触发你。 尝试如果stty -F /dev/ttySx raw low_latency帮助,但不保证。

在个人电脑上,你可以直接和标准的串口直接对话,发出setserial /dev/ttySx uart none来从串口hw解开linux驱动,并通过inb/outb控制端口到端口寄存器。 我试过了,效果很好。

不利的一面是数据到达时不会中断,您必须轮询注册表。 经常。

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

在和更多的工程师讨论这个话题之后,我得出结论:这个问题在用户空间中是不可解的。 由于我们需要穿越桥梁进入内核地区,因此我们计划实施一个内核模块来说明我们的协议,并给出了<1ms的延迟。

—编辑—

原来我完全错了。 所有这一切都是为了提高核心滴答率。 默认的100个ticks增加了10ms的延迟。 1000Hz和串行过程的负面的好价值给了我想要达到的时间行为。

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

你确定你的硬件可以做到这一点? 找到具有缓冲区设计的UART并不罕见,这种设计引入了许多字节的延迟。

在这些线速度下,不应该看到很大的延迟,不管你如何检查准备情况。

您需要确保串行端口处于原始模式(因此您执行“noncanonical reads”),并且VMIN和VTIME设置正确。 你要确保VTIME是零,这样一个字符间的定时器就不会开始。我可能会先把VMIN设置为1,然后从那里调整。

与线路上的时间相比,系统调用的开销没有任何意义,所以select()与poll()等不太可能有所作为。

以下是setserial在端口的文件描述符上设置低延迟的方法:

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

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

我在Modbus上使用了基于FT232RL的USB适配器,速度为115.2 kbs。 我通过ASYNC_LOW_LATENCY在大约20 mS的时间内获得了约5笔交易(至4台设备)。 这包括对慢速设备的两个事务(4 mS响应时间)。 没有ASYNC_LOW_LATENCY总时间约为60毫秒。 使用FTDI USB适配器ASYNC_LOW_LATENCY将芯片本身的字符间定时器设置为1 mS(而不是默认的16 mS)。 我目前正在使用家庭自制的USB适配器,我可以将适配器本身的延迟设置为任何我想要的值。 将其设置为200μS,可以在20 mS的时间内减少另一个mS。