VC_MFC串口通信編程詳解


       在工業控制中,工控機(一般都基於 Windows 平臺)經常需要與智能儀表通過串口
進行通信.串口通信方便易行,應用廣泛.
  一般情況下,工控機和各智能儀表通過 RS485 總線進行通信.RS485 的通信方式是
半雙工的,只能由作爲主節點的工控 PC 機依次輪詢網絡上的各智能控制單元子節點.
每次通信都是由 PC 機通過串口向智能控制單元發佈命令,智能控制單元在接收到正
確的命令後作出應答.
  在 Win32下,可以使用兩種編程方式實現串口通信,其一是使用 ActiveX控件,這種
方法程序簡單,但欠靈活.其二是調用 Windows的 API 函數,這種方法可以清楚地掌握
串口通信的機制,並且自由靈活.下面只介紹 API 串口通信部分.
  串口的操作可以有兩種操作方式:同步操作方式和重疊操作方式(又稱爲異步操作
方式).同步操作時,API 函數會阻塞直到操作完成以後才能返回(在多線程方式中,

雖然不會阻塞主線程,但是仍然會阻塞監聽線程);而重疊操作方式,API 函數會立即
返回,操作在後臺進行,避免線程的阻塞.
無論哪種操作方式,一般都通過四個步驟來完成:
(1)打開串口
(2)配置串口
(3)讀寫串口
(4)關閉串口 
 
一 打開串口 
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",// 串口名稱
  GENERIC_READ|GENERIC_WRITE,//允許讀和寫
  0,//獨佔方式
  NULL,
  OPEN_EXISTING,// 打開而不是創建
  0,//同步方式
  NULL);
if(hCom==(HANDLE)-1)
{
MessageBox("打開 COM 失敗!");
return FALSE;
}
return TRUE;
 
重疊 I/O 打開串口的示例: 
HANDLE hCom;//全局變量,串口句柄
hCom =CreateFile("COM1",//串口名稱
                GENERIC_READ|GENERIC_WRITE,//允許讀和寫
                0,//獨佔方式
                NULL,
                OPEN_EXISTING,//打開而不是創建
                FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//重疊方式
                NULL);
if(hCom==INVALID_HANDLE_VALUE)
{
MessageBox("打開 COM 失敗!");
return FALSE;
}
return TRUE;
 
二 配置串口 
在打開通訊設備句柄後,常需要對串口進行一些初始化配置工作.這需要通過一個
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 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   清除輸入緩衝區
 
三 讀寫串口 
使用 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)
{
MessageBox("讀串口失敗!");
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)
{
MessageBox("寫串口失敗!");
}
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 {
                            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;
 
四 關閉串口 
利用 API 函數關閉串口非常簡單,只需使用 CreateFile 函數返回的句柄作爲參數
調用 CloseHandle 即可: 
BOOL CloseHandle(
    HANDLE hObject;//handle to object to close 
);
爲了更好地理解串口編程,下面分別編寫兩個實例,這兩個實例都實現了工控機與
百特顯示儀表通過 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",// 串口名稱
    GENERIC_READ|GENERIC_WRITE,//允許讀和寫
    0,//獨佔方式
    NULL,
    OPEN_EXISTING,//打開而不是創建
    0,//同步方式
    NULL);
if(hCom==(HANDLE)-1)
{
MessageBox("打開 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)
{
MessageBox("寫串口失敗!");
}
 
}
 
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)
{
 MessageBox("讀串口失敗!");
}
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",// 串口名稱
    GENERIC_READ|GENERIC_WRITE,//允許讀和寫
    0,//獨佔方式
    NULL,
    OPEN_EXISTING,//打開而不是創建
    FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,//重疊方式
    NULL);
if(hCom==(HANDLE)-1)
{
MessageBox("打開 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();
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章