Thorsten Erdmann
Thorsten Erdmann

Reputation: 21

WriteFile to Serial port in Windows waits a long time

I am writing a little serial bus (RS485) monitor in c++. It should read small data packages up to 32 bytes from the serialport an occasionally writes such a databus to the serial port when the user requests it.

Reading works perfectly. I set up a thread for reading using the SetCommMask(hComm, EV_RXCHAR); during initialization and later ReadFile(hComm, RS485PacketBuffer, sizeof(T_PACKET), &nBytes, NULL) to receive the package using timeouts for getting the inter package gaps.

In the main program I use a simple WriteFile(hComm, packet, packet->length + 5, &nBytes, NULL) to write the package.

This WriteFile seems to hang until there are some bytes received from the bus. Only then the package is sent and the bus devices recognize it and answer properly. Why does WriteFile wait for character receiving?

This is my init and thread code

HANDLE uart_init(char *portname, int baudrate)
{
    HANDLE hComm;
    BOOL Write_Status, Read_Status;
    DCB dcbSerialParams;
    COMMTIMEOUTS timeouts = { 0 };

    hComm = CreateFile(L"com8",  //port name
        GENERIC_READ | GENERIC_WRITE,   //Read/Write
        0,                              // No Sharing
        NULL,                           // No Security
        OPEN_EXISTING, // Open existing port only
        0,             // Non Overlapped I/O
        NULL);         // Null for Comm Devices

    if (hComm == INVALID_HANDLE_VALUE)
        printf("Error in opening serial port\n");
    else
        printf("opening serial port successful\n");

    Write_Status = GetCommState(hComm, &dcbSerialParams);     //retreives  the current settings

    if (Write_Status == FALSE) 
    {
        printf("   Error in GetCommState()\n");
        CloseHandle(hComm);
        return NULL;
    }


    dcbSerialParams.BaudRate = baudrate;      // Setting BaudRate = 1200
    dcbSerialParams.ByteSize = 8;             // Setting ByteSize = 8
    dcbSerialParams.StopBits = ONESTOPBIT;    // Setting StopBits = 1
    dcbSerialParams.Parity   = NOPARITY;      // Setting Parity = None

    Write_Status = SetCommState(hComm, &dcbSerialParams);  //Configuring the port according to settings in DCB

    if (Write_Status == FALSE)
    {
        printf("   Error! in Setting DCB Structure\n");
        CloseHandle(hComm);
        return NULL;
    }
    else
    {
        printf("   Setting DCB Structure Successful\n");
        printf("       Baudrate = %d\n", dcbSerialParams.BaudRate);
        printf("       ByteSize = %d\n", dcbSerialParams.ByteSize);
        printf("       StopBits = %d\n", dcbSerialParams.StopBits);
        printf("       Parity   = %d\n\n", dcbSerialParams.Parity);
    }

    // Set COM port timeout settings
    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutConstant = 0;
    timeouts.ReadTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutConstant = 0;
    timeouts.WriteTotalTimeoutMultiplier = 0;
    if (SetCommTimeouts(hComm, &timeouts) == 0)
    {
        printf("Error setting timeouts\n");
        CloseHandle(hComm);
        return NULL;
    }

    Read_Status = SetCommMask(hComm, EV_RXCHAR); //Configure Windows to Monitor the serial device for Character Reception

    if (Read_Status == FALSE)
        printf("    Error! in Setting CommMask\n\n");
    else
        printf("    Setting CommMask successfull\n\n");
    return hComm;
}

unsigned int __stdcall RS485Receiver(void* data)
{
    BOOL status = 0;
    DWORD dwEventMask = 0;
    DWORD nBytes;                     // Bytes read by ReadFile()

    puts("Serial Thread started");
    if (hComm = uart_init("COM8", 1200))
    {
        printf("Waiting for Data Reception...\n");
        status = WaitCommEvent(hComm, &dwEventMask, NULL); //Wait for the character to be received
        if (status == FALSE)
        {
            printf("Error! in Setting WaitCommEvent()\n");
        }
        else //If  WaitCommEvent()==True Read the RXed data using ReadFile();
        {
            _putch('.'); fflush(stdout);
            do
            {
                //Read_Status = ReadFile(hComm, &TempChar, sizeof(TempChar), &NoBytesRead, NULL);
                if ((!ReadFile(hComm, RS485PacketBuffer, sizeof(T_PACKET), &nBytes, NULL)))
                {
                    printf("wrong character");
                }
                else if (nBytes)
                {
                    RS485PrintPacket(RS485PacketBuffer, RS485PacketBuffer->length + 4, stdout);
                }
            } while (1);
        }
        CloseHandle(hComm);//Closing the Serial Port
    }
    puts("Serial Thread stopped");
    return 0;
}
#endif

