1-在C++ Builder 6.0下基於api函數編寫串口通信程序簡介:
在dos/win95/win98的年代,操作系統對串口是不保護的,也就是說將串口的的資源完全開放給用戶,用戶可以用直接操作硬件的函數(比如說TC2.0下的inport()和outport()函數) 跟串口直接打交道,這時候用戶使用直接操作串口的函數怎樣"折磨"串口都是沒有問題的,操作系統根本就不管不問,對串口操作所造成的一切後果都是用戶一個人承擔的,這時候用戶對串口具有高度自由的支配權;但是,這種情況好景不長,從win2000操作系統開始,微軟為了"照顧好"計算機上的硬件,開始實施了對硬件的保護策略,也就是說任何用戶在他的操作系統下企圖操縱串口時必須經過他的同意方可進行,其實也就是變相的將用戶往必須使用他的通信api函數才能操作串口這條"羊腸小路"上趕(當然也有別的方法操作串口,但那些並非我等普通用戶能研究明白的),形象一點說就好像你想怎樣操作串口的意圖必須經過win2000的翻譯(其實是win2000的設備驅動程序)才能轉達給串口一樣,基於這一點我們說(其實是很多資料上說的)win2000下通過api函數操作串口是具有"設備無關性的",什麼意思呢?就是說你想怎樣操作串口就用相應的api函數告訴操作系統你想對串口干什麼,然後操作系統就把你的意思轉告給串口讓其做出相應的動作,相對於dos/win95/win98下來說,據我理解也就相當於你原來寫的直接操作串口的函數在win2000下他替你完成了,但是你必須用win2000通信api函數清楚地向操作系統表達清楚你到底想干什麼,所以說在這種情況下要想寫好串口驅動程序你就必須至少弄明白win2000下的通信api函數都是干什麼的方可,啰裡啰唆唠叨了這麼多... ...sorry,還沒完呢,至少還有一件事我想說,原來在dos/win95/win98系統下有好多高手用c/c++對串口進行直接操作是非常熟練的,尤其是dos時代的turbo 2.0操作串口的高手他們寫的串口驅動程序直到win98的時候還用的非常洋洋得意,但是到了win2000的時候,他們的程序突然不好使了,而他們有的可能還會因為知識結構上的滯後始終弄不明白怎麼回事兒,兄弟們,你們該明白了吧?閒話少敘,下面介紹筆者寫串口通信函數時用到的各個api函數---------
2-CreateFile()
用途:打開串口
原型:HANDLE CreateFile(LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
參數說明:
-lpFileName:要打開的文件名稱。對串口通信來說就是COM1或COM2。
-dwDesiredAccess:讀寫模式設置。此處應該用GENERIC_READ及GENERIC_WRITE。
-dwShareMode:串口共享模式。此處不允許其他應用程序共享,應為0。
-lpSecurityAttributes:串口的安全屬性,應為0,表示該串口不可被子程序繼承。
-dwCreationDistribution:創建文件的性質,此處為OPEN_EXISTING.
-dwFlagsAndAttributes:屬性及相關標志,這裡使用異步方式應該用FILE_FLAG_OVERLAPPED。
-hTemplateFile:此處為0。
操作說明:若文件打開成功,串口即可使用了,該函數返回串口的句柄,以後對串口操作時即可使用該句柄。
舉例:HANDLE hComm;
hComm=CreateFile("COM1", //串口號
GENERIC_READ|GENERIC_WRITE, //允許讀寫0, //通訊設備必須以獨占方式打開
NULL, //無安全屬性
OPEN_EXISTING, //通訊設備已存在FILE_FLAG_OVERLAPPED, //異步I/O 0); //通訊設備不能用模板打開hComm即為函數返回的串口1的句柄。
3-CloseHandle()
用途:關閉串口
原型:BOOL CloseHandle(HANDLE hObjedt)
參數說明:
-hObjedt:串口句柄
操作說明:成功關閉串口時返回true,否則返回false
舉例:CloseHandle(hComm);
4-GetCommState()
用途:取得串口當前狀態
原型:BOOL GetCommState(HANDLE hFile,
LPDCB lpDCB);
參數說明:
-hFile:串口句柄
-lpDCB:設備控制塊(Device Control Block)結構地址。此結構中含有和設備相關的參數。此處是與串口相關的參數。由於參數非常多,當需要設置串口參數 時,通常是先取得串口的參數結構,修改部分參數後再將參數結構寫入。
在此僅介紹少數的幾個常用的參數:
DWORD BaudRate:串口波特率
DWORD fParity:為1的話激活奇偶校驗檢查
DWORD Parity:校驗方式,值0~4分別對應無校驗、奇校驗、偶校驗、校驗置位、校驗清零
DWORD ByteSize:一個字節的數據位個數,范圍是5~8
DWORD StopBits:停止位個數,0~2分別對應1位、1.5位、2位停止位操作舉例:DCB ComDCB; //串口設備控制塊
GetCommState(hComm,&ComDCB);
5-SetCommState()
用途:設置串口狀態,包括常用的更改串口號、波特率、奇偶校驗方式、數據位數等原型:BOOL SetCommState(HANDLE hFile,
LPDCB lpDCB);
參數說明:
-hFile:串口句柄
-lpDCB:設備控制塊(Device Control Block)結構地址。要更改的串口參數包含在此結構中。
操作舉例:DCB ComDCB;
GetCommState(hComm,&ComDCB);//取得當前串口狀態ComDCB.BaudRate=9600;//更改為9600bps,該值即為你要修改後的波特率
SetCommState(hComm,&ComDCB;//將更改後的參數寫入串口6-WriteFile()
用途:向串口寫數據
原型:BOOL WriteFile(HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped);
參數說明:
-hFile:串口句柄
-lpBuffer:待寫入數據的首地址
-nNumberOfBytesToWrite:待寫入數據的字節數長度
-lpNumberOfBytesWritten:函數返回的實際寫入串口的數據個數的地址,利用此變量可判斷實際寫入的字節數和准備寫入的字節數是否相同。
-lpOverlapped:重疊I/O結構的指針
操作舉例:DWORD BytesSent=0;
unsigned char SendBytes[5]={1,2,3,4,5};
OVERLAPPED ov_Write;
ov_Write.Offset=0;
ov_Write.OffsetHigh=0;
WriteFile(hComm, //調用成功返回非零,失敗返回零
SendBytes, //輸出緩沖區
5, //准備發送的字符長度
&BytesSent, //實際發出的字符數
&ov_Write); //重疊結構
如果函數執行成功的話檢查BytesSent的值應該為5,此函數是WriteFile函數執行完畢後自行填充的,利用此變量的填充值可以用來檢查該函數是否將所有的數據成功寫入串口
7-ReadFile()
用途:讀串口數據
原型:BOOL ReadFile(HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
lpNumberOfBytesRead,
lpOverlapped);
參數說明:
-hFile:串口句柄
-lpBuffer:存儲被讀出數據的首地址
-nNumberOfBytesToRead:准備讀出的字節個數
-NumberOfBytesRead:實際讀出的字節個數
-lpOverlapped:異步I/O結構,
操作舉例:unsigned char ucRxBuff[20];
COMSTAT ComStat;
DWORD dwError=0;
DWORD BytesRead=0;
OVERLAPPED ov_Read;
ov_Read.hEvent=CreateEvent(NULL, true, false, NULL);//必須創建有效事件
ClearCommError(hComm,&dwError,&ComStat);//檢查串口接收緩沖區中的數據個數
bResult=ReadFile(hComm, //串口句柄
ucRxBuff, //輸入緩沖區地址
ComStat.cbInQue, //想讀入的字符數
&BytesRead, //實際讀出的字節數的變量指針
&ov_Read); //重疊結構指針
假如當前串口中有5個字節數據的話,那麼執行完ClearCommError()函數後,ComStat
結構中的ComStat.cbInQue將被填充為5,此值在ReadFile函數中可被直接利用。
8-ClearCommError()
用途:清除串口錯誤或者讀取串口現在的狀態
原型:BOOL ClearCommError(HANDLE hFile,
LPDWORD lpErrors,
LPCOMATAT lpStat
);
參數說明:
-hFile:串口句柄
-lpErrors:返回錯誤數值,錯誤常數如下:
1-CE_BREAK:檢測到中斷信號。意思是說檢測到某個字節數據缺少合法的停止位。
2-CE_FRAME:硬件檢測到幀錯誤。
3-CE_IOE:通信設備發生輸入/輸出錯誤。
4-CE_MODE:設置模式錯誤,或是hFile值錯誤。
5-CE_OVERRUN:溢出錯誤,緩沖區容量不足,數據將丟失。
6-CE_RXOVER:溢出錯誤。
7-CE_RXPARITY:硬件檢查到校驗位錯誤。
8-CE_TXFULL:發送緩沖區已滿。
-lpStat:指向通信端口狀態的結構變量,原型如下:
typedef struct _COMSTAT{
...
...
DWORD cbInQue; //輸入緩沖區中的字節數
DWORD cbOutQue;//輸出緩沖區中的字節數
}COMSTAT,*LPCOMSTAT;
該結構中對我們很重要的只有上面兩個參數,其他的我們可以不用管。
操作舉例:COMSTAT ComStat;
DWORD dwError=0;
ClearCommError(hComm,&dwError,&ComStat);
上式執行完後,ComStat.cbInQue就是串口中當前含有的數據字節個數,我們利用此
數值就可以用ReadFile()函數去讀串口中的數據了。
9-PurgeComm()
用途:清除串口緩沖區
原型:BOOL PurgeComm(HANDLE hFile,
DWORD dwFlags
);
參數說明:
-hFile:串口句柄
-dwFlags:指定串口執行的動作,由以下參數組成:
-PURGE_TXABORT:停止目前所有的傳輸工作立即返回不管是否完成傳輸動作。
-PURGE_RXABORT:停止目前所有的讀取工作立即返回不管是否完成讀取動作。
-PURGE_TXCLEAR:清除發送緩沖區的所有數據。
-PURGE_RXCLEAR:清除接收緩沖區的所有數據。
操作舉例:PurgeComm(hComm, PURGE_RXCLEAR|PURGE_TXCLEAR|PURGE_RXABORT|PURGE_TXABORT);
清除串口的所有操作。
10-SetCommMask()
用途:設置串口通信事件。
原型:BOOL SetCommMask(HANDLE hFile,
DWORD dwEvtMask
);
參數說明:
-hFile:串口句柄
-dwEvtMask:准備監視的串口事件掩碼
注:在用api函數撰寫串口通信函數時大體上有兩種方法,一種是查尋法,另外一種是事件通知法。
這兩種方法的區別在於收串口數據時,前一種方法是主動的周期性的查詢串口中當前有沒有數據;後一種方法是事先設置好需要監視的串口通信事件,然後依靠單獨開設的輔助線程進行監視該事件是否已發生,如果沒有發生的話該線程就一直不停的等待直到該事件發生後,將該串口事件以消息的方式通知主窗體,然後主窗體收到該消息後依據不同的事件性質進行處理。
比如說當主窗體收到監視線程發來的RX_CHAR(串口中有數據)的消息後,就可以用ReadFile()
函數去讀串口。該參數有如下信息掩碼位值:
EV_BREAK:收到BREAK信號
EV_CTS:CTS(clear to send)線路發生變化
EV_DSR:DST(Data Set Ready)線路發生變化
EV_ERR:線路狀態錯誤,包括了CE_FRAME\CE_OVERRUN\CE_RXPARITY 3鐘錯誤。
EV_RING:檢測到振鈴信號。
EV_RLSD:CD(Carrier Detect)線路信號發生變化。
EV_RXCHAR:輸入緩沖區中已收到數據。
EV_RXFLAG:使用SetCommState()函數設置的DCB結構中的等待字符已被傳入輸入緩沖區中。
EV_TXEMPTY:輸出緩沖區中的數據已被完全送出。
操作舉例:SetCommMask(hComm,EV_RXCHAR|EV_TXEMPTY);
上面函數執行完畢後將監視串口中有無數據和發送緩沖區中的數據是否全部發送完畢。
11-WaitCommEvent()
用途:用來判斷用SetCommMask()函數設置的串口通信事件是否已發生。
原型:BOOL WaitCommEvent(HANDLE hFile,
LPDWORD lpEvtMask,
LPOVERLAPPED lpOverlapped
);
參數說明:
-hFile:串口句柄
-lpEvtMask:函數執行完後如果檢測到串口通信事件的話就將其寫入該參數中。
-lpOverlapped:異步結構,用來保存異步操作結果。
操作舉例:OVERLAPPED os;
DWORD dwMask,dwTrans,dwError=0,err;
memset(&os,0,sizeof(OVERLAPPED));
os.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
if(!WaitCommEvent(hComm,&dwMask,&os)){
//如果異步操作不能立即完成的話,函數返回FALSE,並且調用GetLastError()函
//數分析錯誤原因後返回ERROR_IO_PENDING,指示異步操作正在後台進行.這種情
//況下,在函數返回之前系統設置OVERLAPPED結構中的事件為無信號狀態,該函數
//等待用SetCommMask()函數設置的串口事件發生,共有9種事件可被監視:
//EV_BREAK,EV_CTS,EV_DSR,EV_ERR,EV_RING,EV_RLSD,EV_RXCHAR,
//EV_RXFLAG,EV_TXEMPTY;當其中一個事件發生或錯誤發生時,函數將
//OVERLAPPED結構中的事件置為有信號狀態,並將事件掩碼填充到dwMask參數中
if(GetLastError()==ERROR_IO_PENDING){
/**************************************************************/
/*在此等待異步操作結果,直到異步操作結束時才返回.實際上此時 */
/*WaitCommEvent()函數一直在等待串口監控的事件之一發生,當事件發*/
/*生時該函數將OVERLAPPED結構中的事件句柄置為有信號狀態,此時 */
/*GetOverlappedResult()函數發現此事件有信號後馬上返回,然後下面*/
/*的程序馬上分析WaitCommEvent()函數等到的事件是被監視的串口事 */
/*件中的哪一個,然後執行相應的動作並發出相應消息. */
/**************************************************************/
GetOverlappedResult(hComm,&os,&dwTrans,true);
switch(dwMask){
case EV_RXCHAR:
PostMessage(Parent,WM_COMM_RXCHAR,0,0);
break;
case EV_TXEMPTY:
PostMessage(Parent,WM_COMM_TXEMPTY,0,0);
break;
case EV_ERR:
switch(dwError){
case CE_FRAME:
err=0;
break;
case CE_OVERRUN:
err=1;
break;
case CE_RXPARITY:
err=2;
break;
default:break;
}
PostMessage(Parent,WM_COMM_ERR,(WPARAM)0,(LPARAM)err);
break;
case EV_BREAK:
PostMessage(Parent,WM_COMM_BREAK,0,0);
break;
case ...://其他用SetCommMask()函數設置的被監視的串口通信事件。
... ...
break;
default:break;
}
}
12-以上簡要介紹了大部分的串口通信api函數,筆者所寫的串口通信軟件用的是事件通知方式,該方式是windows2000下效率較高的一種方式。而且只熟悉這些api函數也還是不夠的,該機制下還要牽涉到多線程和消息機制,其中讀寫串口的動作是由主線程來完成的,比如說操作者按下發送數據的按鈕之後 ,相應函數馬上將某特定區域裡面的數據發送出去,所以說用api函數寫串口發送數據的功能是相對較簡單的。收數據的時候就要麻煩一點,在打開串口後首先主線程要設置要監視的串口通信事件,然後將監視線程打開,用來監視主線程設置的這些串口通信事件是否已發生,當其中的某個事件發生後, 監視線程馬上將該消息發送給主線程,其中監視線程在發送消息之前要確保主線程在收到消息後肯定的知道串口究竟發生了什麼樣的事件,然後根據不同的事件類型進行處理。下面給出大致的主線程和監視線程的大致工作流程:
主線程打開(其實就是主窗體打開之後)
|
|
V
打開串口(設置波特率、校驗方式、數據位數、停止位數)
|
|
V
設置監視線程需要監視的串口通信事件
|
|
V
打開監視線程
|
|
V
等待各種事件的發生(比如發送數據單擊事件,更改通信參數事件,監視線程發來的消息等)
--------------------------------------------------------------------------------
監視線程被打開
|
|
V
串口事件發生否(WaitCommEvent())(無論發生否均進入下面的代碼)
|
|
V
異步操作是否正在後台進行?(if(GetLastError()==ERROR_IO_PENDING))
|
|
V
在此等待異步操作結果(GetOverlappedResult(hComm,&os,&dwTrans,true))
|
|
V
處理通信事件,根據事件類型的不同給主窗體發送不同的消息