Knowledge Base Nr: 00048 serial_com_class.cpp - http://www.swe-kaiser.de

Win32: wrapper class for API functions for serial communication RS232
derived from TTY SDK sample (supports overlapped IO)

  
/**************************************************************/
/*************** H FILE *************************************/
/**************************************************************/
/**************************************************************/

#if !defined(__RS232__)
#define __RS232__

// wrapper class for API functions for serial communication RS232
// derived from TTY SDK sample

//receive rs232 buffer size
#define MAX_INBUFFER (10*1024)

class CCommunication: public CObject
{
private:
//flag for connection state
BOOL m_bIsConnected;

//RS232 port number (range 0..255)
int m_nPort;

//rcs232 port settings:
DWORD m_dwBaudRate; //CBR_110, CBR_300, CBR_600, CBR_1200, CBR_2400,
//CBR_4800, CBR_9600, CBR_14400, CBR_19200, CBR_38400,
//CBR_56000, CBR_128000, CBR_256000
int m_nParity; //NOPARITY, EVENPARITY, ODDPARITY, MARKPARITY, SPACEPARITY
int m_nByteSize;
int m_nStopBits; //ONESTOPBIT, ONE5STOPBITS, TWOSTOPBITS

//handshake:
BOOL m_bDTRDSR;
BOOL m_bRTSCTS;
BOOL m_bXONXOFF;

//events for thread implementation
OVERLAPPED m_osWrite, m_osRead ;

DWORD m_dwReadTotalTimeoutConstant;

protected:
//receiving buffer
BYTE m_byRecvBuffer[MAX_INBUFFER];
int m_nRecvBufferPos;

//free port and clear memory
int CleanUpPortInfo();

//setup communication parameters
int SetupConnection();

public:
virtual ~CCommunication();
CCommunication();

void SetConnected(BOOL bIsConnected) { m_bIsConnected = bIsConnected; }

//handles and ids for rs232 api-functions
//(public because used within thread procedure)
//SHOULD NOT ACCESSED DIRECTLY OUTSIDE THIS CLASS!
HANDLE m_nIdComDev;
DWORD m_dwThreadID;
HANDLE m_hWatchThread;

public:
//setup communication interface; allocate port
int SetupPortInfo(int nPortNo);

//allocate com port
int OpenConnection(int nPortNo, BOOL bUseThread);

//free com port
int CloseConnection();

//write characters via rs232. returns GetLastError()
int WriteCommBlock(LPBYTE lpByte , DWORD dwBytesToWrite);

//read characters via rs232. returns GetLastError(), -1 if buffer too small, -2 if timeout
//waits for nExpectedRecvLength characters (if nExpectedRecvLength > 0).
int ReadCommBlock(LPBYTE lpszBlock, int nMaxLength, int nExpectedRecvLength, int* nBytesRead);

//callback interface for thread implementation
//overwrite this function in a derived class for asynchron receive
//(requires call OpenConnection(bUseThread = TRUE))
virtual int CommReceived(LPSTR szData, int nLength);

//wait dwWaitMiliSecs ms for still incoming characters and purge the rs232 buffers
void ClearBuffers(DWORD dwWaitMiliSecs = 0);

//connection state
BOOL IsConnected() { return m_bIsConnected; }
};

//receiving thread
DWORD FAR PASCAL CommWatchProc( LPSTR );

// constant definitions
#define MAXBLOCK 80

#define ASCII_XON 0x11
#define ASCII_XOFF 0x13

// Flow control flags
#define FC_DTRDSR 0x01
#define FC_RTSCTS 0x02
#define FC_XONXOFF 0x04

#endif //__RS232__

/**************************************************************/
/*************** CPP FILE *************************************/
/**************************************************************/
/**************************************************************/

#include "stdafx.h"

#include "rs232.h"

CCommunication::CCommunication()
{
m_nIdComDev = NULL;

m_bIsConnected = FALSE ;

m_dwReadTotalTimeoutConstant = 1000;

ZeroMemory(m_byRecvBuffer, MAX_INBUFFER);
m_nRecvBufferPos = 0;

m_osRead.hEvent = NULL;
m_osWrite.hEvent = NULL;

m_dwThreadID = 0;
m_hWatchThread = NULL;
}


CCommunication::~CCommunication ()
{
int nErr = CleanUpPortInfo();
ASSERT(nErr == 0);
}


