1. 程式人生 > >MFC操作串列埠,詳細 複製程式碼(ActiveX控制元件和Windows API函式)

MFC操作串列埠,詳細 複製程式碼(ActiveX控制元件和Windows API函式)

複製程式碼

/*******************************************************************
*******函式功能:開啟串列埠裝置連結
*******函式名稱:OpenComm
*******輸入引數:無
*******輸出引數:無
*******返 回 值:TRUE -- 開啟串列埠成功
                FALSE -- 開啟視窗失敗
*******************************************************************/
BOOL CSerial::OpenComm( OPEN_COMM_PARA openCommPara)
{
    
if (m_bOpened) { return FALSE; } char szPort[15] = {0}; char szComParams[50] = {0}; DCB dcb; wsprintf( szPort, "\\\\.\\COM%d", openCommPara.comPort); m_hIDComDev = CreateFile( szPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL );//
if( m_hIDComDev == NULL ) return( FALSE ); memset( &m_OverlappedRead, 0, sizeof( OVERLAPPED ) ); memset( &m_OverlappedWrite, 0, sizeof( OVERLAPPED ) ); COMMTIMEOUTS CommTimeOuts; CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF; CommTimeOuts.ReadTotalTimeoutMultiplier = 0; CommTimeOuts.ReadTotalTimeoutConstant
= 0; CommTimeOuts.WriteTotalTimeoutMultiplier = 0; CommTimeOuts.WriteTotalTimeoutConstant = 5000; SetCommTimeouts( m_hIDComDev, &CommTimeOuts ); m_OverlappedRead.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); m_OverlappedWrite.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); GetCommState(m_hIDComDev,&dcb); dcb.DCBlength = sizeof(DCB); dcb.BaudRate = openCommPara.nBaud; dcb.ByteSize = openCommPara.nBit; dcb.fRtsControl = RTS_CONTROL_DISABLE; dcb.fDtrControl = DTR_CONTROL_DISABLE; if( !SetCommState( m_hIDComDev, &dcb ) || !SetupComm( m_hIDComDev, 10000, 10000 )
     || m_OverlappedRead.hEvent == NULL || m_OverlappedWrite.hEvent == NULL ) { DWORD dwError = GetLastError(); if( m_OverlappedRead.hEvent != NULL ) CloseHandle( m_OverlappedRead.hEvent ); if( m_OverlappedWrite.hEvent != NULL ) CloseHandle( m_OverlappedWrite.hEvent ); CloseHandle( m_hIDComDev ); return( FALSE ); } m_bOpened = TRUE; return(TRUE); }
複製程式碼

 

1、win32下對串列埠的操作可以通過兩種方式: ActiveX控制元件和 Windows API函式,第一種程式簡單但是欠缺靈活,第二種自由靈活程式設計不易。 無論哪一種方式都需要完成四個步驟: 一、開啟串列埠; 二、配置串列埠; 三、讀寫串列埠; 四、關閉串列埠。

2、win32下對檔案的概念進行了擴充套件,無論是檔案、通訊裝置、命名管道、郵槽、磁碟還是控制檯都是用API函式CreateFile開啟或者建立。

複製程式碼
HANDLE CreateFile(  

LPCTSTR lpFileName,   //串列埠號,支援CString,Fomat轉換進來

DWORD dwDesiredAccess,
//訪問模式(寫 / 讀),一般設為:GENERIC_READ | GENERIC_WRITE

DWORD dwShareMode,   
//共享屬性,一般設為0

LPSECURITY_ATTRIBUTES lpSecurityAttributes,
//指向安全屬性的指標 ,一般設為:NULL

DWORD dwCreationDispostion ,
//如何建立,必須設為:OPEN_EXISTING

DWORD dwFlagsAndAttributes,
//檔案屬性,一般設為:FILE_FLAG_OVERLAPPED(允許對檔案進行重疊操作) | FILE_ATTRIBUTE_NORMAL(預設屬性)

HANDLE hTemplateFile       //用於複製檔案控制代碼,對於串列埠而言必須設為NULL
);

