Search code examples
cwindowsserial-port

Serial communcation misses characters when reading (MTTY exemple confuses me)


I've been trying to establish a communication with an Arduino Nano (a cheap Chinese copy actually).

The board sends 3 characters every seconds, I only get the last 2 characters

I found this documentation that refers to the MTTY sample. I went ahead and found it to take it as an exemple.

I'm intersted in understanding how the reading part is done (in READSTAT.C)

This part confuses me :

    while ( !fThreadDone ) {

        //
        // If no reading is allowed, then set flag to
        // make it look like a read is already outstanding.
        //
        if (NOREADING( TTYInfo ))
            fWaitingOnRead = TRUE;
        
        //
        // if no read is outstanding, then issue another one
        //
        if (!fWaitingOnRead) {
            if (!ReadFile(COMDEV(TTYInfo), lpBuf, AMOUNT_TO_READ, &dwRead, &osReader)) {
                if (GetLastError() != ERROR_IO_PENDING)   // read not delayed?
                    ErrorInComm("ReadFile in ReaderAndStatusProc");

                fWaitingOnRead = TRUE;
            }
            else {    // read completed immediately
                if ((dwRead != MAX_READ_BUFFER) && SHOWTIMEOUTS(TTYInfo))
                    UpdateStatus("Read timed out immediately.\r\n");

                if (dwRead)
                    OutputABuffer(hTTY, lpBuf, dwRead);
            }
        }

        //
        // If status flags have changed, then reset comm mask.
        // This will cause a pending WaitCommEvent to complete
        // and the resultant event flag will be NULL.
        //
        if (dwStoredFlags != EVENTFLAGS(TTYInfo)) {
            dwStoredFlags = EVENTFLAGS(TTYInfo);
            if (!SetCommMask(COMDEV(TTYInfo), dwStoredFlags))
                ErrorReporter("SetCommMask");
        }

        //
        // If event checks are not allowed, then make it look
        // like an event check operation is outstanding
        //
        if (NOEVENTS(TTYInfo))
            fWaitingOnStat = TRUE;
        //
        // if no status check is outstanding, then issue another one
        //
        if (!fWaitingOnStat) {
            if (NOEVENTS(TTYInfo))
                fWaitingOnStat = TRUE;
            else {
                if (!WaitCommEvent(COMDEV(TTYInfo), &dwCommEvent, &osStatus)) {
                    if (GetLastError() != ERROR_IO_PENDING)   // Wait not delayed?
                        ErrorReporter("WaitCommEvent");
                    else
                        fWaitingOnStat = TRUE;
                }
                else
                    // WaitCommEvent returned immediately
                    ReportStatusEvent(dwCommEvent);
            }
        }

        //
        // wait for pending operations to complete
        //
        if ( fWaitingOnStat && fWaitingOnRead ) {
            dwRes = WaitForMultipleObjects(NUM_READSTAT_HANDLES, hArray, FALSE, STATUS_CHECK_TIMEOUT);
            switch(dwRes)
            {
                //
                // read completed
                //
                case WAIT_OBJECT_0:
                    if (!GetOverlappedResult(COMDEV(TTYInfo), &osReader, &dwRead, FALSE)) {
                        if (GetLastError() == ERROR_OPERATION_ABORTED)
                            UpdateStatus("Read aborted\r\n");
                        else
                            ErrorInComm("GetOverlappedResult (in Reader)");
                    }
                    else {      // read completed successfully
                        if ((dwRead != MAX_READ_BUFFER) && SHOWTIMEOUTS(TTYInfo))
                            UpdateStatus("Read timed out overlapped.\r\n");

                        if (dwRead)
                            OutputABuffer(hTTY, lpBuf, dwRead);
                    }

                    fWaitingOnRead = FALSE;
                    break;

                //
                // status completed
                //
                case WAIT_OBJECT_0 + 1: 
                    if (!GetOverlappedResult(COMDEV(TTYInfo), &osStatus, &dwOvRes, FALSE)) {
                        if (GetLastError() == ERROR_OPERATION_ABORTED)
                            UpdateStatus("WaitCommEvent aborted\r\n");
                        else
                            ErrorInComm("GetOverlappedResult (in Reader)");
                    }
                    else       // status check completed successfully
                        ReportStatusEvent(dwCommEvent);

                    fWaitingOnStat = FALSE;
                    break;

                //
                // status message event
                //
                case WAIT_OBJECT_0 + 2:
                    StatusMessage();
                    break;

                //
                // thread exit event
                //
                case WAIT_OBJECT_0 + 3:
                    fThreadDone = TRUE;
                    break;

                case WAIT_TIMEOUT:
                    //
                    // timeouts are not reported because they happen too often
                    // OutputDebugString("Timeout in Reader & Status checking\n\r");
                    //

                    //
                    // if status checks are not allowed, then don't issue the
                    // modem status check nor the com stat check
                    //
                    if (!NOSTATUS(TTYInfo)) {
                        CheckModemStatus(FALSE);    // take this opportunity to do
                        CheckComStat(FALSE);        //   a modem status check and
                                                    //   a comm status check
                    }

                    break;                       

                default:
                    ErrorReporter("WaitForMultipleObjects(Reader & Status handles)");
                    break;
            }
        }
    }

