本文介紹了在Microsoft Visual C++ 6.0環境下對RS-232-C串行端口進行編程,以及對后臺監控程序所普遍涉及到的無阻塞后臺運行、數據的實時接收和處理等問題的解決方法。
一、 引言 在實驗室和工業應用中,受信道成本限制,串口常常作為計算機與外部串行設備之間的首選數據傳輸通道,而且由于串行通信方便易行,許多設備和計算機都可以通過串口對外設進行控制、檢測,串口通訊日益成為計算機和外設進行通訊、獲取由外設采集到的監測數據的一個非常重要的手段。本文所描述的程序實例運行于Windows 9x操作系統下,可后臺運行、實時接收、處理從端口傳來的數據,并能通過向串口發送命令來控制外設的動作。為了避免在實時監控數據時引發程序阻塞,在本程序中引入了線程和端口中斷響應等技術。
二、 程序設計思路 由于本程序要對串行端口進行實時監控,這就要求它是一個后臺程序,在監控的同時可以在前臺進行其他一些于之無關的操作。因而在實現時即要避免無時無刻都在反復讀端口的效率低下的輪詢方式,又不能因為來不及處理而將突然到達的監測數據丟失。只有采取端口中斷的異步方式才能實現高效、安全的監控過程,只要一有數據到達端口,馬上拋出中斷請求,中斷處理函數便會及時啟動以處理到來的數據,從而避免了輪詢間隙丟時數據的可能。而在大部分無數據到達的時間內不會有中斷拋出,中斷處理函數也不會執行,即僅僅在有數據到達的一瞬間進行工作,此效率不可謂不高。
綜上所述,要實現上述要求,就要用到下列技術來解決所遇到的關鍵性問題:一是采用多線程來避免在進行文件操作等耗時操作時會引發阻塞現象的發生。同時為了防止多個線程同時對同一個變量進行操作引起時序上的差錯,為了保持線程的同步,還采取了臨界區加解鎖的技術;二是對端口的數據讀取方式采取中斷響應模式,具體原因前面以講的很清楚,在此不再贅述;三是使用了定時器,以滿足實時監控類程序的實時顯示功能,以便及時的將所接收到的動態數據及時的反映到屏
幕上。
三、 RS-232-C串行端口監控軟件的程序實現
(一) 界面風格
由于是實時監控軟件,那就既要監測從外設傳來的實時數據,又要通過串口向外設發送一些具體的指令以控制外設完成預先設定的動作。為了方便向串口發送命令可以在工具條上再加一個類似于"Internet Explorer 瀏覽器"風格的對話條,可以在初建工程時指定"Internet Explorer ReBars"風格,也可以通過添加Microsoft Visual C++ 6.0自帶的"Dialog Bar"組件來實現。而要及時將從外部讀取的數據顯示給管理人員,并且留有相當記錄以備查閱,可以選擇列表視圖來實現。
(二) 串口的參數設置及打開
對RS-232-C串行端口進行參數配置是使用串口進行通訊的必要條件。而且由于場合不同、用途、功能的不同對串口也采取不同的配置方式,為了使本程序更靈活,適應面更廣,采取將所有的可能參數都預先設置在幾個組合框中,可以在程序運行后隨時更改設置。自定義一個設置串口參數的數據結構:
| typedef struct tagCOM_CONFIG { int nPort; file://端口號,從COM1到COM4 int nBaud; file://波特率,從1200bps到57600bps(對應的宏為CBR_1200到CBR_57600) int nData; file://數據位個數,7位或是8位 int nStop; file://停止位個數,可以是1位、1.5位、2位。 int nParity;//采取的校驗方式,有無校驗(NOPARITY)、 file://奇校驗(ODDPARITY)和偶校驗(EVENPARITY)等。 }COM_CONFIG; |
當選擇好適當的參數后就可以根據設置好的端口配置情況打開通訊端口了。與以往DOS下串行通信程序不同的是,Windows操作平臺下不提倡應用程序直接控制硬件(包括端口),也不讓使用中斷(除非打入到Ring0系統級),而是通過Windows操作系統提供的設備驅動程序來進行數據傳遞。在Windows操作系統下串行口和其他通訊端口一樣是作為文件來進行處理的,而不是直接對端口進行操作,對于串行通信,Win 32 提供了相應的文件I/O函數與通信函數,通過了解這些函數的使用,可以編制出符合不同需要的通信程序。與通信設備相關的結構有COMMCONFIG ,COMMPROP,COMMTIMEOUTS,COMSTAT,DCB,MODEMDEVCAPS,MODEMSETTINGS共7個,與通信有關的Windows API函數共有26個,具體說明可參考MSDN幫助文件。下面是打開串口的部分關鍵代碼:
| //以創建文件的形式打開文件,并將返回的端口句柄保存于句柄idComDev之中。 idComDev =CreateFile( g_szCom_Port[g_com_config.nPort], GENERIC_READ GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); …… file://cfg為COMMCONFIG結構的實例對象,獲取當前通訊口的狀態。 cfg.dcb.DCBlength = sizeof( DCB ) ; GetCommState( idComDev, &(cfg.dcb) ) ; file://設置發送、接收緩存大小 SetupComm( idComDev, 4096, 4096 ) ; // PurgeComm()是一個清除函數,它可以用來中止任何未決的后臺讀或寫,并且可以沖掉I/O file://緩沖區.其中:PURGE_TXABORT 用于中止后臺寫操作;PRUGE_RXABORT用于中止后臺 file://讀操作 ;PRUGE_TXCLEAR用于清除發送緩沖區;PRUGE_RXCLEAR用于清除接收緩沖區 PurgeComm(idComDev,PURGE_TXABORT PURGE_RXABORT PURGE_TXCLEAR PURGE_RXCLEAR); file://根據設置的參數填充DCB結構對象dcb的各個數據成員變量 dcb.DCBlength = sizeof( DCB ) ; GetCommState( idComDev, &dcb ) ; file://設置端口通訊參數 dcb.BaudRate =g_Com_Baud[g_com_config.nBaud]; dcb.ByteSize =g_Com_ByteSize[g_com_config.nData]; dcb.Parity =g_Com_Parity[g_com_config.nParity] ; dcb.StopBits =g_Com_StopBits[g_com_config.nStop]; file://硬件流控制 dcb.fDtrControl = DTR_CONTROL_DISABLE ; dcb.fOutxCtsFlow = FALSE ; dcb.fRtsControl = RTS_CONTROL_DISABLE ; file://軟件流控制 dcb.fInX = dcb.fOutX = FALSE ; dcb.XonChar = (char)0xFF ; dcb.XoffChar = (char)0XFF ; dcb.XonLim = 100 ; dcb.XoffLim = 100 ; dcb.EvtChar=0x0d; dcb.fBinary = TRUE ; dcb.fParity = TRUE ;
file://超時控制的設置。超時有兩種:區間超時:(僅對從端口中讀取數據有用)它指定在讀取兩個字符之間要經歷的時間;總超時: 當讀或寫特定的字節數需要的總時間超過某一閾值時,超時觸發。計算超時可以根據公式: file://ReadTotalTimeout = (ReadTotalTimeoutMultiplier * bytes_to_read)+ // ReadToTaltimeoutConstant file://WriteTotalTimeout = (WriteTotalTimeoutMuliplier * bytes_to_write)+ // WritetoTotalTimeoutConstant file://如果在設置超時時參數為0則為無限等待,即無超時。 CommTimeOuts.ReadIntervalTimeout =MAXDWORD; CommTimeOuts.ReadTotalTimeoutMultiplier =0; CommTimeOuts.ReadTotalTimeoutConstant = 0 ; CommTimeOuts.WriteTotalTimeoutMultiplier =2*9600/dcb.BaudRate ; CommTimeOuts.WriteTotalTimeoutConstant = 25 ; SetCommTimeouts(idComDev , &CommTimeOuts ) ; file://根據設置好的dcb結構設置好通訊口的狀態,并開啟用于偵聽端口,監視從外設傳來的數 file://據的線程COMReadThreadProc。 if (SetCommState( idComDev, &dcb )) { m_bComPortOpen=TRUE; g_hCom=idComDev; AfxBeginThread(COMReadThreadProc,NULL,THREAD_PRIORITY_NORMAL); return; } |
(三) 偵聽監視線程
當成功的打開端口之后通過執行線程開啟函數AfxBeginThread(COMReadThreadProc,NULL,THREAD_PRIORITY_NORMAL);開啟了一個用于偵聽端口的工作線程COMReadThreadProc。其具體處理過程
如下: