VC2012/VS2012 MFC串口通讯上位机程序教程笔记

使用MFC来编写串口程序,需要有一定的c++语言功底,要清楚MFC代码的组织方式。

鉴于绝大多数的教程还停留在vc6.0这个骨灰级的环境,特在此说明一下VC2012下的代码组织方式,和大家一起交流下~

本文略去建立窗体的步骤,但是给出了窗体的样式,不会建立窗体的童鞋可以百度一下就知道了,很简单的,所以就不多说啦 ~ 

0、准备工作

使用的通讯控件是:Microsoft Communications Control, Version 6.0

需要安装同时VC6.0和VS2012才可以使用这个通讯控件。

工具->选择工具箱->COM组件 添加到工具条中,然后再添加到窗体上,任何位置都OK,编译运行以后不显示。

新建MFC窗体,win32下,基于对话框,命名为MFC(建议和我一样,这样方便些。)。应该都知道(不知道的可以参考百度文库里,好多,不多说了)

本代码是最简单的串口程序,参数设置都在代码中提醒。只需要设置COM号

窗体样式:



可以核对一下,头文件名:

MFC.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中初始化

函数名:
BOOL CMFCDlg::OnInitDialog()
添加如下:
// TODO: 在此添加额外的初始化代码
	m_ComboBox.SetCurSel(2);//打开软件时串口选择框默认显示COM1  子选项编号的排序是从0开始的。

2.MFCDlg.cpp中BUTTON1消息响应函数 // 打开串口

双击绘图窗口里的BUTTON1控件进入代码编辑区添加代码如下:(想省事,直接看下面的代码)
或者进入类向导(建议此方法,有利于体会MFC的代码组织方式)


添加代码如下:  //  接收下位机的数据  看懂了注释你就可以按照需要自由组织啦。当下只是最简单的,8位数据,1停止,9600,无校验

void 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事件后才会出现)。


添加代码如下:

void 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 消息响应函数 // 打开串口

void 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下
<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下,函数名需要做一下改动:
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);//设置当前接收区数据长度为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

参考资料:(串口程序部分)

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()
{
	// 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事件中修改一句代码如下:

   //         strtemp.Format(_T("%c"),bt);//将字符送入临时变量strtemp中存放    
			strtemp.Format(_T("%02X"),bt);//将字符送入临时变量strtemp中存放 





©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页