I think this code is likely to call ReadFile without calling WaitCommEvent, is it correct to proceed this way ? Also I always tried to :

  • Call WaitCommEvent
  • Call ReadFile
  • Call WaitForSingleObject
  • Call GetOverlappedResult
  • Recall ReadFile to actually get the datas but it doesn't seem to be necessary

I tryied using this exemple without call WaitCommEvent and it works a little bit, the read operations always miss the first character.

I open the port with GENERIC_WRITE, GENERIC_READ, OPEN_EXISTING, FILE_FLAG_OVERLAPPED and I appropriately call SetCommMask with the EV_RXCHAR flag.

DWORD WINAPI read(LPVOID param) {
    CUSTOM_DATA* params = (CUSTOM_DATA*)param;
    HANDLE hComm = params->hComm;
    OVERLAPPED ov = { 0 };
    bool waitingOnStat = FALSE;
    int i = 1;
    while (1) {
        char* buffer = (char*)malloc(100 * sizeof(char));
        ZeroMemory(buffer, 100);
        DWORD bufferSize = 100;
        DWORD nbRead;
        DWORD evtMask;
        DWORD res;
        if (!waitingOnStat) {
            ZeroMemory(&ov, sizeof(OVERLAPPED));
            ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

            if (!ReadFile(hComm, buffer, bufferSize, &nbRead, &ov)) {
                if (GetLastError() != ERROR_IO_PENDING) {
                    printf("ERROR_IO NOT PENDING\n");
                }
                waitingOnStat = true;
            }
            else {
                std::cout.write(buffer, nbRead) << std::endl;
                waitingOnStat = FALSE;
            }
        }

        if (waitingOnStat) {
            printf("Waiting");
            DWORD res = WaitForSingleObject(ov.hEvent, INFINITE);
            switch (res) {
            case WAIT_OBJECT_0:
                waitingOnStat = FALSE;
                DWORD nbBytesTransfered;
                if (!GetOverlappedResult(hComm, &ov, &nbBytesTransfered, TRUE)) {
                        printf("error reading %d\n", GetLastError());
                } else {
                    std::cout.write(buffer, nbRead);
                }
                break;
            default:
                printf("waiting read error %d\n", GetLastError());
                break;
            }
        }
        i++;
    }
}

Why is ReadFile constantly returning true ?

Do I need to call WaitCommEvent


Solution

  • With @Ian Abbott's help I fiddled with the parameters for COMMTIMEOUTS. The documentation specifies some behavior such as :

    If an application sets ReadIntervalTimeout and ReadTotalTimeoutMultiplier to MAXDWORD and sets ReadTotalTimeoutConstant to a value greater than zero and less than MAXDWORD, one of the following occurs when the ReadFile function is called:

    • If there are any bytes in the input buffer, ReadFile returns immediately with the bytes in the buffer.
    • If there are no bytes in the input buffer, ReadFile waits until a byte arrives and then returns immediately.
    • If no bytes arrive within the time specified by ReadTotalTimeoutConstant, ReadFile times out.

    The thing is with my code I was :

    • Calling GetOverlappedResult and then ignoring the nbBytesRead param (because I had read that it should be ignored when using overlapped I/O).

    Taking the following measures seem to have corrected my issue :

    • Use nbBytesTransfered in the when after calling GetOverlappedResult. Although if I set the timeouts to :

      timeouts.ReadIntervalTimeout = 4294967295;
      timeouts.ReadTotalTimeoutConstant = 4294967294;
      timeouts.ReadTotalTimeoutMultiplier = 4294967295;
      

      ReadFile will return FALSE for the first character that will end up being displayed by the GetOverlappedResult call, then ReadFile will return TRUE and will display the remaining characters (I haven't tested this at high speed).

    If I set the timeouts as follow :

    timeouts.ReadIntervalTimeout = 10;
    timeouts.ReadTotalTimeoutConstant = 100;
    timeouts.ReadTotalTimeoutMultiplier = 10;
    

    ReadFile doesn't return TRUE and GetOverlappedResult reads all of the characters (I haven't tested this at high speed).