安徽博微長安電子有限公司 龍 灝 丁旭玲
隨著監(jiān)控系統(tǒng)的發(fā)展,監(jiān)控系統(tǒng)與各個(gè)外圍設(shè)備數(shù)據(jù)傳輸量巨大,通訊方式越來越多樣,CPU處理任務(wù)越來越繁重。串口通訊的數(shù)據(jù)收發(fā)處理是監(jiān)控程序的重要一部分,串口通信中如何結(jié)合異步通信、多線程等技術(shù),編寫出高質(zhì)量、高效率的串口通信程序,具有一定的現(xiàn)實(shí)意義。
在Windows平臺(tái)下,串口被視為一種特殊的文件,串口的操作可以被視為一種文件操作。在實(shí)際的編程中,可以使用文件相關(guān)的API函數(shù),例如CreateFile()、ReadFile()以及WriteFile()等對(duì)串口進(jìn)行關(guān)聯(lián)或者操作。
串口的操作模式分為同步模式和異步模式,同步模式是指程序中的代碼按順序執(zhí)行,只有前面的程序執(zhí)行完畢并返回,才能繼續(xù)執(zhí)行后面的程序,該模式容易造成程序的假死。異步模式下,調(diào)用的函數(shù)會(huì)立即返回,不會(huì)造成線程的阻塞,線程可以在不同的句柄上同時(shí)執(zhí)行I/O操作,甚至可以在同一句柄上同時(shí)進(jìn)行讀寫操作,效率更高。
程序?qū)⒋诓僮骱瘮?shù)及相應(yīng)的變量封裝為串口類,方便主程序調(diào)用。主程序可定義串口類的對(duì)象調(diào)用各種函數(shù),同時(shí)對(duì)多串口進(jìn)行監(jiān)聽及讀寫操作。串口類中的重要函數(shù)如表1所示:
表1 串口類包含的函數(shù)及功能實(shí)現(xiàn)
為了提高程序的效率,串口類中由事件驅(qū)動(dòng)數(shù)據(jù)發(fā)送、接收和線程的退出。類中定義了3個(gè)事件:
m_hShutDownEvent(串口關(guān)閉事件)、m_hWriteEvent(數(shù)據(jù)發(fā)送事件)、Overlapped結(jié)構(gòu)的m_ov(數(shù)據(jù)接收事件)。配合線程中的WaitForMultipleObjects()函數(shù),實(shí)現(xiàn)線程的調(diào)度。為了實(shí)現(xiàn)對(duì)不同串口初始化,需要使用臨界區(qū)對(duì)象,以保證在某一時(shí)刻只進(jìn)行一個(gè)串口的初始化。部分初始化代碼如下:
If(m_ov.hEvent!=NULL)//事件的建立
ResetEvent(m_ov.hEvent);
m_ov.hEvent=CreatEvent(NULL,TRUE,FALSE,NULL);//事件被定義為手動(dòng)復(fù)位
……
m_h Event Array[0]=m_h Shut Dow n Ev ent;//m_hEventArray為事件數(shù)組
m_hEventArray[1]=m_ov.hEvent;
m_hEventArray[2]=m_hWriteEvent;
InitializeCriticalSection(&m_csCommunication);//初始化臨界區(qū)對(duì)象
……
串口的初始化首先需要使用API函數(shù)CreateFile()打開串口并創(chuàng)建與該串口相關(guān)聯(lián)的文件。注意將文件相關(guān)屬性設(shè)置為FILE_FLAG_OVERLAPPED,才可以對(duì)串口進(jìn)行異步操作。打開串口代碼如下:
HANDLE hCom; //定義串口句柄
hCom=CreateFile(szPort,GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,NULL);//創(chuàng)建異步模式文件并關(guān)聯(lián)串口,返回其句柄
表2 串口配置函數(shù)
在獲得通訊設(shè)備句柄后,可使用句柄進(jìn)行串口參數(shù)設(shè)置。在串口API函數(shù)編程時(shí),波特率、校驗(yàn)方式等參數(shù)均被封裝到一個(gè)結(jié)構(gòu)體DCB中。在查詢或配置串口的參數(shù)時(shí),都要使用該結(jié)構(gòu)體。配置串口使用的函數(shù)如表2所示。
串口初始化完成后,可在線程中等待通信事件的發(fā)生。線程函數(shù)中使用WaitForMultipleObjects()函數(shù)對(duì)m_hEventArray數(shù)組中的3個(gè)事件同時(shí)進(jìn)行監(jiān)測(cè)處理。當(dāng)判斷某個(gè)事件為有信號(hào)時(shí),執(zhí)行相應(yīng)的操作。線程函數(shù)部分代碼如下:
for (;;) //循環(huán)
{
……
Event = Wait For Multip leObjects(3, por t->m_hEventArray, FALSE, INFINITE);
//無限等待事件的發(fā)生
switch (Event)
{
case 0://線程關(guān)閉事件為有信號(hào)狀態(tài)
{
port->m_bThreadAlive = FALSE;
AfxEndThread(0);//退出線程
}
case 1: // 讀事件為有信號(hào)狀態(tài)
{
GetCommMask(port->m_hComm, &CommEvent);//得到串口事件
if (CommEvent & EV_RXCHAR)//判斷為接收事件
if(!port->m_bBlockRead)
ReceiveChar(port, comstat);//調(diào)用ReadFile()函數(shù)讀取數(shù)據(jù),每接收一個(gè)字節(jié)數(shù)據(jù)向主程序送WM_COMM_RXCHAR消息和接收到的數(shù)據(jù)
break;
}
case 2: // 寫事件為有信號(hào)狀態(tài)
{
WriteChar(port);// 調(diào)用WriteFile()發(fā)送數(shù)據(jù)
break;
}
}
將串口類的源程序及頭文件導(dǎo)入到主程序工程中,即可在主程序中定義該串口類的對(duì)象,通過對(duì)象引用串口初始化InitPort()、串口監(jiān)視函數(shù)StartMonitoring()等函數(shù)。當(dāng)需要向串口發(fā)送數(shù)據(jù)時(shí),可調(diào)用WritePort()函數(shù)。當(dāng)串口類的線程收到一個(gè)字節(jié)的數(shù)據(jù),會(huì)向主程序發(fā)送WM_COM_RXchar消息和接收到的數(shù)據(jù),因此在主程序中需自定義WM_COMM_RxCHAR消息,并關(guān)聯(lián)消息處理函數(shù)以處理接收的數(shù)據(jù)。主程序消息處理函數(shù)部分代碼如下所示:
OnRecvByte(UINT wParam,LONG lParam)
{
Unsigned char JustRecv=(unsigned char)wParam;
Int PortNum=(int)lParam;
If(PortNum==1)
{
RecvData[Cnt]=JustRecv;//將接收的字節(jié)保存
Cnt++;
}
…
}
上面的代碼為自定義消息WM_COMM_RxCHAR的處理函數(shù)OnRecvByte(),在消息處理函數(shù)中將不同串口的數(shù)據(jù)存儲(chǔ)到指定的數(shù)組中,供主程序使用。
本文介紹了基于API函數(shù)的串口類的實(shí)現(xiàn),綜合使用了異步模式、多線程、事件等方法,并給出VC開發(fā)環(huán)境下實(shí)現(xiàn)串口通信類的關(guān)鍵代碼。在實(shí)際監(jiān)控項(xiàng)目中,該串口類運(yùn)行穩(wěn)定可靠,實(shí)現(xiàn)多串口數(shù)據(jù)的高效率讀寫,具備較強(qiáng)的實(shí)踐價(jià)值和參考意義。
[1]闞能琪.VC++串口通信中多線程技術(shù)的應(yīng)用研究[J].西華大學(xué)學(xué)報(bào),2005,24(4):84-85.
[2]梁偉,等.Visual C++網(wǎng)絡(luò)編程案例實(shí)戰(zhàn)[M].北京:清華大學(xué)出版社,2014,10:35-54.
[3]李琳娜,等.Visual C++編程實(shí)戰(zhàn)寶典[M].北京:清華大學(xué)出版社,2014,9:379-388.
[4]趙永發(fā),由大偉,楊麗,等.Visual C++開發(fā)寶典[M].北京:機(jī)械工業(yè)出版社,2012,4:510-519.