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 :

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 :

    Taking the following measures seem to have corrected my issue :

    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).