转自:https://www.sxzhongrui.com/NNNNNNNNNNNNY/article/details/45341983
本例程是在VS2010环境下,使用MFC做的是一个简单的串口通信程序。欢迎交流。
程序源码(含一个虚拟串口软件):http://www.sxzhongrui.com/detail/nnnnnnnnnnnny/8643049?
1.首先,在VS2010环境下创建基于对话框的MFC应用程序。
a.选择菜单项 文件—>新建—>项目,弹出新建项目对话框。
b.左侧面板中选择Visual C++下的MFC,中间窗口中选择MFC应用程序,然后在下面的名称编辑框中键入工程名称,本例取名SerialTest,在位置编辑框中设置工程的保存路径。点击确定。
c.在弹出的MFC应用程序向导中,点下一步到应用程序类型界面,在应用程序类型下选择基于对话框,其他使用默认设置,点击完成。
2.接下来为对话框添加所需要的控件
a.在资源视图的资源树中双击Dialog下的IDD_SERIALTEST_DIALOG,出现如下对话框编辑窗口(默认新建项目也是出现此界面,所以此步无需操作,仅作说明)
b.选中控件按Delete键删除自动生成的静态文本框TODO:在此放置对话框控件、按钮控件确定和取消。
c.从右侧工具箱中添加两个Button按钮,两个Edit Control编辑框,一个StaticText静态文本框和一个Combo Box组合框。(提示:在工具箱中鼠标左键点下所需控件不放开,并拖到IDD_ADDITION_DIALOG对话框模板上,模板上会出现一个虚线框,我们找到合适的位置松开鼠标左键放下它,即可成功添加所需控件)。
d.适当改变控件大小,并合理布局控件,使之如下图所示(提示:1.用鼠标左键选中控件后周围出现虚线框,然后鼠标移到虚线框上几个黑点的位置会变成双向箭头的形状,此时就可以按下鼠标左键并拖动来改变控件大小了。当然,整个对话框模板也可以用这种方法改变大小。2.几个控件的对齐等操作可按Ctrl键同时选中几个控件,然后选择软件左上侧工具条中的几个按钮进行操作)
e.更改控件属性(选中控件,鼠标移至右侧属性,即出现该控件的属性面板):更改Button1的Caption为清除接收框、ID为IDC_BUTTON_CLEANUP;?Button2的Caption为发送数据、ID为IDC_BUTTON_SEND;下面的编辑框用于显示发送数据,ID设为IDC_EDIT_TXDATA; 下面的编辑框用于显示接收到的数据,ID设为IDC_EDIT_RXDATA;静态文本框的Caption为选择COM口:;ComboBox的?ID设为IDC_COMBO_COM,Type属性设为DropList(即下拉列表式组合框,其编辑框是不能编辑的,只能由用户在下拉列表框中选择了某项后,在编辑框中显示其文本),Sort属性设为False,以取消自动排序显示。
f.添加串口通信控件。首先把串口通信控件加入到工具箱中,因为默认的工具箱是不带 MS 串口通信控件的。方法如下:
在工具箱界面点击鼠标右键出现如下界面:
选择选择项出现如下界面,然后选择COM组件属性页,勾选下图所示项,点击确定。
这样串口控件就已经添加至工具箱了,
将其拖入对话框里,如图所示(串口控件在运行时是看不到的)
至此控件添加基本上就完成了。
3.编写消息处理函数
a. 给Combo Box组合框添加列表项。选中ComboBox控件,右键->添加变量,设置变量名为m_comboCom,如图所示,点击完成。
在左侧类视图面板中,选择CSerialTestDlg类,双击下方的OnInitDialog()函数进入CSerialTestDlg::OnInitDialog()函数
在//TODO:在此添加额外的初始化代码添加如下代码:
// TODO: 在此添加额外的初始化代码
m_comboCom.AddString(_T(
"COM1"));
// 为组合框控件的列表框添加列表项
m_comboCom.AddString(_T(
"COM2"));
m_comboCom.AddString(_T(
"COM3"));
m_comboCom.AddString(_T(
"COM4"));
m_comboCom.AddString(_T(
"COM5"));
m_comboCom.AddString(_T(
"COM6"));
m_comboCom.AddString(_T(
"COM7"));
m_comboCom.AddString(_T(
"COM8"));
m_comboCom.AddString(_T(
"COM9"));
m_comboCom.AddString(_T(
"COM10"));
b.我们希望在组合框中选中的列表项改变时,以打开相应串口并设置串口参数,那么这就要用到CBN_SELCHANGE通知消息。选中ComboBox,右键->添加事件处理程序,出现如图所示界面,点击添加编辑。
在CSerialTestDlg::OnCbnSelchangeComboCom()函数中添加代码之前我们需要给串口控件添加控制变量m_ctrlComm(方法见3.->a.),如图
并在CSerialTestDlg类中添加变量m_setOk(用于标记串口是否设置好),方法:在类视图面板中选中CSerialTestDlg,右键->添加->添加变量,
在弹出的界面中,选择访问:private,变量类型:bool,变量名:m_setOk。这里看到SerialTestDlg.h中已经添加了此变量,并在构造函数中已进行了初始化:
现在我们回到CSerialTestDlg::OnCbnSelchangeComboCom()中,添加如下代码:
void CSerialTestDlg::OnCbnSelchangeComboCom()
{
// TODO: 在此添加控件通知处理程序代码
int nSel;
nSel = m_comboCom.GetCurSel();
//获取组合框控件的列表框中选中项的索引
m_ctrlComm.put_CommPort(nSel+
1);
//选择串口号(这里因为列表框的索引号是从0开始,所以(nSel+1)对应的才是我们所选的串口号)
m_ctrlComm.put_PortOpen(TRUE);
//打开串口
m_ctrlComm.put_RThreshold(
2);
//收到两个字节引发OnComm事件
m_ctrlComm.put_InputMode(
1);
//输入模式选为二进制
m_ctrlComm.put_Settings(_T(
"9600,n,8,1"));
//设置串口参数,波特率,无奇偶校验,位停止位,位数据位
m_ctrlComm.put_InputMode(
1);
// 以二进制方式检取数据
m_ctrlComm.put_RThreshold(
1);
//参数1表示每当串口接收缓冲区中有多于或等于1个字符时将引发一个接收数据的OnComm事件
m_ctrlComm.put_InputLen(
0);
//设置当前接收区数据长度为0
m_ctrlComm.get_Input();
//先预读缓冲区以清除残留数据
m_setOk =
true;
//标记串口设置OK
}
c.添加串口事件消息处理函数。在此步之前,我们先给IDC_EDIT_TXDATA编辑框添加CString变量m_strTXData和IDC_EDIT_TXDATA编辑框添加CString变量m_strRXData(方法见3.->a.,注意变量类型改为CString,类别改为Value)
然后我们添加串口消息事件处理函数(方法见3.->b.),并将函数改名为OnComm
在CSerialTestDlg::OnComm()函数中添加如下代码:
void CSerialTestDlg::OnComm()
{
// TODO: 在此处添加消息处理程序代码
VARIANT variant_inp;
//Variant 是一种特殊的数据类型,除了定长String数据及用户定义类型外,可以包含任何种类的数据。
COleSafeArray safearray_inp;
LONG len,k;
BYTE rxdata[
2048];
//设置BYTE数组 An 8-bit integer that is not signed.
CString strtemp;
if(m_ctrlComm.get_CommEvent() ==
2)
//事件值为2表示接收缓冲区内有字符
{
以下你可以根据自己的通信协议加入处理代码
variant_inp=m_ctrlComm.get_Input();
//读缓冲区
safearray_inp=variant_inp;
//VARIANT型变量转换为ColeSafeArray型变量
len=safearray_inp.GetOneDimSize();
//得到有效数据长度
for(k=
0;k safearray_inp.GetElement(&k,rxdata+k); //转换为BYTE型数组 for(k= 0;k //将数组转换为Cstring型变量 { BYTE bt=*( char*)(rxdata+k); //字符型 strtemp.Format(_T( "%c"),bt); //将字符送入临时变量strtemp存放 m_strRXData+=strtemp; //加入接收编辑框对应字符串 } } UpdateData(FALSE); //更新编辑框内容 } d.添加发送数据按钮的BN_CLICKED消息的处理函数(方法见3.->b.), 在CSerialTestDlg::OnBnClickedButtonSend()中添加如下代码: void CSerialTestDlg::OnBnClickedButtonSend() { // TODO: 在此添加控件通知处理程序代码 if (m_setOk == true) //判断是否打开并初始化串口 { UpdateData(TRUE); //读取编辑框内容 m_ctrlComm.put_Output(COleVariant(m_strTXData)); //发送数据 } else { MessageBox(_T( "请先选择COM口")); } } e.同理,添加清楚接收框按钮的BN_CLICKED消息的处理函数(方法见3.->b.), 在CSerialTestDlg::OnBnClickedButtonCleanup()中添加如下代码: void CSerialTestDlg::OnBnClickedButtonCleanup() { // TODO: 在此添加控件通知处理程序代码 m_strRXData= ""; UpdateData(FALSE); //更新编辑框内容 } 4.生成可执行代码 a.点击启动调试按钮或摁F5,编译成功 b.点击菜单栏生成->配置管理器, 在活动解决方案配置下拉菜单中选择Release,点击关闭。 再次编译,即可看到在目录中生成了的Release文件夹下生成了.exe文件。 5.检测软件是否能正常工作, a.这里首先要下一个虚拟串口。下载地址:http://www.sxzhongrui.com/soft/21406.html(也可在文首提供的连接处下载) 安装完成后,打开软件 虚拟串口COM3和COM5:在右侧端口一、二分别改为COM3和COM5,点击添加端口。 b.然后打开两个刚才生成的.exe文件,分别选择COM3和COM5。 分别在发送数据的编辑框里随便输入几个字符,点击发送数据 成功。 【vs2010基于MFC对话框】实现客户–服务器模式下网络程序的Shocket双向通信 编译环境采用Visual Studio 2010,此工程为学校课堂练习,要求实现基于MFC对话框的两端通信。两端分别为客户端和服务器端。下面是实现客户端的工程过程: 1.建立工程 建立一个新的基于MFC对话框的项目,项目名称为ChatClient。然后在高级功能中勾上wIndows套接字项,点击完成创立工程,如图: 2.添加类 创建好工程后,首先要创建类名为CClientSocket的客户Socket对象,给工程添加MFC类,基类为CAsyncSocket,类名为CClientSocket: 点击类视图可以查看是否添加成功,出现CClientSocket类为成功。 3.添加头文件声明 要使客户端程序能够创建和控制本地的Socket,在客户端工程界面控制模块的头文件ChatClientDlg.h中添加如下两行代码: #include "ClientSocket.h" //使主界面程序能够访问Socket类的代码文件 CClientSocket m_ClientSocket;//为了后面与服务器通信而定义的Socket成员变量 在Socket源文件ClientSocket.cpp中添加头文件声明: #include "ChatClientDlg.h" 4.设计布局界面和关联变量 接下来是给客户端设计一个简洁的界面: 在界面上添加一个IP地址控件和列表框,文本框两个,四个按钮,同时修改好Caption。 下一步,为IP地址控件添加变量ServerIP,为上面的文本框控件添加value类别int型变量sPort,为下面的文本框控件添加value类别CString型变量sWords为列表框关联变量m_ListWords。(可点击类向导查看添加的变量) 5.添加客户端源码 接下来为工程添加灵魂,给每个按钮加入响应代码。 连接服务器按钮代码: //连接服务器 BYTE nFild[4]; CString sIP; UpdateData(); ServerIP.GetAddress(nFild[0],nFild[1],nFild[2],nFild[3]); sIP.Format("%d.%d.%d.%d",nFild[0],nFild[1],nFild[2],nFild[3]); m_ClientSocket.Create(); //创建客户端Socket m_ClientSocket.Connect(sIP,sPort); //发起连接请求 断开按钮代码: //断开与服务器的连接 m_ClientSocket.Close(); //关闭客户端Socket m_ListWords.AddString("从服务器断开"); 发送消息按钮代码: //向服务器发信息 UpdateData(); m_ClientSocket.Send(m_sWords,m_sWords.GetLength()); //发信息 m_ListWords.AddString("发送:" + m_sWords); m_ListWords.SetTopIndex(m_ListWords.GetCount() - 1); 6.重写类函数 在类视图中选中CClientSocket,在属性窗口中单击“重写”按钮,就可以为该Socket类编写被动响应网络事件的函数 选择OnConnect()函数,为其添加代码,系统将该函数的代码自动置于ClientSocket.cpp文件中,OnConnect()代码如下: //确认客户端是否成功连接到服务器 if(nErrorCode) { AfxMessageBox("连接失败,请您重试!"); return; } ((CChatClientDlg*)(AfxGetApp()->m_pMainWnd))->m_ListWords.AddString("连接服务器成功!"); ((CChatClientDlg*)(AfxGetApp()->m_pMainWnd))->m_ListWords.SetTopIndex( ((CChatClientDlg*)(AfxGetApp()->m_pMainWnd))->m_ListWords.GetCount()- 1); 到此,两端中的客户端已完成,再建立一个服务器端工程,即可实现两端通信连接。 VS2010 基于MFC 串口通信上位机,实现串口发送、接收数据及接收数据保存。 提示:不清楚的函数可在MSDN 内查询 打开VS2010 创建 MFC应用程序,选择基于对话框类 ,勾选最大化框、最小化框后 选择下一步完成。 将对话框上原有的控件删除,添加所需的控件。 发送部分:Radio Button、Combo Box 接收部分:Edit Control 选择串口、波特率:Combo Box 按键:打开串口、发送数据、清除数据、保存数据、退出 图标显示:指示串口打开、关闭状态 Activex 串口控件:Microsoft Communications Control,Version 6.0 提示:VS2010 内找不到此Activex 控件的解决方法 :https://www.sxzhongrui.com/madao1234/article/details/82047796 3.为各控件添加控制程序 ① 打开串口按钮 ,显示指示图标 提示:双击打开按钮 会直接跳转到 void C串口Dlg::OnBnClickedButtonOpen() 函数 void C串口Dlg::OnBnClickedButtonOpen() { // TODO: 在此添加控件通知处理程序代码 CString str,str1,n; //定义3个CString变量 GetDlgItemText(IDC_BUTTON_OPEN,str); //获取打开串口按键的Caption,IDC_BUTTON_OPEN:打开串口按键的ID CWnd *h1; //定义句柄类的指针 h1 = GetDlgItem(IDC_BUTTON_OPEN); // h1 赋为 指向IDC_BUTTON_OPEN 按键的句柄 if(!m_mscom.get_PortOpen()) //如果串口未打开 { //m_comb3:波特率选择组合框的 成员变量 m_comb3.GetLBText(m_comb3.GetCurSel(),str1); //取得波特率口当前的选择值,存放在str1里 str1 = str1 + _T(",n,8,1"); m_mscom.put_Settings(str1);// 设置波特率、无校验,8数据位,1个停止位 m_mscom.put_InBufferSize(1024); //接收缓冲区大小设置 m_mscom.put_OutBufferSize(1024);//发送缓冲区大小设置 m_mscom.put_CommPort((m_comb1.GetCurSel()+1)); //选择串口,m_comb1:串口选择组合框的 成员变量 m_mscom.put_InputMode(1); //设置输入方式 二进制方式 m_mscom.put_InputLen(0);// 设置当前接收区数据长度为0,表示全部读取 m_mscom.put_RThreshold(2); // 缓冲区有2个字符引发OnComm事件 m_mscom.put_PortOpen(true); //打开串口 m_mscom.get_Input(); //先预读缓冲区以清除残留数据 if(m_mscom.get_PortOpen()) //如果串口打开 { str = _T("关闭串口"); // str打开按钮的Caption 赋为 “关闭串口” UpdateData(TRUE);//用于将屏幕上控件中的数据交换到变量中 h1->SetWindowText(str); //打开串口按键 文本将被修改 m_Icon.SetIcon(m_hIcon_Open); //显示打开图标 } } else { m_mscom.put_PortOpen(false); //关闭串口 if(str != _T("打开串口")) { str = _T("打开串口"); UpdateData(TRUE); h1->SetWindowText(str); m_Icon.SetIcon(m_hIcon_Close); //显示关闭图标 } } } ② 串口打开、关闭 图标显示 首先,准备好2个Icon 图标, 在资源视图内右键—>添加资源—>导入 ,选择2个Icon。 导入后自动分配了ID: IDI_ICON1\ IDI_ICON2 . 在BOOL C串口Dlg::OnInitDialog() 内添加如下代码: //m_hIcon_Open:串口打开图标的HICON 类型的成员变量 //m_hIcon_Close :串口关闭图标的HICON 类型的成员变量 //LoadIcon从与应用程序实例相关联的可执行文件(.EXE)文件中加载指定的图标资源 m_hIcon_Open = LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON1)); m_hIcon_Close = LoadIcon(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDI_ICON2)); m_Icon.SetIcon(m_hIcon_Close); //设置初始显示为关闭图标 ③选择串口 与 波特率 的 Combo Box 设置 在BOOL C串口Dlg::OnInitDialog() 内添加如下代码: // 串口组合框:显示com1~com15 CString str; for(i=0;i<15;i++) { str.Format(_T("com %d"),i+1); m_comb1.InsertString(i,str); } m_comb1.SetCurSel(0); //波特率组合框 CString str1[]={_T("4800"),_T("9600"),_T("19200"),_T("38400"),_T("43000"),_T("56000"),_T("57600"),_T("115200")}; for(int i = 0; i<8 ; i++) { int judge_tf=m_comb3.AddString(str1[i]); if((judge_tf==CB_ERR)|(judge_tf==CB_ERRSPACE)) MessageBox(_T("设置波特率错误")); } m_comb3.SetCurSel(7); //运行次数选项框 int i; CString str2[]={_T("1"),_T("2"),_T("3"),_T("4"),_T("5"),_T("6"),_T("7"),_T("8"),_T("9")}; for(int i = 0; i<9 ; i++) { int judge_times=m_Runtime.AddString(str2[i]); if((judge_times==CB_ERR)|(judge_times==CB_ERRSPACE)) MessageBox(_T("未设置运行次数")); } m_Runtime.SetCurSel(0); ④ Radio选择框设置,发送数据按钮 多个Radio Button 将第一个的Group改为True 按照Tab顺序会将其他Radio Button 分为一组,返回值分别为0,1,2 … 双击发送数据按钮,添加控制程序: void C串口Dlg::OnBnClickedButtonSend() { // TODO: 在此添加控件通知处理程序代码 CString str,str1,SendData; UpdateData(TRUE); //将控件的状态传给其关联的变量 str.Format(_T("%d"),m_MMSet); //将 单选按钮的 int变量值强制转换成CString类型 m_Runtime.GetLBText(m_Runtime.GetCurSel(),str1);//运行次数选择框返回值 SendData = str + str1; //发送的数据 //UpdateData(TRUE); m_mscom.put_Output(COleVariant(SendData)); //数据发送 } ⑤清除数据按钮 void C串口Dlg::OnBnClickedButtonClean() { // TODO: 在此添加控件通知处理程序代码 m_Edit1_Rev1 = _T(""); //给接收编辑框发送空格符 m_Edit1_Rev2 = _T(""); m_Edit1_Rev3 = _T(""); m_Edit1_Rev4 = _T(""); m_Edit1_Rev5 = _T(""); UpdateData(FALSE);//将数据在屏幕中对应控件中显示出来 } ⑥ 关闭按钮 void C串口Dlg::OnBnClickedButtonClose() { // TODO: 在此添加控件通知处理程序代码 if(m_mscom.get_PortOpen()) m_mscom.put_PortOpen(FALSE); CDialogEx::OnCancel(); } ⑦ 串口控件程序:点击Activex 控件,右键 为控件添加处理程序 功能 :多个接收编辑框分别显示接收数据 void C串口Dlg::OnCommMscomm1() { // TODO: 在此处添加消息处理程序代码 CString strtemp; if(m_mscom.get_CommEvent()==2) //事件值为2 表示接收缓存区内有字符 { BYTE str[2048]={0}; long len,k; COleSafeArray fs; int flag,i=0; VARIANT InputData = m_mscom.get_Input(); //读缓冲区 fs = InputData; //Variant类型变量转换成 COleSafeArray型变量 len = fs.GetOneDimSize(); //得到有效数据长度 for(k = 0;k < len;k++) fs.GetElement(&k,str+k); //转换成BYTE型数组 for(k = 0;k < len;k++) //将数组转换为CString型变量 { BYTE bt=*(char*)(str+k); //字符型 strtemp.Format(_T("%c"),bt); //将字符输入临时变量strtemp //输出为多个显示框,显示位置标志 if(strtemp == 'a') { flag = 0; } else if(strtemp == 'b') { flag = 1; } else if(strtemp == 'c') { flag = 2; } ..... //显示数据写入 switch(flag) { case 0: m_Edit1_Rev1 = _T(""); //清空显示 for(k=1;k { bt=*(char*)(str+k); //字符型 strtemp.Format(_T("%c"),bt); //将字符输入临时变量strtemp m_Edit1_Rev1 += strtemp; } break; case 1: m_Edit1_Rev2 = _T(""); for(k=1;k { bt=*(char*)(str+k); //字符型 strtemp.Format(_T("%c"),bt); //将字符输入临时变量strtemp m_Edit1_Rev2 += strtemp; } break; case 2: m_Edit1_Rev3 = _T(""); //清空显示 for(k=1;k { bt=*(char*)(str+k); //字符型 strtemp.Format(_T("%c"),bt); //将字符输入临时变量strtemp m_Edit1_Rev3 += strtemp; } break; } } UpdateData(FALSE); // 更新编辑框内容 } } ⑧ 数据保存按键 将数据保存为csv格式 void C串口Dlg::OnBnClickedButtonSave() { // TODO: 在此添加控件通知处理程序代码 CString str,str1,str2,str3,str4; //构建文件保存为对话框,默认扩展名为.csv,初始文件名为空... CFileDialog fg(false,_T("*.csv"),NULL,OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT, _T("Text File(*.txt)|*.txt|Excel CSV File(*.csv)|*.csv|All Flie(*.*)|*.*||"),NULL); str1 = _T("文本,");//文本为行标题,换单元格 str2 = _T("文本,"); str3 = _T("文本,"); str4 = _T("文本,"); str8 = _T(",文本,文本,文本\n"); // 文本为列标题 if(fg.DoModal()==IDOK) //创建对话框成功 { CString filename = fg.GetPathName(); //文件的完整路径 CStdioFile mFile(filename,CFile::modeCreate|CFile::modeWrite); //指定文件路径、创建只写模式 //写入第1个 Radio Button控制的数据 switch (m_MMSet) { case 0: mFile.Write(str1,str1.GetLength()); GetDlgItemText(IDC_RADIO1,str); mFile.WriteString(str+"\n"); break; case 1: mFile.Write(str1,str1.GetLength()); GetDlgItemText(IDC_RADIO2,str); mFile.WriteString(str+"\n"); break; case 2: mFile.Write(str1,str1.GetLength()); GetDlgItemText(IDC_RADIO3,str); mFile.WriteString(str+"\n"); break; default: break; } //写入第2个文本 的数据 mFile.Write(str2,str2.GetLength()); GetDlgItemText(IDC_COMBO1,str); mFile.WriteString(str+"\n"); mFile.Write(str8,str8.GetLength()); //写列标题 //写入接收框1的数据 mFile.Write(str3,str3.GetLength()); GetDlgItemText(IDC_EDIT1_Rev1,str); //写入接收框数据 mFile.WriteString(str);//指定一个指向包含空终止文本字符串的缓冲区的指针。 GetDlgItemText(IDC_EDIT2_Rev1,str); mFile.WriteString(","); mFile.WriteString(str); GetDlgItemText(IDC_EDIT3_Rev1,str); mFile.WriteString(","); mFile.WriteString(str+"\n"); //写入接收框2的数据 mFile.Write(str4,str4.GetLength()); GetDlgItemText(IDC_EDIT1_Rev2,str); mFile.WriteString(str); GetDlgItemText(IDC_EDIT2_Rev2,str); mFile.WriteString(","); mFile.WriteString(str); GetDlgItemText(IDC_EDIT3_Rev2,str); mFile.WriteString(","); mFile.WriteString(str+"\n"); } } 使用虚拟串口测试,可以实现 数据发送、接收、保存 。