1. 程式人生 > >vs2010下mfc的串列埠程式設計(MSComm)

vs2010下mfc的串列埠程式設計(MSComm)

串列埠通訊簡介

  一般來說,計算機都有一個或多個串列埠,這些串列埠提供了外部裝置與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個停止位
<span style="color: #0000ff;">if</span>(!<span style="color: #000000;">m_ctrlComm.get_PortOpen())
{
    </span><span style="color: #008000;">/*</span><span style="color: #008000;">
    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  
    </span><span style="color: #008000;">*/</span>
    <span style="color: #0000ff;">try</span><span style="color: #000000;">
    {
        m_ctrlComm.put_PortOpen(</span><span style="color: #0000ff;">true</span>);<span style="color: #008000;">//</span><span style="color: #008000;">開啟串列埠</span>

}
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(</span><span style="color: #800080;">1</span>);<span style="color: #008000;">//</span><span style="color: #008000;">每當串列埠接收緩衝區有多餘或等於1個字元時將引發一個接收資料的oncomm事件</span>
m_ctrlComm.put_InputLen(<span style="color: #800080;">0</span>);<span style="color: #008000;">//</span><span style="color: #008000;">設定當前接收區資料長度為0</span>
m_ctrlComm.get_Input();<span style="color: #008000;">//</span><span style="color: #008000;">預讀緩衝區以清空殘留資料</span></pre>
複製程式碼

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();
    </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">long</span> k=<span style="color: #800080;">0</span>;k&lt;len;k++<span style="color: #000000;">)
    {
        BYTE bt </span>= *(<span style="color: #0000ff;">char</span>*)(rxdata+k);<span style="color: #008000;">//</span><span style="color: #008000;">字元型</span>
        <span style="color: #0000ff;">short</span> <span style="color: #0000ff;">int</span> intDec=(<span style="color: #0000ff;">int</span><span style="color: #000000;">)bt;
        CString strtemp</span>=<span style="color: #000000;">DTC.Dec2Hex(intDec);
        m_strDataRXTemp</span>+=strtemp;<span style="color: #008000;">//</span><span style="color: #008000;">加入接收編輯框對應字串</span>

}
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屬性。

相關連結:

  深入淺出VC++串列埠程式設計(五) 基於第三方類庫:http://blog.csdn.net/nash635/article/details/5339704