Knowledge Base Nr: 00048 serial_com_class.cpp - http://www.swe-kaiser.de
Downloads:
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()