1. 程式人生 > >VS2013開發上位機並呼叫MSCcommm控制元件的方式

VS2013開發上位機並呼叫MSCcommm控制元件的方式

此文章適合VC++串列埠通訊入門

一、頁面佈局及新增控制元件

1, 安裝好vs2010如圖


2, 新建一個基於VC++的MFC專案comm


注意:點選ok,然後next,這時候要將application type改成dialog base,接著next到最後一個對話方塊是將generated dasses改成CcommDlg,然後finish


4, 將新生成的專案的對話方塊預設dialog edit刪去,如圖


5,在對話方塊中新增兩個static text,兩個edit text,兩個按鈕,

成品圖如下


6,新增comm控制元件

    1)在解決方案視窗右擊新建的解決方案,點選add->class

     2)選擇MFC class from activex control如圖


點選add,available activex controls選擇microsoft communication controls versions 6.0,然後點確定就行

這時候對話方塊會出現一個電話圖示,可能有一半白邊去不了,這時候右擊電話圖示點選edit control就可以去掉了。

7,同時定義各個控制元件的型別、ID及相關屬性


注:此專案只添加了傳送和退出程式按鈕

這時候得到了完整的串列埠通訊對話方塊:


8, 新增成員變數,右擊對話方塊,點選class wizard,點選member variables標籤,選中需要新增的id,雙擊即可新增

依次為下表中的ID新增變數


期間,IDC_MSCOMM1控制元件在標籤中沒有,則在生成的對話方塊中右擊comm控制元件點選add variables即可

9, 為mscomm,兩個button新增響應事件,切換到class wizard的virtual function雙擊控制元件ID,新增響應事件,預設即可,也可改為自己想要的標題

為comm控制元件新增響應事件可能雙擊不了(我就是遇到這種問題),這時候只要右擊comm控制元件圖示,點選add event handler即可。如圖



這時候基本介面已經佈置好了,開始新增程式碼了。

二、程式碼新增

1、找到解決方案(solution explorer)的sources files點開,雙擊其中的mscommDlg.cpp我們的所有程式碼將新增到這個原始檔中

2、進行串列埠初始化及其他串列埠設定

將以下程式碼新增到oninitialdialog函式

m_ctrlcomm.put_CommPort(3);//選擇com3口
m_ctrlcomm.put_InputMode(1);//輸入方式為二進位制方式
m_ctrlcomm.put_InBufferSize(1024);//輸入緩衝區大小為1024byte
m_ctrlcomm.put_OutBufferSize(512);//輸出緩衝區大小為512byte
m_ctrlcomm.put_Settings(_T("9600,n,8,1"));//設定串列埠引數:9600波特率,無奇偶校驗,8個數據位,1個停止位
if(!m_ctrlcomm.get_PortOpen())
    m_ctrlcomm.put_PortOpen(1);//開啟串列埠
m_ctrlcomm.put_RThreshold(1);//每當串列埠接收緩衝區有多餘或等於1個字元時將引發一個接收資料的oncomm事件
m_ctrlcomm.put_InputLen(0);//設定當前接收區資料長度為0
m_ctrlcomm.get_Input();//預讀緩衝區以清空殘留資料

m_ctrlcomm.put_CommPort(3);//選擇com3口
m_ctrlcomm.put_InputMode(1);//輸入方式為二進位制方式
m_ctrlcomm.put_InBufferSize(1024);//輸入緩衝區大小為1024byte
m_ctrlcomm.put_OutBufferSize(512);//輸出緩衝區大小為512byte
m_ctrlcomm.put_Settings(_T("9600,n,8,1"));//設定串列埠引數:9600波特率,無奇偶校驗,8個數據位,1個停止位
if(!m_ctrlcomm.get_PortOpen())
    m_ctrlcomm.put_PortOpen(1);//開啟串列埠
