VC串口通信(Windows API)

本文轉載自基於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
);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章