mfc 調用Windows的API函數實現同步異步串口通訊(源碼)

在工業控制中,工控機(通常都基於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          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();
}安全

相關文章
相關標籤/搜索