m_ctrlcomm.put_RThreshold(1);//每當串列埠接收緩衝區有多餘或等於1個字元時將引發一個接收資料的oncomm事件
m_ctrlcomm.put_InputLen(0);//設定當前接收區資料長度為0
m_ctrlcomm.get_Input();//預讀緩衝區以清空殘留資料
2、實現傳送按鈕,退出按鈕相應的響應函式

void CmscommDlg::OnBnClickedOk()
{
// TODO: Add your control notification handler code here
CDialogEx::OnOK();
UpdateData(1);//讀取編輯框內容
m_ctrlcomm.put_Output(COleVariant(m_strsend));//傳送資料
}




void CmscommDlg::OnBnClickedExit()
{
// TODO: Add your control notification handler code here
m_ctrlcomm.put_PortOpen(0);//關閉串列埠
CDialog::OnCancel();//退出程式
}
3、實現MSComm控制元件相應的響應函式OnOnCommMsComm1()

VARIANT variant_inp;
COleSafeArray safearray_inp;
LONG len,k;
BYTE rxdata[2048];//設定byte陣列
CString strtemp;
if(m_ctrlcomm.get_CommEvent()==2)//事件2表示接受緩衝區有字元
{
variant_inp=m_ctrlcomm.get_Input();//讀緩衝區
safearray_inp=variant_inp;//variant資料轉換成colesafearray型變數
len=safearray_inp.GetOneDimSize();//得到有效資料長度
for(k=0;k<len;k++)
safearray_inp.GetElement(&k,rxdata+k);//轉換為byte型陣列
for(k=0;k<len;k++){//將陣列轉換成CString型變數
BYTE bt=*(char *)(rxdata+k);//字元型
strtemp.Format((char) bt);//將字元送入臨時變數strtemp存放
m_strreceive+=strtemp;//加入接收編輯框相應字串
}
}
UpdateData(0);//更新編輯框內容

4、編譯執行程式

在除錯執行時,必須兩臺機子同時執行此程式,並且都要開啟同一個串列埠號

VS2010下MFC的串列埠程式設計

串列埠通訊簡介

  一般來說,計算機都有一個或多個串列埠,這些串列埠提供了外部裝置與PC進行資料傳輸和通訊的通道,在CPU和外設之間充當直譯器的角色。當字元資料從CPU傳送給外設時,這些字元資料將被轉換成序列位元流資料;當接收資料時,位元流資料被轉換為字元資料傳遞給CPU,再進一步說,在作業系統方面,Windows用通訊驅動程式(COMM.DRV)呼叫API函式傳送和接收資料;當用通訊控制元件或宣告呼叫API函式時,它們由COMM.DRV解釋並傳遞給裝置驅動程式。作為一個程式設計師,要編寫通訊程式,只需知道通訊控制元件提供的Windows API通訊函式的介面即可,換句話說,只需設定和監視通訊控制元件的屬性和事件即可。

  串列埠通訊方法一般有以下幾種:

  1. 利用Windows API通訊函式;
  2. 利用Visual C++的標準通訊函式_inp、_inpw、_inpd、_outp、_outpw、_outpd等直接對串列埠進行操作;
  3. 通過微軟的串列埠通訊控制元件MSComm,它是一種ActiveX控制元件;
  4. 利用第3方編寫的通訊類,比如MuMega Technologies公司提供的CSerail類;

  我在專案開發過程中用的是第三種方法——通過MSComm控制元件操作串列埠,下面是我使用此控制元件的筆記。

