描述:一個串口通訊類
應用平台:Windows
版本: v1.0
主要功能:設計了一個簡潔易用的多線程串行通訊接口,可以切換查詢和自動接收模式,進行對串口數據收發的類
接觸VC,很不習慣mscomm等Active控件老讓人去注冊的方式,所以參照Delphi中的SpComm設計了一個類CComPort,對PJ Naughter 的CSerialPort進行了2次封裝,主要目的是簡化串口的使用.使其用簡單的代碼就可以完成串口通訊的過程.做了一個Demo程序演示了CComPort的使用,附圖如下:
下面我從如何使用和類的設計兩個方面說明一下:
一 如何使用:
考慮到使用過程盡可能簡潔,實用,為了滿足不同的使用要求設計4種接收模式, 前兩種為手動接收方式,後兩種為自動類回調方式,下面是使用代碼
1.ManualReceiveByQuery 手動查詢接收
#include "ComPort.h"
LsComm::CComPort m_ComPort; //LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveByquery);
//ReCeive Com Data: 接收語句
DWORD InBufferCount;
byte pbuffer[2048];
InBufferCount = m_ComPort.GetInBufferCount();
if(InBufferCount>0)
{
m_ComPort.GetInput(pbuffer,InBufferCount);
}
//Write Com Data: 寫串口數據
char a[10]="abcdefg";
this->m_ComPort.Output(a,sizeof(a));
2.ManualReceiveByConst(異步模式) 手動定常數接收模式
#include "ComPort.h"
LsComm::CComPort m_ComPort;//LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveByConst);
//ReCeive Com Data: //接收數據
DWORD InBufferCount=0;
byte pbuffer[2048];
InBufferCount=this->m_ComPort.GetInput(pbuffer,10,1000);
//上面我要在1000毫秒內接收10字節的數據,IbufferCount返回實際得到的數據
if(InBufferCount==0)
return;
CString c;
char a[4];
for(int i=0;i<(int)InBufferCount;i++)
{
::sprintf(a,"%2.2X",pbuffer[i]);
c+=a;
c+=" ";
}
c="接收(Receive):"+c;
寫串口數據的過程同上
注意:第3,4種模式為自動接收模式,在用以前你必須先定義回調函數,然後,設置類的接收函數
/* 回調函數定義 */
void OnReceiveData(LPVOID pSender,void* pBuf,DWORD InBufferCount)
{
CString c;
byte a[100];
char b[4]="";
memcpy(a,pBuf,InBufferCount);
CLsCommDemoDlg* pDlg = (CLsCommDemoDlg*) pSender;
CListBox*pList =(CListBox*)pDlg->GetDlgItem(IDC_LIST1);
for(int i=0;i<(int)InBufferCount;i++)
{
::sprintf(b,"%2.2X",a[i]);
c+=" ";
c+=b;
}
c="接收(Receive):"+c;
pList->AddString(c);
}
3.AutoReceiveBySignal 自動信號接收模式
#include "ComPort.h"
LsComm::CComPort m_ComPort;//LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveBySignal );
m_ComPort.SetReceiveFunc((FOnReceiveData)OnReceiveData,this);
m_ComPort.SetBreakHandleFunc(OnComBreak);
//ReCeive Com Data:接收數據函數
in OnReceiveData(LPVOID pSender,void* pBuf,DWORD InBufferCount) //above
//write data
the same as the first mode;
4.AutoReceiveByBreak 中斷接收模式
#include "ComPort.h"
LsComm::CComPort m_ComPort;//LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveByBreak );
m_ComPort.SetReceiveFunc((FOnReceiveData)OnReceiveData,this);
//ReCeive Com Data:接收數據函數
in OnReceiveData(LPVOID pSender,void* pBuf,DWORD InBufferCount) //above
//write data
the same as the first mode;
另外說明2點:
(1)如果你需要處理中斷事件,你可以在每種模式中設置中斷接收事件:如下
//定義中斷事件接收函數
void OnComBreak(LPVOID pSender,DWORD dwMask,COMSTAT stat)
{
//deal with the break of com here
}
m_ComPort.SetBreakHandleFunc(OnComBreak); //設置中斷事件
(2)如何處理如,改變波特率,以及其它參數呢?
m_ComPort.GetSerialPort()可以獲得一個CSerialPort類的指針,如何就可以操作各種com屬性了.
DCB dcb;
this->m_ComPort.GetSerialPort()->GetState(dcb);
二.類的設計與編程
1. 類結構
為了說明一個大概的類構成,我用Rose畫了一下類圖:如下
CComPort內部聚合了一個CSerialPort的串口類,並與一個CReadComThread線程關聯,讓其去讀取串口數據.
LsComm::CComPort m_ComPort;//LsComm is namespace in c++
m_ComPort.Open(2,LsComm::CComPort::AutoReceiveBySignal );
m_ComPort.SetReceiveFunc(OnReceiveData,this);
m_ComPort.SetBreakHandleFunc(OnComBreak);
這些語句是怎麼實現串口數據的發送和讀取的呢?
2. 打開過程CComPort::Open()
void CComPort::Open(int nPort,ReceiveMode mode, DWORD dwBaud, Parity parity, BYTE DataBits,
StopBits stopbits,FlowControl fc)
{
//1.新建串口
this->m_pPort = new CSerialPort();
//2.判斷收發模式
if(mode==ReceiveMode::ManualReceiveByQuery)
{
this->m_IsOverlapped = false;
}
else
{
this->m_IsOverlapped = true;
}
this->m_RecvMode = mode;
//3.轉換參數,打開串口
int index;
index=parity-CComPort::EvenParity;
CSerialPort::Parity spParity=(CSerialPort::Parity)(CSerialPort::EvenParity+index);
…略去
this->m_pPort->Open(nPort,dwBaud,spParity,DataBits,spStopbits,spFC,m_IsOverlapped);
this->m_pPort->Setup(4096,4096);
this->m_pPort->Purge(PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
//it is important!!
COMMTIMEOUTS timeouts;
this->m_pPort->GetTimeouts(timeouts);
timeouts.ReadIntervalTimeout=100;
this->m_pPort->SetTimeouts(timeouts);
this->m_CurPortNum = nPort;
//創建關閉事件
this->m_hCloseEvent = CreateEvent(NULL,true,false,NULL);
ASSERT(this->m_hCloseEvent);
//4.創建線程類
this->m_pReadThread = new CReadComThread();
this->m_pReadThread->BandSerialPort(this);
this->m_pReadThread->Create();
this->m_pReadThread->Resume();
if(this->IsOverlapped())
{
this->m_hWriteEvent = ::CreateEvent(NULL,false,false,NULL);
}
}
主要做的工作是:
新建串口 this->m_pPort = new CSerialPort();
打開串口 this->m_pPort->Open
創建讀取線程 this->m_pReadThread = new CReadComThread();
設立線程類與CComPort的關聯關系this->m_pReadThread->BandSerialPort(this);
void CReadComThread::BandSerialPort(CComPort* pPort)
{
ASSERT(pPort);
this->m_pPort = pPort;
//創建異步讀取事件
if(this->m_pPort->IsOverlapped())
{
this->m_ReadOverlapped.hEvent =::CreateEvent(NULL,false,false,NULL);
ASSERT(this->m_ReadOverlapped.hEvent);
this->m_BreakOverlapped.hEvent = ::CreateEvent(NULL,false,false,NULL);
ASSERT(this->m_BreakOverlapped.hEvent);
}
}
模式主要在線程執行的過程中發揮作用
3.串口的發送數據過程
DWORD CComPort::Output(void* pBuf,DWORD Count)
{
DWORD dwWriteBytes=0;
if(this->IsOverlapped())//異步模式
{
this->m_pPort->Write(pBuf,Count,this->m_WriteOverlapped);
if(WaitForSingleObject(this->m_WriteOverlapped.hEvent,INFINITE)==WAIT_OBJECT_0)
{
this->m_pPort->GetOverlappedResult(this->m_WriteOverlapped,dwWriteBytes,false);
}
}
else
dwWriteBytes= this->m_pPort->Write(pBuf,Count);
return dwWriteBytes;
}
再看this->m_pPort->Write(pBuf,Count);
實際上是:調用
DWORD CSerialPort::Write(const void* lpBuf, DWORD dwCount)
{
ASSERT(IsOpen());
ASSERT(!m_bOverlapped);
DWORD dwBytesWritten = 0;
if (!WriteFile(m_hComm, lpBuf, dwCount, &dwBytesWritten, NULL))
{
TRACE(_T("Failed in call to WriteFile\n"));
AfxThrowSerialException();
}
return dwBytesWritten;
}
或者是BOOL CSerialPort::Write(const void* lpBuf, DWORD dwCount, OVERLAPPED& overlapped, DWORD* pBytesWritten) 異步寫串口的過程
4.串口的讀取過程
分兩種:查詢讀取和線程自動讀取
(1) 查詢讀取
DWORD CComPort::GetInput(void* pBuf,DWORD Count,DWORD dwMilliseconds)
{
//不能在自動模式下getinput
if(this->GetReceiveMode()==CComPort::AutoReceiveByBreak||
this->GetReceiveMode()==CComPort::AutoReceiveBySignal)
{
::AfxMessageBox("Can''t use GetInput methord in this mode!");
return 0;
}
if(this->IsOverlapped())
{
ASSERT(this->m_pReadThread);
DWORD dwBytes = this->m_pReadThread->ReadInput(pBuf,Count,dwMilliseconds);
this->m_pPort->TerminateOutstandingReads();
return dwBytes;
}
else
return this->m_pPort->Read(pBuf,Count);
}
主要是調用m_pPort->Read(pBuf,Count);然後調用API函數ReadFile
(2) 線程等待處理
主要過程:在線程CreadComThread的Execute中
void CReadComThread::Execute()
{
if(this->m_pPort->GetReceiveMode()==CComPort::ManualReceiveByQuery)
{
this->ExecuteByManualQueryRecvMode();
}
else if(this->m_pPort->GetReceiveMode()==CComPort::ManualReceiveByConst)
{
this->ExecuteByManualConstRecvMode();
}
else if(this->m_pPort->GetReceiveMode()==CComPort::AutoReceiveBySignal)
{
this->ExecuteByAutoSignalRecvMode();
}
else//中斷模式
{
this->ExecuteByAutoBreakRecvMode();
}
}
主要是選擇模式然後執行: 下面看看this->ExecuteByAutoSignalRecvMode();
void CReadComThread::ExecuteByAutoSignalRecvMode()
{
DWORD dwMask=0;
HANDLE WaitHandles[3]; //監聽事件數組
DWORD dwSignaledHandle;
WaitHandles[0] = this->m_pPort->GetCloseHandle();
WaitHandles[1] = this->m_ReadOverlapped.hEvent;
WaitHandles[2] = this->m_BreakOverlapped.hEvent;
this->m_pPort->GetSerialPort()->SetMask(EV_ERR | EV_RLSD | EV_RING );
if(!SetBreakEvent(dwMask))
goto EndThread;
//設置讀事件
if(!SetReadEvent(this->m_ReadOverlapped))
goto EndThread;
//設置comEvent
for(;;)
{
dwSignaledHandle=::WaitForMultipleObjects(3,WaitHandles,false,INFINITE);
switch(dwSignaledHandle)
{
case WAIT_OBJECT_0:
goto EndThread;
break;
case WAIT_OBJECT_0+1:
if(!this->HandleReadEvent(this->m_ReadOverlapped))
goto EndThread;
if(!this->SetReadEvent(this->m_ReadOverlapped))
goto EndThread;
break;
case WAIT_OBJECT_0+2:
if(!this->HandleBreakEvent(dwMask))
goto EndThread;
if(!this->SetBreakEvent(dwMask))
goto EndThread;
break;
default:
//goto EndThread;
break;
}
}
EndThread:
this->m_pPort->GetSerialPort()->Purge(PURGE_RXABORT | PURGE_RXCLEAR);
::CloseHandle(this->m_ReadOverlapped.hEvent);
::CloseHandle(this->m_BreakOverlapped.hEvent);
return ;
}
主要是一個等待事件發送然後調用,響應的過程,如果讀取事件發生則調用this->HandleReadEvent(this->m_ReadOverlapped);
bool CReadComThread::HandleReadEvent(OVERLAPPED& overlapped)
{
if(this->m_pPort->GetSerialPort()->GetOverlappedResult(overlapped,this->m_InBufferCount,false))
{
return this->HandleData();
}
DWORD dwError = ::GetLastError();
if(dwError==ERROR_INVALID_HANDLE)
return false;
else
return true;
}
如果查詢有數據,則this->HandleData();
bool CReadComThread::HandleData() //處理讀取數據
{
if(this->m_InBufferCount>0)
{
this->m_pBuffer = new byte[this->m_InBufferCount];
for(int i=0;i<(int)this->m_InBufferCount;i++)
{
this->m_pBuffer[i] = this->m_InputBuffer[i];
}
this->m_pPort->ReceiveData(this->m_pBuffer,this->m_InBufferCount);
delete[] this->m_pBuffer;
}
return true;
}
在這調用了this->m_pPort->ReceiveData(this->m_pBuffer,this->m_InBufferCount);即調用了你傳入的函數.整個讀取過程就這樣了.如果還有不明白,請看我寫的CComPort的類的代碼.
當然,由於串行通訊各種情況綜合在一起比較復雜,另外本人水平有限,所以一時很難考慮全面,這個版本暫時定為1.0,希望大家如果在使用過程中發現什麼問題,請及時的告訴偶(E-Mail:[email protected]),有時間我做個升級什麼的,當然,希望大家多多提出批評意見.
本文配套源碼