Serial communication with minimal delay

rnd_nr_gen picture rnd_nr_gen · Apr 1, 2013 · Viewed 11.9k times · Source

I have a computer which is connected with external devices via serial communication (i.e. RS-232/RS-422 of physical or emulated serial ports). They communicate with each other by frequent data exchange (30Hz) but with only small data packet (less than 16 bytes for each packet).

The most critical requirement of the communication is low latency or delay between transmitting and receiving.

The data exchange pattern is handshake-like. One host device initiates communication and keeps sending notification on a client device. A client device needs to reply every notification from the host device as quick as possible (this is exactly where the low latency needs to be achieved). The data packets of notifications and replies are well defined; namely the data length is known. And basically data loss is not allowed.

I have used following common Win API functions to do the I/O read/write in a synchronous manner: CreateFile, ReadFile, WriteFile

A client device uses ReadFile to read data from a host device. Once the client reads the complete data packet whose length is known, it uses WriteFile to reply the host device with according data packet. The reads and writes are always sequential without concurrency.

Somehow the communication is not fast enough. Namely the time duration between data sending and receiving takes too long. I guess that it could be a problem with serial port buffering or interrupts.

Here I summarize some possible actions to improve the delay. Please give me some suggestions and corrections :)

  1. call CreateFile with FILE_FLAG_NO_BUFFERING flag? I am not sure if this flag is relevant in this context.
  2. call FlushFileBuffers after each WriteFile? or any action which can notify/interrupt serial port to immediately transmit data?
  3. set higher priority for thread and process which handling serial communication
  4. set latency timer or transfer size for emulated devices (with their driver). But how about the physical serial port?
  5. any equivalent stuff on Windows like setserial/low_latency under Linux?
  6. disable FIFO?

thanks in advance!

Answer

PkP picture PkP · Sep 4, 2019

I solved this in my case by setting the comm timeouts to {MAXDWORD,0,0,0,0}.

After years of struggling this, on this very day I finally was able to make my serial comms terminal thingy fast enough with Microsoft's CDC class USB UART driver (USBSER.SYS, which is now built in in Windows 10 making it actually usable).

Apparently the aforementioned set of values is a special value that sets minimal timeouts as well as minimal latency (at least with the Microsoft driver, or so it seems to me anyway) and also causes ReadFile to return immediately if no new characters are in the receive buffer.

Here's my code (Visual C++ 2008, project character set changed from "Unicode" to "Not set" to avoid LPCWSTR type cast problem of portname) to open the port:

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;
}

And here's a USB trace of it working. Please note the OUT transactions (output bytes) followed by IN transactions (input bytes) and then more OUT transactions (output bytes) all within 3 milliseconds:

USB UART packet trace with minimal timeouts

And finally, since if you are reading this, you might be interested to see my function that sends and receives characters over the 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++;
    }

Large transfers are handled elsewhere in code.

On a final note, apparently this is the first fast UART code I've managed to write since abandoning DOS in 1998. O, doest the time fly when thou art having fun.

This is where I found the relevant information: http://www.egmont.com.pl/addi-data/instrukcje/standard_driver.pdf