/////////////////////////////////////////////////////////////////////////////
//function: SetupPortInfo
//setup communication interface; allocate port
//
int CCommunication::SetupPortInfo(int nPortNo)
{
CleanUpPortInfo();

//free handle if already used
if (m_nIdComDev != NULL)
CloseHandle(m_nIdComDev);
m_nIdComDev = NULL;

//restore settings from last session (ini-file)
m_nPort = nPortNo;
m_dwBaudRate = CBR_9600;
m_nParity = NOPARITY;
m_nByteSize = 8;
m_nStopBits = ONESTOPBIT;
m_bDTRDSR = FALSE;
m_bRTSCTS = FALSE;
m_bXONXOFF = FALSE;

m_dwReadTotalTimeoutConstant = 1000;

m_osWrite.Offset = 0 ;
m_osWrite.OffsetHigh = 0 ;
m_osRead.Offset = 0 ;
m_osRead.OffsetHigh = 0 ;

// create I/O event used for overlapped reads / writes
m_osRead.hEvent = CreateEvent(NULL, // no security
TRUE, // explicit reset req
FALSE, // initial event reset
NULL); // no name
if (m_osRead.hEvent == NULL)
{
ASSERT(FALSE);
return GetLastError();
}

m_osWrite.hEvent = CreateEvent( NULL, // no security
TRUE, // explicit reset req
FALSE, // initial event reset
NULL ) ; // no name
if (m_osWrite.hEvent == NULL)
{
ASSERT(FALSE);
CloseHandle(m_osRead.hEvent);
return GetLastError();
}

return 0;
}


/////////////////////////////////////////////////////////////////////////////
//function: CleanUpPortInfo
//free port and clear memory
//
int CCommunication::CleanUpPortInfo()
{
// force connection closed (if not already closed)
if (m_bIsConnected)
CloseConnection();

// clean up event objects
if (m_osRead.hEvent != NULL)
CloseHandle(m_osRead.hEvent);

if (m_osRead.hEvent != NULL)
CloseHandle(m_osWrite.hEvent);

return 0;
}


/////////////////////////////////////////////////////////////////////////////
//function: CloseConnection
//free com port
//
int CCommunication::CloseConnection()
{
// disable event notification and wait for thread
// to halt
SetCommMask(m_nIdComDev, 0);

//wait for thread termination
SetConnected(FALSE);
while(m_dwThreadID != 0)
Sleep(5);

// drop DTR
EscapeCommFunction(m_nIdComDev, CLRDTR);

// purge any outstanding reads/writes and close device handle
PurgeComm(m_nIdComDev, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR );

CloseHandle(m_nIdComDev);
m_nIdComDev = NULL;

//reset buffer
ZeroMemory(m_byRecvBuffer, MAX_INBUFFER);
m_nRecvBufferPos = 0;

return 0;
}


/////////////////////////////////////////////////////////////////////////////
//function: OpenConnection
//allocate com port
//
int CCommunication::OpenConnection(int nPortNo, BOOL bUseThread)
{
SetupPortInfo(nPortNo);

HANDLE hCommWatchThread;
DWORD dwThreadID;
COMMTIMEOUTS CommTimeOuts;

// load the COM prefix string and append port number
CString strPort;
strPort.Format("COM%d", m_nPort);

// open COMM device
HANDLE hCOMDEV = CreateFile(strPort, GENERIC_READ | GENERIC_WRITE,
0, // exclusive access
NULL, // no security attrs
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED, // overlapped I/O
NULL );
if (hCOMDEV == INVALID_HANDLE_VALUE )
{
ASSERT(FALSE);
return GetLastError();
}
else
{
m_nIdComDev = hCOMDEV;
// get any early notifications
SetCommMask(m_nIdComDev, EV_RXCHAR ) ;

// setup device buffers
SetupComm(m_nIdComDev, 4096, 4096 ) ;

// purge any information in the buffer
PurgeComm(m_nIdComDev, PURGE_TXABORT | PURGE_RXABORT |
PURGE_TXCLEAR | PURGE_RXCLEAR ) ;

// set up for overlapped I/O
CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF ;
CommTimeOuts.ReadTotalTimeoutMultiplier = 0 ;
CommTimeOuts.ReadTotalTimeoutConstant = m_dwReadTotalTimeoutConstant;
// CBR_9600 is approximately 1byte/ms. For our purposes, allow
// double the expected time per character for a fudge factor.
CommTimeOuts.WriteTotalTimeoutMultiplier = 2;
CommTimeOuts.WriteTotalTimeoutConstant = 0 ;
SetCommTimeouts(m_nIdComDev, &CommTimeOuts ) ;
}

int nError = SetupConnection();

if (!nError)
{
m_bIsConnected = TRUE ;

if (bUseThread)
{
// Create a secondary thread to watch for an event.
hCommWatchThread = CreateThread((LPSECURITY_ATTRIBUTES) NULL,
0,
(LPTHREAD_START_ROUTINE) CommWatchProc,
(LPVOID) this,
0, &dwThreadID);
if (hCommWatchThread == NULL)
{
m_bIsConnected = FALSE ;
CloseHandle(m_nIdComDev) ;
nError = GetLastError();
}
else
{
m_dwThreadID = dwThreadID;
m_hWatchThread = hCommWatchThread;
}
}

// assert DTR
EscapeCommFunction(m_nIdComDev, SETDTR ) ;
}
else
{
m_bIsConnected = FALSE ;
CloseHandle(m_nIdComDev) ;
}

m_bIsConnected = (nError == 0);

return nError;
}