And this is the write function, which hangs until character receiving:

uint8_t sendPacket(T_PACKET* packet)
{
    DWORD nBytes;

    //calculate checksum
    packet->d[packet->length] = crc8((uint8_t*)packet, packet->length + 4);

    // PORTB &= ~(1 << TRANSENABLE); // ToDo: How do I enable transmitter on PC
    Sleep(10); // short pause to stabilize bus transceiver
    printf("Sende Paket, length = %u\n", packet->length+5);
    if (!WriteFile(hComm, packet, packet->length + 5, &nBytes, NULL))
    {
        printf("Error writing text to RS485 port\n");
        return 6;
    }
    printf("gesendet\n"); fflush(stdout);
    if (nBytes != packet->length + 5) return 7;
    printf("kein Fehler\n");
    // PORTB |= (1 << TRANSENABLE); // ToDo: How do I disable transmitter on PC
    return 0;`
}

I already tried several timout settings. These seems to have no effect to the problem. Maybe it is an issue with the USB/RS485 converter. I think as the RS485 is a bus, the serial port will probably see it's own send bytes. Maybe that causes the problem.

Or maybe the blocking receiver thread which is waiting for characters blocks the whole serial port.

As @Codo mentioned, I tried now to use overlapped IO. I have a complete solution now. See the answer below.

Upvotes: 1

Views: 977

Answers (1)

Thorsten Erdmann
Thorsten Erdmann

Reputation: 21

thanks for all the hints. @Codo's first remark was the solution. I have to use overlapping because Windows blocks also the sender when the receiver is waiting. Stupid, but true. Since overlapping IO is a bit complicated I post my solution her for reference. Now I have to try the same in C#. :-)

HANDLE uart_init(char *portname, int baudrate)
{
    HANDLE hComm;
    BOOL Result;
    DCB dcbSerialParams;
    COMMTIMEOUTS timeouts = { 0 };
    wchar_t wstr[50];
    MultiByteToWideChar(CP_UTF8, 0, portname, -1, wstr, 50);
    hComm = CreateFile(wstr,  //port name
        GENERIC_READ | GENERIC_WRITE,   //Read/Write
        0,                              // No Sharing
        NULL,                           // No Security
        OPEN_EXISTING, // Open existing port only
        FILE_FLAG_OVERLAPPED,           // Non Overlapped I/O
        NULL);         // Null for Comm Devices

    if (hComm == INVALID_HANDLE_VALUE)
        printf("Error in opening serial port\n");
    else
        printf("opening serial port successful\n");

    Result = GetCommState(hComm, &dcbSerialParams);     //retreives  the current settings

    if (Result == FALSE) 
    {
        printf("   Error in GetCommState()\n");
        CloseHandle(hComm);
        return NULL;
    }

    dcbSerialParams.BaudRate        = baudrate;   // Setting BaudRate = 1200
    dcbSerialParams.ByteSize        = 8;          // Setting ByteSize = 8
    dcbSerialParams.StopBits        = ONESTOPBIT; // Setting StopBits = 1
    dcbSerialParams.Parity          = NOPARITY;   // Setting Parity = None
    dcbSerialParams.fBinary         = TRUE;       // has to be TRUE in Windows
    dcbSerialParams.fParity         = FALSE;      // No parity
    dcbSerialParams.fOutxCtsFlow    = FALSE;      // No CTS flow control
    dcbSerialParams.fOutxDsrFlow    = FALSE;      // No DSR flow control
    dcbSerialParams.fDtrControl     = FALSE;      // No DTR low control
    dcbSerialParams.fDsrSensitivity = FALSE;      // Ignore DSR
    dcbSerialParams.fOutX           = FALSE;      // No XON/XOFF flow control
    dcbSerialParams.fInX            = FALSE;      // No XON/XOFF flow control
    dcbSerialParams.fErrorChar      = FALSE;      // do not replace errors
    dcbSerialParams.fNull           = FALSE;      // allow NULL bytes
    dcbSerialParams.fRtsControl     = RTS_CONTROL_ENABLE; // Enable RTS pin
    dcbSerialParams.fAbortOnError   = FALSE;      // do not stop on error

    Result = SetCommState(hComm, &dcbSerialParams);  //Configuring the port according to settings in DCB

    if (Result == FALSE)
    {
        printf("   Error! in Setting DCB Structure\n");
        CloseHandle(hComm);
        return NULL;
    }
    else
    {
        printf("   Setting DCB Structure Successful\n");
        printf("       Baudrate = %d\n", dcbSerialParams.BaudRate);
        printf("       ByteSize = %d\n", dcbSerialParams.ByteSize);
        printf("       StopBits = %d\n", dcbSerialParams.StopBits);
        printf("       Parity   = %d\n\n", dcbSerialParams.Parity);
    }

    // Set COM port timeout settings
    timeouts.ReadIntervalTimeout = 50;
    timeouts.ReadTotalTimeoutConstant = 0;
    timeouts.ReadTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutConstant = 0;
    timeouts.WriteTotalTimeoutMultiplier = 0;
    if (SetCommTimeouts(hComm, &timeouts) == 0)
    {
        printf("Error setting timeouts\n");
        CloseHandle(hComm);
        return NULL;
    }

    Result = SetCommMask(hComm, EV_RXCHAR); //Configure Windows to Monitor the serial device for Character Reception

    if (Result == FALSE)
        printf("    Error in Setting CommMask\n\n");
    else
        printf("    Setting CommMask successfull\n\n");
    return hComm;
}


unsigned int __stdcall RS485Receiver(void* data)
{
    BOOL status = 0;
    DWORD dwEventMask = 0;
    DWORD nBytes;                     // Bytes read by ReadFile()
    DWORD dwRes;
    DWORD dwRead;
    BOOL fWaitingOnRead = FALSE;
    OVERLAPPED osRead = { 0 };

    puts("Serial Thread started");
    if (hComm = uart_init((char *)data, 1200))
    {
        printf("Waiting for Data Reception...\n");

        // Create the overlapped event. Must be closed before exiting
        // to avoid a handle leak.
        osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (osRead.hEvent == NULL)
        {
            CloseHandle(hComm);//Closing the Serial Port
            puts("Serial Thread stopped");
            return 0;
        }

        while (1)
        {
            if (!fWaitingOnRead) 
            {
                // Issue read operation.
                if (!ReadFile(hComm, RS485PacketBuffer, sizeof(T_PACKET), &dwRead, &osRead)) 
                {
                    if (GetLastError() != ERROR_IO_PENDING)     // read not delayed?
                        printf("wrong character");
                    else
                        fWaitingOnRead = TRUE;
                }
                else {
                    // read completed immediately
                    RS485PrintPacket(RS485PacketBuffer, RS485PacketBuffer->length + 4, stdout);
                }
            }

            if (fWaitingOnRead)
            {
                dwRes = WaitForSingleObject(osRead.hEvent, INFINITE);
                switch (dwRes)
                {
                    // Read completed.
                case WAIT_OBJECT_0:
                    if (!GetOverlappedResult(hComm, &osRead, &dwRead, FALSE))
                        printf("wrong character");
                    else
                        // Read completed successfully.
                        RS485PrintPacket(RS485PacketBuffer, RS485PacketBuffer->length + 4, stdout);

                    //  Reset flag so that another opertion can be issued.
                    fWaitingOnRead = FALSE;
                    break;

                case WAIT_TIMEOUT:
                    // Operation isn't complete yet. fWaitingOnRead flag isn't
                    // changed since I'll loop back around, and I don't want
                    // to issue another read until the first one finishes.
                    //
                    // This is a good time to do some background work.
                    break;

                default:
                    // Error in the WaitForSingleObject; abort.
                    // This indicates a problem with the OVERLAPPED structure's
                    // event handle.
                    break;
                }
            }
        }
        CloseHandle(hComm); //Closing the Serial Port
    }
    puts("Serial Thread stopped");
    return 0;
}

/*
 * Send a data packet
 * use this only for Windows
 */
uint8_t sendPacket(T_PACKET* packet)
{
    DWORD nBytes;
    uint8_t fRes = 0;
    OVERLAPPED osWrite = { 0 };

    //calculate checksum
    packet->d[packet->length] = crc8((uint8_t*)packet, packet->length + 4);

    // PORTB &= ~(1 << TRANSENABLE); // ToDo: How do I enable transmitter on PC
    Sleep(10); // short pause to stabilize bus transceiver
    osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (osWrite.hEvent == NULL)
        // error creating overlapped event handle
        return ERR_COMM_WR;

    if (!WriteFile(hComm, packet, packet->length + 5, &nBytes, &osWrite))
    {
        if (GetLastError() != ERROR_IO_PENDING)     // read not delayed?
        {
            fRes = ERR_COMM_WR;
        }
        else
        {
            if (!GetOverlappedResult(hComm, &osWrite, &nBytes, TRUE))
            {
                fRes = ERR_COMM_WR;
            }
            else
            {
                // Read completed successfully.
                RS485PrintPacket(packet, packet->length + 4, stdout);
            }
        }
    }
    CloseHandle(osWrite.hEvent);
    // PORTB |= (1 << TRANSENABLE); // ToDo: How do I disable transmitter on PC
    return fRes;
}

Upvotes: 1

Related Questions