Interaction between target device and PC through RS-232 serial port is proved to be very useful in test process automation. Therefore, the effectiveness and accuracy of the communication along with the design of the protocol is crucial to the success of the testing system. Although a lot of issues regarding relevant technical details which should surely be carefully dealt with may come up during the development of serial port communication program, there is no doubt that one should focus more on things like the deployment of the communcation and structure of the diagram rather than the nuts and bolts of the infrastructure such as the APIs. And only by prudently devising the scheme can one largely avoid those puzzles arises in the form of API inefficiencies, and even bugs.
Below is part of the code I wrote for the particular purpose of a project I'm working on. It now supports a demo program offering simple but promising testing interaction.
== ETermTransceiver.h ==
The Transceiver module deals with routine work of serial port communication and leaves the message parsing to the protocol module (derivate of ETermProtocol, which is not covered here)
#pragma once #include <windows.h> #include <queue> #include "ETermProtocol.h" #include "ETermException.h" class ETermTransceiver : public ETermMessagePusher { public: enum { k_Success = 0, k_GenError = -1, k_TimeOut = -3, }; enum Event { k_Event_NoProtocol = -1, k_Event_ConsultingError = -2, k_Event_WaitCommEventError = -3, k_Event_GetOverlappedResultError = -4, k_Event_Message = -5, }; typedef void (*EventHandler)(Event e, unsigned long data); public: ETermTransceiver(ETermProtocol *protocol); ETermTransceiver(ETermProtocol *protocol, EventHandler eh); ~ETermTransceiver(void); void SetProtocol(ETermProtocol *protocol); void SetEventHandler(EventHandler eh); void Init(int portindex = 1); void Deinit(); /* EV_DSR, EV_CTS, EV_RXCHAR, EV_TXEMPTY, EV_RXFLAG */ BOOL GetCommState(DCB &dcb); BOOL SetCommState(DCB &dcb); BOOL GetCommMask(DWORD &mask); BOOL SetCommMask(DWORD mask); BOOL ConsultProtocol(); void Listen(); void Close(); DWORD SendString(char *str); DWORD SendData(unsigned char *buf, int size); int PopMessage(ETermMessage *&msg, DWORD miliseconds = INFINITE); int ClearMessages(DWORD miliseconds = INFINITE); bool HasMessage(); protected: /* from ETermMessagePusher */ virtual void PushMessage(ETermMessage *msg); private: static DWORD __stdcall PortListen(LPVOID pPar); void ProcessMessage(unsigned char *buf, int len); private: bool m_bRunning; bool m_bInited; HANDLE m_hPort; HANDLE m_hCommEvent; HANDLE m_hMsgIn; HANDLE m_mtxMsgIn; std::queue<ETermMessage *> m_qMsgIn; OVERLAPPED m_osReader; OVERLAPPED m_osWriter; HANDLE m_hListener; ETermProtocol *m_Protocol; EventHandler m_EventHandler; };
== ETermTransceiver.cpp ==
#include "StdAfx.h" #include "ETermTransceiver.h" ETermTransceiver::ETermTransceiver(ETermProtocol *protocol) { m_bInited = false; m_bRunning = false; m_hPort = 0; m_hCommEvent = 0; m_hMsgIn = 0; m_mtxMsgIn = 0; memset(&m_osReader, 0, sizeof(m_osReader)); memset(&m_osWriter, 0, sizeof(m_osWriter)); m_hListener = 0; SetProtocol(protocol); m_EventHandler = NULL; } ETermTransceiver::ETermTransceiver(ETermProtocol *protocol, EventHandler eh) { m_bInited = false; m_bRunning = false; m_hPort = 0; m_hCommEvent = 0; m_hMsgIn = 0; m_mtxMsgIn = 0; memset(&m_osReader, 0, sizeof(m_osReader)); memset(&m_osWriter, 0, sizeof(m_osWriter)); m_hListener = 0; SetProtocol(protocol); SetEventHandler(eh); } ETermTransceiver::~ETermTransceiver(void) { Deinit(); } void ETermTransceiver::SetProtocol(ETermProtocol *protocol) { if (m_bRunning) { throw ETermException("Error.ETermTransceiver.SetProtocol_OnRunning/n"); } m_Protocol = protocol; m_Protocol->AttachMessagePusher(this); } void ETermTransceiver::SetEventHandler(EventHandler eh) { if (m_bRunning) { throw ETermException("Error.ETermTransceiver.SetEventHandler_OnRunning/n"); } m_EventHandler = eh; } void ETermTransceiver::Init(int portindex/*=1*/) { char portname[8]; if (m_bInited) { Deinit(); } if (portindex > 64) { throw ETermException("Error.ETermTransceiver.InvalidPortIndex"); } #if _MSC_VER >= 1400 sprintf_s(portname, "COM%d", portindex); #else sprintf(portname, "COM%d", portindex); #endif /* open port */ m_hPort = CreateFileA(portname, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if ((signed int)m_hPort <= 0) { Deinit(); throw ETermException("Error.ETermTransceiver.OpenPort"); } /* set parameter */ DCB dcb; if (!::GetCommState(m_hPort, &dcb)) { Deinit(); throw ETermException("Error.ETermTransceiver.GetCommState"); } dcb.ByteSize = 8; dcb.fRtsControl = 0; dcb.BaudRate = CBR_115200; if (!::SetCommState(m_hPort, &dcb)) { Deinit(); throw ETermException("Error.ETermTransceiver.SetCommState"); } if (!::SetCommMask(m_hPort, EV_RXCHAR)) { Deinit(); throw ETermException("Error.ETermTransceiver.SetCommMask"); } /* init overlapped */ memset(&m_osReader, 0, sizeof(m_osReader)); m_osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if ((signed int)m_osReader.hEvent <= 0) { Deinit(); throw ETermException("Error.ETermTransceiver.CreateEvent_Reader"); } memset(&m_osWriter, 0, sizeof(m_osWriter)); m_osWriter.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if ((signed int)m_osWriter.hEvent <= 0) { Deinit(); throw ETermException("Error.ETermTransceiver.CreateEvent_Writer"); } m_bRunning = false; m_hCommEvent = 0; m_mtxMsgIn = CreateMutex(NULL, FALSE, NULL); if ((signed int)m_mtxMsgIn <= 0) { Deinit(); throw ETermException("Error.ETermTransceiver.CreateMutex_MsgIn"); } m_hMsgIn = CreateEvent(NULL, FALSE, FALSE, NULL); if ((signed int)m_hMsgIn <= 0) { Deinit(); throw ETermException("Error.ETermTransceiver.CreateEvent_MsgIn"); } m_bInited = true; } void ETermTransceiver::Deinit() { Close(); if ((signed int)m_hMsgIn > 0) { CloseHandle(m_hMsgIn); m_hMsgIn = 0; } if ((signed int)m_mtxMsgIn > 0) { CloseHandle(m_mtxMsgIn); m_mtxMsgIn = 0; } if ((signed int)m_osReader.hEvent > 0) { CloseHandle(m_osReader.hEvent); m_osReader.hEvent = 0; } if ((signed int)m_osWriter.hEvent > 0) { CloseHandle(m_osWriter.hEvent); m_osWriter.hEvent = 0; } if ((signed int)m_hPort > 0) { CloseHandle(m_hPort); m_hPort = 0; } while (!m_qMsgIn.empty()) { ETermMessage *msg = m_qMsgIn.front(); if (msg) { delete msg; } m_qMsgIn.pop(); } if (m_Protocol) { m_Protocol->Reset(); } m_bInited = false; } BOOL ETermTransceiver::GetCommState(DCB &dcb) { return ::GetCommState(m_hPort, &dcb); } BOOL ETermTransceiver::SetCommState(DCB &dcb) { return ::SetCommState(m_hPort, &dcb); } BOOL ETermTransceiver::GetCommMask(DWORD &mask) { return ::GetCommMask(m_hPort, &mask); } BOOL ETermTransceiver::SetCommMask(DWORD mask) { return ::SetCommMask(m_hPort, mask); } BOOL ETermTransceiver::ConsultProtocol() { if (!m_Protocol) { return FALSE; } DCB dcb; DWORD mask; if (!GetCommState(dcb)) { return FALSE; } if (m_Protocol->ModifyCommState(dcb)) { if (!SetCommState(dcb)) { return FALSE; } } if (!GetCommMask(mask)) { return FALSE; } if (m_Protocol->ModifyCommMask(mask)) { if (!SetCommMask(mask)) { return FALSE; } } return TRUE; } void ETermTransceiver::Listen() { DWORD idThreadListen; m_hListener = CreateThread(NULL, 0, PortListen, this, 0, &idThreadListen); if ((signed int)m_hListener <= 0) { Deinit(); throw ETermException("Error.ETermTransceiver.CreateThread_Listener"); } } void ETermTransceiver::Close() { m_bRunning = false; if ((signed int)m_hCommEvent > 0) { SetEvent(m_hCommEvent); m_hCommEvent = 0; } if ((signed int)m_hListener > 0) { if (WaitForSingleObject(m_hListener, 5000) != 0) { TerminateThread(m_hListener, 0); } CloseHandle(m_hListener); m_hListener = 0; } } DWORD ETermTransceiver::SendString(char *str) { SendData((unsigned char*)str, strlen(str) + 1); return 0; } DWORD ETermTransceiver::SendData(unsigned char *buf, int size) { DWORD nWritten; WriteFile(m_hPort, buf, size, &nWritten, &m_osWriter); return 0; } int ETermTransceiver::ClearMessages(DWORD miliseconds /* = INFINITE */) { DWORD dwRet = WaitForSingleObject(m_mtxMsgIn, miliseconds); if (dwRet == WAIT_TIMEOUT) { return k_TimeOut; } else if (dwRet != 0) { return k_GenError; } while (!m_qMsgIn.empty()) { ETermMessage *msg = m_qMsgIn.front(); if (msg) { delete msg; } m_qMsgIn.pop(); } ReleaseMutex(m_mtxMsgIn); return 0; } int ETermTransceiver::PopMessage(ETermMessage *&msg, DWORD miliseconds /*=INFINITE*/) { if (miliseconds >= 0xf0000000) miliseconds = INFINITE; bool empty = true; DWORD timeout = miliseconds; DWORD t1 = ::timeGetTime(); DWORD t; DWORD res = 0; while (1) { if (miliseconds != INFINITE) { t = ::timeGetTime(); DWORD dt = t - t1; timeout = miliseconds > dt? miliseconds - dt : 0; } res = WaitForSingleObject(m_mtxMsgIn, timeout); if (res == WAIT_TIMEOUT) { return k_TimeOut; } else if (res != WAIT_OBJECT_0) { return k_GenError; } empty = m_qMsgIn.empty(); ReleaseMutex(m_mtxMsgIn); if (!empty) break; if (miliseconds != INFINITE) { t = ::timeGetTime(); DWORD dt = t - t1; timeout = miliseconds > dt? miliseconds - dt : 0; } res = WaitForSingleObject(m_hMsgIn, timeout); if (res == WAIT_TIMEOUT) { return k_TimeOut; } else if (res != WAIT_OBJECT_0) { return k_GenError; } } if (miliseconds != INFINITE) { t = ::timeGetTime(); DWORD dt = t - t1; timeout = miliseconds > dt? miliseconds - dt : 0; } res = WaitForSingleObject(m_mtxMsgIn, timeout); if (res == WAIT_TIMEOUT) { return k_TimeOut; } else if (res != WAIT_OBJECT_0) { return k_GenError; } msg = m_qMsgIn.front(); m_qMsgIn.pop(); ReleaseMutex(m_mtxMsgIn); return k_Success; } bool ETermTransceiver::HasMessage() { return !m_qMsgIn.empty(); } void ETermTransceiver::PushMessage(ETermMessage *msg) { WaitForSingleObject(m_mtxMsgIn, INFINITE); m_qMsgIn.push(msg); SetEvent(m_hMsgIn); ReleaseMutex(m_mtxMsgIn); if (m_EventHandler) { m_EventHandler(k_Event_Message, (unsigned long)msg); } } DWORD __stdcall ETermTransceiver::PortListen(LPVOID pPar) { ETermTransceiver* thisobj = (ETermTransceiver*)pPar; if (!thisobj->m_Protocol) { return -1; // No protocol } OVERLAPPED os; memset(&os, 0, sizeof(os)); os.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); thisobj->m_hCommEvent = os.hEvent; DWORD nRead; DWORD dwEvtMask; DWORD dwErrFlags; COMSTAT comstat; BOOL bRet; bool bWaitOnRead = false; DWORD dwRet = 0; thisobj->m_Protocol->Reset(); if (!thisobj->ConsultProtocol()) { if (thisobj->m_EventHandler) thisobj->m_EventHandler(k_Event_ConsultingError, 0); dwRet = k_Event_ConsultingError; goto bail; } thisobj->m_bRunning = true; while (thisobj->m_bRunning) { ClearCommError(thisobj->m_hPort, &dwErrFlags, &comstat); if (comstat.cbInQue) { // '/r' arrives // read message int buflen = comstat.cbInQue; unsigned char *buf = new unsigned char[buflen]; ReadFile(thisobj->m_hPort, buf, buflen, &nRead, &os); thisobj->m_Protocol->ProcessMessage(buf, comstat.cbInQue); delete[] buf; continue; } bRet = WaitCommEvent(thisobj->m_hPort, &dwEvtMask, &os); if (!bRet) { // overlapped dwRet = GetLastError(); if (dwRet == ERROR_IO_PENDING) { bRet = GetOverlappedResult(thisobj->m_hPort, &os, &nRead, TRUE); if (!bRet) { if (thisobj->m_EventHandler) thisobj->m_EventHandler(k_Event_GetOverlappedResultError, 0); dwRet = k_Event_GetOverlappedResultError; goto bail; } } else { if (thisobj->m_EventHandler) thisobj->m_EventHandler(k_Event_GetOverlappedResultError, dwRet); dwRet = k_Event_GetOverlappedResultError; goto bail; } } } bail: thisobj->m_Protocol->Reset(); CloseHandle(os.hEvent); return dwRet; }