使用Win32API實現Windows下非同步串口通訊(上)

來源:互聯網
上載者:User

目錄:
1. 非同步非阻塞串口通訊的優點
2. 非同步非阻塞串口通訊的基本原理
3. 非同步非阻塞串口通訊的基礎知識
4. 非同步非阻塞串口通訊的實現步驟
2005.01.05

一,非同步非阻塞串口通訊的優點

讀寫串列口時,既可以同步執行,也可以重疊(非同步)執行。
在同步執行時,函數直到操作完成後才返回。這意味著在同步執行時線程會被阻塞,從而導致效率下降。
在重疊執行時,即使操作還未完成,調用的函數也會立即返回。費時的I/O操作在後台進行,這樣線程就可以幹別的事情。
例如,線程可以在不同的控制代碼上同時執行I/O操作,甚至可以在同一控制代碼上同時進行讀寫操作。"重疊"一詞的含義就在於此。

二,非同步非阻塞串口通訊的基本原理
首先,確定要開啟的串口名、傳輸速率、同位方式、資料位元、停止位,傳遞給CreateFile()函數開啟特定串口;
其次,為了保護系統對串口的初始設定,調用 GetCommTimeouts()得到串口的原始逾時設定;
然後,初始化DCB對象,調用SetCommState() 設定DCB,調用SetCommTimeouts()設定串口逾時控制;
再次,調用SetupComm()設定串口接收發送資料的緩衝區大小,串口的設定就基本完成,之後就可以啟動讀寫線程了。

三,非同步非阻塞串口通訊的基礎知識
下面來介紹並舉例說明一下編寫非同步非阻塞串口通訊的程式中將會使用到的幾個關鍵函數

CreateFile()
功能:開啟串口裝置
函數原型
HANDLE CreateFile(
LPCTSTR lpFileName, // 串口名稱字串;如: "COM1" 或 "COM2"
DWORD dwDesiredAccess, // 設定讀寫屬性(訪問模式 );一般為 GENERIC_READ|GENERIC_WRITE,
DWORD dwShareMode, // 共用模式;"必須"為 0, 即不能共用
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全屬性;一般為NULL
DWORD dwCreationDistribution, // 建立方式,串口設定必須設定此值; 在這裡"必須"為 OPEN_EXISTING
DWORD dwFlagsAndAttributes, // 檔案屬性和標誌;在這裡我們設定成FILE_FLAG_OVERLAPPED ,實現非同步I/O
HANDLE hTemplateFile // 臨時檔案的控制代碼,通常為NULL 
);
說明:
如果調用成功,那麼該函數返迴文件的控制代碼,如果調用失敗,則函數返回INVALID_HANDLE_VALUE。
Forexample:

Handle m_hComm = CreateFile(com1,GENERIC_READ||GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,0);

