基于VC的串行通信技術(shù)應(yīng)用實(shí)例
基于VC的串行通信技術(shù)應(yīng)用實(shí)例
在工業(yè)控制中,串口是常用的計(jì)算機(jī)與外部串行設(shè)備之間的數(shù)據(jù)傳輸通道,由于串行通信方便易行,所以應(yīng)用廣泛。
本文將介紹在Windows平臺下串行通信的工作機(jī)制和用Visual C++設(shè)計(jì)串行通信程序的編程方法及通信方式。
VC中實(shí)現(xiàn)串行通信的編程技術(shù)
以下我們將介紹VC中幾種實(shí)現(xiàn)串行通信的編程技術(shù):
利用VC++的標(biāo)準(zhǔn)通信函數(shù)
利用VC++的標(biāo)準(zhǔn)通信函數(shù)_inp和_outp可實(shí)現(xiàn)串口通信。下面是一個串口初始化的程序:
void init_com(PORT)
{char i;
outp(PORT+3,0x80);
outp(PORT,0x0C);
outp(PORT+1,0);
outp(PORT+3 ,0x3a);
outp(PORT+3 ,0x03);
i=inp(PORT+5) && 0xfe;
outp(PORT+5,i);}
使用串行通信控件MSComm
串行通信控件MSCOmm32.OCX提供了使用RS-232來進(jìn)行數(shù)據(jù)通信的所有協(xié)議,VC為該控件提供了標(biāo)準(zhǔn)的事件處理函數(shù)、過程,并通過屬性和方法提供了串行通信的設(shè)置。它使用戶能夠方便地訪問Windows串行通信驅(qū)動程序的大多數(shù)特性,包括輸入、輸出緩沖區(qū)的大小及決定何時使用流控制命令掛起數(shù)據(jù)傳輸?shù)取?
在ClassWizard中為新創(chuàng)建的通信控件定義成員對象(CMSComm m_Serial),通過該對象便可以對串口屬性進(jìn)行設(shè)置,MSComm控件共有27個屬性。以下是通過設(shè)置控件屬性對串口進(jìn)行初始化的實(shí)例:
BOOL CSampleDlg:: PortOpen()
{ BOOL m_Opened;
......
m_Serial.SetCommPort(2); // 指定串口號
m_Serial.SetSettings("4800,N,8,1");
// 通信參數(shù)設(shè)置
m_Serial.SetInBufferSize(1024);
// 指定接收緩沖區(qū)大小
m_Serial.SetInBufferCount(0);
// 清空接收緩沖區(qū)
m_Serial.InputMode(1);
// 設(shè)置數(shù)據(jù)獲取方式
m_Serial.SetInputLen(0);
// 設(shè)置讀取方式
m_Opened=m_Serail.SetPortOpen(1);
// 打開指定的串口
return m_Opened;}
打開所需串口后,我們需要考慮串口通信的時機(jī)。在接收或發(fā)送數(shù)據(jù)過程中,可能需要監(jiān)視并響應(yīng)一些事件和錯誤,所以事件驅(qū)動是處理串行端口交互作用的一種非常有效的方法。使用OnComm事件和CommEvent屬性捕捉并檢查通信事件和錯誤的值。發(fā)生通信事件或錯誤時將觸發(fā)OnComm事件,CommEvent屬性的值將被改變,應(yīng)用程序通過檢查CommEvent屬性值并作出相應(yīng)的反應(yīng)。
使用API函數(shù)
控件雖然簡單易用,但由于必須拿到對話框中使用,在一些需要在線程中實(shí)現(xiàn)通信的應(yīng)用場合下,控件的使用顯得捉襟見肘。API是附帶在Windows內(nèi)部的一個極其重要的組成部分。Windows的32位API主要是一系列很復(fù)雜的函數(shù)和消息集合。它可以看作是Windows系統(tǒng)為在其下運(yùn)行的各種開發(fā)系統(tǒng)提供的開放式通用功能增強(qiáng)接口。
通信程序在CreateFile處指定串口設(shè)備及相關(guān)的操作屬性,再返回一個句柄,該句柄將被用于后續(xù)的通信操作,并貫穿整個通信過程。串口打開后,其屬性被設(shè)置為默認(rèn)值,根據(jù)具體需要,通過調(diào)用GetCommState(hComm,&&dcb)讀取當(dāng)前串口設(shè)備控制塊DCB設(shè)置,修改后通過SetCommState(hComm,&&dcb)將其寫入。運(yùn)用ReadFile()與WriteFile()這兩個API函數(shù)實(shí)現(xiàn)串口讀寫操作,若為異步通信方式,兩函數(shù)中最后一個參數(shù)為指向OVERLAPPED結(jié)構(gòu)的非空指針,在讀寫函數(shù)返回值為FALSE的情況下,調(diào)用GetLastError()函數(shù),返回值為ERROR_IO_PENDING,表明I/O操作懸掛,即操作轉(zhuǎn)入后臺繼續(xù)執(zhí)行。此時,可以用WaitForSingleObject()來等待結(jié)束信號并設(shè)置最長等待時間,舉例如下:
BOOL bReadStatus;
bReadStatus = ReadFile( m_hIDComDev, buffer,
dwBytesRead, &&dwBytesRead, &&m_OverlappedRead );
if(!bReadStatus){
if(GetLastError()==ERROR_IO_PENDING){
WaitForSingleObject(m_OverlappedRead.hEvent,1000);
return ((int)dwBytesRead);}
return(0);}
return ((int)dwBytesRead);
多線程下實(shí)現(xiàn)串行通信
Windows內(nèi)部的搶先調(diào)度程序在活動的線程之間分配CPU時間,Windows區(qū)分兩種不同類型的線程,一種是用戶界面線程(User Interface Thread),它包含消息循環(huán)或消息泵,用于處理接收到的消息;另一種是工作線程(Work Thread),它沒有消息循環(huán),用于執(zhí)行后臺任務(wù)、監(jiān)視串口事件的線程即為工作線程。
多線程程序的編寫在端口的配置,連接部分與單線程的相同,在端口配置完畢后,最重要的是根據(jù)實(shí)際情況,建立多線程之間的同步對象,如信號燈、臨界區(qū)和事件等。
一切就緒后即可啟動工作線程,程序如下:
CWinThrea CommThread = AfxBegin
Thread(CommWatchThread, // 線程函數(shù)名
(LPVOID) m_pTTYInfo, // 傳遞的參數(shù)
THREAD_PRIORITY_ABOVE_NORMAL,
// 設(shè)置線程優(yōu)先級
(UINT) 0, // 最大堆棧大小
(DWORD) Create_SUSPENDED , // 創(chuàng)建標(biāo)志
(LPSECURITY_ATTRIBUTES) NULL);
if(WaitCommEvent(pTTYInfo->idComDev,&&dwEvtMask,NULL))
{
if((dwEvtMask && pTTYInfo->dwEvtMask )== pTTYInfo->dwEvtMask)
{
WaitForSingleObject(pTTYInfo->hPostEvent,0xFFFFFFFF);
ResetEvent(pTTYInfo->hPostEvent);
// 置同步事件對象為非信號態(tài)
::PostMessage(CSampleView,ID_COM1_DATA,0,0); // 發(fā)送通知消息}}
BEGIN_MESSAGE_MAP(CSampleView, CView)
//{{AFX_MSG_MAP(CSampleView)
ON_MESSAGE(ID_COM1_DATA, OnProcessCom1Data)
ON_MESSAGE(ID_COM2_DATA, OnProcessCom2Data)
.....
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
多線程的實(shí)現(xiàn)可以使得各端口獨(dú)立,準(zhǔn)確地實(shí)現(xiàn)串行通信,使串行通信具有更廣泛的靈活性與嚴(yán)格性,且充分利用CPU時間。但在具體的實(shí)時監(jiān)控系統(tǒng)中如何協(xié)調(diào)多個線程、線程之間以何種方式實(shí)現(xiàn)同步,這是多線程串行通信程序?qū)崿F(xiàn)的難點(diǎn)。
串行通信的操作方式
下面我們將介紹串行通信的幾種操作方式:
1.同步方式
同步方式中,讀串口的函數(shù)試圖在串口的接收緩沖區(qū)中讀取規(guī)定數(shù)目的數(shù)據(jù),直到規(guī)定數(shù)目的數(shù)據(jù)全部被讀出或設(shè)定的超時時間已到時才返回。例如:
COMMTIMEOUTS timeOver;
memset(&&timeOver,0,sizeof(timeOver));
DWORD timeMultiplier,timeConstant;
timeOver.ReadTotalTimeoutMultiplier=timeMultiplier;
timeOver.ReadTotalTimeoutConstant=timeConstant;
SetCommTimeouts(hComport,&&timeOver);
……
ReadFile(hComport,inBuffer,nWantRead,&&nRealRead,NULL);
COMMTIMEOUTS結(jié)構(gòu)用于設(shè)置讀寫函數(shù)的等待時間。
在ReadFile函數(shù)中hComport為待讀串口句柄;inBuffer為輸入緩沖區(qū)大小;nWantRead為每次調(diào)用ReadFile時,函數(shù)試圖讀出的字節(jié)數(shù);nRealRead為實(shí)際讀出的字節(jié)數(shù);最后一個參數(shù)值NULL代表ReadFile將采用同步文件讀寫的方式。
如果所規(guī)定的待讀取數(shù)據(jù)的數(shù)目nWantRead較大且設(shè)定的超時時間也較長,而接收緩沖區(qū)中數(shù)據(jù)較少,則可能引起線程阻塞。解決這一問題的方法是檢查COMSTAT結(jié)構(gòu)的cbInQue成員,該成員的大小即為接收緩沖區(qū)中處于等待狀態(tài)的數(shù)據(jù)的實(shí)際個數(shù)。如果令nWantRead的值等于COMSTAT.cbInQue,就能較好地防止線程阻塞。
2.查詢方式
查詢方式,即一個進(jìn)程中的某一線程定時地查詢串口的接收緩沖區(qū),如果緩沖區(qū)中有數(shù)據(jù),就讀取數(shù)據(jù);若緩沖區(qū)中沒有數(shù)據(jù),該線程將繼續(xù)執(zhí)行,因此會占用大量的CPU時間,它實(shí)際上是同步方式的一種派生。例如:
COMMTIMEOUTS timeOver;
memset(&&timeOver,0,sizeof(timeOver));
timeOver.ReadIntervalTimeout=MAXWORD;
SetCommTimeouts(hComport.&&timeOver);
……
ReadFile(hComport.inBuffer.nWantRead.&&nRealRead,NULL);
除了COMMTIMEOUTS結(jié)構(gòu)的變量timeOver設(shè)置不同外,查詢方式與同步方式在程序代碼方面很類似,但二者的工作方式卻差別很大。盡管ReadFile采用的也是同步文件讀寫方式,但由于timeOver的區(qū)間超過時間設(shè)置為MAXWORD,所以ReadFile每次將讀出接收隊(duì)列中的所有處于等待狀態(tài)的數(shù)據(jù),一次最多可讀出nWantRead個字節(jié)的數(shù)據(jù)。
3.異步方式
異步方式中,利用Windows的多線程結(jié)構(gòu),可以讓串口的讀寫操作在后臺進(jìn)行,而應(yīng)用程序的其他部分在前臺執(zhí)行。例如:
OVERLAPPED wrOverlapped;
COMMTIMEOUTS timeOver;
memset(&&timeOver.0.sizeof(timeOver));
DWORDtimeMultiplier,timeConstant;
timeOver.ReadTotalTimeoutMultiplier=timeMultiplier;
timeOver.ReadTotalTimeoutConstant=timeConstant;
SetCommTimeouts(hComport,&&timeOver);
wrOverlapped.hEvent=CreateEvent(NULL.TRUE,FALSE,NULL);
……
ReadFile(hComport,inBuffer,nWantRead,&&nRealRead,&&wrOverlapped);
GetOverlappedResult(hComport,&&wrOverlapped,&& nRealRead,TRUE);
……
ResetEvent(wrOverlapped.hEvent);
上面代碼中的ReadFile由于采用了異步方式,所以它只返回?cái)?shù)據(jù)是否已開始讀入的狀態(tài),并不返回實(shí)際的讀入數(shù)據(jù),即ReadFile中的nRealRead無效。實(shí)際讀入的數(shù)據(jù)是由GetOverlappedResult函數(shù)返回的,該函數(shù)的最后一個參數(shù)值為TRUE,表示它等待異步操作結(jié)束后才返回到應(yīng)用程序,此時,GetOverlappedResult函數(shù)與WaitForSingleObject函數(shù)等效。
當(dāng)采用異步方式時,在用CreateFile打開串口設(shè)備時,CreateFile函數(shù)的參數(shù)fdwAttrsAndFlags必須設(shè)為FILE_FLAG_ OVERLAPPED。在Windows中,只有在串行設(shè)備上才支持異步文件讀寫,并且,GetOverlappedResult函數(shù)也只支持串行設(shè)備或用DeviceloControl函數(shù)打開的文件。
4.事件驅(qū)動方式
若對端口數(shù)據(jù)的響應(yīng)時間要求較嚴(yán)格,可采用事件驅(qū)動方式。事件驅(qū)動方式通過設(shè)置事件通知,當(dāng)所希望的事件發(fā)生時,Windows發(fā)出該事件已發(fā)生的通知,這與DOS環(huán)境下的中斷方式很相似。Windows定義了9種串口通信事件,較常用的有以下三種:
EV_RXCHAR:接收到一個字節(jié),并放入輸入緩沖區(qū);
EV_TXEMPTY:輸出緩沖區(qū)中的最后一個字符,發(fā)送出去;
EV_RXFLAG:接收到事件字符(DCB結(jié)構(gòu)中EvtChar成員),放入輸入緩沖區(qū)。
在用SetCommMask()指定了有用的事件后,應(yīng)用程序可調(diào)用WaitCommEvent()來等待事件的發(fā)生。SetCommMask(hComm,0)可使WaitCommEvent()中止。例如:
COMSTAT comStat;
DWORD dwEvent;
SetCommMask(hComport,EV_RXCHAR);
……
if(WaitCommEvent(hComport,&&dwEvent,NULL))
if((dwEvent&&EV_RXCHAR)&&&&comstat.cbInQue)
ReadFile(hComport,inBuffer,comstat.cbInQue,&&nRealRead,NULL);
程序中,我們首先用SetCommMask函數(shù)設(shè)置事件代碼,上面的代碼中為EV_RXCHAR,表示接收到一個字符時觸發(fā)這一事件,然后調(diào)用WaitCommEvent函數(shù)等待該事件的發(fā)生。注意,WaitCommEvent函數(shù)第3個參數(shù)1pOverlapped可以是一個OVERLAPPED結(jié)構(gòu)的變量指針,也可以是NULL,當(dāng)用NULL時,表示該函數(shù)是同步的,否則表示該函數(shù)是異步的。
5.幾種方式的比較
在一般要求情況下,查詢方式是一種最直接的讀串口方式。但定時查詢存在一個致命弱點(diǎn),即查詢是定時發(fā)生的,可能發(fā)生得過早或過晚。在數(shù)據(jù)變化較快的情況下,特別是主控計(jì)算機(jī)的串口通過擴(kuò)展板擴(kuò)展至多個時,需定時地對所有串口輪流查詢,此時容易發(fā)生數(shù)據(jù)的丟失。雖然定時間隔越小,數(shù)據(jù)的實(shí)時性越高,但系統(tǒng)的資源也被占去越多。
Windows中提出文件讀寫的異步方式,主要是針對文件I/O相對較慢的速度而進(jìn)行的改進(jìn),它利用了Windows的多線程結(jié)構(gòu)。雖然在Windows中沒有實(shí)現(xiàn)任何對文件I/O的異步操作,但它卻能對串口進(jìn)行異步操作。采用異步方式,可以提高系統(tǒng)的整體性能,在對系統(tǒng)強(qiáng)壯性要求較高的場合,建議采用這種方式。
事件驅(qū)動方式是一種高效的串口讀方式。這種方式的實(shí)時性較高,特別是對于擴(kuò)展了多個串口的情況,并不要求像查詢方式那樣定時地對所有串口輪流查詢,而是像中斷方式那樣,只有當(dāng)設(shè)定的事件發(fā)生時,應(yīng)用程序得到Windows操作系統(tǒng)發(fā)出的消息后,才進(jìn)行相應(yīng)處理,避免了數(shù)據(jù)丟失。在實(shí)時性要求較高的場合,筆者建議采用這種方式。
在工業(yè)控制中,串口是常用的計(jì)算機(jī)與外部串行設(shè)備之間的數(shù)據(jù)傳輸通道,由于串行通信方便易行,所以應(yīng)用廣泛。
本文將介紹在Windows平臺下串行通信的工作機(jī)制和用Visual C++設(shè)計(jì)串行通信程序的編程方法及通信方式。
VC中實(shí)現(xiàn)串行通信的編程技術(shù)
以下我們將介紹VC中幾種實(shí)現(xiàn)串行通信的編程技術(shù):
利用VC++的標(biāo)準(zhǔn)通信函數(shù)
利用VC++的標(biāo)準(zhǔn)通信函數(shù)_inp和_outp可實(shí)現(xiàn)串口通信。下面是一個串口初始化的程序:
void init_com(PORT)
{char i;
outp(PORT+3,0x80);
outp(PORT,0x0C);
outp(PORT+1,0);
outp(PORT+3 ,0x3a);
outp(PORT+3 ,0x03);
i=inp(PORT+5) && 0xfe;
outp(PORT+5,i);}
使用串行通信控件MSComm
串行通信控件MSCOmm32.OCX提供了使用RS-232來進(jìn)行數(shù)據(jù)通信的所有協(xié)議,VC為該控件提供了標(biāo)準(zhǔn)的事件處理函數(shù)、過程,并通過屬性和方法提供了串行通信的設(shè)置。它使用戶能夠方便地訪問Windows串行通信驅(qū)動程序的大多數(shù)特性,包括輸入、輸出緩沖區(qū)的大小及決定何時使用流控制命令掛起數(shù)據(jù)傳輸?shù)取?
在ClassWizard中為新創(chuàng)建的通信控件定義成員對象(CMSComm m_Serial),通過該對象便可以對串口屬性進(jìn)行設(shè)置,MSComm控件共有27個屬性。以下是通過設(shè)置控件屬性對串口進(jìn)行初始化的實(shí)例:
BOOL CSampleDlg:: PortOpen()
{ BOOL m_Opened;
......
m_Serial.SetCommPort(2); // 指定串口號
m_Serial.SetSettings("4800,N,8,1");
// 通信參數(shù)設(shè)置
m_Serial.SetInBufferSize(1024);
// 指定接收緩沖區(qū)大小
m_Serial.SetInBufferCount(0);
// 清空接收緩沖區(qū)
m_Serial.InputMode(1);
// 設(shè)置數(shù)據(jù)獲取方式
m_Serial.SetInputLen(0);
// 設(shè)置讀取方式
m_Opened=m_Serail.SetPortOpen(1);
// 打開指定的串口
return m_Opened;}
打開所需串口后,我們需要考慮串口通信的時機(jī)。在接收或發(fā)送數(shù)據(jù)過程中,可能需要監(jiān)視并響應(yīng)一些事件和錯誤,所以事件驅(qū)動是處理串行端口交互作用的一種非常有效的方法。使用OnComm事件和CommEvent屬性捕捉并檢查通信事件和錯誤的值。發(fā)生通信事件或錯誤時將觸發(fā)OnComm事件,CommEvent屬性的值將被改變,應(yīng)用程序通過檢查CommEvent屬性值并作出相應(yīng)的反應(yīng)。
使用API函數(shù)
控件雖然簡單易用,但由于必須拿到對話框中使用,在一些需要在線程中實(shí)現(xiàn)通信的應(yīng)用場合下,控件的使用顯得捉襟見肘。API是附帶在Windows內(nèi)部的一個極其重要的組成部分。Windows的32位API主要是一系列很復(fù)雜的函數(shù)和消息集合。它可以看作是Windows系統(tǒng)為在其下運(yùn)行的各種開發(fā)系統(tǒng)提供的開放式通用功能增強(qiáng)接口。
通信程序在CreateFile處指定串口設(shè)備及相關(guān)的操作屬性,再返回一個句柄,該句柄將被用于后續(xù)的通信操作,并貫穿整個通信過程。串口打開后,其屬性被設(shè)置為默認(rèn)值,根據(jù)具體需要,通過調(diào)用GetCommState(hComm,&&dcb)讀取當(dāng)前串口設(shè)備控制塊DCB設(shè)置,修改后通過SetCommState(hComm,&&dcb)將其寫入。運(yùn)用ReadFile()與WriteFile()這兩個API函數(shù)實(shí)現(xiàn)串口讀寫操作,若為異步通信方式,兩函數(shù)中最后一個參數(shù)為指向OVERLAPPED結(jié)構(gòu)的非空指針,在讀寫函數(shù)返回值為FALSE的情況下,調(diào)用GetLastError()函數(shù),返回值為ERROR_IO_PENDING,表明I/O操作懸掛,即操作轉(zhuǎn)入后臺繼續(xù)執(zhí)行。此時,可以用WaitForSingleObject()來等待結(jié)束信號并設(shè)置最長等待時間,舉例如下:
BOOL bReadStatus;
bReadStatus = ReadFile( m_hIDComDev, buffer,
dwBytesRead, &&dwBytesRead, &&m_OverlappedRead );
if(!bReadStatus){
if(GetLastError()==ERROR_IO_PENDING){
WaitForSingleObject(m_OverlappedRead.hEvent,1000);
return ((int)dwBytesRead);}
return(0);}
return ((int)dwBytesRead);
多線程下實(shí)現(xiàn)串行通信
Windows內(nèi)部的搶先調(diào)度程序在活動的線程之間分配CPU時間,Windows區(qū)分兩種不同類型的線程,一種是用戶界面線程(User Interface Thread),它包含消息循環(huán)或消息泵,用于處理接收到的消息;另一種是工作線程(Work Thread),它沒有消息循環(huán),用于執(zhí)行后臺任務(wù)、監(jiān)視串口事件的線程即為工作線程。
多線程程序的編寫在端口的配置,連接部分與單線程的相同,在端口配置完畢后,最重要的是根據(jù)實(shí)際情況,建立多線程之間的同步對象,如信號燈、臨界區(qū)和事件等。
一切就緒后即可啟動工作線程,程序如下:
CWinThrea CommThread = AfxBegin
Thread(CommWatchThread, // 線程函數(shù)名
(LPVOID) m_pTTYInfo, // 傳遞的參數(shù)
THREAD_PRIORITY_ABOVE_NORMAL,
// 設(shè)置線程優(yōu)先級
(UINT) 0, // 最大堆棧大小
(DWORD) Create_SUSPENDED , // 創(chuàng)建標(biāo)志
(LPSECURITY_ATTRIBUTES) NULL);
if(WaitCommEvent(pTTYInfo->idComDev,&&dwEvtMask,NULL))
{
if((dwEvtMask && pTTYInfo->dwEvtMask )== pTTYInfo->dwEvtMask)
{
WaitForSingleObject(pTTYInfo->hPostEvent,0xFFFFFFFF);
ResetEvent(pTTYInfo->hPostEvent);
// 置同步事件對象為非信號態(tài)
::PostMessage(CSampleView,ID_COM1_DATA,0,0); // 發(fā)送通知消息}}
BEGIN_MESSAGE_MAP(CSampleView, CView)
//{{AFX_MSG_MAP(CSampleView)
ON_MESSAGE(ID_COM1_DATA, OnProcessCom1Data)
ON_MESSAGE(ID_COM2_DATA, OnProcessCom2Data)
.....
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
多線程的實(shí)現(xiàn)可以使得各端口獨(dú)立,準(zhǔn)確地實(shí)現(xiàn)串行通信,使串行通信具有更廣泛的靈活性與嚴(yán)格性,且充分利用CPU時間。但在具體的實(shí)時監(jiān)控系統(tǒng)中如何協(xié)調(diào)多個線程、線程之間以何種方式實(shí)現(xiàn)同步,這是多線程串行通信程序?qū)崿F(xiàn)的難點(diǎn)。
串行通信的操作方式
下面我們將介紹串行通信的幾種操作方式:
1.同步方式
同步方式中,讀串口的函數(shù)試圖在串口的接收緩沖區(qū)中讀取規(guī)定數(shù)目的數(shù)據(jù),直到規(guī)定數(shù)目的數(shù)據(jù)全部被讀出或設(shè)定的超時時間已到時才返回。例如:
COMMTIMEOUTS timeOver;
memset(&&timeOver,0,sizeof(timeOver));
DWORD timeMultiplier,timeConstant;
timeOver.ReadTotalTimeoutMultiplier=timeMultiplier;
timeOver.ReadTotalTimeoutConstant=timeConstant;
SetCommTimeouts(hComport,&&timeOver);
……
ReadFile(hComport,inBuffer,nWantRead,&&nRealRead,NULL);
COMMTIMEOUTS結(jié)構(gòu)用于設(shè)置讀寫函數(shù)的等待時間。
在ReadFile函數(shù)中hComport為待讀串口句柄;inBuffer為輸入緩沖區(qū)大小;nWantRead為每次調(diào)用ReadFile時,函數(shù)試圖讀出的字節(jié)數(shù);nRealRead為實(shí)際讀出的字節(jié)數(shù);最后一個參數(shù)值NULL代表ReadFile將采用同步文件讀寫的方式。
如果所規(guī)定的待讀取數(shù)據(jù)的數(shù)目nWantRead較大且設(shè)定的超時時間也較長,而接收緩沖區(qū)中數(shù)據(jù)較少,則可能引起線程阻塞。解決這一問題的方法是檢查COMSTAT結(jié)構(gòu)的cbInQue成員,該成員的大小即為接收緩沖區(qū)中處于等待狀態(tài)的數(shù)據(jù)的實(shí)際個數(shù)。如果令nWantRead的值等于COMSTAT.cbInQue,就能較好地防止線程阻塞。
2.查詢方式
查詢方式,即一個進(jìn)程中的某一線程定時地查詢串口的接收緩沖區(qū),如果緩沖區(qū)中有數(shù)據(jù),就讀取數(shù)據(jù);若緩沖區(qū)中沒有數(shù)據(jù),該線程將繼續(xù)執(zhí)行,因此會占用大量的CPU時間,它實(shí)際上是同步方式的一種派生。例如:
COMMTIMEOUTS timeOver;
memset(&&timeOver,0,sizeof(timeOver));
timeOver.ReadIntervalTimeout=MAXWORD;
SetCommTimeouts(hComport.&&timeOver);
……
ReadFile(hComport.inBuffer.nWantRead.&&nRealRead,NULL);
除了COMMTIMEOUTS結(jié)構(gòu)的變量timeOver設(shè)置不同外,查詢方式與同步方式在程序代碼方面很類似,但二者的工作方式卻差別很大。盡管ReadFile采用的也是同步文件讀寫方式,但由于timeOver的區(qū)間超過時間設(shè)置為MAXWORD,所以ReadFile每次將讀出接收隊(duì)列中的所有處于等待狀態(tài)的數(shù)據(jù),一次最多可讀出nWantRead個字節(jié)的數(shù)據(jù)。
3.異步方式
異步方式中,利用Windows的多線程結(jié)構(gòu),可以讓串口的讀寫操作在后臺進(jìn)行,而應(yīng)用程序的其他部分在前臺執(zhí)行。例如:
OVERLAPPED wrOverlapped;
COMMTIMEOUTS timeOver;
memset(&&timeOver.0.sizeof(timeOver));
DWORDtimeMultiplier,timeConstant;
timeOver.ReadTotalTimeoutMultiplier=timeMultiplier;
timeOver.ReadTotalTimeoutConstant=timeConstant;
SetCommTimeouts(hComport,&&timeOver);
wrOverlapped.hEvent=CreateEvent(NULL.TRUE,FALSE,NULL);
……
ReadFile(hComport,inBuffer,nWantRead,&&nRealRead,&&wrOverlapped);
GetOverlappedResult(hComport,&&wrOverlapped,&& nRealRead,TRUE);
……
ResetEvent(wrOverlapped.hEvent);
上面代碼中的ReadFile由于采用了異步方式,所以它只返回?cái)?shù)據(jù)是否已開始讀入的狀態(tài),并不返回實(shí)際的讀入數(shù)據(jù),即ReadFile中的nRealRead無效。實(shí)際讀入的數(shù)據(jù)是由GetOverlappedResult函數(shù)返回的,該函數(shù)的最后一個參數(shù)值為TRUE,表示它等待異步操作結(jié)束后才返回到應(yīng)用程序,此時,GetOverlappedResult函數(shù)與WaitForSingleObject函數(shù)等效。
當(dāng)采用異步方式時,在用CreateFile打開串口設(shè)備時,CreateFile函數(shù)的參數(shù)fdwAttrsAndFlags必須設(shè)為FILE_FLAG_ OVERLAPPED。在Windows中,只有在串行設(shè)備上才支持異步文件讀寫,并且,GetOverlappedResult函數(shù)也只支持串行設(shè)備或用DeviceloControl函數(shù)打開的文件。
4.事件驅(qū)動方式
若對端口數(shù)據(jù)的響應(yīng)時間要求較嚴(yán)格,可采用事件驅(qū)動方式。事件驅(qū)動方式通過設(shè)置事件通知,當(dāng)所希望的事件發(fā)生時,Windows發(fā)出該事件已發(fā)生的通知,這與DOS環(huán)境下的中斷方式很相似。Windows定義了9種串口通信事件,較常用的有以下三種:
EV_RXCHAR:接收到一個字節(jié),并放入輸入緩沖區(qū);
EV_TXEMPTY:輸出緩沖區(qū)中的最后一個字符,發(fā)送出去;
EV_RXFLAG:接收到事件字符(DCB結(jié)構(gòu)中EvtChar成員),放入輸入緩沖區(qū)。
在用SetCommMask()指定了有用的事件后,應(yīng)用程序可調(diào)用WaitCommEvent()來等待事件的發(fā)生。SetCommMask(hComm,0)可使WaitCommEvent()中止。例如:
COMSTAT comStat;
DWORD dwEvent;
SetCommMask(hComport,EV_RXCHAR);
……
if(WaitCommEvent(hComport,&&dwEvent,NULL))
if((dwEvent&&EV_RXCHAR)&&&&comstat.cbInQue)
ReadFile(hComport,inBuffer,comstat.cbInQue,&&nRealRead,NULL);
程序中,我們首先用SetCommMask函數(shù)設(shè)置事件代碼,上面的代碼中為EV_RXCHAR,表示接收到一個字符時觸發(fā)這一事件,然后調(diào)用WaitCommEvent函數(shù)等待該事件的發(fā)生。注意,WaitCommEvent函數(shù)第3個參數(shù)1pOverlapped可以是一個OVERLAPPED結(jié)構(gòu)的變量指針,也可以是NULL,當(dāng)用NULL時,表示該函數(shù)是同步的,否則表示該函數(shù)是異步的。
5.幾種方式的比較
在一般要求情況下,查詢方式是一種最直接的讀串口方式。但定時查詢存在一個致命弱點(diǎn),即查詢是定時發(fā)生的,可能發(fā)生得過早或過晚。在數(shù)據(jù)變化較快的情況下,特別是主控計(jì)算機(jī)的串口通過擴(kuò)展板擴(kuò)展至多個時,需定時地對所有串口輪流查詢,此時容易發(fā)生數(shù)據(jù)的丟失。雖然定時間隔越小,數(shù)據(jù)的實(shí)時性越高,但系統(tǒng)的資源也被占去越多。
Windows中提出文件讀寫的異步方式,主要是針對文件I/O相對較慢的速度而進(jìn)行的改進(jìn),它利用了Windows的多線程結(jié)構(gòu)。雖然在Windows中沒有實(shí)現(xiàn)任何對文件I/O的異步操作,但它卻能對串口進(jìn)行異步操作。采用異步方式,可以提高系統(tǒng)的整體性能,在對系統(tǒng)強(qiáng)壯性要求較高的場合,建議采用這種方式。
事件驅(qū)動方式是一種高效的串口讀方式。這種方式的實(shí)時性較高,特別是對于擴(kuò)展了多個串口的情況,并不要求像查詢方式那樣定時地對所有串口輪流查詢,而是像中斷方式那樣,只有當(dāng)設(shè)定的事件發(fā)生時,應(yīng)用程序得到Windows操作系統(tǒng)發(fā)出的消息后,才進(jìn)行相應(yīng)處理,避免了數(shù)據(jù)丟失。在實(shí)時性要求較高的場合,筆者建議采用這種方式。
文章版權(quán)歸西部工控xbgk所有,未經(jīng)許可不得轉(zhuǎn)載。