Windows下的Win32串列埠程式設計
在工業控制中,工控機(一般都基於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位停止位
ONE5STOPBITS 1.5位停止位
………
} DCB;
winbase.h檔案中定義了以上用到的常量。如下:
#define NOPARITY 0
#define ODDPARITY 1
#define EVENPARITY 2
#define ONESTOPBIT 0
#define ONE5STOPBITS 1
#define TWOSTOPBITS 2
#define CBR_110 110
#define CBR_300 300
#define CBR_600 600
#define CBR_1200 1200
#define CBR_2400 2400
#define CBR_4800 4800
#define CBR_9600 9600
#define CBR_14400 14400
#define CBR_19200 19200
#define CBR_38400 38400
#define CBR_56000 56000
#define CBR_57600 57600
#define CBR_115200 115200
#define CBR_128000 128000
#define CBR_256000 256000GetCommState函式可以獲得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
);串列埠程式設計的一個例項
為了讓您更好地理解串列埠程式設計,下面我們分別編寫兩個例程(見附帶的原始碼部分),這兩個例程都實現了工控機與百特顯示儀表通過RS485介面進行的串列埠通訊。其中第一個例程採用同步串列埠操作,第二個例程採用非同步串列埠操作。
我們只介紹軟體部分,RS485介面接線方法不作介紹,感興趣的讀者可以查閱相關資料。
例程1
開啟VC++6.0,新建基於對話方塊的工程RS485Comm,在主對話方塊視窗IDD_RS485COMM_DIALOG上新增兩個按鈕,ID分別為IDC_SEND和IDC_RECEIVE,標題分別為“傳送”和“接收”;新增一個靜態文字框IDC_DISP,用於顯示串列埠接收到的內容。
在RS485CommDlg.cpp檔案中新增全域性變數:
HANDLE hCom; //全域性變數,串列埠控制程式碼在RS485CommDlg.cpp檔案中的OnInitDialog()函式新增如下程式碼:
// TODO: Add extra initialization here
hCom=CreateFile("COM1",//COM1口
GENERIC_READ|GENERIC_WRITE, //允許讀和寫
0, //獨佔方式
NULL,
OPEN_EXISTING, //開啟而不是建立
0, //同步方式
NULL);
if(hCom==(HANDLE)-1)
{
AfxMessageBox("開啟COM失敗!");
return FALSE;
}
SetupComm(hCom,100,100); //輸入緩衝區和輸出緩衝區的大小都是1024
COMMTIMEOUTS TimeOuts;
//設定讀超時
TimeOuts.ReadIntervalTimeout=MAXDWORD;
TimeOuts.ReadTotalTimeoutMultiplier=0;
TimeOuts.ReadTotalTimeoutConstant=0;
//在讀一次輸入緩衝區的內容後讀操作就立即返回,
//而不管是否讀入了要求的字元。
//設定寫超時
TimeOuts.WriteTotalTimeoutMultiplier=100;
TimeOuts.WriteTotalTimeoutConstant=500;
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);分別雙擊IDC_SEND按鈕和IDC_RECEIVE按鈕,新增兩個按鈕的響應函式:
void CRS485CommDlg::OnSend()
{
// TODO: Add your control notification handler code here
// 在此需要簡單介紹百特公司XMA5000的通訊協議:
//該儀表RS485通訊採用主機廣播方式通訊。
//序列半雙工,幀11位,1個起始位(0),8個資料位,2個停止位(1)
//如:讀儀表顯示的瞬時值,主機傳送:DC1 AAA BB ETX
//其中:DC1是標準ASCII碼的一個控制符號,碼值為11H(十進位制的17)
//在XMA5000的通訊協議中,DC1表示讀瞬時值
//AAA是從機地址碼,也就是XMA5000顯示儀表的通訊地址
//BB為通道號,讀瞬時值時該值為01
//ETX也是標準ASCII碼的一個控制符號,碼值為03H
//在XMA5000的通訊協議中,ETX表示主機結束符
char lpOutBuffer[7];
memset(lpOutBuffer,''/0'',7); //前7個位元組先清零
lpOutBuffer[0]=''/x11''; //傳送緩衝區的第1個位元組為DC1
lpOutBuffer[1]=''0''; //第2個位元組為字元0(30H)
lpOutBuffer[2]=''0''; //第3個位元組為字元0(30H)
lpOutBuffer[3]=''1''; // 第4個位元組為字元1(31H)
lpOutBuffer[4]=''0''; //第5個位元組為字元0(30H)
lpOutBuffer[5]=''1''; //第6個位元組為字元1(31H)
lpOutBuffer[6]=''/x03''; //第7個位元組為字元ETX
//從該段程式碼可以看出,儀表的通訊地址為001
DWORD dwBytesWrite=7;
COMSTAT ComStat;
DWORD dwErrorFlags;
BOOL bWriteStat;
ClearCommError(hCom,&dwErrorFlags,&ComStat);
bWriteStat=WriteFile(hCom,lpOutBuffer,dwBytesWrite,& dwBytesWrite,NULL);
if(!bWriteStat)
{
AfxMessageBox("寫串列埠失敗!");
}
}
void CRS485CommDlg::OnReceive()
{
// TODO: Add your control notification handler code here
char str[100];
memset(str,''/0'',100);
DWORD wCount=100;//讀取的位元組數
BOOL bReadStat;
bReadStat=ReadFile(hCom,str,wCount,&wCount,NULL);
if(!bReadStat)
AfxMessageBox("讀串列埠失敗!");
PurgeComm(hCom, PURGE_TXABORT|
PURGE_RXABORT|PURGE_TXCLEAR|PURGE_RXCLEAR);
m_disp=str;
UpdateData(FALSE);
}您可以觀察返回的字串,其中有和儀表顯示值相同的部分,您可以進行相應的字串操作取出儀表的顯示值。
開啟ClassWizard,為靜態文字框IDC_DISP新增CString型別變數m_disp,同時新增WM_CLOSE的相應函式:
void CRS485CommDlg::OnClose()
{
// TODO: Add your message handler code here and/or call default
CloseHandle(hCom); //程式退出時關閉串列埠
CDialog::OnClose();
}程式的相應部分已經在程式碼內部作了詳細介紹。連線好硬體部分,編譯執行程式,細心體會串列埠同步操作部分。
例程2
開啟VC++6.0,新建基於對話方塊的工程RS485Comm,在主對話方塊視窗IDD_RS485COMM_DIALOG上新增兩個按鈕,ID分別為IDC_SEND和IDC_RECEIVE,標題分別為“傳送”和“接收”;新增一個靜態文字框IDC_DISP,用於顯示串列埠接收到的內容。在RS485CommDlg.cpp檔案中新增全域性變數:
HANDLE hCom; //全域性變數,串列埠控制程式碼在RS485CommDlg.cpp檔案中的OnInitDialog()函式新增如下程式碼:
hCom=CreateFile("COM1",//COM1口
GENERIC_READ|GENERIC_WRITE, //允許讀和寫
0, //獨佔方式
NULL,
OPEN_EXISTING, //開啟而不是建立
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, //重疊方式
NULL);
if(hCom==(HANDLE)-1)
{
AfxMessageBox("開啟COM失敗!");
return FALSE;
}
SetupComm(hCom,100,100); //輸入緩衝區和輸出緩衝區的大小都是100
COMMTIMEOUTS TimeOuts;
//設定讀超時
TimeOuts.ReadIntervalTimeout=MAXDWORD;
TimeOuts.ReadTotalTimeoutMultiplier=0;
TimeOuts.ReadTotalTimeoutConstant=0;
//在讀一次輸入緩衝區的內容後讀操作就立即返回,
//而不管是否讀入了要求的字元。
//設定寫超時
TimeOuts.WriteTotalTimeoutMultiplier=100;
TimeOuts.WriteTotalTimeoutConstant=500;
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);分別雙擊IDC_SEND按鈕和IDC_RECEIVE按鈕,新增兩個按鈕的響應函式:
void CRS485CommDlg::OnSend()
{
// TODO: Add your control notification handler code here
OVERLAPPED m_osWrite;
memset(&m_osWrite,0,sizeof(OVERLAPPED));
m_osWrite.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
char lpOutBuffer[7];
memset(lpOutBuffer,''/0'',7);
lpOutBuffer[0]=''/x11'';
lpOutBuffer[1]=''0'';
lpOutBuffer[2]=''0'';
lpOutBuffer[3]=''1'';
lpOutBuffer[4]=''0'';
lpOutBuffer[5]=''1'';
lpOutBuffer[6]=''/x03'';
DWORD dwBytesWrite=7;
COMSTAT ComStat;
DWORD dwErrorFlags;
BOOL bWriteStat;
ClearCommError(hCom,&dwErrorFlags,&ComStat);
bWriteStat=WriteFile(hCom,lpOutBuffer,
dwBytesWrite,& dwBytesWrite,&m_osWrite);
if(!bWriteStat)
{
if(GetLastError()==ERROR_IO_PENDING)
{
WaitForSingleObject(m_osWrite.hEvent,1000);
}
}
}
void CRS485CommDlg::OnReceive()
{
// TODO: Add your control notification handler code here
OVERLAPPED m_osRead;
memset(&m_osRead,0,sizeof(OVERLAPPED));
m_osRead.hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
COMSTAT ComStat;
DWORD dwErrorFlags;
char str[100];
memset(str,''/0'',100);
DWORD dwBytesRead=100;//讀取的位元組數
BOOL bReadStat;
ClearCommError(hCom,&dwErrorFlags,&ComStat);
dwBytesRead=min(dwBytesRead, (DWORD)ComStat.cbInQue);
bReadStat=ReadFile(hCom,str,
dwBytesRead,&dwBytesRead,&m_osRead);
if(!bReadStat)
{
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);
m_disp=str;
UpdateData(FALSE);
}開啟ClassWizard,為靜態文字框IDC_DISP新增CString型別變數m_disp,同時新增WM_CLOSE的相應函式:
void CRS485CommDlg::OnClose()
{
// TODO: Add your message handler code here and/or call default
CloseHandle(hCom); //程式退出時關閉串列埠
CDialog::OnClose();
}您可以仔細對照這兩個例程,細心體會串列埠同步操作和非同步操作的區別。
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/haoxingfeng/archive/2008/01/28/2070336.aspx
相關文章
- Linux下串列埠程式設計基礎Linux串列埠程式設計
- Linux 串列埠程式設計 串列埠裝置程式開發Linux串列埠程式設計
- Linux串列埠程式設計Linux串列埠程式設計
- POSIX 串列埠程式設計指南串列埠程式設計
- Linux 串列埠程式設計Linux串列埠程式設計
- Android之串列埠程式設計Android串列埠程式設計
- Linux 串列埠程式設計 使用termios與API進行串列埠程式開發Linux串列埠程式設計iOSAPI
- ROS串列埠程式設計學習筆記ROS串列埠程式設計筆記
- Linux 串列埠程式設計 一些背景Linux串列埠程式設計
- VC++串列埠通訊程式設計詳解C++串列埠程式設計
- Linux下串列埠通訊詳解(下)讀寫串列埠及關閉串列埠Linux串列埠
- Linux 串列埠程式設計 深入瞭解 termiosLinux串列埠程式設計iOS
- Windows下中止佔用埠的程式Windows
- 安卓下的串列埠測試安卓串列埠
- Linux下PCI轉串列埠卡及USB轉串列埠Linux串列埠
- android串列埠程式Android串列埠
- VC++串列埠程式設計之簡訊應用開發(轉)C++串列埠程式設計
- 基於多串列埠ETH005裝置的Socket網路程式設計串列埠程式設計
- 沒有真實串列埠裝置時使用"虛擬串列埠驅動"除錯你的串列埠程式碼串列埠除錯
- 初探WINDOWS下IME程式設計 (轉)Windows程式設計
- .NET Compact Framework下的串列埠通訊Framework串列埠
- ROS環境下串列埠通訊ROS串列埠
- 虛擬串列埠工具和串列埠除錯工具詳解 - 附下載地址串列埠除錯
- Qt 學習筆記 - 第三章 - Qt的三駕馬車之一 - 串列埠程式設計 + 程式打包成Windows軟體QT筆記串列埠程式設計Windows
- Arduino下的STM32的串列埠通訊UI串列埠
- No MFC 程式設計01 - 最精簡的 win32 程式 (轉)程式設計Win32
- Windows下統計埠連結數的批處理Windows
- BPI-M4 Berry在windows系統下連線串列埠除錯Windows串列埠除錯
- IBM串列埠線序以及串列埠線的做法(轉)IBM串列埠
- windows核心程式設計--windows程式的執行Windows程式設計
- Windows 2000下的Raw Socket程式設計 (轉)Windows程式設計
- Win32環境下動態連結庫(DLL)程式設計原理 (轉)Win32程式設計
- 串列埠UART串列埠
- windows下檢視埠占用以及程式名稱Windows
- QT實現串列埠助手中串列埠名的實時更新QT串列埠
- windows 下檢視埠占用Windows
- 帶內串列埠 在串列埠中輸入命令串列埠
- 序列通訊的基本原理及用MFC實現串列埠通訊程式設計 (轉)串列埠程式設計