/////////////////////////////////////////////////////////////////////////////
//function: SetupConnection
//setup communication parameters
//
int CCommunication::SetupConnection()
{
DCB dcb ;
dcb.DCBlength = sizeof( DCB ) ;

GetCommState(m_nIdComDev, &dcb ) ;

dcb.BaudRate = m_dwBaudRate;
dcb.ByteSize = m_nByteSize;
dcb.Parity = m_nParity;
dcb.StopBits = m_nStopBits;

// setup hardware flow control
BYTE bSet = (BYTE) m_bDTRDSR;
dcb.fOutxDsrFlow = bSet ;
if (bSet)
dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
else
dcb.fDtrControl = DTR_CONTROL_ENABLE;

bSet = (BYTE) m_bRTSCTS;
dcb.fOutxCtsFlow = bSet;
if (bSet)
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
else
dcb.fRtsControl = RTS_CONTROL_ENABLE ;

// setup software flow control
bSet = (BYTE) m_bXONXOFF;

dcb.fInX = dcb.fOutX = bSet ;
dcb.XonChar = ASCII_XON ;
dcb.XoffChar = ASCII_XOFF ;
dcb.XonLim = 100 ;
dcb.XoffLim = 100 ;

// other various settings
dcb.fBinary = TRUE ;
dcb.fParity = TRUE ;

BOOL bRetVal = SetCommState(m_nIdComDev, &dcb ) ;
if (!bRetVal)
{
ASSERT(FALSE);
DWORD dwError = GetLastError();
TRACE("<SetCommState: %u>\n", dwError);
return GetLastError();
}

//reset buffer
ZeroMemory(m_byRecvBuffer, MAX_INBUFFER);
m_nRecvBufferPos = 0;

return 0;
}


/////////////////////////////////////////////////////////////////////////////
//function: WriteCommBlock
//write characters via rs232. returns GetLastError()
//
int CCommunication::WriteCommBlock(LPBYTE lpByte , DWORD dwBytesToWrite)
{
DWORD dwBytesWritten;

BOOL fWriteStat = WriteFile(m_nIdComDev, lpByte, dwBytesToWrite
, &dwBytesWritten, &m_osWrite);

// Note that normally the code will not execute the following
// because the driver caches write operations. Small I/O requests
// (up to several thousand bytes) will normally be accepted
// immediately and WriteFile will return true even though an
// overlapped operation was specified

DWORD dwErrorFlags;
COMSTAT ComStat;

if (!fWriteStat)
{
DWORD dwErr = GetLastError();

if (dwErr == ERROR_IO_PENDING)
{
// We should wait for the completion of the write operation
// so we know if it worked or not

// This is only one way to do this. It might be beneficial to
// place the write operation in a separate thread
// so that blocking on completion will not negatively
// affect the responsiveness of the UI

// If the write takes too long to complete, this
// function will timeout according to the
// CommTimeOuts.WriteTotalTimeoutMultiplier variable.
// This code logs the timeout but does not retry
// the write.

DWORD dwBytesSent=0;

while(!GetOverlappedResult(m_nIdComDev, &m_osWrite, &dwBytesWritten, TRUE))
{
DWORD dwError = GetLastError();
if(dwError == ERROR_IO_INCOMPLETE)
{
// normal result if not finished
dwBytesSent += dwBytesWritten;
continue;
}
else
{
// an error occurred, try to recover
TRACE("<GetOverlappedResult: %u>\n", dwError);

ClearCommError(m_nIdComDev, &dwErrorFlags, &ComStat ) ;
if (dwErrorFlags > 0)
TRACE("<ClearCommError: %u>\n", dwErrorFlags);

break;
}
}

dwBytesSent += dwBytesWritten;

if( dwBytesSent != dwBytesToWrite )
{
TRACE("Probable Write Timeout: Total of %ld bytes sent.\n", dwBytesSent);
return GetLastError();
}
else
TRACE("%ld bytes written\n", dwBytesSent);
}

else //(dwErr != ERROR_IO_PENDING)
{
// some other error occurred
ClearCommError(m_nIdComDev, &dwErrorFlags, &ComStat ) ;
if (dwErrorFlags > 0)
TRACE("<ClearCommError: %u>\n", dwErrorFlags);

return GetLastError();
}
}

return 0;
}


