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