MSComm控制元件簡介

  MSComm 是 Microsoft 公司為簡化Windows下串列埠程式設計而提供的ActiveX控制元件,它提供了一系列標準通訊命令的使用介面。MSComm 控制元件通過串列埠(serial port)傳送和接收資料,為應用程式提供了序列通訊功能。在視覺化程式設計盛行的今天,我們可以很方便的在Visual Basic(VB)、Visual C++(VC)、Delphi等語言及開發平臺中應用。處理資料的方式有事件驅動(Event-driver)、查詢法(Inquire)兩種。

  事件驅動法:在使用事件驅動法設計程式時,每當有新字元到達、埠狀態變化或發生錯誤時,MSComm控制元件將觸發OnComm事件,而應用程式在捕獲該事件後,通過檢查MSComm控制元件的CommEvent屬性可以獲知所發生的事件或錯誤,從而採取相應的操作。這種方法的優點是程式響應及時,可靠性高。

  查詢法:這種方法適合於較小的應用程式。在這種情況下,每當應用程式執行完某一序列口操作後,將不斷檢查MSComm控制元件的CommEvent屬性以檢查執行結果或者檢查某一事件是否發生。例如,當程式向序列裝置傳送了某個命令後,可能只是在等待收到一個特定的響應字串,而不是對收到的每一個字元都立刻響應並處理。

  使用的每個MSComm控制元件都與一個串列埠對應。如果在應用程式中需要訪問多個串列埠,必須使用多個MSComm控制元件,可以在Windows 控制面板中修改串列埠地址的中斷地址。

MSComm控制元件的常用屬性

  • CommPort屬性:設定或返回通訊埠號,可以設定為1到16之間的任何值;
  • Settings屬性:以字串形式設定或返回波特率、奇偶校驗、資料位和停止位;
  • PortOpen屬性:設定或返回通訊口的狀態以及開啟和關閉埠,可通過把該屬性設定為true或者false來開啟或者關閉埠;
  • InBufferSize和OutBufferSize屬性:分別設定接收和傳送緩衝區分配的記憶體數量,單位為位元組,預設值分別為1024byte和512byte;
  • InputLen屬性:確定希望從接收緩衝區移出的字元數量,當InputLen=0時,一次把接收緩衝區的字元全部移出;
  • Input屬性:從接收緩衝區中讀出資料,然後將該資料從緩衝區移走。
  • OutPut屬性:向傳送緩衝區傳遞待發送的資料。
  • InBufferCountOutBufferCount屬性:分別確定當前駐留在接收緩衝區等待被取出和傳送緩衝區準備傳送的字元數量,這兩個屬性設定為0,接收和傳送緩衝區的內容將被清除;
  • InputMode屬性:設定接收傳入資料的格式,設定為0採用文字形式,設定為1採用二進位制格式;
  • SThreshold屬性:儲存一個產生髮送OnComm事件的界限值,本系統設定該屬性為0,傳送資料時不產生OnComm事件;
  • RThreshold屬性:設定當接收幾個字元時觸發OnComm事件,本系統設定該屬性為1,每接收一個字元就產生一個OnComm事件;

MSComm控制元件的事件

  MSCOMM控制元件只使用一個事件OnComm,用屬性CommEvent的17個值來區分不同的觸發時機,主要有以下幾個:

  • CommEvent=1時:傳輸緩衝區中的字元個數已少於Sthreshold(可設定的屬性值)個;
  • CommEvent=2時:接收緩衝區中收到Rthreshold(可設定的屬性值)個字元,利用此事件可編寫接收資料的過程;
  • CommEvent=3時:CTS線發生變化;
  • CommEvent=4時:DSR線發生變化;
  • CommEvent=5時:CD線發生變化;
  • CommEvent=6時:檢測到振鈴訊號;

  另外十種情況是通訊錯誤時產生,即錯誤程式碼。

基於VS2010下MFC的MSComm串列埠程式的實現

1、註冊MSComm控制元件

  我在網上下載了MSComm控制元件之後,將其放於專案目錄下,並在當前目錄建了個.bat批處理檔案,其內容如下:

copy .\\MSCOMM\\MSCOMM.SRG %windir%\system32
copy .\\MSCOMM\\MSCOMM32.DEP %windir%\system32
copy .\\MSCOMM\\MSCOMM32.oca %windir%\system32
copy .\\MSCOMM\\mscomm32.ocx %windir%\system32

regsvr32 mscomm32.ocx

雙擊此檔案,即可註冊MSComm控制元件。

