http://www.cnblogs.com/fyhui/archive/2012/05/02/2479595.html
2012
一、串口API
1. 打開串口
使用CreateFile函數(shù)可以打開串口。通常有兩種方式可以打開,一種是同步方式(NonOverlapped),另外一種異步方式(Overlapped)。
HANDLE hComm;
hComm = CreateFile( gszPort, //串口名
GENERIC_READ|GENERIC_WRITE //讀寫
0, //注意:串口為不可共享設(shè)備,本參數(shù)須為0
0,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED, //異步方式
0);
if(hComm == INVALID_HANDLE_VALUE) //打開串口失敗處理
······
2. 配置串口
DCB(Device Control Block)結(jié)構(gòu)定義了串口通信設(shè)備的控制設(shè)置,有3種方式可以初始化DCB。
DCB dcb;
memset(&dcb, 0, sizeof(dcb));
if(!GetCommState(hComm, &dcb)) …… //錯誤處理
else …… //已準(zhǔn)備就緒
DCB dcb;
memset(&dcb, 0, sizeof(dcb));
dcb.DCBlength = sizeof(dcb);
if(!BuildCommDCB(“9600,n,8,1”, &dcb)) …… //參數(shù)配置錯誤
else …… //已準(zhǔn)備就緒
DCB dcb;
memset(&dcb, 0, sizeof(dcb));
if(!GetCommState(hComm, &dcb)) return FALSE;
dcb.BaudRate = CBR_9600;
3. 流控設(shè)備
流控制有如下兩種設(shè)置:
注意:在不設(shè)置流控制方式或軟件流控的情況下,基本上不會出現(xiàn)什么問題,但在硬件流控下,規(guī)范的RTS_CONTROL_HANDSHAKE流控方式的含義本來是當(dāng)緩沖區(qū)快滿的時候RTS會自動OFF通知對方暫停發(fā)送,當(dāng)緩沖區(qū)重新空出來的時候,RTS會自動ON,但很多時候當(dāng)RTS變OFF以后即使已經(jīng)清空了緩沖區(qū),RTS也不會自動的ON,造成對方停在那里不發(fā)送了。所以,如果要用硬件流控制的話,還要在接收后最好加上檢測緩沖區(qū)大小的判斷,具體做法是使用ClearCommError后返回COMSTAT.cbInQue,當(dāng)緩沖區(qū)已經(jīng)空出來的時候,要使用invoke(EscapeCommFunction,hComm,SETRTS)重新將RTS設(shè)置為ON。
4. 串口讀寫操作
串口讀寫有兩種方式:同步方式(NonOverlapped)和異步方式(Overlapped)。同步方式指必須完成了讀寫操作,函數(shù)才返回,這可能會使程序無響應(yīng),因為如果在讀寫時發(fā)生了錯誤,永遠(yuǎn)不返回就會出錯,可能線程將停在原地。而異步方式則靈活的多,一旦讀寫不成功,就將讀寫掛起,函數(shù)直接返回,可以通過GetLastError函數(shù)得知讀寫未成功的原因,所以串口讀寫常常采用異步方式操作。
ReadFile()函數(shù)用于完成讀操作,異步方式的讀操作為:
DWORD dwRead;
BOOL fWaitingOnRead = FALSE;
OVERLAPPED osReader;
memset(&osReader, 0, sizeof(osReader));
osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if(osReader.hEvent == NULL) …… //錯誤處理
if(!fWaitingOnRead)
{
if(!ReadFile(hComm, lpBuf, READ_BUF_SIZE, &dwRead, &osReader)) //讀串口
{
if(GetLastError() != ERROR_IO_PENDING) …… //報告錯誤
else fWaitingOnRead = TRUE;
}
}
else
{
//讀取完成,不必在調(diào)用GetOverlappedResults函數(shù)
HandleASuccessfulRead(lpBuf, dwRead);
}
//如果讀操作被掛起,可以調(diào)用WaitForSingleObject()函數(shù)或
//WaitForMuntilpleObjects()等待讀操作完成或者超時發(fā)生,
//再調(diào)用GetOverlappedResult()得到想要的信息。
if(fWaitingOnRead)
{
dwRes = WaitForSingleObject(osReader.hEvent, READ_TIMEOUT);
switch(dwRes)
{
case WAIT_OBJECT_0: //完成讀操作
if(!GetOverlappedResult(hComm, &osReader, &dwRead, FALSE)) …… //錯誤
else …… //全部讀取成功
HandleASuccessfulRead(lpBuf, dwRead);
fWaitintOnRead = FALSE;
break;
case WAIT_TIMEOUT: //操作尚未完成
……. //處理其他任務(wù)
break;
default:
…… //出現(xiàn)錯誤
break;
}
}
注意上述代碼在處理多線程串口在windows系列下存在一些問題,修改完成后代碼參考1.4節(jié)。
5. 關(guān)閉串口
程序結(jié)束或需要釋放串口資源時,必須正確關(guān)閉串口。調(diào)用CloseHandle函數(shù)關(guān)閉串口的句柄即可,
CloseHandle(hComm);
值得注意的是,在關(guān)閉串口前必須保證讀寫串口線程已經(jīng)退出,否則會引起誤操作,一般采用的辦法是使用事件驅(qū)動機制,啟動一事件,通知串口讀寫線程強制退出。
6. 其他問題
串口通信中其他必須處理的問題主要有如下幾個:
二、串口操作方式
1. 同步方式
同步(NonOverlapped)方式是比較簡單的一種方式,編寫代碼長度明顯少于異步(Overlapped)方式。同步方式中,讀串口的函數(shù)試圖在串口的接收緩沖區(qū)中讀取規(guī)定數(shù)據(jù)的數(shù)據(jù),直到規(guī)定數(shù)據(jù)的數(shù)據(jù)全部被讀出或設(shè)定超時時間已到時才返回。例如:
COMMTIMEOUTS timeOver;
memset(&timeOver, 0, sizeof(timeOver));
DWORD timeMultiplier, timeConstant;
……
timeOver.ReadTotalTimeoutMultiplier = timeMultiplier;
timeOver.ReadTotalTimeoutConstant = timeConstant;
SetCommTimeouts(hComm, &timeOver);
……
ReadFile(hComm, inBuffer, nWantRead, &nRealRead, NULL); //NULL指采用同步文件讀寫
如果所規(guī)定的待讀取數(shù)據(jù)的數(shù)目nWantRead較大且設(shè)定的超時時間較長,而接收緩沖區(qū)中數(shù)據(jù)較少,則可能引起線程阻塞。解決這一問題的方法是檢查COSTAT結(jié)構(gòu)的cbInQue成員,該成員的大小即為接收緩沖區(qū)中處于等待狀態(tài)的實際個數(shù)。如果令nWantRead的值等于COMSTAT.cbInQue,就能很好的防止線程阻塞。
2. 異步方式
在異步方式中,利用Windows的多線程結(jié)構(gòu),可以讓串口的讀寫操作在后臺進行,而應(yīng)用程序的其他部分在前臺執(zhí)行。例如:
OVERLAPPED wrOverlapped;
COMMTIMEOUTS timeOVer;
memset(&timeOver, 0, sizeof(timeOver));
DWORD timeMultiplier, timeConstant;
…… //給timeMultiplier, timeConstant賦值
timeOver.ReadTotalTimeoutMultiplier = timeMultiplier;
timeOver.ReadTotalTimeoutConstant = timeConstant;
SetCommTimeouts(hComm, &timeOver);
wrOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
……
ReadFile(hComm, inBuffer, nWantRead, &nRealRead, &wrOverlapped);
GetOverlappedResult(hComm, &wrOverlapped, &nRealRead,TRUE);
……
ResetEvent(wrOverlapped.hEvent);
上面代碼中的ReadFile由于采用了異步方式,所以只返回數(shù)據(jù)是否已經(jīng)開始讀入的狀態(tài),并不返回實際的讀入數(shù)據(jù),即ReadFile中的nRealRead無效。實際讀入的數(shù)據(jù)由GetOverlappedResult返回的,該函數(shù)的最后一個參數(shù)值為TRUE,表示它等待異步操作結(jié)束后才返回到應(yīng)用程序,此時,GetOverlappedResult與WaitForSingleObject函數(shù)無效。
3. 查詢方式
即一個進程中的某一線程定時地查詢串口的接收緩沖區(qū),如果緩沖區(qū)中有數(shù)據(jù),就讀取數(shù)據(jù);若緩沖區(qū)沒有數(shù)據(jù),該線程將繼續(xù)執(zhí)行,因此會占用大量的CPU時間,它實際上是同步方式的一種派生。例如:
COMMTIMEOUTS timeOver,
Memset(&timeOver, 0, sizeof(timeOver));
timeOver.ReadIntervalTimeout = MAXWORD;
SetCommTimeouts(hComm, &timeOver);
……ReadFile(hComm, inBuffer, nWantRead, &nRealRead, NULL);
除了COMMTIMEOUTS結(jié)構(gòu)的變量timeOver設(shè)置不同外,查詢方式與同步方式在程序代碼方面很類似,但二者的工作方式卻差別很大。盡管ReadFile采用的也是同步文件讀寫方式,但由于timeOver的區(qū)間超過時間設(shè)置為MAXWORD,所以ReadFile每次將讀出接收隊列中的所有處于等待狀態(tài)的數(shù)據(jù),一次最多可讀出nWantRead個字節(jié)的數(shù)據(jù)。
4. 事件驅(qū)動方式
若對端口數(shù)據(jù)的響應(yīng)時間要求較嚴(yán)格,可采用事件驅(qū)動方式。事件驅(qū)動方式通過設(shè)置事件通知,當(dāng)所希望的事件發(fā)生時,Windows發(fā)出該事件已經(jīng)發(fā)生的通知。Windows定義了9中串口通信事件,常用的有以下3中:
在用SetCommMask()制定了有用的事件后,應(yīng)用程序可調(diào)用WaitCommEvent()來等待事件的發(fā)生。SetCommMask可使WaitCommEvent()中止。例如:
COMSTAT comStat;
DWORD dwEvent;
SetCommMask(hComm, EV_RXCHAR);
……
if(WaitCommEvent(hComm, &dwEvent, NULL))
if((dwEvent & EV_RXCHAR) && comstat.cbInQue)
ReadFile(hComm, inBuffer, comstat.cbInQue, &nRealRead, NULL);
5. 總結(jié)
一般要求情況下,查詢方式是一種最直接的讀串口的方式。但定時查詢存在一個致命的弱點,即查詢是定時發(fā)生的,可能發(fā)生的過早或過晚。在數(shù)據(jù)變化較快的情況下,特別是主控計算機的串口通過擴展板擴展多個時,需定時對所有串口輪流查詢,容易發(fā)生數(shù)據(jù)的丟失。雖然定時間隔越小,數(shù)據(jù)的實時性越高,但系統(tǒng)的資源也被占用越多。
Windows中提出文件讀寫的異步方式,主要是針對文件IO相對較慢的速度而進行的改進,它利用了系統(tǒng)的多線程結(jié)構(gòu),雖然在Windows中沒有實現(xiàn)任何對文件IO的異步操作,但它卻能對串口進行異步操作。采用異步方式,可以提高系統(tǒng)整體性能,在對系統(tǒng)強壯性要求高的場合,建議采用這種方式。
事件驅(qū)動方式是一種高效的串口讀方式。這種方式實時性較高,特別對擴展了多個串口的情況,并不要求像查詢方式那樣定時地對所有串口輪詢,而像中斷方式那樣,只有當(dāng)設(shè)定的事件發(fā)生時,應(yīng)用程序得到windows操作系統(tǒng)發(fā)出的消息后,才進行相應(yīng)處理,以免數(shù)據(jù)丟失。