doynax
doynax

Reputation: 4445

Waiting for serial transmission to complete in Win32

I seem to be having a bit of trouble in waiting for the completion of serial data transmissions.

My interpretation of the relevant MSDN article is the EV_TXEMPTY event is the correct signal and which indicates that:

EV_TXEMPTY - The last character in the output buffer was sent.

However in my tests the event always fires immediately as soon as the data has been submitted to the buffer and long before the final has actually reached the wire. See the repro code below where the period is always zero.

Have I made an error in the implementation, am I misunderstanding the purpose of the flag, or is this feature simply not supported by modern drivers? In the latter case is there a viable workaround, say some form of synchronous line state request?

For the record the tests were conducted with FTDI USB-RS485 and TTL-232R devices in a Windows 10 system, a USB-SERIAL CH340 interface on a Windows 7 system, as well as the on-board serial interface of a 2005-vintage Windows XP machine. In the FTDI case sniffing the USB bus reveals only bulk out transactions and no obvious interrupt notification of the completion.

#include <stdio.h>
#include <windows.h>

static int fatal(void) {
    fprintf(stderr, "Error: I/O error\n");
    return 1;
}

int main(int argc, const char *argv[]) {
    static const char payload[] = "Hello, World!";
    // Use a suitably low bitrate to maximize the delay
    enum { BAUDRATE = 300 };
    // Ask for the port name on the command line
    if(argc != 2) {
        fprintf(stderr, "Syntax: %s {COMx}\n", argv[0]);
        return 1;
    }
    char path[MAX_PATH];
    snprintf(path, sizeof path, "\\\\.\\%s", argv[1]);
    // Open and configure the serial device
    HANDLE handle = CreateFileA(path, GENERIC_WRITE, 0, NULL,
        OPEN_EXISTING, 0, NULL);
    if(handle == INVALID_HANDLE_VALUE)
        return fatal();
    DCB dcb = {
        .DCBlength = sizeof dcb,
        .BaudRate = BAUDRATE,
        .fBinary = TRUE,
        .ByteSize = DATABITS_8,
        .Parity = NOPARITY,
        .StopBits = ONESTOPBIT
    };
    if(!SetCommState(handle, &dcb))
        return fatal();
    if(!SetCommMask(handle, EV_TXEMPTY))
        return fatal();
    // Fire off a write request
    DWORD written;
    unsigned int elapsed = GetTickCount();
    if(!WriteFile(handle, payload, sizeof payload, &written, NULL) ||
        written != sizeof payload)
        return fatal();
    // Wait for transmit completion and measure time elapsed
    DWORD event;
    if(!WaitCommEvent(handle, &event, NULL))
        return fatal();
    if(!(event & EV_TXEMPTY))
        return fatal();
    elapsed = GetTickCount() - elapsed;
    // Display the final result
    const unsigned int expected_time =
        (sizeof payload * 1000 /* ms */ * 10 /* bits/char */) / BAUDRATE;
    printf("Completed in %ums, expected %ums\n", elapsed, expected_time);
    return 0;
}

The background is that this is part of a Modbus RTU protocol test suite where I am attempting to inject >3.5 character idle delays between characters on the wire to validate device response. Admittedly, an embedded realtime system would have been more far suitable for the task but for various reasons I would prefer to stick to a Windows environment while controlling the timing as best as possible.

Upvotes: 3

Views: 1352

Answers (1)

doynax
doynax

Reputation: 4445

According to the comments by @Hans Passant and @RbMm the output buffer being referred in the EV_TXEMPTY documentation is an intermediate buffer and the event indicates that data has been forwarded to the driver. No equivalent notification event is defined which encompasses the full chain down to the final device buffers.

No general workaround is presently clear to me short of a manual delay based upon the bitrate and adding a significant worst-case margin for any remaining buffer layers to be traversed, inter-character gaps, clock skew, etc.

I would therefore very much appreciate answers with better alternate solutions.


Nevertheless, for my specific application I have implemented a viable workaround.

The target hardware is a half-duplex bus with a FTDI RS485 interface. This particular device offers an optional local-echo mode in which data actively transmitted onto the bus is not actively filtered from the reception.

After each transmission I am therefore able to wait for the expected echo to appear as a round-trip confirmation. In addition, this serves to detect certain faults such as a short-circuited bus.

Upvotes: 2

Related Questions