无码人妻精品一区二区三区9厂-国产精品人人做人人爽人人添-在线永久免费观看黄网站-国产精品久久久久一区二区三区

Windows 95下串行通信編程技術及其實現

郭峰林 朱才連

  摘 要 本文首先簡單討論MSDOS、16位Windows和Windows95下的通信編程差別,然后著重講述32位Windows95 環境下的通信編程技術,最后給出利用該技術實現串行通信的實例。
  關鍵詞 API函數,串行通信,中斷,查詢,線程,同步,異步,阻塞

TECHNIQUE OF SERIAL COMMUNICATION PROGRAMMING
AND ITS REALIZATION IN WINDOWS95

Guo Fenglin Zhu Cailian
Institute of Geodesy & Geophysics, Chinese Academy of Sciences, Hubei.Wuhan 430077

  Abstract This paper discusses the difference of communication programming in the operating system of MSDOS,16 bit Windows and Windows95.Then tells of some problems about serial communication programming technique in 32 bit OS of windows95. Finally, an example of the application of this technique in windows95is provided.
  Keywords API function, Serial communication, Interrupt, Poll, Thread, Synchronization, Asynchronization

1 前言
  Windows95以其形象的圖形界面設計、操作簡單、功能強大、可靠性高等優點,贏得了越來越多的用戶,開發Windows95應用程序已經成為當今的主流。在諸多的應用開發中,與外部硬件設備通信是常見的應用,而串行通信以其簡單的硬件連接方式常常成為應用開發者的首選。然而串行通信編程從MSDOS、Windows3.1到Windows95各不相同,雖然在功能上越來越強,但是編程的復雜度也相應增大。
  筆者最近在Windows95環境下開發一套“公安110智能報警系統”,該系統需要對報警電話進行實時監控,以便能實時地進行接警和處警。報警電話的監控是通過檢測從電話交換機中饋送的RS232標準的串行通信信號,其中串行口通信采用3線方式。該系統采用Windows95下的Visual C++ 5.0編寫,由于有關Windows95的串行口通信編程方面的資料少,串行通信編程的實例也不多見,筆者在成功開發“公安110智能報警系統”的基礎上,取得了一些經驗,現在將有關串行口通信方面的一些關鍵技術寫出來,供廣大的編程者借鑒、參考。

2 下串行通信編程特征
  MSDOS下的串行通信編程較簡單,通信編程可以直接對串口的物理地址進行編程操作同時配合BIOS調用,即可實現串行口數據讀寫。
  在Windows下,串行口作為系統資源,由設備驅動程序統一管理,用戶不能象在MSDOS下一樣直接對串行口硬件端口進行編程。16位的Windows3.1操作系統提供了專門的串行通信的API函數:OpenComm()、CloseComm()、ReadComm()、 WriteComm()等,通過這些專用API(Application Programming Interfaces)函數來設置和讀、寫串行口。而Windows95將串行口和其它通信設備如Modern、傳真機等統一視作文件,對串行口的打開、關閉、讀寫等操作與操作普通文件的API函數相同,如CreateFile()、CloseHandel()、ReadFile()、WriteFile(),正是由于這些函數的“多態性”, 同時還由于需要結合Windows95的線程編程、事件驅動等新技術,因而使得Windows95下的串行口通信編程比較復雜。

3 Windows95下串行通信API函數
  在Windows95中將串行口與文件的統一了起來,對它們的打開、讀、寫、關閉等操作都使用相同的API函數,但是它們之間又有差別,譬如串行口不能象文件一樣可以被刪除,這些差別體現在API函數中部分參數的設置上。
  弄清串行通信API函數的用法是掌握串行通信編程技術的關鍵。下面介紹幾個與串行通信編程密切相關的API函數,著重說明這些API函數在進行串行通信時參數設置需要注意的地方。其它沒有提及的函數及參數可以參考Windows95 API函數手冊。
