1
Serial Data transmission seems a bit difficult for those who are new to the world of serial communication and VC++. Long ago, I had searched on codeguru.com for some help on serial data transmission and I got some valuable information. It was my dream to develop a simple class for implementing serial data transmission since then.
After getting seven months practical experience in the field of serial communication, I have developed a simple class for implementing serial transmission using WinAPI functions. Before going into the details of this class it is essential to know the basics of serial data transmission.
Serial Port
In serial data transmission the data is transmitted in serial format with the 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 would like to use, either 'EVEN' or 'ODD' through software.
The various steps to be performed for sending and receiving
data through the serial port of a PC are listed below:-
- Open the communication port
- Configure the communication port by setting the Baud rate, parity, no. of data bits, etc.
- Set time-outs for communication.
- Write data to the port.
- Read data from the port.
- Close the port.
Opening The Serial Port
The CreateFile()
function
opens a communications port. There are two ways to call CreateFile()
to
open the port - OVERLAPPED
and NON-OVERLAPPED
.
You can open a Communication Port for OVERLAPPED
IO
operation and NON-OVERLAPPED
IO
operation. The CSerialCom
class
is written for NON-OVERLAPPED
IO
operation. For more details on OVERLAPPED
& NON-OVERLAPPED
IO,
please refer to the MSDN documentation.
Configuring Serial Ports
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, no. of Stop Bits, etc. in accordance with the requirements
of the external device by calling appropriate WinAPI functions.
Configuring Time-Outs
An application must always set communication
time-outs using the COMMTIMEOUTS
structure
each time it opens a communication port. If this structure is not
configured, the port uses default time-outs supplied by the driver, or
time-outs from a previous communication application. By assuming specific
time-out 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 & write time-outs by calling the appropriate
WinAPI functions.
Writing to a Serial Port
The WriteFile()
function
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()
function
to receive data from a device at the other end of a serial connection.
Closing a Serial Port
You must close the communications port after
serial transmission in order to make this port available for other
applications which use this resource. As long as you are working with a port
(i.e. the port is in an open state), other threads or applications will not
be able to access to this port till you close the handle to that port in NON-OVERLAPPED
IO
operation. Call the CloseHandle()
function
to close the serial port. CloseHandle()
has
one parameter, which is the handle returned by the CreateFile()
call
that opened the port.
CSerialCom Class
The CSerialCom
class
uses six member functions to achieve the above mentioned functionality. They
are:
BOOL CSerialCom::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 etc. If the function succeeds the return
value is true, otherwise it is false.
BOOL CSerialCom::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.
DWORD BaudRate
It represents the Baud rate for communication supported by external device.
For example, you can give this parameter as 9600 or CBR_9600
for
a BaudRate of 9600. The available Standard Baud rates supported by a 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,CBR_256000
BYTE ByteSize
This 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 parameter 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:
-
EVENPARITY
-
MARKPARITY
-
NOPARITY
-
ODDPARITY
-
SPACEPARITY
BYTE StopBits
Specifies the number of stop bits to be used. This member can be one of the
following values:
-
ONESTOPBIT
-
ONE5STOPBITS
-
TWOSTOPBITS
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 CTS/RTS and Xon/Xoff
hardware flow control. You can modify this to your requirements by changing
the values of the members of DCB
which
are responsible for it, in the implementation of ConfigurePort()
inSerialCom.cpp.
ConfigurePort(CBR_9600, 8, true, EVENPARITY , ONESTOPBIT )
If the function succeeds the return value is true otherwise false.
BOOL CSerialCom::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 & read timeouts for data transmission. The
parameters to be passed to this function are given below.
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 time-outs are not used. A value ofMAXDWORD
,
combined with zero values for both the ReadTotalTimeout
constant
andReadTotalTimeoutMultiplier
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
time-out period for read operations. For each read operation, this value is
added to the product of the ReadTotalTimeoutMultiplier
member
and the requested number of bytes. A value of zero for both the ReadTotalTimeoutMultiplier
andReadTotalTimeoutConstant
members
indicates that total time-outs are not used for read operations.
ReadTotalTimeoutMultiplier
Specifies the multiplier, in milliseconds, used to calculate the total
time-out 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
time-out period for write operations. For each write operation, this value
is added to the product of the WriteTotalTimeoutMultiplier
member
and the number of bytes to be written.
WriteTotalTimeoutMultiplier
Specifies the multiplier, in milliseconds, used to calculate the total
time-out 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 time-outs are not used for write operations.
For example, if your device transmits a block
of characters with a max. timeout value of 500 ms between each characters,
you can set the time-out function as SetCommunicationTimeouts(0,500,0,0,0);
.
If the function succeeds the return value is true otherwise false.
BOOL CSerialCom::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()
till
all data bytes are transmitted.
If the function succeeds the return value is true otherwise false.
BOOL CSerialCom::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 the 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()
till
all data bytes are received. If you know exactly the no. of response bytes
from the external device you can call the ReadByte()
function
in a loop till all characters are received or a time out occurs. Sometimes
you may not be able to predict the no. of response bytes from the external
device. In that case callReadByte
file
repeatedly till you get a time out 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 'ETX' character. So use the ReadByte()
function
properly in accordance with the protocol supported by your external device.
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 CSerialCom::ClosePort() { CloseHandle(hComm); return; }The
ClosePort()
member
function closes a communication port which is already in an Open state.
How To Use 'CSerialCom' Class
Take the following steps to use the CSerialCom
class
- Copy the SerialCom.h & SerialCom.cpp files and paste into your project directory.
- In your VC++ IDE, add the files to your project
-
Add the line
#include "SerialCom.h"
in your dialog's header file -
Create an instance of the
CSerialCom
class in your dialog's header file.
You can now call the member functions of CSerialCom
when
you want to communicate with external device as shown below.
In your dialog's .cpp file:
// Open Communication Port. Please check functions return value to ensure whether // Port opened successfully. port.OpenPort( ); // Configure Port for Communication. Please check functions return value to // ensure whether Port is configured successfully. port.ConfigurePort( ); // Set communication time outs. Please check functions return // value to ensure whether communication time outs configured // successfully. port.SetCommunicationTimeouts( ); // call this function in a loop till all bytes are written. Please check // functions return value to ensure whether Write operation completed // successfully. port.WriteByte(); // call this function in a loop till all bytes are received. Please check // functions return value to ensure whether Read operation completed // successfully or a time out occurred. port.ReadByte( ); // Call this function to close the handle to the port. // Process the received Data port.ClosePort();
Note
This code has been tested with an RS-232 Connector, whose TXD pin & RXD pin were shorted, connected to 'com1' (E.g. for case1: where the no. of databytes to be read is predefined or constant (in this case 1) and with a Smart Card Reader with baud rate 9600 supporting 3964 Protocol for communication (Eg. for case2: where the no. 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 ('ETX' Character for 3964 Protocol) in win-98/2000 and it is found working properly.
Some of the explanations given in this article are taken from the MSDN library.