Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By Mickey Williams and David Bennett

Serial Communications

Handling serial port I/O presents special problems because of the many different hardware-specific settings, the need for timeouts, and bidirectional asynchronous data transfer.

Parallel ports are a similar communications resource, and although they generally are used in more unidirectional applications (such as printers), they still must provide bidirectional asynchronous transfer.

The following sections discuss some of the aspects of serial data transfer.

Opening and Configuring Serial Ports

You can open a serial port device using the CreateFile() function by specifying a filename that refers to the specific port, such as COM1 or COM2.

When you open a serial port, it is opened automatically for exclusive access, so you should pass a zero for the CreateFile()'s third parameter and OPEN_EXISTING for the open mode (fifth parameter). You can add a combination of the special file mode flags to indicate overlapped I/O or any special buffering requirements, as normal.

If the serial port opens successfully, a Win32 file object handle is returned. Otherwise, INVALID_HANDLE_VALUE is returned. The following example opens the port named by m_strPort for overlapped I/O and doesn't perform any buffering:

m_hCommPort = CreateFile(m_strPort,GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED |
FILE_FLAG_NO_BUFFERING,NULL);
if (m_hCommPort == INVALID_HANDLE_VALUE) return FALSE;

After you open a serial port, you must set the many flags required to configure the device. These flags are held in a device control block (DCB) structure. You can either fill in the entire DCB structure or use one of the helper functions to fill in some of the details. The GetCommState() function fills in a DCB structure with the current settings from the hardware, and you can use a corresponding SetCommState() function to specify the new settings from a DCB structure.

You can use BuildCommDCB() to set some (but not all) of the settings from a command string, as shown here:

"baud = 9600 parity = N data = 8 stop =1"

The following example turns off all flow control (such as XON/XOFF, CTS/RTS, and DSR/DTR) and sets the baud rate, data bits, and parity settings from the m_strBaud command string:

DCB dcb;
memset(&dcb,0,sizeof(dcb));
dcb.DCBlength=sizeof(dcb);
GetCommState(m_hCommPort,&dcb);
if (!BuildCommDCB((LPCTSTR)m_strBaud,&dcb))
{
    TRACE("Unable to build Comm Port config = '%s', err = %d\n",
(LPCTSTR)m_strBaud,GetLastError());
    return FALSE;
}

// Common settings
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fDtrControl = FALSE;
dcb.fDsrSensitivity = FALSE;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
dcb.fNull = FALSE;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fAbortOnError = FALSE;
if (!SetCommState(m_hCommPort,&dcb))
{
    TRACE("Unable to set Comm Port config = '%s', err = %d\n",
(LPCTSTR)m_strBaud,GetLastError());
    return FALSE;
}

You also can find the default communication settings by calling the GetDefaultCommConfig() function, which fills a COMMCONFIG structure. This structure holds a DCB structure and a number of application-specific values. To change these settings, you can pass a COMMCONFIG structure with your customized settings to SetDefaultCommConfig().

Asynchronous Communications

After configuring the serial port, you can start transferring data via ReadFile() and WriteFile() functions. However, you should remember that if you haven't specified the FILE_FLAG_OVERLAPPED flag in the CreateFile() flags parameter, ReadFile() will block waiting for input. This probably is good if your program spawns another thread that specifically waits for incoming serial port characters, but not if you want to issue a ReadFile() and periodically check to see whether any characters have arrived.

If you have specified the FILE_FLAG_OVERLAPPED flag, however, you must provide pointers to OVERLAPPED structures for the read and write functions and handle the asynchronous I/O.

The following example demonstrates asynchronous overlapped I/O. This console application issues a read request and then writes a string with the current time to the console. It then waits for either five seconds or for completion of the read request. If five seconds pass with no received characters, the loop repeats and writes the time again. If a character is received, a string is printed in response.

Example 8.2. Asynchronous Overlapped I/O

#include "windows.h"
#include "time.h"
#include "string.h"
#include "stdio.h"

BOOL SetCommDefaults(HANDLE hSerial);