複製程式碼 lpfilename:是將要開啟的 串列埠邏輯名; dwDesiredAccess:指定串列埠訪問型別; dwsharemoude:指定共享屬性 ,串列埠的屬性必須為0; lpsecurityattributes:引用安全性屬性結構,預設值為NULL; dwcreationdistribution:建立標識,對串列埠操作該引數必須設定為OPEN_EXISTING; dwflagsandattributes:屬性描述,改製為FILE_FLAG_OVERLAPPED時表示使用非同步的I/O,為0是表示使用同步的I/O操作; htemplatefile:對串列埠而言引數必須為NULL。

使用同步方式開啟的程式碼:

複製程式碼
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;
複製程式碼

使用重疊方式(非阻塞式)開啟串列埠的方法:

複製程式碼
HANDLE hCOM;  
hCOM=CreateFile("COM1",GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED);  
if(hCOM==INVALID_HANDLE_VALUE) { MessageBox("開啟COM失敗!"); return FALSE; } return TRUE;
複製程式碼 3、串列埠的配置需要採用 DCB結構進行,這個結構體中包含了諸如 波特率、資料位數、奇偶校驗和停止位數等資訊。在查詢和配置串列埠屬性時,都要用DCB結構作為緩衝區。

一般採用CreateFile開啟串列埠之後,呼叫GetCommState函式來獲取串列埠的初始配置,要修改串列埠的配置,應該先修改DCB結構,然後再呼叫SetCommState函式設定串列埠。

複製程式碼
BOOL GetCommState
(  
  HANDLE hFile,  // 通訊裝置的控制代碼 
  LPDCB lpDCB    // 裝置控制塊,指向一個DCB結構的指標  
);  
BOOL SetCommState(  
HANDLE hFile,  //通訊裝置的控制代碼
LPDCB lpDCB    //裝置控制塊,指向一個DCB結構的指標 
);
複製程式碼

除了在BCD中的設定外,程式一般還需要設定I/O緩衝區的大小和超時,Windows用I/O緩衝區來快取串列埠輸入和輸出的資料。如果通訊的速率較高,應該設定較大的緩衝區。呼叫SetupComm函式可以設定序列口的輸入和輸出緩衝區的大小。

BOOL SetupComm(  
HANDLE hFile,   
DWORD dwInQueue,//設定輸入緩衝區的大小(位元組數)   
DWORD dwOutQueue//設定輸出緩衝區的大小  
);   

在用ReadFile和WriteFile讀寫串列埠時,需要考慮超時問題。超時的作用是在指定的時間內沒有讀入或傳送指定數量的字元,ReadFile和WriteFile的操作依然會結束。要查詢當前的超時設定應呼叫GetCommTimeout函式,該函式會填充一個COMMTIMEOUTS結構,呼叫SetCommTimeout可以用某一個COMMTIMEOUTS結構的內容來設定超時。讀寫串列埠的超時包括間隔超時和總超時,間隔超時是指在接收時兩個字元之間的最大時延,總超時是指讀寫操作總共話費的最大時間。寫操作只支援總超時,讀操作支援兩種超時。

複製程式碼
typedefstruct _COMMTIMEOUTS 
{   
DWORD ReadIntervalTimeout;         //讀間隔超時  
DWORD ReadTotalTimeoutMultiplier;       //讀時間係數  
DWORD ReadTotalTimeoutConstant;   //讀時間常數  
DWORD WriteTotalTimeoutMultiplier;   //寫時間係數  
DWORD WriteTotalTimeoutConstant;  //寫時間常量  
} COMMTIMEOUTS,*LPCOMMTIMEOUTS;
複製程式碼

簡單定義示範:

    COMMTIMEOUTS CommTimeOuts;    //建立一個例項
    CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF;//讀取間隔一直讀取
    CommTimeOuts.ReadTotalTimeoutMultiplier = 0;//讀時間係數
    CommTimeOuts.ReadTotalTimeoutConstant = 0;//讀時間常數
    CommTimeOuts.WriteTotalTimeoutMultiplier = 0;//寫時間係數
    CommTimeOuts.WriteTotalTimeoutConstant = 5000;//寫時間常量

 

COMMTIMEOUTS結構的成員都以毫秒為單位,總超時的計算方式是:

總超時=時間係數*要求讀/寫的字元數+時間常量

如果所有寫超時係數都為0,就不適用寫超時,如果ReadIntervalTimeOut為0,就不適用讀間隔超時,如果ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都為0,則不使用讀總超時

4、串列埠的讀寫採用ReadFile和WriteFile函式,其同步還是非同步都由CreateFile函式決定,如果在CreateFile建立控制代碼時制定了FILE_FLAG_OVERLAPPED標誌,就呼叫ReadFile和WriteFile函式是重疊的,如果未指定重疊標誌,則讀寫操作應該是同步的。如果操作成功,這兩個函式都返回TRUE,當都返回FALSE時也不一定就是失敗的,應該呼叫GetLastError函式分析返回的結果,如果返回值是ERROR_IO_PENDING,說明重疊操作未完成

5、序列通訊傳輸方式分為訊號傳輸方式和線路傳輸方式兩類。訊號傳輸方式是按訊號原樣傳輸的基波傳輸或是利用原訊號調製成高頻載波的載波傳輸;線路傳輸方式是指通訊雙方裝置的線路可以選擇單工、半雙工、全雙工和多雙工傳輸。

2、RS232,RS485等均採用訊號傳輸方式,這種方式實現簡單,但是通訊距離有限制。遠距離傳輸時一般採用Modem,通過Modem可以將原訊號調製成高頻的模擬訊號,然後通過電話網路,進行遠距離通訊。線路傳輸方式一般分成單工、半雙工和全雙工、多工四種方式,其中半雙工採用一條傳輸線,全雙工採用兩根線可以同時對發,多工傳輸方式採用複用技術將一個通道劃分為若干個頻帶或時間片,從而使多路訊號同時共享通道,這就是多工傳輸方式。使用複用器和集中器可以降低成本,提高通訊網的傳輸效率。

3、在高階通訊控制程式中一般採用迴圈冗餘程式碼CRC校驗以自動糾錯方式傳送。

4、波特率:1波特=1bit/s,波特率是衡量通訊線路基本電訊號傳送率的一種量度,它僅僅是電學上的度量單位,不是資訊的度量單位,代表通訊線路上的電脈衝速率

5、傳送器在傳送時鐘的有效沿(下降沿)作用下將移位暫存器的資料按位移位序列輸出,在接收資料時,接收器在接收時鐘的有效沿(上升沿)作用下對接收資料按位取樣,傳送/接收時鐘頻率=1/16/64*傳送/接收波特率。

6、序列傳輸協議一般有兩類:非同步通訊同步通訊。非同步傳輸格式也成為起止式非同步協議,特點是通訊雙方以一個字元作為資料傳輸單位,且傳送方傳送字元的間隔時間是不定的。在傳輸一個字元時總是以起始位開始,以停止位結束。起始位恆為0,長度1,停止位恆為1,長度1、1.5、2可選,起始位和停止位及其中間內容稱為一幀。

7、非同步傳輸的錯誤檢測:接收方能檢測到的錯誤一般有:奇偶錯超越錯(也成為溢位錯)和幀格式錯(因為時鐘不匹配或者不穩定未能按照協議裝配成一個完整的字元幀等)。

8、面向字元的同步傳輸協議:SYN控制字,是同步字元,每一幀開始都有SYN,加一個SYN的稱為單同步,加兩個SYN的稱為雙同步,同步字元的作用是為了聯絡。SOH是序始字元,它表示標題的開始,標題中包括源地址、目標地址和路由指示等資訊。STX是文始字元,標誌著傳送的正文的開始,資料塊是被傳送的正文內容,由多個字元組成。資料塊後面是ETB組終字元或ETX文終字元,在對很長的文段分段傳送時每個分資料塊後面用組終字元ETB,最後一個分資料塊後面用文終字元ETX。在幀的最後是校驗碼,它對從SOH開始直到ETX或ETB欄位進行校驗。面向字元的傳輸有未解決的問題,需要在資料字元前加轉義字元DLE,這樣的實現比較麻煩,所以出現了面向位元的同步傳輸協議。

