Serial WriteFile returns 完成前

Serial WriteFile returns before completion

我一直在研究一个通过串行 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, &dwWritten, 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;
}

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

每条消息长度为210字节,串口运行 at 115200 bit/s,意味着我应该每隔~18.2ms发送一条消息。 (210 字节 * 10 位 / 115200) 但是,当我测量两条消息之间经过的时间时,有时我得到的持续时间比预期的 18 毫秒要短得多(它可以下降到 11 毫秒)。

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

我用std::chrono::high_resolution_clock::now()std::chrono::duration<double, std::milli>(end - start).count()得到了帧的时长,真的准确吗?

由于Windows不是real-timeOS而是multi-process&multi-threadOS,时间的准确性不能保证.

如果系统负载较轻,大部分都会按预期工作,但并非总是如此。

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

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

最好认为WriteFile()可能完成,即使不是所有的数据真正到达对方。

相当于将文件数据写入硬盘。写入磁盘上的文件是在系统缓冲区中完成的,应该在不同的时间写入实际媒体。


如果由于条件不好,在上一次的WriteFile数据全部到达对方之前调用下一个serial_send()函数,有可能会导致之前的部分传输数据丢失丢弃。

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

PurgeComm function

PURGE_TXABORT 0x0001 Terminates all outstanding overlapped write operations and returns immediately, even if the write operations have not been completed.

PURGE_TXCLEAR 0x0004 Clears the output buffer (if the device driver has one).

If a thread uses PurgeComm to flush an output buffer, the deleted characters are not transmitted. To empty the output buffer while ensuring that the contents are transmitted, call the FlushFileBuffers function (a synchronous operation).

解决方法是不调用 PurgeComm()

如果是串口API,可能可以用SetCommMask()/WaitCommEvent()specifying/detectingEV_TXEMPTY等待传输完成,但是这个只会更复杂。

SetCommMask function / WaitCommEvent function

EV_TXEMPTY 0x0004 The last character in the output buffer was sent.


那么您对 ​​WriteFile() + WaitForSingleObject() + GetOverlappedResult() 的使用最终将类似于同步调用 WriteFile()。

异步操作并不总是必要的,但最好根据您的系统需要什么样的行为来详细考虑。