VC++串口通訊編程詳解

來源:互聯網
上載者:User

       在工業控制中,工控機(一般都基於Windows平台)經常需要與智能儀錶通過串口進行通訊。串口通訊方便易行,應用廣泛。
一般情況下,工控機和各智能儀錶通過RS485匯流排進行通訊。RS485的通訊方式是半雙工的,只能由作為主節點的工控PC機依次輪詢網路上的各智能控制單元子節點。每次通訊都是由PC機通過串口向智能控制單元發布命令,智能控制單元在接收到正確的命令後作出應答。
  在Win32下,可以使用兩種編程方式實現串口通訊,其一是使用ActiveX控制項,這種方法程式簡單,但欠靈活。其二是調用Windows的API函數,這種方法可以清楚地掌握串口通訊的機制,並且自由靈活。本文我們只介紹API串口通訊部分。
  串口的操作可以有兩種操作方式:同步操作方式和重疊操作方式(又稱為非同步作業方式)。同步操作時,API函數會阻塞直到操作完成以後才能返回(在多線程方式中,雖然不會阻塞主線程,但是仍然會阻塞監聽線程);而重疊操作方式,API函數會立即返回,操作在後台進行,避免線程的阻塞。

       無論那種操作方式,一般都通過四個步驟來完成:
       (1) 開啟串口
       (2) 配置串口
       (3) 讀寫串口
       (4) 關閉串口

       1、開啟串口

Win32系統把檔案的概念進行了擴充。無論是檔案、通訊裝置、具名管道、郵件槽、磁碟、還是控制台,都是用API函數CreateFile來開啟或建立的。該函數的原型為:
C++代碼
HANDLE CreateFile( LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);   

       lpFileName:將要開啟的串口邏輯名,如“COM1”;
       dwDesiredAccess:指定串口訪問的類型,可以是讀取、寫入或二者並列;
       dwShareMode:指定共用屬性,由於串口不能共用,該參數必須置為0;
       lpSecurityAttributes:引用安全性屬性結構,預設值為NULL;
       dwCreationDistribution:建立標誌,對串口操作該參數必須置為OPEN_EXISTING;
       dwFlagsAndAttributes:屬性描述,用於指定該串口是否進行非同步作業,該值為FILE_FLAG_OVERLAPPED,表示使用非同步I/O;該值為0,表示同步I/O操作;

       hTemplateFile:對串口而言該參數必須置為NULL。
       同步I/O方式開啟串口的範例程式碼:
C++代碼
HANDLE hCom; //全域變數,串口控制代碼  
hCom=CreateFile("COM1",//COM1口 
 GENERIC_READ|GENERIC_WRITE, //允許讀和寫 
 0, //獨佔方式 
 NULL, 
 OPEN_EXISTING, //開啟而不是建立 
 0, //同步方式 
 NULL);  
if(hCom==(HANDLE)-1)  

   AfxMessageBox("開啟COM失敗!"); 
   return FALSE;  

return TRUE;   
       重疊I/O開啟串口的範例程式碼:
C++代碼
HANDLE hCom; //全域變數,串口控制代碼    
hCom =CreateFile("COM1", //COM1口   
 GENERIC_READ|GENERIC_WRITE, //允許讀和寫   
 0, //獨佔方式   
 NULL,   
 OPEN_EXISTING, //開啟而不是建立    
 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函數設定串口。
DCB結構包含了串口的各項參數設定,下面僅介紹幾個該結構常用的變數:

typedef struct _DCB{ ……… //傳輸速率,指定通訊裝置的傳輸速率。這個成員可以是實際傳輸速率值或者下面的常量值之一: DWORD BaudRate; CBR_110,CBR_300,CBR_600,CBR_1200,CBR_2400,CBR_4800,CBR_9600,CBR_19200, CBR_38400, CBR_56000, CBR_57600, CBR_115200, CBR_128000, CBR_256000, CBR_14400 DWORD fParity; // 指定同位使能。若此成員為1,允許同位檢查
… BYTE ByteSize; // 通訊位元組位元,4—8 BYTE Parity; //指定同位方法。此成員可以有下列值: EVENPARITY 偶校正 NOPARITY 無校正 MARKPARITY 標記校正 ODDPARITY 奇數同位 BYTE StopBits; //指定停止位的位元。此成員可以有下列值: ONESTOPBIT 1位停止位 TWOSTOPBITS 2位停止位
ON 5STOPBITS   1.5位停止位
                                                                                                                                                                      

GetCommState函數可以獲得COM口的裝置控制塊,從而獲得相關參數:
BOOL GetCommState( HANDLE hFile, //標識通訊連接埠的控制代碼 LPDCB lpDCB //指向一個裝置控制塊(DCB結構)的指標 ); SetCommState函數設定COM口的裝置控制塊: BOOL SetCommState( HANDLE hFile, LPDCB lpDCB ); 

除了在BCD中的設定外,程式一般還需要設定I/O緩衝區的大小和逾時。Windows用I/O緩衝區來暫存串口輸入和輸出的資料。如果通訊的速率較高,則應該設定較大的緩衝區。調用SetupComm函數可以設定串列口的輸入和輸出緩衝區的大小。

BOOL SetupComm( HANDLE hFile, // 通訊裝置的控制代碼 DWORD dwInQueue, // 輸入緩衝區的大小(位元組數) DWORD dwOutQueue // 輸出緩衝區的大小(位元組數) ); 

在用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,那麼在讀一次輸入緩衝區的內容後讀操作就立即返回,而不管是否讀入了要求的字元。
在用重疊方式讀寫串口時,雖然ReadFile和WriteFile在完成操作以前就可能返回,但逾時仍然是起作用的。在這種情況下,逾時規定的是操作的完成時間,而不是ReadFile和WriteFile的返回時間。
配置串口的範例程式碼:
 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); 

在讀寫串口之前,還要用PurgeComm()函數清空緩衝區,該函數原型:
BOOL PurgeComm( HANDLE hFile, //串口控制代碼 DWORD dwFlags // 需要完成的操作 ); 
參數dwFlags指定要完成的操作,可以是下列值的組合:
PURGE_TXABORT 中斷所有寫操作並立即返回,即使寫操作還沒有完成。 PURGE_RXABORT 中斷所有讀操作並立即返回,即使讀操作還沒有完成。 PURGE_TXCLEAR 清除輸出緩衝區 PURGE_RXCLEAR 清除輸入緩衝區 

       3、讀寫串口
       我們使用ReadFile和WriteFile讀寫串口,下面是兩個函數的聲明:

BOOL ReadFile( HANDLE hFile, //串口的控制代碼 // 讀入的資料存放區的地址, // 即讀入的資料將儲存在以該指標的值為首地址的一片記憶體區 LPVOID lpBuffer, DWORD nNumberOfBytesToRead, // 要讀入的資料的位元組數 // 指向一個DWORD數值,該數值返回讀操作實際讀入的位元組數 LPDWORD lpNumberOfBytesRead, // 重疊操作時,該參數指向一個OVERLAPPED結構,同步操作時,該參數為NULL。 LPOVERLAPPED
lpOverlapped ); BOOL WriteFile( HANDLE hFile, //串口的控制代碼 // 寫入的資料存放區的地址, // 即以該指標的值為首地址的nNumberOfBytesToWrite // 個位元組的資料將要寫入串口的發送資料緩衝區。 LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, //要寫入的資料的位元組數 // 指向指向一個DWORD數值,該數值返回實際寫入的位元組數 LPDWORD lpNumberOfBytesWritten, //
重疊操作時,該參數指向一個OVERLAPPED結構, // 同步操作時,該參數為NULL。 LPOVERLAPPED lpOverlapped ); 
在用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; bReadStat=ReadFile(hCom,str,100,&wCount,NULL); if(!bReadStat) { AfxMessageBox("讀串口失敗!"); return FALSE; } return TRUE; //同步寫串口 char lpOutBuffer[100]; DWORD dwBytesWrite=100; COMSTAT ComStat; DWORD
dwErrorFlags; BOOL bWriteStat; ClearCommError(hCom,&dwErrorFlags,&ComStat); bWriteStat=WriteFile(hCom,lpOutBuffer,dwBytesWrite,& dwBytesWrite,NULL); if(!bWriteStat) { AfxMessageBox("寫串口失敗!"); } PurgeComm(hCom, PURGE_TXABORT| PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR); 

在重疊操作時,操作還未完成函數就返回。

重疊I/O非常靈活,它也可以實現阻塞(例如我們可以設定一定要讀取到一個資料才能進行到下一步操作)。有兩種方法可以等待操作完成:一種方法是用象WaitForSingleObject這樣的等待函數來等待OVERLAPPED結構的hEvent成員;另一種方法是調用GetOverlappedResult函數等待,後面將示範說明。
下面我們先簡單說一下OVERLAPPED結構和GetOverlappedResult函數:
OVERLAPPED結構
OVERLAPPED結構包含了重疊I/O的一些資訊,定義如下:
typedef struct _OVERLAPPED { // o DWORD Internal; DWORD InternalHigh; DWORD Offset; DWORD OffsetHigh; HANDLE hEvent; } OVERLAPPED; 

在使用ReadFile和WriteFile重疊操作時,線程需要建立OVERLAPPED結構以供這兩個函數使用。線程通過OVERLAPPED結構獲得當前的操作狀態,該結構最重要的成員是hEvent。hEvent是讀寫事件。當串口使用非同步通訊時,函數返回時操作可能還沒有完成,程式可以通過檢查該事件得知是否讀寫完畢。
當調用ReadFile, WriteFile 函數的時候,該成員會自動被置為無訊號狀態;當重疊操作完成後,該成員變數會自動被置為有訊號狀態。
GetOverlappedResult函數 BOOL GetOverlappedResult( HANDLE hFile, // 串口的控制代碼 // 指向重疊操作開始時指定的OVERLAPPED結構 LPOVERLAPPED lpOverlapped, // 指向一個32位變數,該變數的值返回實際讀寫操作傳輸的位元組數。 LPDWORD lpNumberOfBytesTransferred, // 該參數用於指定函數是否一直等到重疊操作結束。 // 如果該參數為TRUE,函數直到操作結束才返回。 // 如果該參數為FALSE,函數直接返回,這時如果操作沒有完成,
// 通過調用GetLastError()函數會返回ERROR_IO_INCOMPLETE。 BOOL bWait ); 
該函數返回重疊操作的結果,用來判斷非同步作業是否完成,它是通過判斷OVERLAPPED結構中的hEvent是否被置位來實現的。

非同步讀串口的範例程式碼:
char lpInBuffer[1024]; DWORD dwBytesRead=1024; COMSTAT ComStat; DWORD dwErrorFlags; OVERLAPPED m_osRead; memset(&m_osRead,0,sizeof(OVERLAPPED)); m_osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); ClearCommError(hCom,&dwErrorFlags,&ComStat); dwBytesRead=min(dwBytesRead,(DWORD)ComStat.cbInQue);
if(!dwBytesRead) return FALSE; BOOL bReadStatus; bReadStatus=ReadFile(hCom,lpInBuffer, dwBytesRead,&dwBytesRead,&m_osRead); if(!bReadStatus) //如果ReadFile函數返回FALSE { if(GetLastError()==ERROR_IO_PENDING) //GetLastError()函數返回ERROR_IO_PENDING,表明串口進行中讀操作 { WaitForSingleObject(m_osRead.hEvent,2000);
//使用WaitForSingleObject函數等待,直到讀操作完成或延時已達到2秒鐘 //當串口讀操作進行完畢後,m_osRead的hEvent事件會變為有訊號 PurgeComm(hCom, PURGE_TXABORT| PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR); return dwBytesRead; } return 0; } PurgeComm(hCom, PURGE_TXABORT| PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
return dwBytesRead; 
對以上代碼再作簡要說明:在使用ReadFile 函數進行讀操作前,應先使用ClearCommError函數清除錯誤。ClearCommError函數的原型如下:
BOOL ClearCommError( HANDLE hFile, // 串口控制代碼 LPDWORD lpErrors, // 指向接收錯誤碼的變數 LPCOMSTAT lpStat // 指向通訊狀態緩衝區 ); 

該函數獲得通訊錯誤並報告串口的目前狀態,同時,該函數清除串口的錯誤標誌以便繼續輸入、輸出操作。
參數lpStat指向一個COMSTAT結構,該結構返回串口狀態資訊。 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; 

本文只用到了cbInQue成員變數,該成員變數的值代表輸入緩衝區的位元組數。

最後用PurgeComm函數清空串口的輸入輸出緩衝區。
這段代碼用WaitForSingleObject函數來等待OVERLAPPED結構的hEvent成員,下面我們再示範一段調用GetOverlappedResult函數等待的非同步讀串口範例程式碼:

char lpInBuffer[1024]; DWORD dwBytesRead=1024; BOOL bReadStatus; DWORD dwErrorFlags; COMSTAT ComStat; OVERLAPPED m_osRead; 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, //函數會一直等待,直到讀操作完成或由於錯誤而返回。 return
dwBytesRead; } return 0; } return dwBytesRead; 
非同步寫串口的範例程式碼:
char buffer[1024]; DWORD dwBytesWritten=1024; DWORD dwErrorFlags; COMSTAT ComStat; OVERLAPPED m_osWrite; BOOL bWriteStat; bWriteStat=WriteFile(hCom,buffer,dwBytesWritten, &dwBytesWritten,&m_OsWrite); if(!bWriteStat) { if(GetLastError()==ERROR_IO_PENDING) {
WaitForSingleObject(m_osWrite.hEvent,1000); return dwBytesWritten; } return 0; } return dwBytesWritten; 

       4、關閉串口
       利用API函數關閉串口非常簡單,只需使用CreateFile函數返回的控制代碼作為參數調用CloseHandle即可:

BOOL CloseHandle(
    HANDLE hObject; //handle to object to close
);
本文來源於雞啄米 http://www.jizhuomi.com/ , 原文地址:http://www.jizhuomi.com/software/309.html

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.