CloseHandle();
功能:關閉串口
BOOL CloseHandle(
HANDLE hObject // handle to object to close

這個,我想就不多說了吧!

GetCommState()
功能:獲得串口狀態
BOOL GetCommState(
HANDLE hFile, // handle of communications device
LPDCB lpDCB // address of device-control block structure
);

SetCommState()
功能:設定串口狀態 
BOOL SetCommState(
HANDLE hFile, // handle of communications device
LPDCB lpDCB // address of device-control block structure
);
說明:
在開啟通訊裝置控制代碼後,常常需要對串列口進行一些初始化工作。這需要通過一個DCB結構來進行。DCB結構包含了諸如傳輸速率、每個字元的資料位元數、同位和停止位元等資訊。在查詢或配置置串列口的屬性時,都要用DCB結構來作為緩衝區。
調用GetCommState函數可以獲得串口的配置,該函數把當前配置填充到一個DCB結構中。一般在用CreateFile開啟串列口後,可以調用GetCommState函數來擷取串列口的初始配置。要修改串列口的配置,應該先修改DCB結構,然後再調用SetCommState函數用指定的DCB結構來設定串列口
Forexample:
DCB dcb;
memset(&dec,0,dizeof(dcb));
if(!GetCommState(HComm,&dcb))//擷取當前DCB配置
 return FALSE; 
dcb.BaudRate = CBR_9600;//修改資料轉送率
............
if(SetCommState(hComm,&dcb))//設定新參數
......    //錯誤處理

BuildCommDCB()
功能:初始化DCB結構
BOOL BuildCommDCB(
LPCTSTR lpDef, // pointer to device-control string
LPDCB lpDCB // pointer to device-control block
);
Forexample:
DCB dcb;
memset(&dcb,0,sizeof(dcb));
dcb.DCBlength = sizeof(dcb);
if(!BuildCommDCb("9600,n,8,1",&dcb))//"baud=9600 parity=N data=8 stop=1"
{
 ......  //參數修改錯誤
 return FALSE;
}
else
{
......  //己準備就緒
}
說明:功能同上面的例子。

SetupComm()
功能:設定I/O緩衝區的大小
函數原型:
BOOL SetupComm(
  HANDLE hFile,     // handle to communications device
  DWORD dwInQueue,  // size of input buffer
  DWORD dwOutQueue  // size of output buffer
);
說明:
除了在DCB中的設定外,程式一般還需要設定I/O緩衝區的大小和逾時。Windows用I/O緩衝區來暫存串列口輸入和輸出的資料,如果通訊的速率較高,則應該設定較大的緩衝區。調用SetupComm函數可以設定串列口的輸入和輸出緩衝區的大小。

先介紹一個結構:COMMTIMEOUTS

typedef struct _COMMTIMEOUTS { 
DWORD ReadIntervalTimeout; // 讀間隔逾時
DWORD ReadTotalTimeoutMultiplier; // 讀時間係數
DWORD ReadTotalTimeoutConstant; // 讀時間常量
DWORD WriteTotalTimeoutMultiplier; // 寫時間係數
DWORD WriteTotalTimeoutConstant; // 寫時間常量
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

再介紹兩個函數
GetCommTimeouts
功能:讀取TimeOut的值
函數原型:
BOOL GetCommTimeouts(
HANDLE hFile, // handle of communications device
LPCOMMTIMEOUTS lpCommTimeouts // address of comm. time-outs structure
);
SetCommTimeouts
功能:設定TimeOUt的值
函數原型:
BOOL SetCommTimeouts(
HANDLE hFile, // handle of communications device
LPCOMMTIMEOUTS lpCommTimeouts // address of communications time-out structure
);

這裡順便介紹一下TimeOut機制的兩個性質:

逾時函數
說明:
在用ReadFile和WriteFile讀寫串列口時,需要考慮逾時問題。如果在指定的時間內沒有讀出或寫入指定數量的字元,那麼ReadFile或WriteFile的操作就會結束。要查詢當前的逾時設定應調用GetCommTimeouts函數,該函數會填充一個COMMTIMEOUTS結構。調用SetCommTimeouts可以用某一個COMMTIMEOUTS結構的內容來設定逾時。

  有兩種逾時:間隔逾時和總逾時。間隔逾時是指在接收時兩個字元之間的最大時延,總逾時是指讀寫操作總共花費的最大時間。寫操作只支援總逾時,而讀操作兩種逾時均支援。用COMMTIMEOUTS結構可以規定讀/寫操作的逾時,該結構的定義為: COMMTIMEOUTS結構的成員都以毫秒為單位。總逾時的計算公式是:

總逾時=時間係數×要求讀/寫的字元數 + 時間常量

  例如,如果要讀入10個字元,那麼讀操作的總逾時的計算公式為:

讀總逾時=ReadTotalTimeoutMultiplier×10 + ReadTotalTimeoutConstant

  可以看出,間隔逾時和總逾時的設定是不相關的,這可以方便通訊程式靈活地設定各種逾時。

  如果所有寫逾時參數均為0,那麼就不使用寫逾時。如果ReadIntervalTimeout為0,那麼就不使用讀間隔逾時,如果ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都為0,則不使用讀總逾時。如果讀間隔逾時被設定成MAXDWORD並且兩個讀總逾時為0,那麼在讀一次輸入緩衝區中的內容後讀操作就立即完成,而不管是否讀入了要求的字元。

  在用重疊方式讀寫串列口時,雖然ReadFile和WriteFile在完成操作以前就可能返回,但逾時仍然是起作用的。在這種情況下,逾時規定的是操作的完成時間,而不是ReadFile和WriteFile的返回時間。

Forexample:
COMMTIMEOUTS timeOver;
memset(&&timeOver.0.sizeof(timeOver));
DWORDtimeMultiplier,timeConstant;
timeOver.ReadTotalTimeoutMultiplier=timeMultiplier;
timeOver.ReadTotalTimeoutConstant=timeConstant;
SetCommTimeouts(hComport,&&timeOver);

ReadFile()
功能:讀取資料
函數原型:
BOOL ReadFile(
HANDLE hFile, // 串口名稱字串(檔案控制代碼 )
LPVOID lpBuffer, // 讀緩衝區
DWORD nNumberOfBytesToRead, // 要求讀入的位元組數
LPDWORD lpNumberOfBytesRead, // 實際讀入的位元組數
LPOVERLAPPED lpOverlapped // 指向一個OVERLAPPED結構
); //若返回TRUE則表明操作成功

Forexample:
char *pReciveBuf;
DWORD nWantRead = 100,
 nReadRead;
LPOVERLAPPED m_OverlappedRead;
BOOL bReadStatus = ReadFile( m_hComm, preciveBuf,nWantRead, &&nReadRead, &&m_OverlappedRead );

WriteFile()
功能:來將資料寫入Serial port.
函數原型:
BOOL WriteFile(
HANDLE hFile, // handle to file to write to
LPCVOID lpBuffer, // pointer to data to write to file
DWORD nNumberOfBytesToWrite, // number of bytes to write
LPDWORD lpNumberOfBytesWritten, // pointer to number of bytes written
LPOVERLAPPED lpOverlapped // pointer to structure needed for overlapped I/O
);

說明:
 ReadFile函數只要在串列口輸入緩衝區中讀入指定數量的字元,就算完成操作。
而WriteFile函數不但要把指定數量的字元拷入到輸出緩衝中,而且要等這些字元從串列口送出去後才算完成操作。

當ReadFile和WriteFile返回FALSE時,不一定就是操作失敗,線程應該調用GetLastError函數分析返回的結果。例如,在重疊操作時如果操作還未完成函數就返回,那麼函數就返回FALSE,而且GetLastError函數返回ERROR_IO_PENDING。

如果GetLastError函數返回ERROR_IO_PENDING,則說明重疊操作還為完成,線程可以等待操作完成。
有兩種等待辦法:一種辦法是用象WaitForSingleObject這樣的等待函數來等待OVERLAPPED結構的hEvent成員,
可以規定等待的時間,在等待函數返回後,調用GetOverlappedResult。
另一種辦法是調用GetOverlappedResult函數等待,如果指定該函數的bWait參數為TRUE,
那麼該函數將等待OVERLAPPED結構的hEvent 事件。
GetOverlappedResult可以返回一個OVERLAPPED結構來報告包括實際傳輸位元組在內的重疊操作結果。

如果規定了讀/寫操作的逾時,那麼當超過規定時間後,hEvent成員會變成有訊號的。因此,在逾時發生後,WaitForSingleObject和GetOverlappedResult都會結束等待。WaitForSingleObject的dwMilliseconds參數會規定一個等待逾時,該函數實際等待的時間是兩個逾時的最小值。注意GetOverlappedResult不能設定等待的時限,因此如果hEvent成員無訊號,則該函數將一直等待下去

ClearCommError()
功能: 從字面上的意思看來, 它是用來清除錯誤情況用的, 但是實際上它還可以拿來取得目前通訊裝置的一些資訊.
函數原型:
BOOL ClearCommError(
HANDLE hFile, // handle to communications device
LPDWORD lpErrors, // pointer to variable to receive error codes
LPCOMSTAT lpStat // pointer to buffer for communications status
);
說明:
 在調用ReadFile和WriteFile之前,線程應該調用ClearCommError函數清除錯誤標誌。
該函數負責報告指定的錯誤和裝置的目前狀態。

PurgeComm()
功能:終止目前進行中的讀或寫的動作
函數原型:
BOOL PurgeComm(
HANDLE hFile, // handle of communications resource
DWORD dwFlags // action to perform
);
參數說明:
HANDLE hFile,//串口名稱字串
dwFlags 共有四種 flags:

  PURGE_TXABORT: 終止目前進行中的(背景)寫入動作
  PURGE_RXABORT: 終正目前進行中的(背景)讀取動作
  PURGE_TXCLEAR: flush 寫入的 buffer
  PURGE_TXCLEAR: flush 讀取的 buffer
調用PurgeComm函數可以終止進行中的讀寫操作,該函數還會清除輸入或輸出緩衝區中的內容。

GetCommMask()
功能:得到設定的通訊事件的掩碼
函數原型:
BOOL GetCommMask(
HANDLE hFile, // handle of communications device
LPDWORD lpEvtMask // address of variable to get event mask
);

SetCommMask()
功能:設定想要得到的通訊事件的掩碼
函數原型:
BOOL SetCommMask(
HANDLE hFile, // handle of communications device
DWORD dwEvtMask // mask that identifies enabled events
);
說明:
可設定的通訊事件標誌(即SetCommMask()函數所設定的掩碼)
可以有EV_BREAK、EV_CTS、EV_DSR、 EV_ERR、EV_RING、EV_RLSD、EV_RXCHAR、EV_RXFLAG、EV_TXEMPTY。

註:若對連接埠資料的回應時間要求較嚴格,可採用事件驅動I/O讀寫,Windows定義了9種串口通訊事件,較常用的有:

  EV_RXCHAR: 接收到一個位元組,並放入輸入緩衝區。

  EV_TXEMPTY: 輸出緩衝區中的最後一個字元發送出去。

  EV_RXFLAG: 接收到事件字元(DCB結構中EvtChar成員),放入輸入緩衝區。

下面是MSDN上的解釋:
EV_BREAK A break was detected on input.
EV_CTS The CTS (clear-to-send) signal changed state.
EV_DSR The DSR (data-set-ready) signal changed state.
EV_ERR A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
EV_RING A ring indicator was detected.
EV_RLSD The RLSD (receive-line-signal-detect) signal changed state.
EV_RXCHAR A character was received and placed in the input buffer.
EV_RXFLAG The event character was received and placed in the input buffer. The event character is specified in the device's DCB structure, which is applied to a serial port by using the SetCommState function.
EV_TXEMPTY The last character in the output buffer was sent.

WaitCommEvent()
功能:等待設定的通訊事件的發生
函數原型:
BOL WaitCommEvent(
HANDLE hFile, // handle of communications device
LPDWORD lpEvtMask, // address of variable for event that occurred
LPOVERLAPPED lpOverlapped, // address of overlapped structure
);
說明:
WaitCommEvent() 會一直 block(阻塞) 到你所設定的通訊事件發生為止.
所以當 WaitCommEvent() 返回時, 你可以由 lpEvtMask 取得究竟是那一事件發生, 再來決定要如何處理.

WaitForSingleObject()
功能:保證線程同步的等待函數
函數原型:
DWORD WaitForSingleObject(HANDLE hHandle,//同步對象的控制代碼
     DWORD dwMilliseconds//以毫秒為單位的逾時間隔,如果設為INFINITE,則逾時間隔是無限的
    );
說明:
傳回值    含義 
WAIT_FAILED   函數失敗
WAIT_OBJECT_0  指定的同步對象處於有訊號的狀態
WAIT_ABANDONED  擁有一個mutex的線程已經中斷了,但未釋放該MUTEX
WAIT_TIMEOUT   逾時返回,並且同步對象無訊號

WaitForMultipleObjects()
功能:可以同時監測多個同步對象
函數原型:
DWORD WaitForMultipleObjects(DWORD nCount,//控制代碼數組中控制代碼的數目
     CONST HANDLE *lpHandles,//代表一個控制代碼數組
     BOOL bWaitAll, //說明了等待類型(),如果為TRUE,那麼函數在所有對象都有訊號後才返回,
      //如果為FALSE,則只要有一個對象變成有訊號的,函數就返回
    DWORD dwMilliseconds//以毫秒為單位的逾時間隔
     );
說明:
 傳回值        含義
WAIT_OBJECT_0到WAIT_ OBJECT_0+nCount-1    若bWaitAll為TRUE,則傳回值表明所有對象都是有訊號的。
       如果bWaitAll為FALSE,則傳回值減去WAIT_OBJECT_0就是數組中有訊號對       象的最小索引。
 
WAIT_ABANDONED_0到WAIT_ ABANDONED_ 0+nCount-1  若bWaitAll為TRUE,則傳回值表明所有對象都有訊號,但有一個mutex被       放棄了。若bWaitAll為FALSE,則傳回值減去WAIT_ABANDONED_0就是被放棄       mutex在對象數組中的索引。
 WAIT_TIMEOUT      逾時返回

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.