3.1 打開串行口API函數
  Windows95通信會話以調用CreateFile()函數打開串行口開始。調用CreateFile()打開串口成功,返回一個操作句柄。該句柄供隨后對串行口的設置、讀寫等操作用。
  CreateFile()函數原型:
 HANDLE CreateFile(LPCTSTR szDevice, DWORD dwAccess,
DWORD dwShareMode, LPSECURITYATTRIBUTES lpSA,
DWORD dwCreate, DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile );
  調用此函數要注意這幾個參數的設置:dwShareMode指定該端口的共享屬性。該參數是為文件共享提供的,串行口不能作為共享設備。故參數值必須為0,這是文件與通信設備之間的主要差異之一;dwCreate必須為OPENEXISTING。因為CreateFile()只能打開存在的端口,而不能象創建新文件一樣創建物理上不存在的新串口;dwFlagsAndAttributes描述了該端口的各種屬性。對于文件來說,具有多種屬性(只讀、隱藏、系統)是可能的,但是對于串行口,唯一有意義的設置是FILEFLAGOVERLAPPED;參數hTemplateFile必須為NULL。
  返回值:若成功,返回創建的句柄;否則返回,INVALIDHANDLEVALUE.
  舉例:打開串行口1
 HANDLE hComm;                           //定義句柄變量
 hComm = CreateFile( "COM1",
 GENERICREAD|GENERICWRITE,NULL,NULL,
 OPENEXISTING,FILEFLAGOVERLAPPED,NULL);
 if (hComm == INVALIDHANDLEVALUE) {…….
 // 打開串口錯誤的處理}

3.2 配置串行口API函數
  串行口打開成功,接下來可以配置串行口通信參數如波特率、數據位數、停止位、校驗位等。修改這些參數時要和設備控制塊DCB(Device Control Block)打交道,DCB有近30個數據成員,是一個很復雜的數據結構,全部弄清楚它們的含義相當費時。而對于采用3線方式的串行通信來說,DCB結構中絕大多數參數可以不予考慮,因為只要設置好波特率、數據位、停止位、校驗位等幾個關鍵參數就行。這里介紹一種簡捷的方法可以做到不了解DCB的詳細內容也可以設置好串行通信參數。
  通過下面的程序來說明串行通信參數的設置方法。例程中利用BuildCommDCB函數來設置DCB,然后用函數SetCommState()配置串行通信口。
DCB dcb ;                              //定義設備控制塊
GetCommState(hComm,&dcb);                 //取出系統缺省設備控制塊
BuildCommDCB("COM2:9600,N,8,1",&dcb);                //設置DCB主要參數
SetCommState(hComm,&dcb);
3.3 超時設置API函數
  編寫通信應用程序的一個很關鍵的問題就是如何處理通信中的不可預測的事件。譬如接收數據過程中突然被中斷,或者發送數據突然停止等等。如果不認真對待,這些情況可能會引起I/O線程掛起或者線程被無限阻塞。Windows95對于這類問題提供了安全措施,它讓你通過超時設置來決定通信是否異常并作相應處理。因此超時設置在串行通信中顯得尤為重要。
  超時設置過程分為兩步,首先設置COMMTIMEOUTS結構中的五個變量,然后調用SetCommTimeouts()函數設置超時值。COMMTIMEOUTS結構的定義如下:
 typedef struct  COMMTIMEOUTS {
DWORD ReadIntervalTimeout;                      //讀端口間隔超時
DWORD ReadTotalTimeoutMultiplier;                //讀端口總超時乘數
DWORD ReadTotalTimeoutConstant;                //讀端口總超時常數(ms)
DWORD WriteTotalTimeoutMultiplier;                //寫端口總超時乘數
DWORD WriteTotalTimeoutConstant;               //寫端口總超時常數(ms)
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
3.4 讀串口API函數
  串行口打開后,可以對它進行讀寫操作。讀串行口的函數原型:
 BOOL ReadFile (HANDLE hFile, LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped data);
  其中,第一個參數hFile是由CreateFile()返回的句柄。參數lpBuffer是讀取的數據緩沖區指針,要注意給該數據緩沖區分配足夠的空間;參數nNumberOfBytesToRead是要讀取的字節數;參數lpNumberOfBytesRead是實際讀取的字節數;最后一個參數lpOverlapped 是指向一個可重疊I/O(異步)的數據結構指針。如果lpOverlapped設置為NULL,則ReadFile()工作在同步方式;如果lpOverlapped指向一個重疊結構,則工作在異步方式。
3.5 寫串口API函數
 BOOL WriteFile (HANDLE hFile,             // 由CreateFile()返回的句柄
              LPCVOID lpBuffer,            // 寫緩沖區指針
           DWORD nNumberOfBytesToWrite,         // 要寫的字節數
           LPDWORD lpNumberOfBytesWritten,       // 實際寫的字節數
           LPOVERLAPPED lpOverlapped  // 指向一個可重疊I/O的數據結構);
            WriteFile()函數的工作方式選擇與ReadFile()的相同,在此不重復。
3.6 關閉串口API函數
  串行口是非共享資源,某應用程序打開串行口后,即獨占該資源,使其它應用程序無法再訪問,直到該應用程序釋放串口。所以打開串口后,一定要關閉串口。關閉串口函數較簡單。函數原型:BOOL CloseHandle( HANDLE hObject );其中hObject參數為CreateFile()返回的端口句柄。返回值非0,則調用成功。

4 Windows95的串行通信工作方式
  串行通信會話以調用CreateFile()函數打開串行口開始,接著設置串行口波特率、數據位、校驗位、停止位等參數以及超時參數,最后選擇一種工作方式讀、寫串行口。在Windows95中,串行通信有兩種工作方式可供選擇:查詢方式和事件驅動方式。這兩種工作方式各有優缺點,用戶可以根據應用程序的實際需要選擇其中的一種工作方式,下面對這兩種工作方式分別介紹。
4.1 查詢方式
  對于從串口讀取數據來說,查詢是最為直接、易于理解的技術。但是查詢會占用大量的CPU時間,效率較低。利用查詢方式讀取串口數據時通常要建立一個線程,建立線程使用CreateThread()函數。循環查詢在線程里進行。舉例:(假設端口已經打開)
 DWORD ReadThread(LPDWORD lpdwParam)
{ BYTE Buff[100];                          //讀數據緩沖區
DWORD nBytesRead;                         //實際讀取的字節數
COMMTIMEOUTS Timeouts;                          //超時設置
Memset(&Timeouts,0,sizeof(COMMTIMEOUTS));
Timeouts.ReadIntervalTimeout = MAXDWORD;
SetCommTimeouts(hComm,&Timeouts);
While(bReading){
if(!ReadFile(hComm,Buff,100,&nBytesRead,NULL))
{………                             //讀取數據出錯處理}
else{………                          //正確讀取數據的處理}
}
PurgeComm(hComm,PURGERXCLEAR);
return 0;
}
  例程中,線程的退出由bReading標志控制,當bReading為TRUE時,循環串口;當breading為FALSE時,線程退出。
4.2 事件驅動方式
  事件驅動I/O方式是指線程通過監視通信資源中的一組事件來進行I/O操作,這種方式類似于MSDOS下的中斷工作方式,效率高。可被監視的事件列表如下:

事件掩碼 含義
EVBREAK 檢測到輸入終止
EVCTS CTS(清除發送)信號改變狀態
EVDSR DSR(數據設置就緒)信號改變狀態
EVERR 發生了線路狀態錯誤
EVRING 檢測到振鈴
EVRLSD RLSD信號改變狀態
EVRXCHAR 收到任何字符并放進輸入緩沖區
EVRXFLAG 收到事件字符,并放進輸入緩沖區
EVTXEMPTY 輸出緩沖區中最后一個字符發送出去
  實際編程中,對串行口的讀、寫操作需要建立兩個工作者線程。在讀或寫線程中可以通過SetCommMask()函數設置事件屏蔽來監視指定通信資源上的事件。指定一組事件后,線程可以使用WaitCommEvent()函數等待其中一個事件發生,在等待過程中它將花費極少的CPU時間。注意:WaitCommEvent()函數和讀寫操作函數一樣可以同步使用,也可以異步使用,這主要取決于在第三個參數中是否指定OVERLAPPED結構。如果指定為NULL,該函數就是同步的,必須等到SetCommMask()中指定的事件有一個發生時它才返回;如果指定了一個OVERLAPPED結構,該函數即工作在異步方式。通常將該函數工作在同步方式。
  下面的例程演示了利用事件驅動I/O方式從串行口讀取數據。
DWORD ReadThread(LPDWORD lpdwParam)
{ BYTE Buff[100];                          //讀數據緩沖區
DWORD nBytesRead, dwEvent, dwError;
COMMTIMEOUTS Timeouts;                          //超時設置
Memset(&Timeouts,0,sizeof(COMMTIMEOUTS));
Timeouts.ReadTotalTimeoutMultiplier=5;
Timeouts.ReadTotalTimeoutConstant=100;
SetCommTimeouts(hComm,&Timeouts);
SetCommMask(hComm,EVRXCHAR);    //設置EVRXCHAR掩碼,當任何字符到達時產生事件
While(bReading){
if(WaitCommEvent(hComm,&dwEvent,NULL))
{                              //接收緩沖區有字符到達
if(dwEvent & EVRXCHAR)
{                               //確認是EVRXCHAR事件
if(!ReadFile(hComm,Buff,1,&nBytesRead,NULL))
{……                                 //處理讀錯誤}
else{……                            //正確接收字符處理}
}else{……                         //非EVRXCHAR事件的處理}
}}
PurgeComm(hComm,PURGERXCLEAR);
return 0;}
  在上面的例程中,設置EVRXCHAR事件掩碼,則告訴Windows無論何時接收到一個字節,就產生一個事件。在WaitCommEvent()返回后,比較該函數返回的事件掩碼,如果是EVRXCHAR,則說明接收緩沖區中至少有一個字符處于等待狀態;否則,就是錯誤事件,需要進行錯誤處理。

5 效果
  作者在開發的“公安110智能報警系統”時利用事件驅動方式的串行通信編程技術處理多種系統設備間頻繁的數據交換任務,應用非常成功。系統可實時地監控從市話網上不斷傳來的報警電話。

作者簡介:郭峰林 碩士研究生。
朱才連 博士生導師,研究員。

作者單位:中國科學院測量與地球物理研究所 湖北.武昌(430077)

參考文獻
[1] Microsoft Corporation,著.Win32程序員參考大全(二). 欣 力,等譯.北京:清華大學出版社,1995,4
[2] Microsoft Corporation. Microsoft Developer Network
[3] Peter W.Gofton,著.精通串行通信.王仲文,等譯.北京:電子工業出版社,1995,2
[4] Charles A.Mirho, Andre Terrisse,著. Windows95通信編程.賀 軍,等譯.北京:清華大學出版社,1997,12
[5] Scott Stanfield, RalphArvesen,著. Visual C++4開發人員指南.北京:機械工業出版社1997,6