標籤:
串口通訊簡介
一般來說,電腦都有一個或多個序列埠,這些串口提供了外部裝置與PC進行資料轉送和通訊的通道,在CPU和外設之間充當解譯器的角色。當字元資料從CPU發送給外設時,這些字元資料將被轉換成串列位元流資料;當接收資料時,位元流資料被轉換為字元資料傳遞給CPU,再進一步說,在作業系統方面,Windows用通訊驅動程式(COMM.DRV)調用API函數發送和接收資料;當用通訊控制項或聲明調用API函數時,它們由COMM.DRV解釋並傳遞給裝置驅動程式。作為一個程式員,要編寫通訊程式,只需知道通訊控制項提供的Windows API通訊函數的介面即可,換句話說,只需設定和監視通訊控制項的屬性和事件即可。
串口通訊方法一般有以下幾種:
- 利用Windows API通訊函數;
- 利用Visual C++的標準通訊函數_inp、_inpw、_inpd、_outp、_outpw、_outpd等直接對串口進行操作;
- 通過微軟的串口通訊控制項MSComm,它是一種ActiveX控制項;
- 利用第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屬性:向發送緩衝區傳遞待發送的資料。
- InBufferCount和OutBufferCount屬性:分別確定當前駐留在接收緩衝區等待被取出和發送緩衝區準備發送的字元數量,這兩個屬性設定為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%\system32copy .\\MSCOMM\\MSCOMM32.DEP %windir%\system32copy .\\MSCOMM\\MSCOMM32.oca %windir%\system32copy .\\MSCOMM\\mscomm32.ocx %windir%\system32regsvr32 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屬性。
相關連結:
深入淺出VC++串口編程(五) 基於第三方類庫:http://blog.csdn.net/nash635/article/details/5339704
VS2010下MFC的串口編程