本文轉載自基於Windows API的VC++串口通信詳解,以下附上大白的理解。
簡介
在Win32下,可以使用兩種編程方式實現串口通信,其一是使用ActiveX控件,這種方法程序簡單,但欠靈活。其二是調用Windows的API函數,這種方法可以清楚地掌握串口通信的機制,並且自由靈活。本文我們只介紹API串口通信部分。串口的操作可以有兩種操作方式:同步操作方式和重疊操作方式(又稱爲異步操作方式)。同步操作時,API函數會阻塞直到操作完成以後才能返回(在多線程方式中,雖然不會阻塞主線程,但是仍然會阻塞監聽線程);而重疊操作方式,API函數會立即返回,操作在後臺進行,避免線程的阻塞。無論那種操作方式,一般都通過四個步驟來完成:
(1) 打開串口
(2) 配置串口
(3) 讀寫串口
(4) 關閉串口
1.打開串口
Win32系統把文件的概念進行了擴展。無論是文件、通信設備、命名管道、郵件槽、磁盤、還是控制檯,都是用API函數CreateFile來打開或創建的。
CreateFile函數的原型爲:
HANDLE CreateFile( LPCTSTR lpFileName, //將要打開的串口邏輯名,如“COM1”
DWORD dwDesiredAccess, //指定串口訪問的類型,可以是讀取、寫入或二者並列
DWORD dwShareMode, //指定共享屬性,由於串口不能共享,該參數必須置爲0
LPSECURITY_ATTRIBUTES lpSecurityAttributes, //引用安全性屬性結構,缺省值爲NULL
DWORD dwCreationDistribution, //創建標誌,對串口操作該參數必須置爲OPEN_EXISTING
DWORD dwFlagsAndAttributes, //用於指定該串口是否進行異步操作: = FILE_FLAG_OVERLAPPED,表示使用異步的I/O;= 0,表示同步I/O操作
HANDLE hTemplateFile//對串口而言該參數必須置爲NULL
);
打開串口的示例代碼:
HANDLE hCom; //全局變量,串口句柄
hCom=CreateFile("COM1",//COM1口 ; 注意串口號如果大於COM9應該在前面加上\\.\,比如COM10表示爲"\\\\.\\COM10"
GENERIC_READ|GENERIC_WRITE, //允許讀和寫
0, //獨佔方式
NULL,
OPEN_EXISTING, //打開而不是創建
0, //0同步方式 ; FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED異步方式
NULL);
if(hCom==INVALID_HANDLE_VALUE) {
AfxMessageBox("打開COM失敗!");
return FALSE;
}
return TRUE;
2.配置串口
在打開通訊設備句柄後,常常需要對串口進行一些初始化配置工作。這需要通過一個DCB結構來進行。DCB結構包含了諸如波特率、數據位數、奇偶校驗和停止位數等信息。在查詢或配置串口的屬性時,都要用DCB結構來作爲緩衝區。一般用CreateFile打開串口後,可以調用GetCommState函數來獲取串口的初始配置。要修改串口的配置,應先修改DCB結構,再調用SetCommState函數設置串口。
(1)DCB結構包含了串口的各項參數設置,下面僅介紹幾個該結構常用的變量:
typedef struct _DCB {
………
DWORD BaudRate//波特率,指定通信設備的傳輸速率。這個成員可以是實際波特率值或者常量值
DWORD fParity; // 指定奇偶校驗使能。若此成員爲1,允許奇偶校驗檢查
BYTE ByteSize; // 通信字節位數,4—8
BYTE Parity; //指定奇偶校驗方法。此成員可以有下列值: EVENPARITY 偶校驗;NOPARITY 無校驗;MARKPARITY 標記校驗;
//ODDPARITY 奇校驗
BYTE StopBits; //指定停止位的位數。此成員可以有下列值: ONESTOPBIT 1位停止位;TWOSTOPBITS 2位停止位;
//ONE5STOPBITS 1.5位停止位
}
(2)GetCommState函數可以獲得COM口的設備控制塊,從而獲得相關參數:
BOOL GetCommState( HANDLE hFile, //標識通訊端口的句柄
LPDCB lpDCB //指向一個設備控制塊(DCB結構)的指針
);
(3)SetCommState函數設置COM口的設備控制塊:
BOOL SetCommState( HANDLE hFile, LPDCB lpDCB );
(4)SetupComm函數設置串行口的輸入和輸出緩衝區的大小:
除了在DCB中設置外,程序一般還需要設置I/O緩衝區的大小和超時。Windows用I/O緩衝區來暫存串口輸入和輸出的數據。如果通信的速率較高,則應該設置較大的緩衝區。調用SetupComm函數可以設置串行口的輸入和輸出緩衝區的大小。
BOOL SetupComm( HANDLE hFile, // 通信設備的句柄
DWORD dwInQueue, // 輸入緩衝區的大小(字節數)
DWORD dwOutQueue // 輸出緩衝區的大小(字節數)
);
(5)GetCommTimeouts函數查詢超時:
在用ReadFile和WriteFile讀寫串行口時,需要考慮超時問題。超時的作用是在指定的時間內沒有讀入或發送指定數量的字符,ReadFile或WriteFile的操作仍然會結束。要查詢當前的超時設置應調用GetCommTimeouts函數,該函數會填充一個COMMTIMEOUTS結。調用SetCommTimeouts可以用某一個COMMTIMEOUTS結構的內容來設置超時。讀寫串口的超時有兩種:間隔超時和總超時。間隔超時是指在接收時兩個字符之間的最大時延。總超時是指讀寫操作總共花費的最大時間。寫操作只支持總超時,而讀操作兩種超時均支持。用COMMTIMEOUTS結構可以規定讀寫操作的超時。
COMMTIMEOUTS結構的定義爲:
typedef struct _COMMTIMEOUTS {
DWORD ReadIntervalTimeout; //讀間隔超時
DWORD ReadTotalTimeoutMultiplier; //讀時間係數
DWORD ReadTotalTimeoutConstant; //讀時間常量
DWORD WriteTotalTimeoutMultiplier; // 寫時間係數
DWORD WriteTotalTimeoutConstant; //寫時間常量
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
COMMTIMEOUTS結構的成員都以毫秒爲單位。總超時的計算公式是:總超時=時間係數×要求讀/寫的字符數+時間常量。例如,要讀入10個字符,那麼讀操作的總超時的計算公式爲:讀總超時=ReadTotalTimeoutMultiplier×10+ReadTotalTimeoutConstant。可以看出:間隔超時和總超時的設置是不相關的,這可以方便通信程序靈活地設置各種超時。如果所有寫超時參數均爲0,那麼就不使用寫超時。如果ReadIntervalTimeout爲0,那麼就不使用讀間隔超時。如果ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant 都爲0,則不使用讀總超時。如果讀間隔超時被設置成MAXDWORD並且讀時間係數和讀時間常量都爲0,那麼在讀一次輸入緩衝區的內容後讀操作就立即返回,而不管是否讀入了要求的字符。
(6)PurgeComm()函數清空緩衝區:
在配置完串口之後和寫串口之前,還要用PurgeComm()函數清空緩衝區,讀串口時會把讀取出來的數據在緩衝區清除,所以讀串口可以不用該函數。該函數原型:
BOOL PurgeComm( HANDLE hFile, //串口句柄
DWORD dwFlags // 需要完成的操作
);
參數dwFlags指定要完成的操作,可以是下列值的組合:
PURGE_TXABORT 中斷所有寫操作並立即返回,即使寫操作還沒有完成;
PURGE_RXABORT中斷所有讀操作並立即返回,即使讀操作還沒有完成;
PURGE_TXCLEAR 清除輸出緩衝區;
PURGE_RXCLEAR 清除輸入緩衝區;
配置串口的示例代碼:
SetupComm(hCom,1024,1024); //輸入緩衝區和輸出緩衝區的大小都是1024
COMMTIMEOUTS TimeOuts;
TimeOuts.ReadIntervalTimeout=1000; //設定讀超時
TimeOuts.ReadTotalTimeoutMultiplier=500;
TimeOuts.ReadTotalTimeoutConstant=5000;
TimeOuts.WriteTotalTimeoutMultiplier=500; //設定寫超時
TimeOuts.WriteTotalTimeoutConstant=2000;
SetCommTimeouts(hCom,&TimeOuts); //設置超時
DCB dcb;
GetCommState(hCom,&dcb);
dcb.BaudRate=9600; //波特率爲9600
dcb.ByteSize=8; //每個字節有8位
dcb.Parity=NOPARITY; //無奇偶校驗位
dcb.StopBits=TWOSTOPBITS; //兩個停止位
SetCommState(hCom,&dcb);
PurgeComm(hCom,PURGE_TXCLEAR | PURGE_RXCLEAR);
3.讀寫串口
(1)ReadFile函數讀串口:
BOOL ReadFile( HANDLE hFile, //串口的句柄
LPVOID lpBuffer,// 讀入的數據存儲的地址:即讀入的數據將存儲在以該指針的值爲首地址的一片內存區
DWORD nNumberOfBytesToRead, // 要讀入的數據的字節數
LPDWORD lpNumberOfBytesRead,// 指向一個DWORD數值,該數值返回讀操作實際讀入的字節數
LPOVERLAPPED lpOverlapped// 重疊操作時,該參數指向一個OVERLAPPED結構,同步操作時,該參數爲NULL );
(2)WriteFiel函數寫串口:
BOOL WriteFile( HANDLE hFile, //串口的句柄
LPCVOID lpBuffer, // 寫入的數據存儲的地址:即以該指針的值爲首地址的的數據將要寫入
DWORD nNumberOfBytesToWrite, //要寫入的數據的字節數
LPDWORD lpNumberOfBytesWritten,// 指向指向一個DWORD數值,該數值返回實際寫入的字節數
LPOVERLAPPED lpOverlapped// 重疊操作時,該參數指向一個OVERLAPPED結構;同步操作時,該參數爲NULL。
);
在用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。這說明重疊操作還未完成。
同步讀串口代碼示例:
char str[100];
DWORD wCount;//讀取的字節數
BOOL bReadStat;
DWORD dwErrorFlags;
COMSTAT ComStat;
ClearCommError(hCom,&dwErrorFlags,&ComStat);
bReadStat=ReadFile(hCom,str,100,&wCount,NULL);
if(!bReadStat) {
AfxMessageBox("讀串口失敗!");
}
同步寫串口代碼示例:
char lpOutBuffer[100];
DWORD dwBytesWrite=100;
BOOL bWriteStat;
PurgeComm(hCom, PURGE_TXABORT| PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
bWriteStat=WriteFile(hCom,lpOutBuffer,dwBytesWrite,& dwBytesWrite,NULL);
if(!bWriteStat) {
AfxMessageBox("寫串口失敗!");
}
(3)等待讀寫操作完成函數WaitForSingleObject和GetOverlappedResult:
在重疊操作時,操作還未完成函數就返回。重疊I/O非常靈活,它也可以實現阻塞(例如我們可以設置一定要讀取到一個數據才能進行到下一步操作)。有兩種方法可以等待操作完成:一種方法是用象WaitForSingleObject這樣的等待函數來等待OVERLAPPED結構的hEvent成員;另一種方法是調用GetOverlappedResult函數等待,後面將演示說明。下面我們先簡單說一下OVERLAPPED結構和GetOverlappedResult函數:
OVERLAPPED結構:包含了重疊I/O的一些信息,定義如下:
typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
在使用ReadFile和WriteFile重疊操作時,線程需要創建OVERLAPPED結構以供這兩個函數使用。線程通過OVERLAPPED結構獲得當前的操作狀態,該結構最重要的成員是hEvent。hEvent是讀寫事件。當串口使用異步通訊時,函數返回時操作可能還沒有完成,程序可以通過檢查該事件得知是否讀寫完畢。當調用ReadFile, WriteFile 函數的時候,該成員會自動被置爲無信號狀態;當重疊操作完成後,該成員變量會自動被置爲有信號狀態。
GetOverlappedResult函數:
BOOL GetOverlappedResult( HANDLE hFile, //串口的句柄
LPOVERLAPPED lpOverlapped,//指向重疊操作開始時指定的OVERLAPPED結構
LPDWORD lpNumberOfBytesTransferred,//指向一個32位變量,該變量的值返回實際讀寫操作傳輸的字節數。
BOOL bWait// 該參數用於指定函數是否一直等到重疊操作結束:如果該參數爲TRUE,函數直到操作結束才返回;
//如果該參數爲FALSE,函數直接返回,這時如果操作沒有完成,通過調用GetLastError()函數會
//返回ERROR_IO_INCOMPLETE。
);
在使用ReadFile 函數進行讀操作前,應先使用ClearCommError函數清除錯誤並獲取讀緩衝區字節數。ClearCommError函數的原型如下:
BOOL ClearCommError( HANDLE hFile, // 串口句柄
LPDWORD lpErrors, // 指向接收錯誤碼的變量
LPCOMSTAT lpStat // 指向通訊狀態緩衝區
);
該函數獲得通信錯誤並報告串口的當前狀態,同時,該函數清除串口的錯誤標誌以便繼續輸入、輸出操作。參數lpStat指向一個COMSTAT結構,該結構返回串口狀態信息。 本文只用到了cbInQue成員變量,該成員變量的值代表輸入緩衝區的字節數。COMSTAT結構 COMSTAT結構包含串口的信息,結構定義如下:
typedef struct _COMSTAT {
// cst
DWORD fCtsHold : 1; // Tx waiting for CTS signal
DWORD fDsrHold : 1; // Tx waiting for DSR signal
DWORD fRlsdHold : 1; // Tx waiting for RLSD signal
DWORD fXoffHold : 1; // Tx waiting, XOFF char rec''d
DWORD fXoffSent : 1; // Tx waiting, XOFF char sent
DWORD fEof : 1; // EOF character sent
DWORD fTxim : 1; // character waiting for Tx
DWORD fReserved : 25; // reserved
DWORD cbInQue; // bytes in input buffer
DWORD cbOutQue; // bytes in output buffer
} COMSTAT, *LPCOMSTAT;
異步方式讀串口代碼示例:
char lpInBuffer[1024];
DWORD dwBytesRead=1024;
BOOL bReadStatus;
DWORD dwErrorFlags;
COMSTAT ComStat;
OVERLAPPED m_osRead;
memset(&m_osRead,0,sizeof(OVERLAPPED));//注意每次讀取串口時都要初始化OVERLAPPED
m_osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
ClearCommError(hCom,&dwErrorFlags,&ComStat);
if(!ComStat.cbInQue) return 0; //讀緩衝區沒有字節
dwBytesRead=min(dwBytesRead,(DWORD)ComStat.cbInQue); //防止讀取字節數超過數組最大值
bReadStatus=ReadFile(hCom, lpInBuffer,dwBytesRead, &dwBytesRead,&m_osRead);
if(!bReadStatus) { //如果ReadFile函數返回FALSE
if(GetLastError()==ERROR_IO_PENDING) {
GetOverlappedResult(hCom, &m_osRead,&dwBytesRead,TRUE); // GetOverlappedResult函數的最後一個參數設爲TRUE,函數會一直
//等待,直到讀操作完成或由於錯誤而返回。
}
}
異步方式寫串口代碼示例:
char buffer[1024];
DWORD dwBytesWritten=1024;
DWORD dwErrorFlags;
COMSTAT ComStat;
OVERLAPPED m_osWrite;
BOOL bWriteStat;
memset(&m_odWrite,0,sizeof(OVERLAPPED));
m_odWrite.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
PurgeComm(hCom, PURGE_RXABORT|PURGE_TXABORT|PURGE_RXCLEAR|PURGE_TXCLEAR);
bWriteStat=WriteFile(hCom,buffer,dwBytesWritten, &dwBytesWritten,&m_OsWrite);
if(!bWriteStat) {
if(GetLastError()==ERROR_IO_PENDING) {
switch(GetLastError())
{
case ERROR_IO_PENDING:
WaitForSingleObject(m_odWrite.hEvent, 1000);
break;
case ERROR_INVALID_PARAMETER:
MessageBox(_T("系統錯誤"),NULL,MB_ICONERROR);
break;
case ERROR_ACCESS_DENIED:
MessageBox(_T("拒絕訪問"),NULL,MB_ICONERROR);
break;
case ERROR_INVALID_HANDLE:
MessageBox(_T("打開串口失敗"),NULL,MB_ICONERROR);
break;
case ERROR_BAD_COMMAND:
MessageBox(_T("非法斷開"),NULL,MB_ICONERROR);
break;
}
}
return 0;
}
4.關閉串口
BOOL CloseHandle(
HANDLE hObject; //handle to object to close
);