李 林
(中國電子科技集團公司 第二十研究所,陜西 西安 710068)
在導航設備及測試儀器中,顯控計算機(一般都基于Windows平臺)經常需要與下層DSP設備模塊通過串口進行通信。串口通信方便易行,應用廣泛。一般情況下,顯控計算機通過RS232總線進行通信.RS232的通信方式是雙工的。每次通信都是由顯控計算機通過串口向DSP設備模塊命令,DSP設備模塊在接收到正確的命令后作出應答.在Win32下,可以使用兩種編程方式實現串口通信,其一是使用ActiveX控件,這種方法程序簡單,但欠靈活.其二是調用Windows的API函數,這種方法可以清楚地掌握串口通信的機制,并且自由靈活。本文使用API串口通信。串口的操作可以有兩種操作方式:同步操作方式和重疊操作方式(又稱為異步操作方式)。同步操作時,API函數會阻塞直到操作完成以后才能返回(在多線程方式中,雖然不會阻塞主線程,但是仍然會阻塞監聽線程);而重疊操作方式,API函數會立即返回,操作在后臺進行,避免線程的阻塞.無論哪種操作方式,一般都通過四個步驟來完成:(1)打開串口;(2)配置串口;(3)讀寫串口;(4)關閉串口。
Win32系統把文件的概念進行了擴展.無論是文件、通信設備、命名管道、郵件槽、磁盤、還是控制臺,都是用API函數CreateFile來打開或創建的。該函數的原型為:


在打開串口設備句柄后,常需要對串口進行一些初始化配置工作.這需要通過一個DCB結構來進行。DCB結構包含了諸如波特率、數據位數、奇偶校驗和停止位數等信息。在查詢或配置串口的屬性時,都要用DCB結構來作為緩沖區。一般用CreateFile打開串口后,可以調用GetCommState函數來獲取串口的初始配置.要修改串口的配置,應該先修改DCB結構,然后再調用SetCommState函數設置串口。DCB結構包含了串口的各項參數設置,下面僅介紹幾個該結構常用的變量。

除了在BCD中的設置外,程序一般還需要設置I/O緩沖區的大小和超時.Windows用I/O緩沖區來暫存串口輸入和輸出的數據。如果通信的速率較高,則應該設置較大的緩沖區。調用SetupComm函數可以設置串行口的輸入和輸出緩沖區的大小。


在用ReadFile和WriteFile讀寫串行口時,需要考慮超時問題.超時的作用是在指定的時間內沒有讀入或發送指定數量的字符,ReadFile或WriteFile的操作仍然會結束。要查詢當前的超時設置應調用GetCommTimeouts函數,該函數會填充一個COMMTIMEOUTS結構,調用SetCommTimeouts可以用某一個COMMTIMEOUTS結構的內容來設置超時。讀寫串口的超時有兩種:間隔超時和總超時。間隔超時是指在接收時兩個字符之間的最大時延,總超時是指讀寫操作總共花費的最大時間,寫操作只支持總超時,而讀操作兩種超時均支持。用COMMTIMEOUTS結構可以規定讀寫操作的超時,COMMTIMEOUTS結構的定義為:

COMMTIMEOUTS結構的成員都以毫秒為單位,總超時的計算公式是:
總超時=時間系數×要求讀/寫的字符數+時間常量
例如要讀入10個字符,那么讀操作的總超時的計算公式為:
讀總超時=ReadTotalTimeoutMultiplier×10+ReadTotalTimeoutConstant
可以看出:間隔超時和總超時的設置是不相關的,這可以方便通信程序靈活地設置各種超時。如果所有寫超時參數均為0,那么就不使用寫超時。如果ReadIntervalTimeout為0,那么就不使用讀間隔超時。如果 ReadTotalTimeoutMultiplier和 ReadTotalTimeoutConstant都為 0,則不使用讀總超時。如果讀間隔超時被設置成MAXDWORD并且讀時間系數和讀時間常量都為0,那么在讀一次輸入緩沖區的內容后讀操作就立即返回,而不管是否讀入了要求的字符。在用重疊方式讀寫串口時,雖然ReadFile和WriteFile在完成操作以前就可能返回,但超時仍然是起作用的。在這種情況下,超時規定的是操作的完成時間,而不是ReadFile和WriteFile的返回時間。
配置串口的示例:


