串口是常用的計算機與外部串行設備之間的數據傳輸通道,由于串行通信方便易行,所以應用廣泛。我們可以利用Windows API 提供的通信函數編寫出高可移植性的串行通信程序。
使用MFC來編寫串口程序,需要有一定的c++語言功底,要清楚MFC代碼的組織方式。
準備工作
使用的通訊控件是:Microsoft Communications Control, Version 6.0
需要安裝同時VC6.0和VS2012才可以使用這個通訊控件。
工具-》選擇工具箱-》COM組件 添加到工具條中,然后再添加到窗體上,任何位置都OK,編譯運行以后不顯示。
新建MFC窗體,win32下,基于對話框,命名為MFC(建議和我一樣,這樣方便些。)。應該都知道(不知道的可以參考百度文庫里,好多,不多說了)
本代碼是最簡單的串口程序,參數設置都在代碼中提醒。只需要設置COM號
窗體樣式:
可以核對一下,頭文件名:
[cpp] view plain copyMFC.h
MFCDlg.h // main frame 主要窗體、父窗體的頭文件,主要的修改和添加代碼區
Resource.h
stdafx.h
targetver.h
源文件名:
MFC.cpp
MFCDlg.cpp // main frame 主要窗體、父窗體的cpp文件,主要的修改和添加代碼區
stdafx.cpp
一、準備工作
1.設置控件屬性
各ID如下:
IDC_COMBO_CommSeclect 屬性里面的Data:COM1;COM2;COM3;COM4;COM5;COM6; // 注意使用;分隔
2.使用 項目-》類向導 定義變量如下
編譯一下,應該不會有錯。
下面準備添加代碼
主要的代碼區是 MFCDlg.h 和 MFCDlg.cpp 特別要注意,不要亂 ~
二、準備添加代碼,接收下位機發來的數據并顯示
1.MFCDlg.cpp中初始化
函數名:
[cpp] view plain copyBOOL CMFCDlg::OnInitDialog()
添加如下:
[cpp] view plain copy// TODO: 在此添加額外的初始化代碼
m_ComboBox.SetCurSel(2);//打開軟件時串口選擇框默認顯示COM1 子選項編號的排序是從0開始的。
2.MFCDlg.cpp中BUTTON1消息響應函數 // 打開串口
雙擊繪圖窗口里的BUTTON1控件進入代碼編輯區添加代碼如下:(想省事,直接看下面的代碼)
或者進入類向導(建議此方法,有利于體會MFC的代碼組織方式)
添加代碼如下: // 接收下位機的數據 看懂了注釋你就可以按照需要自由組織啦。當下只是最簡單的,8位數據,1停止,9600,無校驗
[cpp] view plain copyvoid CMFCDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知處理程序代碼
//m_Index_int = ((CComboBox*)GetDlgItem(IDC_COMBO_CommSelect))-》GetCurSel();//當前選中的
/***********
GetCurSel() 函數:用以得到用戶選中下拉列表框中數據的索引值。返回的值是重0開始的,如果沒有選擇任何選項將會返回-1
************/
//返回的是打開的端口號
switch(m_ctrlComm.get_PortOpen())//點擊打開或關閉串口按鍵時,根據當前的串口是否打開經行相應操作
{
case 0://當前串口是關閉的,則進行打開串口操作
m_Index = ((CComboBox*)GetDlgItem(IDC_COMBO_CommSeclect))-》GetCurSel();//當前選中的行
m_ctrlComm.put_CommPort(m_Index + 1);//如果要打開串口則應先選擇哪個串口
m_ctrlComm.put_PortOpen(TRUE);//打開串口
UpdateData(FALSE);//更新按鍵狀態
if(m_ctrlComm.get_PortOpen())//如過已經打開串口,
{
SetDlgItemText(IDC_BUTTON1,_T(“關閉串口”));//更改按鍵提示
m_ctrlComm.put_Settings(_T(“9600,n,8,1”));//打開軟件時端口設置默認波特率9600,無校驗位,8位數據,1位停止
m_ctrlComm.put_InputMode(1);//1:表示以二進制方式撿取數據;
//0:表示以文本方式撿取數據
m_ctrlComm.put_RThreshold(1);//參數1 表示每當串口接收緩沖區中有多余或等于一個字符時將引發一個接收數據的OnComm事件
//參數0 表示數據傳輸事件不會引發OnComm事件,即不響應。
m_ctrlComm.put_InputLen(0);//0: 缺省值。表示使MSComm控件讀取接收緩沖區中的所有內容。
m_ctrlComm.get_Input();//先預讀緩沖區以清除殘留數據
UpdateData(FALSE);
}
else
AfxMessageBox(_T(“串口打開失敗”));
break;
case 1:
//當前串口是打開的則進行關串口操作
// m_ctrlComm.SetCommPort(m_Index_int + 1);//如果要打開串口則應先選擇哪個串口
m_ctrlComm.put_PortOpen(FALSE);
if(!m_ctrlComm.get_PortOpen())//如果已經關閉串口,
{
SetDlgItemText(IDC_BUTTON1,_T(“打開串口”));
UpdateData(FALSE);
}
else
AfxMessageBox(_T(“串口關閉失敗”));
break;
default:
AfxMessageBox(_T(“cannot open Serial Port”));
break;
}
m_ComboBox.SetCurSel(m_Index);//打開軟件時串口選擇框默認顯示COM1 子選項編號的排序是從0開始的。
// m_BaudRate_M.SetCurSel(m_BaudRate);//打開軟件時波特率選擇框默認顯示9600
// m_Data_Select_M.SetCurSel(m_Data_Select);//打開軟件時數據位選擇框默認顯示8
// m_StopBit_M.SetCurSel(m_StopBit);//打開軟件時停止位選擇框默認顯示N 無停止位
// m_ParityCheck_M.SetCurSel(m_ParityCheck);// 奇偶校驗
}
3.MFCDlg.cpp中Oncomm事件響應(就是那個“電話”控件的事件響應)
注意,此刻的頭文件和源文件的內容有一些變化了,可以看一下,這兩個是IDC_MSCOMM1控件帶來的(添加OnComm事件后才會出現)。
添加代碼如下:
[cpp] view plain copyvoid CMFCDlg::OnOncommMscomm1()
{
// TODO: 在此處添加消息處理程序代碼
VARIANT variant_inp;
COleSafeArray safearry_inp;
LONG len,k;
BYTE rxdata[2048];
CString strtemp;
int order;
if(m_ctrlComm.get_CommEvent() == 2)//事件值為2表示接收緩沖區內有數據
{
//以下根據自己的通訊協議添加處理代碼
variant_inp = m_ctrlComm.get_Input();//讀緩沖區
safearry_inp = variant_inp;//VARIANT轉化為COleSafeArray
len = safearry_inp.GetOneDimSize();//字符長度
for(k=0;k《len;k++)
{
safearry_inp.GetElement(&k,rxdata+k);//轉化為BYTE型數組
}
for(k=0;k《len;k++)//將數組轉化成Cstring型變量
{
BYTE bt = *(char*)(rxdata+k);
//if(m_ctrlHexSend.GetCheck())
// strtemp.Format(“%02x”,bt);
//else
strtemp.Format(_T(“%c”),bt);//將字符送入臨時變量strtemp中存放
m_strRXData+=strtemp;//加入接收編輯框對應字符串
/*******************
以上的語句可以進行對sbuf的讀取。
***********************/
order = _ttoi(strtemp);//order是字符轉化后的int值
}
UpdateData(FALSE);//更新編輯框內容(主要是接收編輯框中的)
}
m_ComboBox.SetCurSel(m_Index);//打開軟件時串口選擇框默認顯示COM1 子選項編號的排序是從0開始的。
// m_BaudRate_M.SetCurSel(m_BaudRate);//打開軟件時波特率選擇框默認顯示9600
// m_Data_Select_M.SetCurSel(m_Data_Select);//打開軟件時數據位選擇框默認顯示8
// m_StopBit_M.SetCurSel(m_StopBit);//打開軟件時停止位選擇框默認顯示N 無停止位
// m_ParityCheck_M.SetCurSel(m_ParityCheck);// 奇偶校驗
}
編譯運行,Bingo!
三、準備添加代碼,向下位機發送數據
1.MFCDlg.cpp中 IDC_SendBtn 消息響應函數 // 打開串口
[cpp] view plain copyvoid CMFCDlg::OnBnClickedSendbtn()
{
// TODO: 在此添加控件通知處理程序代碼
UpdateData(TRUE);
long len;
CByteArray array;
len = m_strTXData.GetLength();//發送數據的長度
array.RemoveAll();
array.SetSize(len);
for(int i=0;i《len;i++)
array.SetAt(i, m_strTXData[i]);
m_ctrlComm.put_Output(COleVariant(array)); // 發送數據
}
然后,就沒有然后啦~
四、最后說明下一些tips
1.VC2012 和 VC6.0的一些函數名的變化(坑死人)
比如:在VC6.0下
[cpp] view plain copy《span style=“white-space:pre”》 《/span》m_ComPort.SetPortOpen(FALSE);
m_ComPort.SetCommPort(m_comn+1); //設置串口號
m_ComPort.SetInBufferSize(1024); //接收緩沖區
m_ComPort.SetOutBufferSize(1024);//發送緩沖區
m_ComPort.SetInputLen(0);//設置當前接收區數據長度為0,表示全部讀取
m_ComPort.SetInputMode(1);//以二進制方式讀寫數據
m_ComPort.SetRThreshold(1);//接收緩沖區有1個及1個以上字符時,將引發接收數據的OnCommMscomm事件
在VC2012下,函數名需要做一下改動:
[cpp] view plain copym_ComPort.put_PortOpen(FALSE);
m_ComPort.put_CommPort(m_comn+1); //設置串口號
m_ComPort.put_InBufferSize(1024); //接收緩沖區
m_ComPort.put_OutBufferSize(1024);//發送緩沖區
m_ComPort.put_InputLen(0);//設置當前接收區數據長度為0,表示全部讀取
m_ComPort.put_InputMode(1);//以二進制方式讀寫數據
m_ComPort.put_RThreshold(1);//接收緩沖區有1個及1個以上字符時,將引發接收數據的OnCommMscomm事件
還有GetPortOpen() 要改成 get_PortOpen()
以此類推。
2.本方法必須安裝VC6.0環境,不然這個控件就不能使用了。
3.分享是這個時代的基本品質 ~ 祝大家成功 ~
五、功能補充,使用十六進制發送和接收(modbus通訊協議)
modbus的串口通訊的發送方式是使用16進制數,和上文的字符發送方式不同,但是本質上都是使用2進制傳送。
其最大差別在于比如要傳送(01),字符方式:0-》48(ASCII值),0x30(ASCII值的十六進制形式)-》0010 0000 第一次發送;
1-》49(ASCII值),0x31(ASCII值的十六進制形式)-》0010 0001第二次發送。結束
16進制方式: 0x01-》1(10進制),對應的ASCII字符(SOH(start of headling))-》0000 0001 一次發送結束。
注意,這里標出來的不是說要經過這樣的轉化以后才能發送,本質上串口發出去的都是二進制。差別在于你要選擇發什么數據給put_Output()。不理解的話,可以實踐一下加深理解。
TOOL 1: 串口調試器,可以監控,不占COM。http://pan.baidu.com/s/1qXTZudm
TOOL 2: 虛擬串口 http://pan.baidu.com/s/1nuUfzT3
1.16進制方式發送
添加兩個函數(第一個)
函數名在.h文件中聲明Public:int String2Hex(CString str,CByteArray &senddata);
函數體如下:
[cpp] view plain copyint CSerialModbus331Dlg::String2Hex(CString str,CByteArray &senddata)
{
int hexdata,lowhexdata;
int hexdatalen = 0;
int len = str.GetLength();
senddata.SetSize(len/2);
for(int i = 0;i《len;)
{
char lstr;
char hstr = str[i];
if(hstr == ‘ ’)
{
i++;
continue;
}
i++;
if(i》len)
break;
lstr = str[i];
hexdata = ConvertHexChar(hstr);
lowhexdata = ConvertHexChar(lstr);
if((hexdata == 16)||(lowhexdata == 16))
break;
else
hexdata = hexdata*16 + lowhexdata;
i++;
senddata[hexdatalen] = (char)hexdata;
hexdatalen++;
}
senddata.SetSize(hexdatalen);
return hexdatalen;
}
函數(第二個)
函數名在.h文件中聲明Public:char ConvertHexChar(char ch);
函數體如下:
[cpp] view plain copychar CSerialModbus331Dlg::ConvertHexChar(char ch)
{
if((ch》=‘0’)&&(ch《=‘9’))
return ch-0x30;
else if((ch》=‘A’)&&(ch《=‘F’))
return ch-‘A’+10;
else if((ch》=‘a’)&&(ch《=‘f’))
return ch-‘a’+10;
else return (-1);
}
最后修改發送函數(使用按鈕click事件)
[cpp] view plain copyvoid CSerialModbus331Dlg::OnBnClickedButtonSendhex()
{
// TODO: 在此添加控件通知處理程序代碼
UpdateData(TRUE); //讀取編輯框內容
CByteArray hexdata;
int len = String2Hex(m_strTXData,hexdata); //此處返回的len可以用于計算發送了多少個十六進制數
m_ctrlComm.put_Output(COleVariant(hexdata)); //發送十六進制數據
}
注意一個問題
網絡復制的代碼常出現 error C3872: “0xa0”: 此字符不允許在標識符中使用
參考:http://blog.csdn.net/qqyuanhao163/article/details/40785983
原因是存在中文的空格,解決方法是把提示區的空格全部替換成英文空格。
2.16進制方式顯示
在OnComm事件中修改一句代碼如下:
[cpp] view plain copy// strtemp.Format(_T(“%c”),bt);//將字符送入臨時變量strtemp中存放
strtemp.Format(_T(“%02X”),bt);//將字符送入臨時變量strtemp中存放
評論