天天看點

Win 32同步與異步序列槽通訊

  在工業控制中,工控機(一般都基于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_14400DWORD fParity; // 指定奇偶校驗使能。若此成員為1,允許奇偶校驗檢查 …BYTE ByteSize; // 通信位元組位數,4—8BYTE 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 256000

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 );

序列槽程式設計的一個執行個體

  為了讓您更好地了解序列槽程式設計,下面我們分别編寫兩個例程(見附帶的源碼部分),這兩個例程都實作了工控機與百特顯示儀表通過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();}

您可以仔細對照這兩個例程,細心體會序列槽同步操作和異步操作的差別。

好了,就到這吧,祝您好運。

轉載位址:http://hi.baidu.com/renmingcan/item/52371fdf6b228ae4785daa2b

http://www.vckbase.com/index.php/wv/1439#%E6%89%93%E5%BC%80%E4%B8%B2%E5%8F%A3

繼續閱讀