/////////////////////////////////////////////////////////////////////////////
//function: ReadCommBlock
//read characters via rs232. returns GetLastError(), -1 if buffer too small, -2 if timeout
//waits for nExpectedRecvLength characters (if nExpectedRecvLength > 0).
//
int CCommunication::ReadCommBlock(LPBYTE lpBlock, int nMaxLength, int nExpectedRecvLength, int* nBytesRead)
{
*nBytesRead = 0;

LPBYTE lpDatagram = &m_byRecvBuffer[0];

DWORD dwRecvTimeOut = m_dwReadTotalTimeoutConstant;
DWORD dwStartRecv = GetTickCount();

while (TRUE)
{
// only try to read number of bytes in queue
COMSTAT ComStat;
DWORD dwErrorFlags;

ClearCommError(m_nIdComDev, &dwErrorFlags, &ComStat);

DWORD dwLength = min((DWORD) nMaxLength, ComStat.cbInQue);

if (dwLength > 0)
{
//data received -> reset timeout
dwStartRecv = GetTickCount();

BOOL fReadStat = ReadFile(m_nIdComDev, lpDatagram, dwLength, &dwLength, &m_osRead);

if (!fReadStat)
{
if (GetLastError() == ERROR_IO_PENDING)
{
TRACE("\n\rIO Pending");
// We have to wait for read to complete.
// This function will timeout according to the
// CommTimeOuts.ReadTotalTimeoutConstant variable
// Every time it times out, check for port errors
while(!GetOverlappedResult(m_nIdComDev, &m_osRead, &dwLength, TRUE ))
{
DWORD dwError = GetLastError();
if(dwError == ERROR_IO_INCOMPLETE)
continue; // normal result if not finished
else
{
// an error occurred, try to recover
TRACE("<GetOverlappedResult: %u>\n", dwError);
ClearCommError(m_nIdComDev, &dwErrorFlags, &ComStat ) ;
if (dwErrorFlags > 0)
TRACE("<ClearCommError: %u>\n", dwErrorFlags);
break;
}
}
}
else
{
// some other error occurred
dwLength = 0 ;
ClearCommError(m_nIdComDev, &dwErrorFlags, &ComStat ) ;
if (dwErrorFlags > 0)
TRACE("<ClearCommError: %u>\n", dwErrorFlags);
return GetLastError();
}
}

*nBytesRead += dwLength;
if (*nBytesRead > nMaxLength)
return -1; //buffer too small

lpDatagram += dwLength;

if (nExpectedRecvLength <= 0) //do not wait for further recv. data -> return immediately
break;
}

//timeout?
static BOOL s_bTimeoutEnabled = TRUE; //usefull for debugging (can be set to FALSE within debugger)

if (((GetTickCount() - dwStartRecv) > dwRecvTimeOut) && s_bTimeoutEnabled)
return -2; //timeout
}

CopyMemory(lpBlock, m_byRecvBuffer, *nBytesRead);

return GetLastError();
}

void CCommunication::ClearBuffers(DWORD dwWaitMiliSecs)
{
Sleep(dwWaitMiliSecs); //wait for still incoming characters

PurgeComm(m_nIdComDev, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR );
}

int CCommunication::CommReceived(LPSTR szData, int nLength)
{
while (nLength--)
TRACE("%c", *szData++);

return 0;
}

/////////////////////////////////////////////////////////////////////////////
//function: CommWatchProc
//asecondary thread that will watch for COMM events.
//add this thread to watch the communications device and
//post notifications to the associated window.
//
DWORD FAR PASCAL CommWatchProc(LPSTR lpData)
{
DWORD dwEvtMask ;
CCommunication* pCom = (CCommunication*) lpData;
OVERLAPPED os ;
int nLength ;
BYTE abIn[ MAXBLOCK + 1] ;

memset( &os, 0, sizeof( OVERLAPPED ) ) ;

// create I/O event used for overlapped read

os.hEvent = CreateEvent( NULL, // no security
TRUE, // explicit reset req
FALSE, // initial event reset
NULL ) ; // no name
if (os.hEvent == NULL)
{
MessageBox(NULL, "Failed to create event for thread!", "TTY Error!",
MB_ICONEXCLAMATION | MB_OK ) ;
return ( FALSE ) ;
}

if (!SetCommMask(pCom->m_nIdComDev, EV_RXCHAR ))
return ( FALSE ) ;

while (pCom->IsConnected())
{
dwEvtMask = 0 ;

WaitCommEvent(pCom->m_nIdComDev, &dwEvtMask, NULL );

if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR)
{
do
{
int nErr = pCom->ReadCommBlock(abIn, MAXBLOCK, 0, &nLength);
if (nErr)
break;
if (nLength)
{
nErr = pCom->CommReceived((LPSTR) abIn, nLength);
if (nErr)
break;
}
} while (nLength > 0);
}
}

// get rid of event handle
CloseHandle(os.hEvent);

// clear information in structure (kind of a "we're done flag")
pCom->m_dwThreadID = 0 ;
pCom->m_hWatchThread = NULL ;

return 0;
} // end of CommWatchProc()