串口是常用的電腦與外部串列裝置之間的資料轉送通道,由於串列通訊方便易行,所以應用廣泛。我們可以利用Windows API 提供的通訊函數編寫出高可移植性的串列通訊程式。
在Win16中,可以利用OpenComm、CloseComm和WriteComm等函數開啟、關閉和讀寫串口。但在Win32中,串口和其他通訊裝置均被作為檔案處理,串口的開啟、關閉和讀寫等操作所用的API函數與操作檔案的函數相同。可通過CreateFile函數開啟串口,通過CloseFile函數關閉串口,通過CommProp、DCB結構、GetCommProperties、SetCommProperties、GetCommState及SetCommState等函數設定串口狀態,通過函數ReadFile和WritFile讀寫串口。
VC++ 6.0是Windows應用程式開發的主流語言之一,它具有良好的圖形設計介面並支援物件導向的程式設計方法。本文結合一個執行個體介紹在VC++ 6.0下如何利用Win32 API 實現串列通訊程式。
實現原理
本文的執行個體來自一個水泥發貨系統,在系統中,需要將通過總量感應器採集到的倉重值傳入到電腦中,以便系統做出相應的處理。這需要使用串列通訊來完成採集資料的傳遞工作。
對於串列通訊裝置,Win32 API支援同步和非同步兩種I/O操作。同步操作方式的程式設計相對比較簡單,但I/O操作函數在I/O操作結束前不能返回,這將掛起調用線程,直到I/O操作結束。非同步作業方式相對要複雜一些,但它可讓耗時的I/O操作在後台進行,不會掛起調用線程,這在大資料量通訊的情況下對改善調用線程的響應速度是相當有效。非同步作業方式特別適合約時對多個串列裝置進行I/O操作和同時對一個串列裝置進行讀/寫操作。這兩種操作方式的程式設計基本思想是相似的,本文將針對同步操作方式給出具體的通訊程式設計,同時簡單說明如何?非同步I/O操作。
串列裝置的初始化
串列裝置的初始化是利用CreateFile函數實現的。該函數獲得串列裝置控制代碼並對其進行通訊參數設定,包括設定輸出/接收緩衝區大小、逾時控制和事件監視等。
//串列裝置控制代碼;
HANDLE hComDev=0;
//串口開啟標誌;
BOOL bOpen=FALSE;
//線程同步事件控制代碼;
HANDLE hEvent=0;
BOOL SetupSynCom()
{
DCB dcb;
COMMTIMEOUTS timeouts;
//裝置已開啟
if(bOpen) return FALSE;
//開啟COM1
if((hComDev=CreateFile(“COM1”,GENERICREAD|GENERICWRITE,0,NULL,OPENEXISTING,FILEATTRIBUTENORMAL,NULL))==
INVALIDHANDLEVALUE)
return FALSE;
//設定逾時控制
SetCommTimeouts(hComDev,&timeouts);
//設定接收緩衝區和輸出緩衝區的大小
SetupComm(hComDev,1024,512);
//擷取預設的DCB結構的值
GetCommState(hComDev,&dcb);
//設定傳輸速率為9600 bps
dcb.BaudRate=CBR9600;
//設定無同位
dcb.fParity=NOPARITY;
//設定資料位元為8
dcb.ByteSize=8;
//設定一個停止位
dcb.StopBits=ONESTOPBIT;
//監視串口的錯誤和接收到字元兩種事件
SetCommMask(hComDev,EVERR|EVRXCHAR);
//設定串列裝置控制參數
SetCommState(hComDev,&dcb);
//裝置已開啟
bOpen=TRUE;
//建立人工重設、未發訊號的事件
hEvent=CreateEvent(NULL,FALSE,FALSE,
“WatchEvent”);
//建立一個事件監視線程來監視串口事件
AfxBeginThread(CommWatchProc,pParam);
}
在設定串口DCB結構的參數時,不必設定每一個值。首先讀出DCB預設的參數設定,然後只修改必要的參數,其他參數都取預設值。由於對串口進行的是同步I/O操作,所以除非指定進行監測的事件發生,否則WaitCommEvent函數不會返回。在串列裝置初始化的最後要建立一個單獨的監視線程來監視串口事件,以免掛起當前調用線程,其中pParam可以是一個對事件進行處理的視窗類別指標。
如果要進行非同步I/O操作,開啟裝置控制代碼時,CreateFile的第6個參數應增加FILEFLAGOVERLAPPED 標誌。
資料發送
資料發送利用WriteFile函數實現。對於同步I/O操作,它的最後一個參數可為NULL;而對非同步I/O操作,它的最後一個參數必需是一個指向OVERLAPPED結構的指標,通過OVERLAPPED結構來獲得當前的操作狀態。
BOOL WriteComm(LPCVOID lpSndBuffer,DWORD
dwBytesToWrite)
{ //lpSndBuffer為發送資料緩衝區指標,
dwBytesToWrite為將要發送的位元組長度
//裝置已開啟
BOOL bWriteState;
//實際發送的位元組數
DWORD dwBytesWritten;
//裝置未開啟
if(!bOpen) return FALSE;
bWriteState=WriteFile(hComDev,lpSndBuffer,
dwBytesToWrite,&dwBytesWritten,NULL);
if(!bWriteState || dwBytesToWrite!=dwBytesWritten)
//發送失敗
return FALSE;
else
//發送成功
return TRUE;
}
資料接收
接收資料的任務由ReadFile函數完成。該函數從串口接收緩衝區中讀取資料,讀取資料前,先用ClearCommError函數獲得接收緩衝區中的位元組數。接收資料時,同步和非同步讀取的差別同發送資料是一樣的。
DWORD ReadComm(LPVOID lpInBuffer,DWORD
dwBytesToRead)
{ //lpInBuffer為接收資料的緩衝區指標, dwBytesToRead為準備讀取的資料長度(位元組數)
//串列裝置狀態結構
COMSTAT ComStat;
DWORD dwBytesRead,dwErrorFlags;
//裝置未開啟
if(!bOpen) return 0;
//讀取串列裝置的目前狀態
ClearCommError(hComDev,&dwErrorFlags,&ComStat);
//應該讀取的資料長度
dwBytesRead=min(dwBytesToRead,ComStat.cbInQue);
if(dwBytesRead>0)
//讀取資料
if(!ReadFile(hComDev,lpInBuffer,dwBytesRead,&dwBytesRead,NULL))
dwBytesRead=0;
return dwBytesRead;
}
事件監視線程
事件監視線程對串口事件進行監視,當監視的事件發生時,監視線程可將這個事件發送(SendMessage)或登記(PostMessage)到對事件進行處理的視窗類別(由pParam指定)中。
UINT CommWatchProc(LPVOID pParam)
{ DWORD dwEventMask=0; //發生的事件;
while(bOpen)
{ //等待監視的事件發生
WaitCommEvent(hComDev, &dwEventMask,
NULL);
if ((dwEventMask & EVRXCHAR) ==
EVRXCHAR)
……//接收到字元事件後,可以將此訊息登記到由pParam有指定的視窗類別中進行處理
if(dwEventMask & EVERR)==EVERROR)
……//發生錯誤時的處理
}
SetEvent(hEvent);
//發訊號,指示監視線程結束
return 0;
}
關閉串列裝置
在整個應用程式結束或不再使用串列裝置時,應將串列裝置關閉,包括取消事件監視,將裝置開啟標誌bOpen置為FALSE以使事件監視線程結束,清除發送/接收緩衝區和關閉裝置控制代碼。
void CloseSynComm()
{
if(!bOpen) return;
//結束事件監視線程
bOpen=FALSE;