最小延迟的串行通信

10

我有一台电脑通过串行通信(即物理或仿真串口的RS-232/RS-422)连接外部设备。它们通过频繁的数据交换(30Hz)进行通信,但每个数据包都很小(每个数据包小于16字节)。

通信的最关键要求是低延迟或传输延迟。

数据交换模式类似握手。一个主机设备启动通信并在客户端设备上不断发送通知。客户端设备需要尽快回复主机设备的每个通知(这正是需要实现低延迟的地方)。通知和回复的数据包都是明确定义的;即已知数据长度。 基本上不允许数据丢失。

我使用以下常见的Win API函数以同步方式进行I/O读写:

CreateFile,ReadFile,WriteFile

客户端设备使用ReadFile从主机设备中读取数据。一旦客户端读取完整的已知长度的数据包,它就会使用WriteFile根据相应的数据包答复主机设备。读取和写入始终是顺序而不是并发的。

总体来说,通信速度还不够快。也就是说,数据发送和接收之间的时间持续时间太长。我猜测这可能是串口缓冲或中断的问题。

下面我总结了一些可能改善延迟的措施。请给我一些建议和纠正 :)

  1. 使用FILE_FLAG_NO_BUFFERING标志调用CreateFile?我不确定这个标志在这种情况下是否相关。
  2. 在每个WriteFile之后调用FlushFileBuffers?或者采取任何可以通知/中断串口立即传输数据的操作?
  3. 为处理串行通信的线程和进程设置更高的优先级
  4. 为仿真设备(使用其驱动程序)设置潜伏计时器或传输大小。但物理串行端口怎么办?
  5. Windows上类似于Linux下的setserial / low_latency的等效方法?
  6. 禁用FIFO?

提前感谢您的解答!


你尝试过调整超时时间了吗?具体是指什么“不够快”?重叠IO,事件驱动...? - dyp
这将取决于硬件。您使用的是USB串行端口、串行端口卡还是位于主板上的串行端口? - In silico
设置超时可能对我的情况没有帮助。我的设备需要进行类似握手的数据交换,延迟非常低,并且不允许任何数据丢失。 "不够快" 意味着从一个设备发送数据到另一个设备接收数据之间的时间持续太长。 - rnd_nr_gen
@DyP,重叠IO在我的情况下有用吗?你所说的事件驱动是什么意思? - rnd_nr_gen
1
请记住,如果您在端到端确认消息,则每个消息的每个端点也会受到调度影响。更高的线程优先级可能有所帮助,但实时调度线程会更好。 - marko
显示剩余3条评论
2个回答

7

在我的情况下,我通过将 comm 超时设置为 {MAXDWORD,0,0,0,0} 来解决了此问题。

多年来,我一直苦于使用 Microsoft 的 CDC 类 USB UART 驱动程序(USBSER.SYS),但在今天,我终于成功地使我的串行通信终端更快,并且该驱动程序现已内置于 Windows 10 中,因此变得实际可用。

显然,上述一组值是一个特殊值,它设置了最小超时时间和最小延迟时间(至少在我看来是这样的,而且也会导致如果接收缓冲区中没有新字符,则 ReadFile 立即返回)。

以下是我的代码(Visual C++ 2008,将项目字符集从“Unicode”更改为“未设置”,以避免 portname 的 LPCWSTR 类型转换问题)来打开端口:

static HANDLE port=0;
static COMMTIMEOUTS originalTimeouts;

static bool OpenComPort(char* p,int targetSpeed) { // e.g. OpenComPort ("COM7",115200); 
    char portname[16];
    sprintf(portname,"\\\\.\\%s",p);
    port=CreateFile(portname,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,0);
    if(!port) {
        printf("COM port is not valid: %s\n",portname);
        return false;
    }
    if(!GetCommTimeouts(port,&originalTimeouts)) {
        printf("Cannot get comm timeouts\n");
        return false;
    }
    COMMTIMEOUTS newTimeouts={MAXDWORD,0,0,0,0};
    SetCommTimeouts(port,&newTimeouts);
    if(!ComSetParams(port,targetSpeed)) {
        SetCommTimeouts(port,&originalTimeouts);
        CloseHandle(port);
        printf("Failed to set COM parameters\n");
        return false;
    }
    printf("Successfully set COM parameters\n");
    return true;
}

static bool ComSetParams(HANDLE port,int baud) {
    DCB dcb;
    memset(&dcb,0,sizeof(dcb));
    dcb.DCBlength=sizeof(dcb);
    dcb.BaudRate=baud;
    dcb.fBinary=1;
    dcb.Parity=NOPARITY;
    dcb.StopBits=ONESTOPBIT;
    dcb.ByteSize=8;
    return SetCommState(port,&dcb)!=0;
}

以下是它正常工作时的 USB 跟踪数据。请注意在 3 毫秒内连续发生的 OUT 事务(输出字节)、IN 事务(输入字节)以及更多 OUT 事务(输出字节):

USB UART packet trace with minimal timeouts

最后,由于您正在阅读此文,可能会对查看我发送和接收 UART 字符的功能感兴趣:

    unsigned char outbuf[16384];
    unsigned char inbuf[16384];
    unsigned char *inLast = inbuf;
    unsigned char *inP = inbuf;
    unsigned long bytesWritten;
    unsigned long bytesReceived;

    // Read character from UART and while doing that, send keypresses to UART.
    unsigned char vgetc() { 
        while (inP >= inLast) { //My input buffer is empty, try to read from UART
            while (_kbhit()) { //If keyboard input available, send it to UART
                outbuf[0] = _getch(); //Get keyboard character
                WriteFile(port,outbuf,1,&bytesWritten,NULL); //send keychar to UART
            }
            ReadFile(port,inbuf,1024,&bytesReceived,NULL); 
            inP = inbuf;
            inLast = &inbuf[bytesReceived]; 
        }
        return *inP++;
    }

大规模的传输在代码中另外处理。

最后,值得一提的是,自从1998年放弃DOS以来,这似乎是我写的第一个快速UART代码。哦,时间过得真快,当你玩得开心时。

这是我找到相关信息的地方:http://www.egmont.com.pl/addi-data/instrukcje/standard_driver.pdf


4

我曾经遇到过串口的类似问题。 在我的情况下,我通过降低串口的延迟来解决了这个问题。 您可以使用控制面板更改每个端口的延迟(默认设置为16ms)。 您可以在此处找到方法: http://www.chipkin.com/reducing-latency-on-com-ports/

祝你好运!


1
有趣的信息,但链接不再有效。 - oak
这恰好就是我看到的延迟!! - BaldDude
请参阅此帖子:https://stackoverflow.com/questions/65941007/usefulness-of-com-port-latency - BaldDude

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接