使用MFC来编写串口程序,需要有一定的c++语言功底,要清楚MFC代码的组织方式。
鉴于绝大多数的教程还停留在vc6.0这个骨灰级的环境,特在此说明一下VC2012下的代码组织方式,和大家一起交流下~
本文略去建立窗体的步骤,但是给出了窗体的样式,不会建立窗体的童鞋可以百度一下就知道了,很简单的,所以就不多说啦 ~
0、准备工作
使用的通讯控件是:Microsoft Communications Control, Version 6.0
需要安装同时VC6.0和VS2012才可以使用这个通讯控件。或则单独安装MSCOMM控件。安装方法:http://blog.csdn.net/tianyake_1/article/details/65012181
工具->选择工具箱->COM组件 添加到工具条中,然后再添加到窗体上,任何位置都OK,编译运行以后不显示。
新建MFC窗体,win32下,基于对话框,命名为MFC(建议和我一样,这样方便些。)。应该都知道(不知道的可以参考百度文库里,好多,不多说了)
本代码是最简单的串口程序,参数设置都在代码中提醒。只需要设置COM号
窗体样式:
可以核对一下,头文件名:
- MFC.h
- MFCDlg.h
- Resource.h
- stdafx.h
- targetver.h
- 源文件名:
- MFC.cpp
- MFCDlg.cpp
- stdafx.cpp
一、准备工作
1.设置控件属性
各ID如下:
IDC_COMBO_CommSeclect 属性里面的Data:COM1;COM2;COM3;COM4;COM5;COM6; // 注意使用;分隔
2.使用 项目->类向导 定义变量如下
编译一下,应该不会有错。
下面准备添加代码
主要的代码区是 MFCDlg.h 和 MFCDlg.cpp 特别要注意,不要乱 ~
二、准备添加代码,接收下位机发来的数据并显示
1.MFCDlg.cpp中初始化
函数名:
- BOOL CMFCDlg::OnInitDialog()
添加如下:
2.MFCDlg.cpp中BUTTON1消息响应函数 // 打开串口
双击绘图窗口里的BUTTON1控件进入代码编辑区添加代码如下:(想省事,直接看下面的代码)
或者进入类向导(建议此方法,有利于体会MFC的代码组织方式)
添加代码如下: // 接收下位机的数据 看懂了注释你就可以按照需要自由组织啦。当下只是最简单的,8位数据,1停止,9600,无校验
- void CMFCDlg::OnBnClickedButton1()
- {
-
-
-
-
-
-
- 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"));
- m_ctrlComm.put_InputMode(1);
-
- m_ctrlComm.put_RThreshold(1);
-
- m_ctrlComm.put_InputLen(0);
- m_ctrlComm.get_Input();
- UpdateData(FALSE);
- }
- else
- AfxMessageBox(_T("串口打开失败"));
-
- break;
- case 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);
-
-
-
-
-
- }
3.MFCDlg.cpp中Oncomm事件响应(就是那个“电话”控件的事件响应)
注意,此刻的头文件和源文件的内容有一些变化了,可以看一下,这两个是IDC_MSCOMM1控件带来的(添加OnComm事件后才会出现)。
添加代码如下:
- void CMFCDlg::OnOncommMscomm1()
- {
-
- VARIANT variant_inp;
- COleSafeArray safearry_inp;
- LONG len,k;
- BYTE rxdata[2048];
- CString strtemp;
- int order;
- if(m_ctrlComm.get_CommEvent() == 2)
- {
-
- variant_inp = m_ctrlComm.get_Input();
- safearry_inp = variant_inp;
- len = safearry_inp.GetOneDimSize();
- for(k=0;k<len;k++)
- {
- safearry_inp.GetElement(&k,rxdata+k);
- }
- for(k=0;k<len;k++)
- {
- BYTE bt = *(char*)(rxdata+k);
-
-
-
- strtemp.Format(_T("%c"),bt);
- m_strRXData+=strtemp;
-
-
-
- order = _ttoi(strtemp);
-
- }
- UpdateData(FALSE);
-
- }
- m_ComboBox.SetCurSel(m_Index);
-
-
-
-
-
- }
编译运行,Bingo!
三、准备添加代码,向下位机发送数据
1.MFCDlg.cpp中 IDC_SendBtn 消息响应函数 // 打开串口
- void CMFCDlg::OnBnClickedSendbtn()
- {
-
- 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下
- <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);
- m_ComPort.SetInputMode(1);
- m_ComPort.SetRThreshold(1);
在VC2012下,函数名需要做一下改动:
- m_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);
- m_ComPort.put_InputMode(1);
- m_ComPort.put_RThreshold(1);
还有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
参考资料:(串口程序部分)
http://wenku.baidu.com/view/9e9b258c6c175f0e7cd137b6.html?from=search
1.16进制方式发送
添加两个函数(第一个)
函数名在.h文件中声明Public:int String2Hex(CString str,CByteArray &senddata);
函数体如下:
- int 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);
函数体如下:
- char 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事件)
- void CSerialModbus331Dlg::OnBnClickedButtonSendhex()
- {
-
- UpdateData(TRUE);
- CByteArray hexdata;
- int len = String2Hex(m_strTXData,hexdata);
- m_ctrlComm.put_Output(COleVariant(hexdata));
-
- }
注意一个问题
网络复制的代码常出现 error C3872: "0xa0": 此字符不允许在标识符中使用
参考:http://blog.csdn.NET/qqyuanhao163/article/details/40785983
原因是存在中文的空格,解决方法是把提示区的空格全部替换成英文空格。
2.16进制方式显示
在OnComm事件中修改一句代码如下:
-
- strtemp.Format(_T("%02X"),bt);