"Times New Roman";mso-hansi-font-family:"Times New Roman"'>基於API函數的串口通訊編程
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>用到的串口通訊編程方法有:使用通訊控制項、在進階語言中嵌入彙編以及使用API函數。在這幾種方法中,使用API函數編寫的串口通訊程式最為高效、靈活。串口通訊編程將用到三種API函數——串口通訊相關API函數、多線程API函數和實現訊息機制的API函數,下面將分別介紹這幾種API函數。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>1 與串口通訊有關的API函數
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> Windows系統通訊一般都以WOSA(Windows
Open Services Architecture,即Windows開放式服務體系)模型為基礎,在此模型中位於上層的應用程式通過調用各種通訊API與位於下層的裝置驅動程式進行資料交換。下面將按一般串口通訊程式的流程順序介紹這些API函數。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 1.1 開啟串口
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> Win32作業系統把串口看作一個檔案,因此開啟串口我們將要用到CreateFile函數。該函數第一個參數指明要開啟的檔案名稱,對串口操作來說,就是COM1、COM2等。第二個參數為讀寫入模式設定,因為要對串口進行讀寫,所以該參數應設為GENERIC_READ|GENERIC_WRITE。第三個參數值必須為0,表示不將串口與其他應用程式共用。第四個參數指向一個Security
Attribute結構,通常設為NULL。第五個參數指定如何開啟檔案,在開啟裝置(串口是一種裝置)時,此參數必須指定為OPEN_EXISTING。第六個參數指定檔案屬性及相關標 志,但是對於串列口,唯一有意義的設定是FILE_FLAG_OVERLAPPED或0;最後一個參數必須為NULL。若該函數開啟串口成功,則返回建立的控制代碼,該控制代碼供隨後對串列口的設定、讀寫等操作用;否則返回INVALID_HANDLE_VALUE。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 1.2 設定串口
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 串口開啟後,即可進行一系列初始化設定。最基本的初始化設定將通過GetCommState和SetCommState函數來實現。先調用GetCommState函數擷取當前串口配置填充裝置控制塊(DCB),然後將DCB結構中幾個重要參數如傳輸速率、資料位元、停止位、校正位改成符合實際設計要求的值,最後用SetCommState將剛剛所做的改動重新設定串口。串口I/O緩衝區的大小用SetupComm函數設定。通訊速率越高,緩衝區應設定得越大,但不能超出裝置驅動程式所能
處理的範圍。另一個很重要的設定是串口逾時設定。通訊中因未知原因將出現不可預測的事件,譬如:接收資料過程中突然被中斷,或者發送資料突然停止等。如果不認真對待,這些情況可能會引起I/O線程掛起或者線程被無限阻塞。Windows對於這類問題提供了安全措施,它可通過逾時設定來決定通訊是否異常並作相應處理,因此逾時設定在串列通訊中顯得尤為重要。逾時設定過程分為兩步,首先設定Commtimeouts結構中的五個成員,然後調用SetCommTimeouts函數設定逾時值。Commtimeouts結構的五個成員分別是:讀串口間隔逾時、讀串口總逾時乘數、讀串口總逾時常數(ms)、寫串口總逾時乘數、寫串口總逾時常數(ms)。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 1.3 讀寫串口
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 設定工作完成後,即可用ReadFile和WriteFile對串口進行讀寫操作。在調用讀寫操作函數之前,應先用ClearCommError函數清除錯誤標誌和擷取當前串口狀態。讀寫操作分為同步和重疊I/O(非同步)。同步執行時,函數直到操作完成後才返回;重疊I/O操作時,即使操作尚未完成,調用的函數也會立即返回,費時的I/O操作在後台進行。可見,同步操作線程被阻塞,效率低,只能用在對通訊要求比較低的場合,我們一般用到的都是效率較高的重疊I/O操作。前面提到的CreateFile函數第六個參數設定為FILE_FLAG_OVERLAPPED即可指定Read
File和WriteFile函數為重疊I/O執行。使用重疊I/O還需為讀寫函數指定一個Overlapped結構,該結構有五個資料成員,對串口通訊來說,其中的Offset和hEvent成員是很重要的。Offset指示檔案指標位移量,在重疊I/O操作時系統不能自動維護檔案指標,所以要靠Offset在程式中手動調整檔案指標。而hEvent標誌讀寫操作是否完成。若操作完成,
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 則將hEvent置為訊號態;否則即置為非訊號態。最後要說明的是,在重疊I/O操作時,讀寫函數傳回值是FALSE並不能說明操作失敗,應該調用GetLastError函數分析返回結果。如果
此時GetLastError函數傳回值是ERROR_IO_PENDING,則說明操作未完成(並不是操作失敗)。我們將用等待函數來等待操作的完成。典型的兩個等待函數有WaitForSingleObject和GetOverlappedResult。函數的相同之處為都是等待讀寫操作指定的Overlapped結構hEvent成員置為訊號態(即代表操作完成);不同之處是WaitForSingleObject是可設定逾時,但無法得到重疊I/O操作的結果,GetOverlappedResult用來得到重疊I/O操作的結果,但無法設定逾時。因此,我們經常兩者結合起來使用,在用WaitForSingleObject等待操作結束後,用GetOverlap-pedResult得到操作結果。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 1.4 關閉串口
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 串列口是非共用資源,某應用程式開啟串列口後,即獨佔該資源,使其它應用程式無法再訪問,直到該應用程式釋放串口。所以對串口操作完成後,一定要關閉串口。關閉串口使用CloseHandle函數,該函數唯一參數即為用CreateFile開啟串口時所建立的控制代碼。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 2 多線程API函數
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> Windows是多線程(multi-threaded)、搶先多任務的(preemptible
)。Windows中,一個可執行程式的運行時刻執行個體稱為進程(process)。一個進程可以有多個線程(thread),Windows是按照線程分配CPU時間片的,而分配的機制就是搶先多任務方式。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 對於讀寫串口這種耗時的工作,使用多線程技術,建立輔助線程來管理串口是一個常用的方案。這樣在進行串口讀寫的同時,能對讀入的資料進行處理。如果使用單線程,就需要等待串口讀寫操作完成,整個進程都被阻塞。而使用多線程就可以避免這種情況。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 多線程也會帶來一些新的問題,其中的一個問題就是線程的同步,如果同步問題解決不好,程式的穩定性會受到很大的影響。通常用到的幾種線程同步的方法有互斥體對象(Mutex)、利用訊號(Semaphore)、利用事件對象(Event)和設定臨界區(Critical
Section)。筆者在實際應用中使用的是事件對象結合Windows訊息機制使線程同步,收到了很好的效果。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 建立線程函數為CreateThread,用SuspendThread和ResumeThread函數來掛起和喚醒線程。建立事件函數為CreateEvent,用SetEvent和ResetEvent函數來將事件置為訊號態和非訊號態,以此來同步線程。串口通訊的輔助線程管理經常還要用到SetCommMask和WaitCommEvent函數。SetCommMask用來指定一系
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 列事件監視串口,比如監視串口是否有資料收到;WaitCommEvent則用來等待指定的事件發生。筆者在實際應用中,就是在輔助線程中用SetCommMask指定串口監視接收資料事件,然後用WaitCommEvent等到串口真的接收到資料時,用PostMessage發出訊息通知主線程,由主線程處理接收到的資料。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 3 實現訊息機制的API函數
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> Windows是一個訊息驅動作業系統,簡單的說訊息就是指通過輸入裝置向程式發出的指令以要求"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 其執行某個操作。具體的操作由訊息處理函數實現。使用者可以自訂訊息線上程之間傳遞。把WM_USER(它的值等於0×0400)當作基數,然後順序地去加序號,譬如:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> WM_COMMNOTIFY equ WM_USER+100h(小於WM_USER的值是Windows系統的保留值,大於該值留給使用者來使用)。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 前一節已經提到在串口通訊編程中對訊息機制的利用,這裡將繼續說明怎樣實現訊息機制。由於筆者使用的編程工具是Borland公司的C++
Builder(BCB),因此對於訊息機制的實現有其特殊之處。在BCB中實現訊息的方法有三種:使用訊息映射Me"Times New Roman";mso-bidi-font-family:宋體'>ssage宋體;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
Map重載TObject的Dispatch虛成員函數;重載TControl的WndProc方法;重載Ap"Times New Roman"'>pli
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> cation的OnMessage方法。其中以第三種方法最快,因為一般情況下,BCB會為每個程式自動產生一個TApplication類的執行個體,訊息到達BCB程式時,最先得到它們的就是TApplication對象。經由TApplication之後,才傳遞給Form的。前兩種方法都是重載TForm的方法,顯然比直接重載Application的OnMessage方法要晚一些收到訊息。我們要做的只是定義好自己的訊息處理函數:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> void __fastcall TForm1MyOnMessaget"Times New Roman"'>agMSG &Msg
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>bool &Handled宋體;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 宋體;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> TMessage Message宋體;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> switch(Msg.message)
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 宋體;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> case WM_COMMNOTIFY宋體;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> Message.Msg=Msg.message;
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> Message.WParam=Msg.wParam;
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> Message.LParam=Msg.lParam;
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> //此處添加處理該訊息的代碼
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> Handled=true;
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> break;
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 宋體;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 宋體;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 然後在視窗建立時用自訂的訊息處理函數重載Application的OnMessage方法:
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> void __fastcall TForm1FormCreate(TObject"Times New Roman"'> 宋體">Sender)mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 宋體;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> Application->OnMessage
= MyOnMessage;
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 宋體;mso-ascii-font-family:"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 這樣就可以在程式中收到自訂的訊息並作出相應處理。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>值得注意的是,使用Application->OnMessage並不能捕獲非隊列訊息,它無法捕獲使用SendMessage直接發送給視窗的訊息,這是因為其不通過訊息佇列。但可以使用另一個發送訊息的API函數——PostMessage,該函數發出的訊息是隊列訊息。
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 4 結束語
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 利用多線程、訊息機制和重疊I/O的API函數進行串口編程的方法,可實現串口通訊的即時高效,為開發Windows系統下串口驅動程式提供了有益參考。&
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 參考文獻
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 1 範逸之.江文賢.陳立元.C++
Builder與RS-232串列通訊控制[M].清華大學出版社,2002
"Times New Roman";mso-hansi-font-family:"Times New Roman"'> 2 黃軍等.Delphi串口通訊編程[M].人民郵電出版社,2001
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>3 張志明.李蓉豔.王磊.WIN32環境下串列通訊編程技術研究[J].《電腦應用研究》
2002;9
"Times New Roman";mso-hansi-font-family:"Times New Roman"'>