串口通信簡介
一般來說,計算機都有一個或多個串行端口,這些串口提供了外部設備與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%\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屬性。
相關鏈接:
深入淺出VC++串口編程(五) 基於第三方類庫:http://blog.csdn.net/nash635/article/details/5339704