用Win32 API實現(xiàn)串行通信
串口是常用的計算機與外部串行設(shè)備之間的數(shù)據(jù)傳輸通道,由于串行通信方便
易行,所以應(yīng)用廣泛。我們可以利用Windows API 提供的通信函數(shù)編寫出高可移植性的
串行通信程序。
在Win16中,可以利用OpenComm、CloseComm和WriteComm等函數(shù)打開、關(guān)閉和
讀寫串口。但在Win32中,串口和其他通信設(shè)備均被作為文件處理,串口的打開、關(guān)閉
和讀寫等操作所用的API函數(shù)與操作文件的函數(shù)相同??赏ㄟ^CreateFile函數(shù)打開串口
,通過CloseFile函數(shù)關(guān)閉串口,通過CommProp、DCB結(jié)構(gòu)、GetCommProperties、
SetCommProperties、GetCommState及SetCommState等函數(shù)設(shè)置串口狀態(tài),通過函數(shù)
ReadFile和WritFile讀寫串口。
VC++ 6.0是Windows應(yīng)用程序開發(fā)的主流語言之一,它具有良好的圖形設(shè)計
界面并支持面向?qū)ο蟮某绦蛟O(shè)計方法。本文結(jié)合一個實例介紹在VC++ 6.0下如何利用
Win32 API 實現(xiàn)串行通信程序。
實現(xiàn)原理
本文的實例來自一個水泥發(fā)貨系統(tǒng),在系統(tǒng)中,需要將通過總量傳感器采集到
的倉重值傳入到計算機中,以便系統(tǒng)做出相應(yīng)的處理。這需要使用串行通信來完成采集
數(shù)據(jù)的傳遞工作。
對于串行通信設(shè)備,Win32 API支持同步和異步兩種I/O操作。同步操作方式的
程序設(shè)計相對比較簡單,但I/O操作函數(shù)在I/O操作結(jié)束前不能返回,這將掛起調(diào)用線程
,直到I/O操作結(jié)束。異步操作方式相對要復雜一些,但它可讓耗時的I/O操作在后臺進
行,不會掛起調(diào)用線程,這在大數(shù)據(jù)量通信的情況下對改善調(diào)用線程的響應(yīng)速度是相當
有效的。異步操作方式特別適合同時對多個串行設(shè)備進行I/O操作和同時對一個串行設(shè)
備進行讀/寫操作。這兩種操作方式的程序設(shè)計基本思想是相似的,本文將針對同步操
作方式給出具體的通信程序設(shè)計,同時簡單說明如何實現(xiàn)異步的I/O操作。
串行設(shè)備的初始化
串行設(shè)備的初始化是利用CreateFile函數(shù)實現(xiàn)的。該函數(shù)獲得串行設(shè)備句柄并
對其進行通信參數(shù)設(shè)置,包括設(shè)置輸出/接收緩沖區(qū)大小、超時控制和事件監(jiān)視等。
//串行設(shè)備句柄;
HANDLE hComDev=0;
//串口打開標志;
BOOL bOpen=FALSE;
//線程同步事件句柄;
HANDLE hEvent=0;
BOOL SetupSynCom()
{
DCB dcb;
COMMTIMEOUTS timeouts;
//設(shè)備已打開
if(bOpen) return FALSE;
//打開COM1
if((hComDev=CreateFile(“COM1”,GENERICREAD|GENERICWRITE,0,NULL,OPEN
EXISTING,FILEATTRIBUTENORMAL,NULL))==
INVALIDHANDLEVALUE)
return FALSE;
//設(shè)置超時控制
SetCommTimeouts(hComDev,&timeouts);
//設(shè)置接收緩沖區(qū)和輸出緩沖區(qū)的大小
SetupComm(hComDev,1024,512);
//獲取缺省的DCB結(jié)構(gòu)的值
GetCommState(hComDev,&dcb);
//設(shè)定波特率為9600 bps
dcb.BaudRate=CBR9600;
//設(shè)定無奇偶校驗
dcb.fParity=NOPARITY;
//設(shè)定數(shù)據(jù)位為8
dcb.ByteSize=8;
//設(shè)定一個停止位
dcb.StopBits=ONESTOPBIT;
//監(jiān)視串口的錯誤和接收到字符兩種事件
SetCommMask(hComDev,EVERR|EVRXCHAR);
//設(shè)置串行設(shè)備控制參數(shù)
SetCommState(hComDev,&dcb);
//設(shè)備已打開
bOpen=TRUE;
//創(chuàng)建人工重設(shè)、未發(fā)信號的事件
hEvent=CreateEvent(NULL,FALSE,FALSE,
“WatchEvent”);
//創(chuàng)建一個事件監(jiān)視線程來監(jiān)視串口事件
AfxBeginThread(CommWatchProc,pParam);
}
在設(shè)置串口DCB結(jié)構(gòu)的參數(shù)時,不必設(shè)置每一個值。首先讀出DCB缺省的參數(shù)設(shè)
置,然后只修改必要的參數(shù),其他參數(shù)都取缺省值。由于對串口進行的是同步I/O操作
,所以除非指定進行監(jiān)測的事件發(fā)生,否則WaitCommEvent函數(shù)不會返回。在串行設(shè)備
初始化的最后要建立一個單獨的監(jiān)視線程來監(jiān)視串口事件,以免掛起當前調(diào)用線程,其
中pParam可以是一個對事件進行處理的窗口類指針。
如果要進行異步I/O操作,打開設(shè)備句柄時,CreateFile的第6個參數(shù)應(yīng)增加FILEFLAG
OVERLAPPED 標志。
數(shù)據(jù)發(fā)送
數(shù)據(jù)發(fā)送利用WriteFile函數(shù)實現(xiàn)。對于同步I/O操作,它的最后一個參數(shù)可為
NULL;而對異步I/O操作,它的最后一個參數(shù)必需是一個指向OVERLAPPED結(jié)構(gòu)的指針,
通過OVERLAPPED結(jié)構(gòu)來獲得當前的操作狀態(tài)。
BOOL WriteComm(LPCVOID lpSndBuffer,DWORD
dwBytesToWrite)
{ //lpSndBuffer為發(fā)送數(shù)據(jù)緩沖區(qū)指針,
dwBytesToWrite為將要發(fā)送的字節(jié)長度
//設(shè)備已打開
BOOL bWriteState;
//實際發(fā)送的字節(jié)數(shù)
DWORD dwBytesWritten;
//設(shè)備未打開
if(!bOpen) return FALSE;
bWriteState=WriteFile(hComDev,lpSndBuffer,
dwBytesToWrite,&dwBytesWritten,NULL);
if(!bWriteState || dwBytesToWrite!=dwBytesWritten)
//發(fā)送失敗
return FALSE;
else
//發(fā)送成功
return TRUE;
}
數(shù)據(jù)接收
接收數(shù)據(jù)的任務(wù)由ReadFile函數(shù)完成。該函數(shù)從串口接收緩沖區(qū)中讀取數(shù)據(jù),
讀取數(shù)據(jù)前,先用ClearCommError函數(shù)獲得接收緩沖區(qū)中的字節(jié)數(shù)。接收數(shù)據(jù)時,同步
和異步讀取的差別同發(fā)送數(shù)據(jù)是一樣的。
DWORD ReadComm(LPVOID lpInBuffer,DWORD
dwBytesToRead)
{ //lpInBuffer為接收數(shù)據(jù)的緩沖區(qū)指針, dwBytesToRead為準備讀取的數(shù)據(jù)長度(字
節(jié)數(shù))
//串行設(shè)備狀態(tài)結(jié)構(gòu)
COMSTAT ComStat;
DWORD dwBytesRead,dwErrorFlags;
//設(shè)備未打開
if(!bOpen) return 0;
//讀取串行設(shè)備的當前狀態(tài)
ClearCommError(hComDev,&dwErrorFlags,&ComStat);
//應(yīng)該讀取的數(shù)據(jù)長度
dwBytesRead=min(dwBytesToRead,ComStat.cbInQue);
if(dwBytesRead>0)
//讀取數(shù)據(jù)
if(!ReadFile(hComDev,lpInBuffer,dwBytesRead,&dwBytesRead,NULL))
dwBytesRead=0;
return dwBytesRead;
}
事件監(jiān)視線程
事件監(jiān)視線程對串口事件進行監(jiān)視,當監(jiān)視的事件發(fā)生時,監(jiān)視線程可將這個
事件發(fā)送(SendMessage)或登記(PostMessage)到對事件進行處理的窗口類(由pParam指
定)中。
UINT CommWatchProc(LPVOID pParam)
{ DWORD dwEventMask=0; //發(fā)生的事件;
while(bOpen)
{ //等待監(jiān)視的事件發(fā)生
WaitCommEvent(hComDev, &dwEventMask,
NULL);
if ((dwEventMask & EVRXCHAR) ==
EVRXCHAR)
……//接收到字符事件后,可以將此消息登記到由pParam有指定的窗口類中進行處理
if(dwEventMask & EVERR)==EVERROR)
……//發(fā)生錯誤時的處理
}
SetEvent(hEvent);
//發(fā)信號,指示監(jiān)視線程結(jié)束
return 0;
}
關(guān)閉串行設(shè)備
在整個應(yīng)用程序結(jié)束或不再使用串行設(shè)備時,應(yīng)將串行設(shè)備關(guān)閉,包括取消事
件監(jiān)視,將設(shè)備打開標志bOpen置為FALSE以使事件監(jiān)視線程結(jié)束,清除發(fā)送/接收緩沖
區(qū)和關(guān)閉設(shè)備句柄。
void CloseSynComm()
{
if(!bOpen) return;
//結(jié)束事件監(jiān)視線程
bOpen=FALSE;
SetCommMask(hComDev,0);
//取消事件監(jiān)視,此時監(jiān)視線程中的WaitCommEvent將返回
WaitForSingleObject(hEvent,INFINITE);
//等待監(jiān)視線程結(jié)束
CloseHandle(hEvent); //關(guān)閉事件句柄
//停止發(fā)送和接收數(shù)據(jù),并清除發(fā)送和接收緩沖區(qū)
PurgeComm(hComDev,PURGETXABORT|
PURGERXABORT|PURGETXCLEAR|
PURGERXCLEAR);
//關(guān)閉設(shè)備句柄
CloseHandle(hComDev);
}
小 結(jié)
以上給出了用Win32 API 設(shè)計串行通信的基本思路,對這個同步I/O操作的串
行通信程序稍加改造就可進行異步I/O操作。在實際應(yīng)用中,我們可以將這些串行通信
函數(shù)和成員變量加到一個已有的CWnd類或其派生類中來實現(xiàn)串行通信,也可設(shè)計一個新
的串行通信類來包含這些成員函數(shù)和成員變量??傊?,利用Win32 API可以設(shè)計出滿足
各種需要的串行通信程序。
(審核編輯: 智匯小新)