Windows下的Win32串列埠程式設計

zxg131x發表於2010-07-20

在工業控制中,工控機(一般都基於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

相關文章