API串口通信
串行端口是系統資源的一部分,其本質是作為CPU和串行設備間的編碼轉換器。當數據從 CPU經過串行端口發送出去時,字節數據轉換為串行的位(Bit); 接收數據時,串行的位被轉換為字節數據。應用程序要使用串口進行通信,必須在使用之前向操作系統提出資源申請要求(即打開串口),通信完成后再釋放資源(即關閉串口)。
串行通信一般可以分為同步和異步兩種操作方式。所謂同步方式是指在串口的接收緩沖區中讀取規定數目的數據,直到規定數目的數據全部被讀出或設定的超時時間已到才返回。如果規定的待讀取數據量大且設定的超時時間也較長,而接收緩沖區較小,則可能引起線程阻塞。而異步方式是利用Windows的多線程結構,讓串口的讀寫操作在后臺進行,而應用程序的其他部分在前臺執行。
如果按驅動方式分,串口通信也可分為查詢和事件驅動兩種操作類型。所謂查詢方式是指一個進程中的某一線程定時查詢串口的接收緩沖區,如果緩沖區中有數據,就讀取數據;若緩沖區中沒有數據,該線程將繼續執行。查詢方式會占用大量的CPU時間,它實際上是同步方式的一種派生。查詢方式是一種最直接的讀串口方式,但定時查詢可能發生得過早或過晚,在數據變化較快的情況下,特別是主控計算機的串口通過擴展板擴展至多個時,容易發生數據的丟失。雖然指定時間隔越小,數據的實時性越高,但系統的資源也被占去越多。而事件驅動方式則是一種高效的串口讀寫方式,通過設置事件來通知系統工作,即當所希望的事件發生時,Windows發出該事件已發生的通知,系統才進行相應處理,避免了數據丟失,與DOS環境下的中斷方式很相似,實時性較高。Windows中提供文件讀寫的異步方式,主要是針對文件I/O相對較慢的特點而進行的改進,它利用了Windows的多線程結構。雖然在Windows中沒有實現任何對文件I/O的異步操作,但它卻能對串口進行異步操作,因此可以提高系統的整體性能。
通過Visual C++的標準通信函數_inp和_outp可直接通過串口輸入和輸出數據。一般來說,在Visual C++中開發串口通信程序主要有調用API函數和使用ActiveX控件技術兩種方式;静襟E為:打開串口設備,設置串口通信屬性,進行串口讀寫操作,關閉串口。下面將較為詳細地討論在VC中實現串口通信的上述兩種方法。
使用Win32的API
API是附帶在Windows內部的一個極其重要的組成部分。Windows的32位API主要是一系列復雜的函數和消息集合,可以看做是Windows系統為其下運行的各種開發系統提供的開放式通用功能增強接口。Windows環境下對串行端口進行操作,是把它作為文件來處理的,其中涉及到大量API函數,操作起來比較復雜,可以概括為以下的幾個操作步驟:
1. 打開串行通信設備。在VC中使用CreateFile函數打開串口,CreateFile將返回串口的句柄。該句柄將被用于后續的通信操作,并貫穿整個通信過程。當采用異步方式時,CreateFile函數的參數fdwAttrsAndFlags必須設為FILE_FLAG_ OVERLAPPED,如:
m_hComFile =CreateFile(“COM1”,
//HANDLE m_hComFile,全局變量
GENERIC_READ | GENERIC_WRITE,
// 允許讀寫操作
0, // 此項必須為0
NULL, // 安全設置
OPEN_EXISTING, //設置打開方式
FILE_FLAG_OVERLAPPED,
//使用異步通信標志
NULL );
2. 指定并初始化讀寫緩沖區。程序通過調用SetupComm函數來指定讀寫緩沖區的大小,并執行重新分配內部輸入和輸出緩沖的任務,用PurgeComm函數對輸入和輸出緩沖進行初始化,如:
SetCommMask(m_hComFile, EV_RXCHAR | EV_TXEMPTY ); //設置事件驅動的類型
SetupComm(m_hComFile, 1024,1024) ;
//設置輸入、輸出緩沖區的大小
PurgeComm(m_hComFile,
PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR );
//清空輸入、輸出緩沖區
3.設置串口屬性,配置DCB結構。當用CreateFile函數完成串口打開操作時,默認繼承設備控制塊(DCB結構)設置。通過調用GetCommState函數讀取當前串口設備控制塊DCB設置,修改后通過SetCommState函數將其寫入。也可以使用GetCommProperties獲取COMMPROP結構,其中記載了系統支持的各項設置,包括當前所使用的串行設備、數據傳輸波特率、輸入輸出緩沖區大小等。例如:
DCB dcb ;
//定義設備控制塊結構
GetCommState(m_hComFile, &dcb ) ;
//讀取串口原來的參數設置
dcb.BaudRate =9600;
dcb.ByteSize =8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT ;
dcb.fBinary = TRUE ;
dcb.fParity = FALSE;
SetCommState(m_hComFile, &dcb ) ;
//串口參數配置
4. 設置超時值。串口打開后,I/O操作的超時值采用默認值。超時值的設置與結構COMMTIMEOUTS及函數GetCommTimeouts和SetCommTimeouts有關。用GetCommTimeouts函數可以獲得當前I/O操作的超時值配置,而調用SetCommTimeouts函數可以修改此配置,如:
COMMTIMEOUTS timeouts ;
//定義超時結構,并填寫該結構
timeouts.ReadIntervalTimeout = 500;
timeouts.ReadTotalTimeoutMultiplier = 1;
timeouts.ReadTotalTimeoutConstant = 1000;
timeouts.WriteTotalTimeoutMultiplier = 1;
timeouts.WriteTotalTimeoutConstant = 1000; SetCommTimeouts(m_hComFile,&timeouts );
//設置讀寫操作所允許的超時
其中,區間超時(ReadIntervalTimeout)指的是在讀取兩個字符之間的時間間隔,它僅對從端口中讀取數據有效;總超時指的是當讀或寫特定的字節數需要的總時間超過某一閾值時,超時觸發。超時的計算公式如下:
ReadTotalTimeout= (ReadTotalTimeoutMultiplier * bytes_to_read)+ ReadToTaltimeoutConstant
WriteTotalTimeout = (WriteTotalTimeoutMuliplier * bytes_to_write) + WritetoTotalTimeoutConstant
5. 進行串行數據通信。調用函數ReadFile和WriteFile讀寫串口。若采用異步通信方式,兩函數中最后一個參數為指向OVERLAPPED結構的非空指針,在讀寫函數返回值為FALSE的情況下,調用GetLastError函數,返回值為ERROR_IO_PENDING,表明I/O操作懸掛,即操作轉入后臺繼續執行。此時,可以用WaitForSingleObject函數來等待結束信號并設置最長等待時間。下面的例子中,在主線程中發送命令,用一個輔助線程來監視串口,有數據到達時依靠事件驅動讀入數據并向主線程報告。
下面的代碼實現在主線程中準備并發送數據:
BOOL fWriteStat ;
char sndBuffer[count];
...... // sndBuffer[]中存放待發送的數據
OVERLAPPED overwrite;
//設置用于異步操作的OVERLAPPED結構
overwrite. hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
fWriteStat = WriteFile(m_hComFile, sndBuffer, dwBytesToWrite, &dwBytesWritten, &overwrite); //寫數據
if (!fWriteStat){
if (GetLastError() == ERROR_IO_PENDING) {……}
}
創建輔助線程:
hReadThread=CreateThread( (LPSECURITY_ATTRIBUTES) NULL,
//安全屬性
0, //初始化線程棧的大小,缺省為與主線程大小相同
(LPTHREAD_START_ROUTINE) CommReadProc, //線程函數
GetSafeHwnd(), //此處傳入主框架的句柄
0, (LPDWORD)lpThreadID );
在輔助線程中監視串口并接收數據:
UINT CommReadProc(HWND hSendWnd){
DWORD dwEvtMask=0 ;
SetCommMask(m_hComFile, EV_RXCHAR|EV_TXEMPTY );
//設置串口事件驅動
WaitCommEvent(m_hComFile, &dwEvtMask, os ); //等待串口事件
if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR){ //緩沖區中有數據到達
DWORD dwLength = ComStat.cbInQue ;
//輸入緩沖區數據長度
COMSTAT ComStat ;
ClearCommError(m_hComFile, &dwErrorFlags, &ComStat ) ;
OVERLAPPED overread;
overread. hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
if (dwLength > 0) {
BOOL fReadStat = ReadFile(m_hComFile, lpBuffer,dwLength, &dwBytesRead,&overread);
//讀數據
if (!fReadStat){
if (GetLastError() == ERROR_IO_PENDING){
……}
}
::PostMessage((HWND)hSendWnd,
WM_NOTIFYPROCESS,0,0);
//通知主線程,串口收到數據
}
6. 關閉串行端口。調用函數CloseHandle即可。
總體說來,調用API 函數實現串行通信,程序更為復雜,但應用更加靈活。在API串口通信中可以將串口的屬性設置和操作封裝成一個專用的串口類,同時結合Windows非阻塞通信、多線程、動態鏈接庫等手段,編寫出高質量的通信程序,特別是在CPU處理任務比較繁重、與外圍設備中有大量的通信數據時,更具實際意義。