VC++串列埠通訊程式設計詳解
總結來看串列埠通訊原理,(也可以說大多數通訊原理也是如此)。
通訊首先要有個通訊,可以簡單的把通訊看成一個小桶,傳送方住水桶裡裝水,接收方從水桶中取水。如果你要和對方通訊首先需要將桶蓋開啟,再將水裝入到桶中,這時接收方才能夠從桶中取到水。這裡就存在著一定的問題,
1,如果桶蓋還沒有開啟,傳送方已經傳送了。這時接收方再從桶中取水,肯定取的水不對,會不一部分缺失了。解決方式就是讓桶蓋開啟再往其中加水。
2,但是桶蓋何時開啟,傳送方何時傳送,這個不好把握。解決方法:接收方接到資料時,要返回一個應答標誌,告訴傳送方我已經取到資料了,而且是取得到正確資料才應答,否則不理會,繼續取資料。或者一直查詢,直到與傳送方發來的資料一致再停止取資料。
一般的,進行串列埠通訊總有一個是主動方一個是被動方,而且二者傳輸資料時,會有一定的協商好的資料格式,二者傳送接收都按照此資料格式進行。
在工業控制中,工控機(一般都基於Windows平臺)經常需要與智慧儀表通過串列埠進行通訊。串列埠通訊方便易行,應用廣泛。
一般情況下,工控機和各智慧儀表通過RS485匯流排進行通訊。RS485的通訊方式是半雙工的,只能由作為主節點的工控PC機依次輪詢網路上的各智慧控制單元子節點。每次通訊都是由PC機通過串列埠向智慧控制單元釋出命令,智慧控制單元在接收到正確的命令後作出應答。
在Win32下,可以使用兩種程式設計方式實現串列埠通訊,其一是使用ActiveX控制元件,這種方法程式簡單,但欠靈活。其二是呼叫Windows的API函式,這種方法可以清楚地掌握串列埠通訊的機制,並且自由靈活。本文我們只介紹API串列埠通訊部分。
串列埠的操作可以有兩種操作方式:同步操作方式和重疊操作方式(又稱為非同步操作方式)。同步操作時,API函式會阻塞直到操作完成以後才能返回(在多執行緒方式中,雖然不會阻塞主執行緒,但是仍然會阻塞監聽執行緒);而重疊操作方式,API函式會立即返回,操作在後臺進行,避免執行緒的阻塞。
無論那種操作方式,一般都通過四個步驟來完成:
(1) 開啟串列埠
(2) 配置串列埠
(3) 讀寫串列埠
(4) 關閉串列埠
1、開啟串列埠
Win32系統把檔案的概念進行了擴充套件。無論是檔案、通訊裝置、命名管道、郵件槽、磁碟、還是控制檯,都是用API函式CreateFile來開啟或建立的。該函式的原型為:
- 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方式開啟串列埠的示例程式碼:
- 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開啟串列埠的示例程式碼:
- 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, //串列埠的控制程式碼
// 寫入的資料儲存的地址,
// 即以該指標的值為首地址的
//要寫入的資料的位元組數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
);
相關文章
- VC++ 串列埠通訊(轉)C++串列埠
- VC++ 的串列埠通訊 (轉)C++串列埠
- VC++串列埠程式設計之簡訊應用開發(轉)C++串列埠程式設計
- Linux下串列埠通訊詳解(下)讀寫串列埠及關閉串列埠Linux串列埠
- 串列埠通訊串列埠
- 詳解linux下的串列埠通訊開發Linux串列埠
- 串列埠通訊利器:SerialPortStream庫詳解,輕鬆實現C#串列埠開發串列埠C#
- 串列埠通訊 (轉)串列埠
- 串列埠通訊應用程式的解決方案 (轉)串列埠
- C#串列埠通訊程式SerialPort類C#串列埠
- linux 串列埠通訊Linux串列埠
- 串列埠通訊協議串列埠協議
- Android 串列埠通訊Android串列埠
- C# 串列埠通訊C#串列埠
- 11. 串列埠通訊串列埠
- 串列埠通訊型別串列埠型別
- (10)uart串列埠通訊串列埠
- 序列通訊的基本原理及用MFC實現串列埠通訊程式設計 (轉)串列埠程式設計
- 通過串列埠進行通訊 :串列埠
- 串列埠資料抓取及串列埠通訊模擬串列埠
- Linux 串列埠程式設計 串列埠裝置程式開發Linux串列埠程式設計
- Qt編寫串列埠通訊程式全程圖文講解(一)QT串列埠
- Qt編寫串列埠通訊程式全程圖文講解(二)QT串列埠
- 安卓串列埠通訊疑問安卓串列埠
- java串列埠通訊例項 -Java串列埠
- 串列埠無法正常通訊串列埠
- Linux串列埠程式設計Linux串列埠程式設計
- POSIX 串列埠程式設計指南串列埠程式設計
- Linux 串列埠程式設計Linux串列埠程式設計
- VC++深入詳解(12):網路程式設計C++程式設計
- AndroidSerialPort:安卓串列埠通訊庫Android安卓串列埠
- 串列埠通訊gui介面顯示串列埠GUI
- ROS環境下串列埠通訊ROS串列埠
- Android藍芽串列埠通訊Android藍芽串列埠
- 小型plc串列埠通訊簡介串列埠
- Android之串列埠程式設計Android串列埠程式設計
- Linux 串列埠程式設計 深入瞭解 termiosLinux串列埠程式設計iOS
- STM32串列埠通訊串列埠