9、面向位元的同步傳輸協議:又稱為二進位制同步傳輸,協議包括三種:同步資料鏈路控制規程SDLC;高階資料鏈路控制規程HDLC;先進資料通訊規程ADCCP。

10、SDLC/HDLC協議規定所有資訊傳輸必須以一個標誌符開始,且以同一個識別符號結束。這個識別符號是01111110,稱為標誌場。所有的資訊都是以幀的形式傳送的,而標誌字元提供了每一幀的邊界,接收端可以通過搜尋01111110確定幀的開始和結束。

11、常用的通訊標準:一是計算機與外設之間的物理介面標準,屬於七層模型中的物理層。二是按介面標準設定計算機與外設之間進行序列通訊的介面電路。

DCE:資料通訊裝置,該裝置和其與通訊網路的連線構成了網路終端的使用者網路介面。它提供了到網路的一條物理連線、轉發業務量,並且提供了一個用於同步DCE裝置和DTE裝置之間資料傳輸的時鐘訊號。調變解調器和介面卡都是DCE裝置的例子。:

DTE:資料終端裝置,指的是位於使用者網路介面使用者端的裝置,它能夠作為信源、信宿或同時為二者。資料終端裝置通過資料通訊裝置(例如,調變解調器)連線到一個數據網路上,並且通常使用資料通訊裝置產生的時鐘訊號。資料終端裝置包括計算機、協議翻譯器以及多路分解器等裝置

標準指出DTE應該擁有一個插頭(針輸出)DCE擁有一個插座(孔輸出)。

常用的介面電路:一RS-232C(15m)標準,採用負邏輯,-15~-3V為邏輯1,+3~+15為邏輯0.

DTE和DCE之間的連線直連即可,兩臺DTE之間的連線需要用到硬體握手訊號。

二、RS-423A(1.2),RS-422A(1.2),RS-485(1.2-1.5)。RS-423A採用差分非平衡傳輸,RS-422A採用差分平衡傳輸,用兩根訊號線,RS-485採用差分平衡傳輸,並擴充套件了RS-422A的功能,在RS-422A中只允許電路有一個傳送器,而RS-485標準允許電路中有多個傳送器。他們的主要區別在於RS-485只能工作在半雙工方式,RS-422A卻可以工作在全雙工方式。

三、USB介面標準。USB通用序列匯流排,是一種應用於PC領域的介面技術,在工業領域太麻煩--!!

12、握手處理:在半雙工方式下的握手訊號可以通過硬體握手處理或軟體握手處理兩種方式完成。其中硬體握手處理即使用專門的導線來作為握手聯絡訊號,握手訊號和資料訊號不在同一條線路上流通;軟體握手處理不使用專門的握手導線,而是與資料訊號一起在同一條導線上傳輸,它通過在資料線上傳送特定的字元來作為握手的專用訊號。

RS-232C支援硬體握手和軟體握手。硬體握手使用DTR/DCR/RTS/CTS四個訊號,DTE裝置通過TxD向DCE裝置傳送資料的條件是:DTR/DCR/RTS/CTS四個訊號引腳電壓必須都為正電壓。

13、串列埠除錯的注意事項:

一、DTE和DCE的區別,若連線兩個DTE,必須要將2號線和3號線交叉連線;

二、決不能帶電拔插串列埠。在連線和拔下串列埠時,一定要保證至少有一端是不帶電的,否則容易燒壞計算機或裝置中的串列埠通訊晶片。

17、使用MSComm控制元件的OnComm事件中接收資料:

 

