Implementing Serial Communication in Win9X/2000

Environment: Win9X/2000 (hasn’t been tested on other OSs), VC++

Introduction

Serial data transmission seems a bit difficult for those who are new to the world of serial communication and VC++. A long time ago, I searched codeguru.com to get some help on serial data transmission; I got some valuable information from this site. Since then, it has been my dream to develop a simple class to implement serial data transmission. After getting seven months of practical experience in the field of serial communication, I have developed a simple class to implement Serial transmission using WINAPI functions. Before going into the details of this class, it is essential to know the basics of serial data transmission.

In serial data transmission, the data is transmitted in serial format with LSB of the byte to be transmitted shifted out first among the data bits.The general format for serial transmission is Start Bit + Data Bits + Parity Bit (Optional) + Stop Bit.

The parity bit is optional. It is used for error checking in communication. You can enable or disable parity checking by software modifications. Also, you can specify which parity you want to use, either EVEN or ODD through software.

The various steps you need to perform to send and receive data through the serial port of a PC are listed below:

  1. Open the communication port.
  2. Configure the communication port, setting the baud rate, parity, number of data bits, and so forth.
  3. Set timeouts for communication.
  4. Write data to the port.
  5. Read data from the port.
  6. Close the port.

Opening the Serial Port

The CreateFile( ) WINAPI opens a communications port. There are two ways to call CreateFile( ) to open the communications port—overlapped and non-overlapped. You can open a Communication Port for OVERLAPPED IO operation and NON-OVERLAPPED IO operation. CSerialPort class is written for NON-OVERLAPPED IO Operation. For more details on OVERLAPPED and NON-OVERLAPPED IO, please refer to the MSDN documentation.

Configuring the Serial Port

The most critical phase in serial communication programming is configuring the port settings with the DCB structure. Erroneously initializing the DCB structure is a common problem. When a serial communications function does not produce the expected results, the DCB structure may be in error. A call to the CreateFile( ) function opens a serial port with default port settings. Usually, the application needs to change the defaults. You must set the baud rate for communication, parity functions, number of stop bits, and so on, in accordance with the requirements of the external device by calling the appropriate WINAPI functions.

Configuring Timeouts

An application must always set communication timeouts using the COMMTIMEOUTS structure each time it opens a communication port. If this structure is not configured, the port uses default timeouts supplied by the driver, or timeouts from a previous communications application. By assuming specific timeout settings when the settings are actually different, an application can have read/write operations that never complete or complete too often. You must configure the read and write timeouts for communication by calling the appropriate WINAPI functions.

Writing to a Serial Port

The WriteFile( ) WINAPI transfers data through the serial connection to another device. Before calling this function, an application must open and configure a serial port.

Reading from a Serial Port

An application calls the ReadFile( ) WINAPI to receive data from a device at the other end of a serial connection. ReadFile( ).

Closing a Serial Port

You must close the communications port after serial transmission to make this port available for other applications that use this resource. As long as you are working with a port (the port is in the open state), other threads or applications will not get access to this port until you close the handle to that port in a NON-OVERLAPPED IO operation. Call the CloseHandle( ) WINAPI to close a serial port. CloseHandle has one parameter, which is the handle returned by the CreateFile( ) WINAPI call that opened the port.

The CSerialPort Class

The CSerialPort Class uses six member functions to achieve the above-mentioned things. They are:

BOOL CSerialPort::OpenPort(CString portname)
{
portname= "//./" + portname;

hComm = CreateFile(portname,
                      GENERIC_READ | GENERIC_WRITE,
                      0,
                      0,
                      OPEN_EXISTING,
                      0,
                      0);
if(hComm==INVALID_HANDLE_VALUE){
  return false;}
  else
  return true;

}

The OpenPort( ) member function opens a communication port for data transmission. The parameter to be passed to this function is a string containing the port name; for example, “com1” for COM1, “com2” for COM2, and so forth. If the function succeeds, the return value is true; otherwise, it is false.

Here’s an example.

OpenPort("com1");

BOOL CSerialPort::ConfigurePort(DWORD BaudRate, BYTE ByteSize,
                                DWORD fParity, BYTE Parity,
                                BYTE StopBits)
{
  if((m_bPortReady = GetCommState(hComm, &m_dcb))==0){
    MessageBox("GetCommState Error","Error",MB_OK+MB_ICONERROR);
    CloseHandle(hComm);
  return false;}
m_dcb.BaudRate =BaudRate;
m_dcb.ByteSize = ByteSize;
m_dcb.Parity =Parity ;
m_dcb.StopBits =StopBits;
m_dcb.fBinary=TRUE;
m_dcb.fDsrSensitivity=false;
m_dcb.fParity=fParity;
m_dcb.fOutX=false;
m_dcb.fInX=false;
m_dcb.fNull=false;
m_dcb.fAbortOnError=TRUE;
m_dcb.fOutxCtsFlow=FALSE;
m_dcb.fOutxDsrFlow=false;
m_dcb.fDtrControl=DTR_CONTROL_DISABLE;
m_dcb.fDsrSensitivity=false;
m_dcb.fRtsControl=RTS_CONTROL_DISABLE;
m_dcb.fOutxCtsFlow=false;
m_dcb.fOutxCtsFlow=false;

m_bPortReady = SetCommState(hComm, &m_dcb);
if(m_bPortReady ==0){
    MessageBox("SetCommState Error","Error",MB_OK+MB_ICONERROR);
    CloseHandle(hComm);
  return false;}
return true;
}

The ConfigurePort( ) member function configures a communication port for data transmission. The parameters to be passed to this function are given below.

Parameter Explanation
DWORD BaudRate Represents the baud rate for communication supported by an external device. For example, you can give this parameter as 9600 or CBR_9600 for a baud rate of 9600. The available standard baud rates supported by the PC are CBR_110, CBR_300, CBR_600, CBR_1200, CBR_2400, CBR_4800, CBR_9600, CBR_14400, CBR_19200, CBR_38400, CBR_56000, CBR_57600, CBR_115200, CBR_128000, and CBR_256000.
BYTE ByteSize Represents the number of bits in the bytes transmitted and received. Standard values are 8 or 4.
DWORD fParity Specifies whether parity checking is enabled. If this member is TRUE, parity checking is performed and errors are reported. If FALSE, no parity checking is performed.
BYTE Parity Specifies the parity scheme to be used. This member can be one of the following values:

Value                    Meaning
EVENPARITY      Even
MARKPARITY     Mark
NOPARITY           No parity
ODDPARITY        Odd
SPACEPARITY    Space

BYTE StopBits Specifies the number of stop bits to be used. This member can be one of the following values:

Value                      Meaning
ONESTOPBIT       1 stop bit
ONE5STOPBITS   1.5 stop bits
TWOSTOPBITS    2 stop bits

NOTE: The ConfigurePort( ) function is written on the assumption that the communication flow control is completely controlled on the basis of the protocol supported by the external device. It transmits and receives data without checking the CTS/RTS and Xon/Xoff hardware flow control. You can modify this to your requirements by changing the values of the members of DCB that are responsible for it, in the implementation of ConfigurePort( ) in SerialPort.cpp.

Here’s another example.

ConfigurePort(CBR_9600, 8, true, EVENPARITY , ONESTOPBIT )

If the function succeeds, the return value is true; otherwise, it is false.

BOOL CSerialPort::SetCommunicationTimeouts
                 (DWORD ReadIntervalTimeout,
                  DWORD ReadTotalTimeoutMultiplier,
                  DWORD ReadTotalTimeoutConstant,
                  DWORD WriteTotalTimeoutMultiplier,
                  DWORD WriteTotalTimeoutConstant)
{
if((m_bPortReady = GetCommTimeouts (hComm, &m_CommTimeouts))==0)
   return false;
m_CommTimeouts.ReadIntervalTimeout =ReadIntervalTimeout;
m_CommTimeouts.ReadTotalTimeoutConstant =ReadTotalTimeoutConstant;
m_CommTimeouts.ReadTotalTimeoutMultiplier
              =ReadTotalTimeoutMultiplier;
m_CommTimeouts.WriteTotalTimeoutConstant
             = WriteTotalTimeoutConstant;
m_CommTimeouts.WriteTotalTimeoutMultiplier
              =WriteTotalTimeoutMultiplier;
    m_bPortReady = SetCommTimeouts (hComm, &m_CommTimeouts);
    if(m_bPortReady ==0){
MessageBox("StCommTimeouts function failed","Com Port Error",
           MB_OK+MB_ICONERROR);
    CloseHandle(hComm);
    return false;}
    return true;
}

The SetCommunicationTimeouts( ) member function sets the write and read timeouts for data transmission. The parameters to be passed to this function are given below.

Parameter Explanation
DWORD ReadIntervalTimeout Specifies the maximum time, in milliseconds, allowed to elapse between the arrival of two characters on the communications line. During a ReadFile( ) operation, the time period begins when the first character is received. If the interval between the arrival of any two characters exceeds this amount, the ReadFile operation is completed and any buffered data is returned. A value of zero indicates that interval timeouts are not used. A value of MAXDWORD, combined with zero values for both the ReadTotalTimeout Constant and ReadTotalTimeoutMultiplier members, specifies that the read operation is to return immediately with the characters that have already been received, even if no characters have been received.
ReadTotalTimeoutConstant Specifies the constant, in milliseconds, used to calculate the total timeout period for read operations. For each read operation, this value is added to the product of the ReadTotal Timeout Multiplier member and the requested number of bytes. A value of zero for both the ReadTotalTimeoutMultiplier and ReadTotalTimeoutConstant members indicates that total timeouts are not used for read operations.
ReadTotalTimeoutMultiplier Specifies the multiplier, in milliseconds, used to calculate the total timeout period for read operations. For each read operation, this value is multiplied by the requested number of bytes to be read.
WriteTotalTimeoutConstant Specifies the constant, in milliseconds, used to calculate the total timeout period for write operations. For each write operation, this value is added to the product of the WriteTotal TimeoutMultiplier member and the number of bytes to be written.
WriteTotalTimeoutMultiplier Specifies the multiplier, in milliseconds, used to calculate the total timeout period for write operations. For each write operation, this value is multiplied by the number of bytes to be written.

A value of zero for both the WriteTotalTimeoutMultiplier and WriteTotalTimeoutConstant members indicates that total timeouts are not used for write operations.

For example, if your device transmits a block of characters with a maximum timeout value of 500 ms between characters, you can set the timeout function as:

SetCommunicationTimeouts(0,500,0,0,0) ;

If the function succeeds, the return value is true; otherwise, it is false.

BOOL CSerialPort::WriteByte(BYTE bybyte)
{
iBytesWritten=0;
if(WriteFile(hComm,&bybyte,1,&iBytesWritten,NULL)==0)
return false;
else return true;
}

The WriteByte( ) member function writes the data byte to the communication port. The parameter to be passed to this function is the byte to be transmitted. You can call this function repeatedly in a loop with your data to be written placed in an array. Each time you send characters, increment the index of the array and call WriteByte( ) until all data bytes are transmitted.

If the function succeeds, the return value is true; otherwise, it is false.

BOOL CSerialPort::ReadByte(BYTE &resp)
{
BYTE rx;
resp=0;

DWORD dwBytesTransferred=0;

if (ReadFile (hComm, &rx, 1, &dwBytesTransferred, 0)){
  if (dwBytesTransferred == 1){
  resp=rx;
  return true;}}
  return false;
}

The ReadByte( ) member function reads data bytes from the communication port. The parameter to be passed to this function is the address of the variable in which received data byte is to be stored. You can call this function repeatedly in a loop with your received data moved to an array. Each time you receive characters, increment the index of the array and call ReadByte( ) until all data bytes are received.

If you know the exact number of response bytes from the external device, you can call the ReadByte( ) function in a loop until all characters are received or a timeout occurs. Sometimes you may not be able to predict the number of response bytes from the external device. In that case, call the ReadByte file repeatedly until you get a timeout and if the character received previously is a character representing end of transmission in your protocol format, the communication process is successfully completed. For example, for a device following 3964, the end of transmission is the ETX character. So use the ReadByte( ) function properly in accordance with the protocol your external device supports.

If ReadByte( ) succeeds, the return value is true and the received byte will be stored in the location pointed by the address of ReadByte( )’s parameter. If a timeout occurs, the return value will be false.

void CSerialPort::ClosePort()
{
CloseHandle(hComm);
return;
}

The ClosePort( ) member function closes a communication port that is already in an open state.

How to Use the CSerialPort Class

Do the following steps to use the CSerialPort class:

  1. Copy the “SerialPort .h” and “SerialPort .cpp” files and paste them in your Project directory.
  2. In your VC++ IDE take Project tab, choose Add to Project->Files. Select “SerialPort .h” and “SerialPort .cpp” and click OK.
  3. Add the line #include “SerialPort .h” in your dialog’s header file (include SerialPort .h to your dialog’s header file).
  4. Create an instance of the class CSerialPort in your dialog’s header file, say CSerialPort port.

Now call the member functions of CSerialPort when you want to communicate with external device as given below.

In your dialog’s .cpp file, add these lines:

    port.OpenPort( ); //Open Communication Port. Please check the
                      //function's return value to ensure whether
                      //the port opened successfully.

    port.ConfigurePort( ); //Configure Port for Communication.
                           //Please check the function's return
                           //value to ensure whether the port is
                           //configured successfully.

    port.SetCommunicationTimeouts( ); //Set communication
                                      //timeouts. Please check the
                                      //function's return value to
                                      //ensure whether communication
                                      //timeouts configured
                                      //successfully.
    port.WriteByte( );   //Call this function in a loop until all
                         //bytes are written. Please check the
                         //function's return value to ensure
                         //whether the write operation completed
                         //successfully.

    port.ReadByte( );    //Call this function in a loop until all
                         //bytes are received. Please check the
                         //function's return value to ensure
                         //whether the read operation completed
                         //successfully or a timeout occurred.

    port.ClosePort();    //Call this function to close the handle
                         //to the port.

    //Process the received Data

NOTE: This code has been tested with an RS-232 connector, whose TXD pin and RXD pin were shorted, connected to “com1” (for example, for case1: where the number of databytes to be read is predefined) or constant (in this case, 1) and with a Smart Card Reader with a baud rate of 9600 supporting the 3964 protocol for communication (as in case2: where the number of databytes to be read from the external device is unknown and the end of data transmission is detected by a timeout with the last character received being the End-of-transmission character in that protocol (the “ETX” character for the 3964 protocol) in Win98/2000 and it is found to be working properly).

Some of the explanation given in this article are taken from MSDN library.

Downloads


Download demo project – 33 Kb


Download source – 3 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read