int main(int argc, char* argv[])
{
    HANDLE hSerial = CreateFile("COM2",
GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED |
FILE_FLAG_NO_BUFFERING,NULL);
    if (hSerial == INVALID_HANDLE_VALUE) return GetLastError();
    SetCommDefaults(hSerial);

    HANDLE hReadEvent = CreateEvent(NULL,TRUE,FALSE,"RxEvent");
    OVERLAPPED ovRead;
    OVERLAPPED ovWrite;
    memset(&ovRead,0,sizeof(ovRead));
    memset(&ovWrite,0,sizeof(ovWrite));

    ovRead.hEvent = hReadEvent;

    char szRxChar = 0;
    DWORD dwBytesRead = 0;
    DWORD dwBytesWritten = 0;

    while(szRxChar != 'q')
    {
        // Check if a read is outstanding
        if (HasOverlappedIoCompleted(&ovRead))
        {
            // Issue a serial port read
            if (!ReadFile(hSerial,&szRxChar,1,
                    &dwBytesRead,&ovRead))
            {
                DWORD dwErr = GetLastError();
                if (dwErr!=ERROR_IO_PENDING)
                    return dwErr;
            }
        }

        // Write the time out to the serial port
        time_t t_time = time(0);
        char buf[50];
        sprintf(buf,"Time is %s\n\r",ctime(&t_time));
        if (HasOverlappedIoCompleted(&ovWrite))
        {
            WriteFile(hSerial,buf,strlen(buf),
                    &dwBytesWritten,&ovWrite);
        }


        // ... Do some other processing

        // Wait 5 seconds for serial input
        if (!(HasOverlappedIoCompleted(&ovRead)))
            WaitForSingleObject(hReadEvent,5000);

        // Check if serial input has arrived
        if (GetOverlappedResult(hSerial,&ovRead,
                &dwBytesRead,FALSE))
        {
            // Wait for the write
            GetOverlappedResult(hSerial,&ovWrite,
                &dwBytesWritten,TRUE);
            // Display a response to input
            sprintf(buf,"You pressed the '%c' key\n\r",
                szRxChar);
            WriteFile(hSerial,buf,strlen(buf),
                    &dwBytesWritten,&ovWrite);
        }

    }

    CloseHandle(hSerial);
    CloseHandle(hReadEvent);
    return 0;
}

BOOL SetCommDefaults(HANDLE hSerial)
{
    DCB dcb;
    memset(&dcb,0,sizeof(dcb));
    dcb.DCBlength=sizeof(dcb);
    if (!GetCommState(hSerial,&dcb))
        return FALSE;
    dcb.BaudRate=9600;
    dcb.ByteSize=8;
    dcb.Parity=0;
    dcb.StopBits=ONESTOPBIT;
    if (!SetCommState(hSerial,&dcb))
        return FALSE;
    return TRUE;
}

You'll notice that CreateFile() specifies the FILE_FLAG_OVERLAPPED flag for asynchronous I/O. A manual reset event (hReadEvent) is created to signal when incoming serial characters have been read by the overlapped ReadFile() function.

After ReadFile() is called, it returns immediately, and other processing such as the WriteFile() can be performed before finally waiting in the WaitForSingleObject() function for a signal from the read event. This wait has a timeout of five seconds, so if no characters are read, the loop is repeated. GetOverlappedResult() is used to check whether characters are received, and a WriteFile( ) response is issued to any incoming characters.

Setting Communication Timeouts

At times, you'll probably need to set up various types of timeouts, especially when implementing protocol-driven data transfers.

The SetCommTimeouts() function can help you simplify timeout implementation. The function requires a pointer to a COMMTIMEOUTS structure that contains a number of DWORD members that let you set timeouts in milliseconds, as well as multipliers for those timeouts.

The ReadIntervalTimeout member lets you set the maximum allowable time between reading two characters. If the timeout is exceeded, the ReadFile() operation is completed. You can set this member to zero to indicate that you don't want to use interval timeouts. Alternatively, you can set it to MAXDWORD and set the ReadTotalTimeoutMultiplier and ReadTotalTimeoutConstant members to zero to indicate that the ReadFile() should return immediately.

You can use the ReadTotalTimeoutMultiplier to specify a total timeout for the read operation. This millisecond value is multiplied by the total characters to be read to calculate an overall ReadFile() timeout. The ReadTotalTimeoutConstant value is added to the calculation to let you add a constant to the overall timeout duration. If you don't want to set an overall timeout, you can set these values to zero.

Two corresponding members—WriteTotalTimeoutMultiplier and WriteTotalTimeoutConstant—are used to calculate an overall timeout value for WriteFile() operations.

You can find the current timeout settings using the GetCommTimeout() function, which fills a passed COMMTIMEOUTS structure. The aptly named BuildCommDCBAndTimeouts() does just what it says and lets you set both the DCB settings and timeouts in one go.

The GetCommProperties() function can fill a COMMPROP structure with details about spe cific driver settings, such as buffer sizes and maximum supported baud rates.

Communication Events

You can set an event mask to enable reporting of various types of communication events. The SetCommMask() function lets you specify a number of flag values, such as EV_BREAK, EV_RXCHAR, or EV_CTS (break signal, received character, and clear-to-send signal).

After you set an event mask, you can use the WaitCommEvent() function to wait for one of those events to occur. This can be a useful way of waiting for received characters before issuing a ReadFile(). The WaitCommEvent() function lets you pass a pointer to a DWORD variable to store the actual event received, as well as a pointer to an OVERLAPPED structure to let you issue asynchronous WaitCommEvent() operations that can be tested and completed using the GetOverlappedResult() function.

You can get the current mask settings by calling the corresponding GetCommMask() function.

If you want to issue special communication events, you can use a number of special functions, such as SetCommBreak(), ClearCommBreak(), and EscapeCommFunction().

You can purge any pending input or output characters by calling the PurgeComm() function. If any hardware errors are detected, you can retrieve the details with ClearCommError(); the error condition is reset so that the device can continue.

+ Share This