複製程式碼
voidCTTYDlg::OnOnCommMscomm1()   
{  
// TODO: 在此處新增控制元件通知處理程式程式碼  
    VARIANT input1;  
    BYTE rxdata[2048],aa1;  
    long len1,k;  
    COleSafeArray safearray1;  
    CString strDis;  
    switch(mycomm.GetCommEvent())  
    {  
        case2:  
        input1=mycomm.GetInput();  
        safearray1=input1;  
        len1=safearray1.GetOneDimSize();  
        for(k=0;k<len1;k++)  
        {  
            safearray1.GetElement(&k,rxdata+k);  
        }  
        for(k=0;k<len1;k++)  
        {  
            if(rxdata[k]==13)  
            {  
            m_edit.SetSel(100000,100000);  
            m_edit.ReplaceSel("\15\12");  
            UpdateData(FALSE);  
            }  
            else  
            {  
                if(rxdata[k]<=126&& rxdata[k]>=32)  
                {  
                    strDis+=rxdata[k];  
                    m_edit.SetSel(100000,100000);  
                    m_edit.ReplaceSel(strDis);  
                    strDis="";  
                    UpdateData(FALSE);  
                }  
            }  
        }  
    }  
}  
複製程式碼 18、對於這麼一種情況:使用MSComm控制元件在單文件程式中進行通訊,因為單文件中並沒有可以拖動新增控制元件的視窗,所以需要使用程式來建立CMSComm類的例項。為使建立的例項在整個程式中都能訪問到,一般建立在CMainFrame中。其過程包括: 一、在主框架標頭檔案 MainFrame.h中新增程式碼; 二、在主框架實現檔案 MainFrm.cpp中新增程式碼; 三、在手工定義資原始檔中新增ID,名稱為 myID. 一、在引用區新增: #include "mscomm.h"

在類宣告中新增:

複製程式碼
classCMainFrame:publicCFrameWnd  
{  
    protected:  
    CMSComm mycomm;//宣告類物件  
    afx_msg voidOnCommMscomm();//宣告響應函式  
    DECLARE_EVENTSINK_MAP();//宣告事件引用巨集  
}  
複製程式碼

在cpp中新增:

BEGIN_EVENTSINK_MAP(CMainFrame,CFrameWnd)  
ON_EVENT(CMainFrame,myID,1,OnCommMscomm,VTS_NONE)  
END_EVENTSINK_MAP()  

在主框架類成員函式OnCreate函式中新增程式碼:

複製程式碼
intCMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)  
{  
    DWORD style=WS_VISIBLE|WS_CHILD;  
    if(!myComm.Create(NULL,style,CRect(0,0,0,0),this,myID))  
    {  
        Afx~~~  
    }  
}  
複製程式碼

新增OnCommMscomm成員函式,實現對控制元件事件的響應。

voidCMainFrame::OnCommMscomm()  
{  
    //事件響應程式碼                                                                                              
}  
由控制元件拖動產生的程式碼,編譯器會自動在對話方塊的成員函式OnInitDialog()中自動生成建立控制元件例項的程式碼,初始化及開啟串列埠的操作可以放在該建立程式碼之後;而對手動建立的CMSComm類可以在建立時候後新增 初始化程式碼(Afx~~處)。當然在剛才編過的程式裡初始化什麼的顯得離散的原因,你知道的,是因為按鈕和選擇功能的分開就是了。 19、首先知道一點:對於CEditView類,在MFC的建立嚮導的6 of step6選擇,錯過的話就 無法具備文字編輯功能。 20、使用API程式設計時可以採用同步方式和重疊的非同步方式。 非同步方式採用多執行緒。因為API中串列埠被當做是一個檔案,可以進行檔案的操作。所以可以採用CreateFile()函式開啟串列埠,該函式返回一個串列埠控制代碼,使用該控制代碼初始化串列埠引數。然後使用WriteFile()傳送序列資料,使用ReadFile()可以從串列埠讀取序列資料。操作完畢之後,使用closeHandle()函式關閉串列埠。 21、建立串列埠:在串列埠開啟函式CreateFile中,lpFileName是串列埠號如"COM2"等,dwFlagsAndAttributes在重疊時是FILE_FLAG_OVERLAPPED|FILE_ATTRIBUTE_NORMAL,不重疊時為FILE_ATTRIBUTE_NORMAOL。 SetupCommState函式可以設定串列埠引數,hFile指向一個串列埠裝置的控制代碼,該控制代碼由CreateFile()函式返回。dwInQueue,dwOutQueue是緩衝區大小,以位元組為單位。 22、關閉串列埠:只用採用CloseHandle即可。 BOOL CloseHandle(HANDLE hObject); 23、傳送資料:使用WriteFile函式 24、接收資料:使用ReadFile函式。 25、定時接收資料,需要將相應函式語句寫在定時器響應函式中: