串行WriteFile在完成之前返回

问题描述

我一直在研究一个程序,该程序通过串行RS422总线与外部设备进行对话。目标是将命令发送到设备,然后将答案发送回设备。 到目前为止,用于发送消息的代码如下:

OVERLAPPED osWrite = {0};

void init()
{
    // Create this write operation's OVERLAPPED structure's hEvent.
    osWrite.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
    if (osWrite.hEvent == NULL)
        // error creating overlapped event handle
        std::cout << "Error osWrite.hEvent" << std::endl; // Error in communications; report it.

    *hPort = CreateFile("\\\\.\\COM18",(GENERIC_READ | GENERIC_WRITE),FILE_SHARE_READ | FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,NULL);

    if (*hPort == INVALID_HANDLE_VALUE) {
        std::cout << "Invalid port com handle" << GetLastError() << std::endl;
        return;
    }
    
    COMMTIMEOUTS commTimeout;
    if (GetCommTimeouts(*hPort,&commTimeout)) {
        commTimeout.ReadIntervalTimeout = 10;
        commTimeout.ReadTotalTimeoutConstant = 10;
        commTimeout.ReadTotalTimeoutMultiplier = 10;
        commTimeout.WritetotalTimeoutConstant = 10;
        commTimeout.WritetotalTimeoutMultiplier = 10;
    } else
        return;

    if (!SetCommTimeouts(*hPort,&commTimeout)) {
        std::cout << "Error comm timeout" << std::endl;
    }

    DCB dcb;

    if (!GetCommState(*hPort,&dcb)) {
        std::cout << "Invalid port com settings" << std::endl;
        return;
    }

    dcb.Baudrate = CBR_115200;
    dcb.ByteSize = 8;
    dcb.Parity = nopARITY;
    dcb.StopBits = OnesTOPBIT;

    SetCommMask(*hPort,EV_RXCHAR);
    SetCommState(*hPort,&dcb);
    
    return;
}

DWORD serial_send(HANDLE *hPort,char *msg,int length) {

    DWORD dwWritten;
    DWORD dwRes;
    BOOL fRes;

    PurgeComm(*hPort,PURGE_TXCLEAR);

    ResetEvent(osWrite.hEvent);

    // Issue write.
    if (!WriteFile(*hPort,msg,length,&dwWritten,&osWrite)) {
        if (GetLastError() != ERROR_IO_PENDING) {
            // WriteFile Failed,but isn't delayed. Report error and abort.
            fRes = FALSE;
        } else {
            fRes = FALSE;
            while (!fRes) {
                // Write is pending.
                dwRes = WaitForSingleObject(osWrite.hEvent,INFINITE);
                switch (dwRes) {
                // OVERLAPPED structure's event has been signaled.
                case WAIT_OBJECT_0:
                    if (!GetoverlappedResult(*hPort,&osWrite,FALSE))
                        fRes = FALSE;
                    else
                        // Write operation completed successfully.
                        fRes = TRUE;
                    break;

                default:
                    // An error has occurred in WaitForSingleObject.
                    // This usually indicates a problem with the
                    // OVERLAPPED structure's event handle.
                    fRes = FALSE;
                    break;
                }
            }
        }
    } else {
        // WriteFile completed immediately.
        fRes = TRUE;
    
    }
    return dwWritten;
}

在写入操作成功之前,最后一个函数无法返回。 The init()函数加载没有错误。 我从这里使用了很多代码https://docs.microsoft.com/en-us/previous-versions/ff802693(v=msdn.10)

每条消息的长度为210字节,并且串行端口以115200 bit / s的速度运行,这意味着我应该每隔〜18.2ms发送一条消息。 (210字节* 10位/ 115200) 但是,当我测量2条消息之间的时间时,有时我得到的持续时间要比预期的18ms要短得多(可能会降低到11ms)。

这是异步WriteFile + WaitForSingleObject的正常行为吗? 如果我在11毫秒后发送另一条消息,它会破坏前一条消息还是被缓冲,会发生什么情况?

我使用std::chrono::high_resolution_clock::Now()std::chrono::duration<double,std::milli>(end - start).count()获取帧的持续时间,真的准确吗?

解决方法

由于Windows不是实时操作系统,而是多进程和多线程的操作系统,因此不能保证时间准确性。

如果系统负载较轻,则大多数系统将按预期运行,但并非总是如此。

相反,取决于硬件和设备驱动程序堆栈配置,WriteFile()的完成可能比实际通知的更早。

例如,可以认为该过程是在数据完全存储在设备驱动程序的缓冲区中或将最后一个数据写入接口芯片的FIFO缓冲区时完成的。

最好以为WriteFile()可以完成,即使并非所有数据实际上都到达了另一方。

被认为与将文件数据写入硬盘相同。写入磁盘上文件的操作是在系统缓冲区中完成的,应在其他时间写入实际介质。


如果由于恶劣的条件在上次所有WriteFile数据到达对方之前调用了下一个serial_send()函数,则有可能会丢弃某些先前的传输数据。

因为PurgeComm(*hPort,PURGE_TXCLEAR);在serial_send()函数的开头被调用。
它不像指定PURGE_TXABORT那样重要,但是仍然存在使用PURGE_TXCLEAR丢弃数据的可能性。

PurgeComm function

PURGE_TXABORT 0x0001终止所有未完成的重叠写操作并立即返回,即使写操作尚未完成。

PURGE_TXCLEAR 0x0004清除输出缓冲区(如果设备驱动程序有一个)。

如果线程使用 PurgeComm 刷新输出缓冲区,则不会传输已删除的字符。要在确保传输内容的同时清空输出缓冲区,请调用FlushFileBuffers函数(同步操作)。

解决方法是根本不致电PurgeComm()

如果是串行端口API,则可以通过使用SetCommMask()/ WaitCommEvent()指定/检测EV_TXEMPTY来等待传输完成,但这只会很复杂。

SetCommMask function / WaitCommEvent function

EV_TXEMPTY 0x0004发送了输出缓冲区中的最后一个字符。


然后,您对WriteFile()+ WaitForSingleObject()+ GetOverlappedResult()的使用最终将类似于同步调用WriteFile()。

异步操作并非总是必要的,但最好根据系统所需的行为来详细考虑。