2、新增MSComm控制元件

  首先將MSComm控制元件新增進VS2010工具箱,再給專案新增該ActiveX控制元件對應的“基於MFC的ATL類”,最後將工具箱中的MSComm控制元件(電話圖示)拖至對話方塊即可。在對話方塊中新增MSComm控制元件後,其側面會有白色,右擊此控制元件,選擇“編輯控制元件”,即可去除白色。

3、新增控制元件變數

  在主對話方塊中新增與MSComm控制元件相關聯的控制元件變數(成員物件),通過此成員變數可操作串列埠。

4、串列埠資訊配置及開啟串列埠

  在對話方塊模板上右擊MSComm控制元件,選擇Property選單項,即可設定MSComm控制元件各項屬性。在調變解調器通訊的程式中,設定“Control”屬性頁中Handshaking項為“2-comRTS”,否則國內部分廠家modem不能正常通訊,其它接受預設設定。另外亦可通過修改對話方塊類的OnInitDialog()函式來設定控制元件的屬性。具體參考MSDN中的關於Comm Control的詳細說明。

  我程式的串列埠設定程式碼大致如下:

    //*********************** 串列埠設定 **************************//
    m_ctrlComm.put_CommPort(port);//選擇com口
    m_ctrlComm.put_InputMode(1);//輸入方式為二進位制方式
    m_ctrlComm.put_InBufferSize(1024);//輸入緩衝區大小為1024byte
    m_ctrlComm.put_OutBufferSize(512);//輸出緩衝區大小為512byte

    CString strBaudrate;
    strBaudrate.Format(_T("%ld"),baudrate);
    m_ctrlComm.put_Settings(strBaudrate+_T(",n,8,1"));//設定串列埠引數:9600波特率,無奇偶校驗,8個數據位,1個停止位
    
    if(!m_ctrlComm.get_PortOpen())
    {
        /*
        HANDLE m_hCom;        
        CString strCom;  
        strCom.Format(_T("\\\\.\\COM%d"),(int)(m_ctrlComm.get__CommPort()));  
        // 這裡的CreateFile函式起了很大的作用,可以用來建立系統裝置檔案,
        //如果該裝置不存在或者被佔用,則會返回一個錯誤,即下面的 INVALID_HANDLE_VALUE ,
        //據此可以判斷可使用性。詳細參見MSDN中的介紹。  
        m_hCom = CreateFile(strCom, 0, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);  
        if(m_hCom == INVALID_HANDLE_VALUE)//如果沒有該裝置,或者被其他應用程式在用  
        {  
            int errornum=GetLastError();  
            if(errornum==2)  
                strCom.Format(_T("埠%d 不存在"),(int)(m_ctrlComm.get__CommPort()));  
            else if(errornum==5)  
                strCom.Format(_T("埠%d被佔用"),(int)(m_ctrlComm.get__CommPort()));  
            AfxMessageBox(strCom);  
            CloseHandle(m_hCom); // 關閉檔案控制代碼,後面我們採用控制元件,不用API  
            return ;//這是因為串列埠初始化封裝在另一個函式裡面在OnInitDialog呼叫。  
        }  
        CloseHandle(m_hCom); // 關閉檔案控制代碼,後面我們採用控制元件,不用API  
        */
        try
        {
            m_ctrlComm.put_PortOpen(true);//開啟串列埠
        }
        catch(COleDispatchException *e)
        {
            CString strError;
            strError.Format(_T("開啟串列埠失敗!\n\nError Number: %d \nError Message: %s"),
                e->m_wCode,e->m_strDescription);
            MessageBoxW(strError,_T("錯誤提示"),MB_ICONERROR);
            return;
        }
    }
    else
    {
        //MessageBox(_T("Cannot open serial port!"));
    }

    m_ctrlComm.put_RThreshold(1);//每當串列埠接收緩衝區有多餘或等於1個字元時將引發一個接收資料的oncomm事件
    m_ctrlComm.put_InputLen(0);//設定當前接收區資料長度為0
    m_ctrlComm.get_Input();//預讀緩衝區以清空殘留資料