使用ReadFile和WriteFile讀寫串口,下面是兩個函數的聲明:BOOL ReadFile(HANDLE hFile,//串口的句柄

在用ReadFile和WriteFile讀寫串口時,既可以同步執行,也可以重疊執行。在同步執行時,函數直到操作完成后才返回。這意味著同步執行時線程會被阻塞,從而導致效率下降。在重疊執行時,即使操作還未完成,這兩個函數也會立即返回,費時的I/O操作在后臺進行。ReadFile和WriteFile函數是同步還是異步由CreateFile函數決定,如果在調用CreateFile創建句柄時指定FILE_FLAG_OVERLAPPED標志,那么調用ReadFile和WriteFile對該句柄進行的操作就應該是重疊的;如果未指定重疊標志,則讀寫操作應該是同步的。ReadFile和WriteFile函數的同步或者異步應該和CreateFile函數相一致。ReadFile函數只要在串口輸入緩沖區中讀入指定數量的字符,就算完成操作。而WriteFile函數不但要把指定數量的字符拷入到輸出緩沖區,而且要等這些字符從串行口送出去后才算完成操作。如果操作成功,這兩個函數都返回TRUE。需要注意的是,當ReadFile和WriteFile返回FALSE時,不一定就是操作失敗,線程應該調用GetLastError函數分析返回的結果。例如,在重疊操作時如果操作還未完成函數就返回,那么函數就返回FALSE,而且GetLastError函數返回ERROR_IO_PENDING,這說明重疊操作還未完成。而較多使用異步重疊操作方式。有兩種方法可以等待操作完成:一種方法是用象WaitForSingleObject這樣的等待函數來等待OVERLAPPED結構的hEvent成員;另一種方法是調用GetOverlappedResult函數等待,后面將演示說明。下面先簡單介紹一下OVERLAPPED結構和GetOverlappedResult函數:OVERLAPPED結構OVERLAPPED結構包含了重疊I/O的一些信息,定義如下:

在使用ReadFile和WriteFile重疊操作時,線程需要創建OVERLAPPED結構以供這兩個函數使用。線程通過OVERLAPPED結構獲得當前的操作狀態,該結構最重要的成員是hEvent.hEvent是讀寫事件。當串口使用異步通訊時,函數返回時操作可能還沒有完成,程序可以通過檢查該事件得知是否讀寫完畢。當調用ReadFile,WriteFile函數的時候,該成員會自動被置為無信號狀態;當重疊操作完成后,該成員變量會自動被置為有信號狀態。


該函數返回重疊操作的結果,用來判斷異步操作是否完成,它是通過判斷OVERLAPPED結構中的hEvent是否被置位來實現的。
異步讀串口的示例:

該函數獲得通信錯誤并報告串口的當前狀態,同時,該函數清除串口的錯誤標志以便繼續輸入、輸出操作。參數lpStat指向一個COMSTAT結構,該結構返回串口狀態信息。COMSTAT結構COMSTAT結構包含串口的信息,結構定義如下:

這里只用到了cbInQue成員變量,該成員變量的值代表輸入緩沖區的字節數。最后用PurgeComm函數清空串口的輸入輸出緩沖區。
異步寫串口的示例:

利用API函數關閉串口非常簡單,只需使用CreateFile函數返回的句柄作為參數調用CloseHandle即可:

在具體開發過程中要用到多線程方式,讓接收模塊用一個守護線程來把串口實時的監控起來,當有數據收到時候就告知其他應用線程,這時還要用到事件類來維護應用線程和守護線程之間的同步通信方式。還有一個問題就是接收過程中時序不確定性,可能是由于對串口硬件本身通信機制沒有徹底把握清楚,所以在接收函數中需要加適當的延時,這個延時的大小就需要建立一個輔助文件來打印下所接受的數據,進而隨著時延變化觀察分析所接收的數據是否符合預期后,確定相應時延。這些是應用串口通信過程中所得到的體會。
[1]李現勇.Visual C++串口通信技術與工程實踐[M].北京:人民郵電出版社,2009.
[2]張筠莉,劉書智.Visual C++實踐與提高:串口應用與工程應用篇[M].北京:中國鐵道出版社,2011.
[3]周韌研.Visual C++串口通信開發入門與編程實踐[M].北京:電子工業出版社,2006.
[4]劉書智.Visual C++串口通信與工程應用實踐[M].北京:中國鐵道出版社,2007.
[5]譚思亮,鄒超群.Visual C++串口通信工程開發實例導航[M].北京:人民郵電出版社,2009.