5、串列埠資料的讀寫

  MSComm 類的讀寫函式比較簡單:get_Input()put_Output()。函式原形分別為VARIANT get_Input()和void put_Output(const VARIANT newValue),均使用VARIANT型別。但PC機發送和接收資料時習慣用字串形式。MSDN中查閱VARIANT型別,可以用BSTR表示字串,但所有的BSTR都包含寬字元,而只有Windows NT支援寬字元,Windows 9X並不支援。所以要完成一個適應各平臺的串列埠應用程式必須解決這個問題,這裡使用CByteArray解決之。

  新增接收資料函式,在對話方塊中雙擊Comm Control,接受預設函式,則對話方塊類的成員函式為OnCommMscomm(),其大致程式碼如下:

   CDataTypeConverter DTC;
    //電話圖示可能有一半白邊去不了,右擊電話圖示點選edit control就可以去掉
    if(m_ctrlComm.get_CommEvent()==2)//事件值為2表示接收事件
    {
        BYTE rxdata[255]={0};//設定BYTE陣列
        VARIANT variant_inp=m_ctrlComm.get_Input();//讀緩衝區
        COleSafeArray safearray_inp = variant_inp;//VARIANT型變數轉換為COleSafeArray變數
        long len=safearray_inp.GetOneDimSize();//得到有效資料長度
        for(long k=0;k<len;k++)
            safearray_inp.GetElement(&k,rxdata+k);//轉換為BYTE陣列
        m_ctrlComm.put_OutBufferCount(0);//清空傳送緩衝區
        m_ctrlComm.put_InBufferCount(0);//滑空接收緩衝區
        safearray_inp.Clear();
            
        for(long k=0;k<len;k++)
        {
            BYTE bt = *(char*)(rxdata+k);//字元型
            short int intDec=(int)bt;
            CString strtemp=DTC.Dec2Hex(intDec);
            m_strDataRXTemp+=strtemp;//加入接收編輯框對應字串
        }
        m_strDataRX=m_strDataRXTemp;
        m_strDataRXTemp="";
   }

其中,Dec2Hex()函式的程式碼如下:

CString CDataTypeConverter::Dec2Hex(unsigned int intDec)
{
    CString strHex;
    char charHex[255];
    sprintf(charHex,"%x",intDec);
    strHex=charHex;
    if(strHex.GetLength()==1)
        strHex=_T("0")+strHex;
    return strHex;
}

  傳送資料的程式碼大致如下:

//UpdateData(true);//讀取編輯框內容m_strDataTX

//傳送的字串上表面為十六進位制格式
CString m_strCtrlLightBL;
m_strCtrlLightBL="55AA0AAA6B4310100000";//"55aa0aaa6b4310100000"
    
CDataTypeConverter DTC;
COleVariant m_OleVariant=DTC.HexM2OleVariant(m_strCtrlLightBL);

m_ctrlComm.put_Output(m_OleVariant);//傳送資料

其中,HexM2OleVariant()函式定義如下:

COleVariant CDataTypeConverter::HexM2OleVariant(CString strHexM)
{
    BYTE bt[255];
    short int len=strHexM.GetLength();
    short int length=0;
    short int intDec;
    for(int n=0,i=0;n<len-1;n+=2,i++)
    {        
        intDec=Hex2Dec(strHexM.Mid(n,2));
        bt[i]=char(intDec);
        length=i+1;
    }
    CByteArray m_Array;
    m_Array.RemoveAll();
    m_Array.SetSize(length);
    for(int i=0;i<length;i++)
        m_Array.SetAt(i,bt[i]);
    return COleVariant(m_Array);
}
注意:接收資料時,RThreshold屬性很重要,因為它影響著OnComm事件的觸發條件,在程式中可以通過put_RThreshold()函式來設定RThreshold屬性。