【C++】基于MFC设计一个串口调试助手

1. 界面设计 

更换为VS2010的风格:下载连接见我往期博客。

更改后出现提示:点击是。

点击运行查看效果:

控件号命名:

串口号ID:IDC_COMPORT_NUM

波特率ID:  IDC_COMPORT_BAUD

数据位ID:   IDC_COMPORT_DATA

停止位ID:   IDC_COMPORT_STOP

校验位ID:   IDC_COMPORT_CHECK

打开串口按钮ID:IDC_OPEN_COMPORT

2. 下拉菜单初始化设置

2.1 输入下拉菜单数据

数据位、停止位、校验位控件设置同上,具体数据见下表:

波特率:
110
300
600
1200
2400
4800
9600
14400
19200
38400
56000
57600
115200
128000
256000
230400
460800
921600

数据位:
5
6
7
8

停止位:
1
1.5
2

校验位:
None
Odd
Even 

2.2 下拉菜单指定默认数据

MFC 控件下拉选择和输入的 ComboBox使用

初始化波特率默认为115200;首先看115200这组数在整个数据中排第几行。这里是第12行;同样数据位第3行,停止位0行,校验位0行。

为此我们需要增加一个成员函数用来初始化控件:

软件设计思路是建立两个数组,一个存放控件ID,一个存放控件ID中存放的数组。然后用for循环去遍历两个数组,让其匹配。

void CMyComportDlg::InitCtrl()
{
    UINT ID[] = {IDC_COMPORT_BAUD, IDC_COMPORT_DATA, IDC_COMPORT_STOP, IDC_COMPORT_CHECK};
	UINT sel[] = {12, 3, 0, 0};

	for(int i=0; i<sizeof(ID)/sizeof(ID[0]); i++)
	{
		((CComboBox*)(GetDlgItem(ID[i])))->SetCurSel(sel[i]);//设置第n行内容为显示的内容
	}
}

其中i<sizeof(ID)/sizeof(ID[0])是计算数组长度。

其次建立对象指针去操作控件,使其下拉控件显示我们想要的数据,具体方法如下:

1. ((CComboBox*)GetDlgItem(IDC_COMBO_CF))->SetCurSel(n)//设置第n行内容为显示的内容。

2. CComboBox::SetCurSel(n)//设置第n行内容为显示的内容

2.3 将该初始化控件函数放入OnInitDialog()函数中

初始化对话框的时候会调用到OnInitDialog函数,并执行里面的代码。什么样的东西需要在初始化都可以放在里面。比如成员变量的初始化,一些按钮、列表状态等的初始化,具体根据程序的需要。这个函数只在初始化的时候调用一次。

BOOL CMyComportDlg::OnInitDialog()
{
   .....
   InitCtrl();
   .....
}

运行效果:

3. 串口号遍历显示

3.1 添加串口相关文件

将串口类放入工程文件夹下,并将这两个文件添加到工程,下载链接:

A/O哪里来的?相关软件配置配置请参见我的往期博客 -> VC++6.0常见问题

3.2 创建串口成员函数

创建串口对象:

MyComportDlg.h文件中添加对象CComBase m_comport,记得把头文件加入。

// MyComportDlg.h : header file
//............
#include "comport.h"
//............
class CMyComportDlg : public CDialog
{
//............
public:
	CComBase m_comport;
};

创建遍历串口事件:

该对象创建于MyComportDlg.cpp : implementation file 

void CMyComportDlg::InitComport()
{
    CComboBox* pcombox = (CComboBox*)GetDlgItem(IDC_COMPORT_NUM);//对象指针
}
#include <vector>
using namespace std;

说明:关于#include <vector>的介绍

因为在comport.h中EnumComPort()函数遍历串口得到CString元素存放在vector:

    static void EnumComPort(std::vector<CString>& strComArr);

故:

void CMyComportDlg::InitComport()
{
    CComboBox* pcombox = (CComboBox*)GetDlgItem(IDC_COMPORT_NUM);//对象指针
	vector<CString> vstr;//定义容器存放字符串
    m_comport.EnumComPort(vstr);//引用串口遍历函数
	for(int i = 0; i<vstr.size(); i++)
	{
        CString szStr;
		szStr = vstr[i];//将第i个串口号赋值
		pcombox->AddString(szStr);//向下拉列表添加字符串
	}
}

说明1:

string vestr; //定义一个CString的变量

vestr.size(); //用于计算string中元素的个数,返回字符串长度,如果没有定义返回类型就是返回无符号整型。

vector的元素不仅仅可以装int,double,string,还可以是结构体。

说明2:

CComboBox::AddString//添加lpszString 至组合框尾部

注释:根据一个对话窗或一个控件的ID, 返回一个指向这个对话窗或控件的对象的指针。其中IDC_COMPORT_NUM是控件的ID;

CComboBox* pcombox = (CComboBox*)GetDlgItem(IDC_COMPORT_NUM);可以写成

CComboBox* pcombox; 

pcombox = (CComboBox*)GetDlgItem(IDC_COMPORT_NUM);

的形式,也就是用GetDlgItem(IDC_COMPORT_NUM);返回一个指向这个控件的指针,CComboBox*,存入pcombox

最后同样将InitComport()添加到初始化函数中:

BOOL CMyComportDlg::OnInitDialog()
{
   .....
   InitCtrl();
   InitComport();
   .....
}

运行后可见:

 查看电脑的串口如下,说明上述方法有效:

我们想让第一个串口号在下拉列表中显示出来,只需要添加一个函数:

void CMyComportDlg::InitComport()
{
    CComboBox* pcombox = (CComboBox*)GetDlgItem(IDC_COMPORT_NUM);//对象指针
	vector<CString> vstr;//定义容器存放字符串
    m_comport.EnumComPort(vstr);//引用串口遍历函数
	for(int i = 0; i<vstr.size(); i++)
	{
        CString szStr;
		szStr = vstr[i];//将第i个串口号赋值
		pcombox->AddString(szStr);//向下拉列表添加字符串
	}
	pcombox->SetCurSel(0);//引用第0个数据
}

GetCurSel() 函数:用以得到用户选中下拉列表框中数据的索引值.返回的值是重0开始的,如果没有选择任何选项将会返回-1

运行结果如下:


4 打开串口

双击打开串口按钮控件,写入如下程序:

void CMyComportDlg::OnOpenComport() 
{
	CString szStr;
	if(m_comport.IsOpen()){  //查看串口是否打开
		return;
	}
    GetDlgItemText(IDC_COMPORT_NUM, szStr);
	if(!m_comport.Create(szStr)) //界面获取字符串,如果创建失败
	{
		AfxMessageBox("创建串口失败");//显示创建串口失败
		return;
	}else{
        AfxMessageBox("创建串口成功");
	}
}

说明1:

comport.cpp中IsOpen()函数原型:

BOOL CComBase::IsOpen()
{
    return m_hFile != INVALID_HANDLE_VALUE;    
}

说明2: 

GetDlgItemText函数里面的4个参数:

第一个是句柄  API都得通过句柄操作,如果在MFC中,本项可以不写,默认为this
第二个为ID,即你想要得到那个控件的ID
第三个为TEXT值, 你得先定义一个字符串用来获取该值
第四个为文本最大长度

comport.cpp中GetData()函数原型: 

BOOL CComBase::Create(CString str)
{
	str = _T("\\\\.\\") + str; 				 //兼容10以上的串口
	m_hFile = ::CreateFile(
		str,
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL  | FILE_FLAG_OVERLAPPED,
		NULL
		);

	return m_hFile != INVALID_HANDLE_VALUE;
}

运行看一下效果:

拓展:可以在此基础上完善一下,用一个按钮开显示串口打开的状态。 

设置打开串口ID为:IDC_OPEN_COMPORT;指示文本ID为IDC_OPEN_STATE

按钮在点击时会出现变化:

void CMyComportDlg::OnOpenComport() 
{
	CString szStr;

    GetDlgItemText(IDC_OPEN_COMPORT, szStr);  //获得串口字符串
	if(szStr == "打开串口"){

		SetDlgItemText(IDC_OPEN_COMPORT, "关闭串口");
        SetDlgItemText(IDC_OPEN_STATE, "已打开");
	}
	if(szStr == "关闭串口"){
		SetDlgItemText(IDC_OPEN_COMPORT, "打开串口");
		SetDlgItemText(IDC_OPEN_STATE, "未打开");
	}
}

将打开串口的功能加入到上面的框架中。

void CMyComportDlg::OnOpenComport() 
{
	CString szStr;

    GetDlgItemText(IDC_OPEN_COMPORT, szStr);  //获得串口字符串
	if(szStr == "打开串口"){
/*******************创建串口********************/
// 		if(m_comport.IsOpen()){  //查看串口是否打开
// 			return;              //可以省略,因为已经做过条件判断
// 		}
		GetDlgItemText(IDC_COMPORT_NUM, szStr);
		if(!m_comport.Create(szStr)) //界面获取字符串,如果创建失败
		{
			AfxMessageBox("创建串口失败");//显示创建串口失败
			return;
		}else{
			AfxMessageBox("创建串口成功");
		}
		SetDlgItemText(IDC_OPEN_COMPORT, "关闭串口");
        SetDlgItemText(IDC_OPEN_STATE, "已打开");
	}
	if(szStr == "关闭串口"){
		SetDlgItemText(IDC_OPEN_COMPORT, "打开串口");
		SetDlgItemText(IDC_OPEN_STATE, "未打开");
	}
}

将四个参数传入,传入参数我们用到的函数是comport.cpp中的SetComParam()

BOOL CComBase::SetComParam(ULONG nBaudRate, UCHAR nDataBit, UCHAR nStopBits, UCHAR nCheckBits)
{
	DCB bcd;

	if(GetCommState(m_hFile,&bcd))
	{
		bcd.BaudRate = nBaudRate;
		bcd.ByteSize = nDataBit;
		bcd.StopBits = nStopBits;				
		bcd.Parity  = nCheckBits;  
		BOOL is = SetCommState(m_hFile,&bcd);
		return SetCommMask(m_hFile,EV_RXCHAR) && is;
	}
	return FALSE;
}

解释:return FALSE;这里取的就是bool类型的返回值。false是假,true是真。

例如有函数:

BOOL fun()

{

if(...)

return true;

else

return false;

}

然后后面调用bool  b = fun(); 调用函数后将返回一个值true或者false,然后赋值给b。然后通过b的值来判断fun()函数里面执行的if里面的代码还是else里面的代码。

 最终配置如下:

/*********************打开串口*************************/
void CMyComportDlg::OnOpenComport() 
{
	CString szStr;

    GetDlgItemText(IDC_OPEN_COMPORT, szStr);  //获得串口字符串
	if(szStr == "打开串口"){
/*******************创建串口********************/
// 		if(m_comport.IsOpen()){  //查看串口是否打开
// 			return;              //可以省略,因为已经做过条件判断
// 		}
		GetDlgItemText(IDC_COMPORT_NUM, szStr);
		if(!m_comport.Create(szStr)) //界面获取字符串,如果创建失败
		{
			AfxMessageBox("创建串口失败");//显示创建串口失败
			return;
		}else{
			AfxMessageBox("创建串口成功");
		}
/*******************设置串口参数********************/
		BOOL state = m_comport.SetComParam(
			GetDlgItemInt(IDC_COMPORT_BAUD),//获取编辑框中的数据
			GetDlgItemInt(IDC_COMPORT_DATA),
			((CComboBox*)(GetDlgItem(IDC_COMPORT_STOP)))->GetCurSel(),//是一个CWin*的一个指针,需要强制类型转化
			((CComboBox*)(GetDlgItem(IDC_COMPORT_CHECK)))->GetCurSel()
			);//传四个参数
		if(!state){
			AfxMessageBox("串口配置失败");
		}else{
            AfxMessageBox("串口配置成功");
		}
		SetDlgItemText(IDC_OPEN_COMPORT, "关闭串口");
        SetDlgItemText(IDC_OPEN_STATE, "已打开");
	}
	if(szStr == "关闭串口"){
		m_comport.Close();
		SetDlgItemText(IDC_OPEN_COMPORT, "打开串口");
		SetDlgItemText(IDC_OPEN_STATE, "未打开");
	}
打开按钮测试
打开串口测试by罗平生

/*****************************设置串口参数部分代码解释*********************************/

说明1:

串口编程_DCB结构体中定义的停止位为:

 typedef struct _DCB {  
    BYTE StopBits;        /* 0,1,2 = 1, 1.5, 2               */
 } DCB, *LPDCB; 

可以看到1,1.5,2是由0,1,2来表示,与上面波特率的数值不同,故使用    ((CComboBox*)(GetDlgItem(IDC_COMPORT_STOP)))->GetCurSel(),来获取这个指针

说明2:return FALSE;这里取的就是bool类型的返回值。false是假,true是真。

然后后面调用BOOL  state = m_comport.SetComParam(); 调用函数后将返回一个值true或者false,然后赋值给state。然后通过state的值来判断CComBase::SetComParam()函数里面执行的if里面的代码还是返回值FALSE。

其次根据判断state的状态,进而进行文本对话框提示。state为假,则表示串口配置失败。

说明3:

文本显示“关闭串口”就要调用m_comport.Close()执行关闭串口的动作。

判断串口句柄是否是有效值,INVALID_HANDLE_VALUE代表无效句柄值,!=无效即为有效,判断逻辑是,我点击关闭,就要判断串口是否运行,如果有效,执行关闭。

		if(m_comport.m_hFile != INVALID_HANDLE_VALUE){
			m_comport.Close();
		}

5 利用串口发送数据

将发送按钮的ID改为:IDC_SEND_DATA_BUTTON

首先判断串口是否被打开:

/*********************发送数据*************************/
void CMyComportDlg::OnSendDataButton() 
{
	// TODO: Add your control notification handler code here
	if(!m_comport.IsOpen()){
		AfxMessageBox("串口没有打开");
		return;
	}
}

测试效果: 

发送按钮测试By罗平生
发送按钮测试By罗平生

其次将发送文本框ID改为:IDC_SEND_DATA_EDIT,为发送数据做准备

我们想要实现的效果就是点击发送按钮,输入的数据就能从发送文本框中发送出去。

	CString szStr;
	GetDlgItemText(IDC_SEND_DATA_EDIT, szStr);
	m_comport.SendData(szStr.GetBuffer(szStr.GetLength()),szStr.GetLength());

编辑后报错:

需要进行强制类型转换:

	CString szStr;//声明一个字符串容器对象
	GetDlgItemText(IDC_SEND_DATA_EDIT, szStr);//获取文本框中的数据放在szStr中
	m_comport.SendData((UCHAR*)szStr.GetBuffer(szStr.GetLength()),szStr.GetLength());

着重讲解一下最后一句:

m_comport.SendData()函数原型为:

BOOL CComBase::SendData(PUCHAR pBuff, ULONG nLen,ULONG nTimeOut)
{
	DWORD d;
	
	if(m_hFile == INVALID_HANDLE_VALUE)
		return FALSE;
	if(!WriteFile(m_hFile,pBuff,nLen,&d,&m_wOv))
	{
		DWORD res = WaitForSingleObject(m_wOv.hEvent,nTimeOut);
		if(res == WAIT_OBJECT_0 ){
			return TRUE;
		}else{
			return FALSE;
		}
	}
	return TRUE;
}

说明1:PUCHAR pBuff:字符串;ULONG nLen:字符串长度;ULONG nTimeOut:等待

说明2:LPTSTR GetBuffer(int nMinBufLength)

其间涉及到几个函数,CString的成员函数GetBuffer()。GetBuffer()主要作用是将字符串的缓冲区长度锁定

GetBuffer()主要是把CString对象的字符串转化为C类型的字符串,CString对象有一个缓冲区,GetBuffer返回的就是这份缓冲区的拷贝,同时要为这个缓冲区设置一个最小长度。

说明3:_AFX_INLINE int CString::GetLength() const{...}

szStr.GetLength(); 返回的是字符个数(不包括结尾的空字符)。

运行测试一下:

串口发送测试By罗平生
串口发送测试By罗平生

6 发送字节数计数和清空功能

6.1 发送字节计数功能设计

首先定义一个成员变量:

MyComportDlg.h文件中添加对象UINT m_SendLength。

// MyComportDlg.h : header file
//............
#include "comport.h"
//............
class CMyComportDlg : public CDialog
{
//............
public:
	CComBase m_comport;
    UINT m_SendLength;  //4个字节
};

在初始化函数中将其赋值为0;养成好习惯,定义一个变量就初始化。

BOOL CMyComportDlg::OnInitDialog()
{
   .....
   InitCtrl();
   InitComport();
   m_SendLength = 0;
   .....
}

将接受字节的文本ID改为 IDC_SEND_LENGTH

 依旧在发送按钮的事件下void CMyComportDlg::OnSendDataButton() 进行统计,点击一下发送,就统计一组数据的个数,发送一次累加一次。

/*******************发送字节数统计********************/
    m_SendLength += szStr.GetLength();//点击一次累加一次
	SetDlgItemInt(IDC_SEND_LENGTH, m_SendLength);

发送按钮完整程序:

/*********************发送数据*************************/
void CMyComportDlg::OnSendDataButton() 
{
	// TODO: Add your control notification handler code here
	if(!m_comport.IsOpen()){
		AfxMessageBox("串口没有打开");
		return;
	}
	CString szStr;//声明一个字符串容器对象
	GetDlgItemText(IDC_SEND_DATA_EDIT, szStr);//获取文本框中的数据放在szStr中
	m_comport.SendData((UCHAR*)szStr.GetBuffer(szStr.GetLength()),szStr.GetLength());
/*******************发送字节数统计********************/
    m_SendLength += szStr.GetLength();//点击一次累加一次
	SetDlgItemInt(IDC_SEND_LENGTH, m_SendLength);
}

6.2 发送字节计数功能设计(改良版)

上述的方法虽然可以自动更新发送字节数,但是存在一个缺点,就是文本和数字文本不是一个text文件,如果改成凹陷造型外观上两个文本并不是一体的,因此为了让程序更加美观,我们对上述方法进行改良。

首先我们将text的初始文本改成如下样式,以发送字节数为例子,将text的属性标题改为“发送字节数:0”ID改为“IDC_SEND_LENGTH”样式改为垂直居中凹陷

然后改程序:在程序中找到:

void CMyComportDlg::SendString(CString szStr)
{
	/*********************字符串发送**********************/
	m_comport.SendData((UCHAR*)szStr.GetBuffer(szStr.GetLength()),szStr.GetLength());
	/*******************发送字节数统计********************/
    m_SendLength += szStr.GetLength();//点击一次累加一次
	SetDlgItemInt(IDC_SEND_LENGTH, m_SendLength);
}

 我们采用CString的Format函数:举个例子

SCtring str;

str.format("hello %d",100)

其输出值就是 hello 100

因此我们这么设计:只需要将其中的SetDlgItemInt(IDC_SEND_LENGTH, m_SendLength);改为下列函数即可。后续的HEX发送字节数统计都采用这种方法

/*    SetDlgItemInt(IDC_SEND_LENGTH, m_SendLength);*/
	CString strSend;
	strSend.Format("发送字节数:%d",m_SendLength);
	GetDlgItem(IDC_SEND_LENGTH)->SetWindowText(strSend);

清零函数这样设计:

/*********************清空按钮*************************/
void CMyComportDlg::OnClearSendlength() 
{
	// TODO: Add your control notification handler code here
	m_SendLength = 0;
/*	SetDlgItemInt(IDC_SEND_LENGTH, 0);*/
	CString strSend;
	strSend.Format("发送字节数:%d",m_SendLength);
	GetDlgItem(IDC_SEND_LENGTH)->SetWindowText(strSend);
}

效果展示:

发送字节改良测试By罗平生
发送字节改良测试By罗平生

6.3 发送功能清空设计

将清空接收按钮改成IDC_CLEAR_SENDLENGTH

/*********************清空按钮*************************/
void CMyComportDlg::OnClearSendlength() 
{
	// TODO: Add your control notification handler code here
	m_SendLength = 0;
	SetDlgItemInt(IDC_SEND_LENGTH, 0);
}

效果展示:

串口字节统计清空测试
串口字节统计清空测试By罗平生

6.3 改变提示窗口标题

点击“插入”->“资源”在资源类型中选择“String Table”->在String 属性中的ID里面找到“AFX_IDS_APP_TITLE”并修改标题为“温馨提示”。

运行可见:

 

7 利用串口兼容发送HEX和字符数据

我们要实现两种数据切换发送,首先建立两个函数分别实现字符串发送HEX格式发送这两个功能。

在classView视图中AddMemberFunction建立如下两个函数:

7.1 字符串发送函数:

void SendString(CString szStr)//传入的字符串参数只读

将字符串发送函数直接剪切到这里:

void CMyComportDlg::SendString(CString szStr)
{
	/*********************字符串发送**********************/
	m_comport.SendData((UCHAR*)szStr.GetBuffer(szStr.GetLength()),szStr.GetLength());
	/*******************发送字节数统计********************/
    m_SendLength += szStr.GetLength();//点击一次累加一次
	SetDlgItemInt(IDC_SEND_LENGTH, m_SendLength);
}

7.2 HEX格式发送函数:

void CMyComportDlg::SendHex(CString szStr)

void CMyComportDlg::SendHex(CString szStr)
{
   CString szEditStr = szStr;//对szStr重新定义
   szEditStr.Replace(" ","");//将字符串中的空格替换掉
   if(szEditStr.GetLength()%2 == 1){
	   AfxMessageBox("请输入偶数,多余补零");
	   return;
   }
   UCHAR SendBuff[500];//设置一个缓冲区
   CString szStrTemp = "";
   for(int i = 0; i<szEditStr.GetLength()/2; i++)
   {
	   szStrTemp = (szEditStr.Left((i+1)*2)).Right(2);//从左边1开始获取前 nCount 个字符
	   char* buff;
	   UCHAR data =(int)strtol(szStrTemp, &buff, 16);//将字符串十六进制
	   SendBuff[i] = data;//把数据放进缓冲区数组中
   }
   m_comport.SendData(SendBuff, szEditStr.GetLength()/2);//长度的一半
   m_SendLength += szEditStr.GetLength()/2;

   SetDlgItemInt(IDC_SEND_LENGTH, m_SendLength);//点击一次累加一次
}

函数解释1:字面意思应该是把一个范围内的每个元素的值=OldValue换成NewValue。 

 szEditStr.Replace(" ","");//将字符串中的空格替换掉。

函数解释2:

szEditStr.GetLength(); 返回的是字符个数(不包括结尾的空字符) ;除2余数是1,说名输入的字符是奇数,提醒重新输入。

if(szEditStr.GetLength()%2 == 1){
       AfxMessageBox("请输入偶数,多余补零");
       return;
   }

 函数解释3:CString类常用方法----Left(),Mid(),Right()……

CString Left( int nCount ) const;                   //从左边1开始获取前 nCount 个字符

CString Mid( int nFirst ) const;                      //从左边第 nCount+1 个字符开始,获取后面所有的字符

CString Mid( int nFirst, int nCount ) const;    //从左边第 nFirst+1 个字符开始,获取后面  nCount 个字符

CString Right( int nCount ) const;                  //从右边1开始获取从右向左前 nCount 个字符

注:

     在函数后面加 const 的意思是:

     如果一个类声明了一个常量对象,这个对象只能使用后边带 const 这个的方法.

例:

 CString a,b;
 a = "123456789";


 b = a.Left(4);   //值为:1234
 b = a.Mid(3);    //值为:456789
 b = a.Mid(2, 4); //值为:3456
 b = a.Right(4);  //值为:6789

 函数解释4:strtol()函数

表头文件: #include <stdlib.h>
定义函数: long int strtol(const char *nptr, char **endptr, int base)
函数说明: 

str是要转换的字符/         enptr是指向第一个不可转换的字符位置的指针        /base的基数,表示转换成为几进制的数

strtol()会将参数nptr字符串根据参数base来转换成长整型数。参数base范围从2至36,或0。参数base代表采用的进制方式,如base值为10则采用10进制(字符串以10进制表示),若base值为16则采用16进制(字符串以16进制表示)

拿到这两个功能函数开始做一个判断:首先将ChexkBox的ID改为IDC_IS_HEX,他是一个CButton类型的对象指针,如下:

    //判断是否选择该控件
    if(((CButton*)(GetDlgItem(IDC_IS_HEX)))->GetCheck())
	{
        //发送hex
        SendHex(szStr);
	}else{
        //发送字符串
        SendString(szStr);
	}

主要写在这两个部分:

1.发送数据按钮下

/*********************发送数据*************************/
void CMyComportDlg::OnSendDataButton() 
{
	// TODO: Add your control notification handler code here
	if(!m_comport.IsOpen()){
		AfxMessageBox("串口没有打开");
		return;
	}
	CString szStr;//声明一个字符串容器对象
	GetDlgItemText(IDC_SEND_DATA_EDIT, szStr);//获取文本框中的数据放在szStr中

//判断是否选择该HEX CHECK控件
    if(((CButton*)(GetDlgItem(IDC_IS_HEX)))->GetCheck())
	{
		SendHex(szStr);//发送Hex格式
	}else{
		SendString(szStr); //发送字符串
	}

2.协议发送按钮下,7.3节会详细介绍。

7.3 “发送协议”文本框设置

从左到右依次是ID设置为:IDC_SEND1_CHECK1; IDC_SEND1_EDIT1; IDC_SEND1设置完毕后双击按钮1进入程序编辑界面;首先程序应该判断CHECK按钮是否被选中。程序为:

void CMyComportDlg::OnSend1() 
{
	// TODO: Add your control notification handler code here
	/******************串口判断*******************/
	if(!m_comport.IsOpen()){
		AfxMessageBox("请打开串口");
		return;
	}
	/******************选中判断********************/
	if(!((CButton*)(GetDlgItem(IDC_SEND1_CHECK1)))->GetCheck())
	{
	  AfxMessageBox("请选中后再点击");
      return;
	}
	CString szStr;//声明一个字符串容器对象
	GetDlgItemText(IDC_SEND1_EDIT1, szStr);//获取文本框中的数据放在szStr中
    //判断是否选择该HEX CHECK控件
    if(((CButton*)(GetDlgItem(IDC_IS_HEX)))->GetCheck())
	{
		SendHex(szStr);//发送Hex格式
	}else{
		SendString(szStr); //发送字符串
	}
}

运行测试一下 :

发送协议测试By罗平生
发送协议测试By罗平生

剩下5个协议发送口按照这个代码格式编写即可。 

8 搭建串口接收线程

创建线程函数:CreateRecThread()

在BOOL CMyComportDlg::OnInitDialog()初始化接收线程函数, 或者在打开串口按钮函数中创建接收线程。

BOOL CMyComportDlg::OnInitDialog()
{
   .....
   InitCtrl();
   InitComport();
   m_SendLength = 0;
   CreateRecThread();//创建串口接收线程函数
   .....
}
或者在
/*********************打开串口*************************/
void CMyComportDlg::OnOpenComport() 
{
		SetDlgItemText(IDC_OPEN_COMPORT, "关闭串口");
        SetDlgItemText(IDC_OPEN_STATE, "已打开");
		CreateRecThread();//创建串口接收线程函数
}

CreateRecThread()函数中输入:

//创建接收线程
void CMyComportDlg::CreateRecThread()
{
    CWinThread* pThread = AfxBeginThread(OnComReceive, this, THREAD_PRIORITY_NORMAL);
	if(pThread == NULL)
	{
         AfxMessageBox("线程创建失败");
		 return;
	}
}

重要:上述中的OnComReceive()函数首先要在CMyComportDlg中的class CMyComportDlg : public CDialog类中建立线程的静态成员函数:

说明:C++里静态的成员函数就是全局函数

class CMyComportDlg : public CDialog
{
public:
	static UINT OnComReceive(LPVOID pParam);//线程中成员静态函数
};

在这个成员函数OnComReceive()中写入代码:

//类的静态全局函数(等价于全局函数,之是作用域不同)
UINT CMyComportDlg::OnComReceive(LPVOID pParam)
{
	CMyComportDlg* pDlg = (CMyComportDlg*)pParam;
	pDlg->RecFunc();
	return TRUE;
}

上述函数中的RecFunc();就是处理接受数据的函数:

首先建立这个函数:

void CMyComportDlg::RecFunc()
{ 
	while(1)
	{
		//读取串口接收的数据
		Sleep(200);
	}
}

如果想让串口退出线程也关闭则这样设计将while(1)改成while(m_IsRecRun)

首先:创建变量m_IsRecRun

 并将该变量在初始化函数中设置为:

BOOL CMyComportDlg::OnInitDialog()
{
    ............
	m_SendLength = 0;
	m_IsRecRun = FALSE;
	InitCtrl();//波特率初始化
	InitComport();//串口遍历初始化
    ............
}

 然后在串口打开的函数下面写入该BOOL变量,具体思路就是:

打开串口,线程开启  m_IsRecRun = TRUE;

关闭串口,线程关闭  m_IsRecRun = FALSE;

更新后的打开串口函数整体结构如下:

/*********************打开串口*************************/
void CMyComportDlg::OnOpenComport() 
{
    CString szStr;

    GetDlgItemText(IDC_OPEN_COMPORT, szStr);  //获得串口字符串
    if(szStr == "打开串口"){
/*******************创建串口********************/
//         if(m_comport.IsOpen()){  //查看串口是否打开
//             return;              //可以省略,因为已经做过条件判断
//         }
        GetDlgItemText(IDC_COMPORT_NUM, szStr);
        if(!m_comport.Create(szStr)) //界面获取字符串,如果创建失败
        {
            AfxMessageBox("创建串口失败");//显示创建串口失败
            return;
        }else{
            AfxMessageBox("创建串口成功");
        }
/*******************设置串口参数********************/
        BOOL state = m_comport.SetComParam(
            GetDlgItemInt(IDC_COMPORT_BAUD),//获取编辑框中的数据
            GetDlgItemInt(IDC_COMPORT_DATA),
            ((CComboBox*)(GetDlgItem(IDC_COMPORT_STOP)))->GetCurSel(),//是一个CWin*的一个指针,需要强制类型转化
            ((CComboBox*)(GetDlgItem(IDC_COMPORT_CHECK)))->GetCurSel()
            );//传四个参数
        if(!state){
            AfxMessageBox("串口配置失败");
        }else{
            AfxMessageBox("串口配置成功");
        }
        SetDlgItemText(IDC_OPEN_COMPORT, "关闭串口");
        SetDlgItemText(IDC_OPEN_STATE, "已打开");
        CreateRecThread();//创建串口接收线程函数
        m_IsRecRun = TRUE;

    }
    if(szStr == "关闭串口"){
        m_comport.Close();
        SetDlgItemText(IDC_OPEN_COMPORT, "打开串口");
        SetDlgItemText(IDC_OPEN_STATE, "未打开");
        m_IsRecRun = FALSE;
    }
}

 接收线程函数框架如下:

void CMyComportDlg::RecFunc()
{ 
	while(m_IsRecRun)
	{
		//读取串口接收的数据
		Sleep(200);
	}
}

9 实现串口接收字符

9.1 关闭键响应操作

注意:在对对话框关闭时一定要先把串口关闭。

首先右击CMyComportDlg并选择ADD Windows Message Handler

在左侧找到WM_CLOSE并双击添加到右侧,再点击确定。此时出现一个关闭事件类,我们在里面添加关闭串口和线程的代码。

void CMyComportDlg::OnClose() 
{
	// TODO: Add your message handler code here and/or call default
	m_comport.Close();//关闭串口
	m_IsRecRun = FALSE;//关闭线程
	CDialog::OnClose();
}

9.2 接收线程代码编写

 首先在启动定时器

1. 在CreateRecThread()函数中输入:

//创建接收线程
void CMyComportDlg::CreateRecThread()
{
    CWinThread* pThread = AfxBeginThread(OnComReceive, this, THREAD_PRIORITY_NORMAL);
	if(pThread == NULL)
	{
         AfxMessageBox("线程创建失败");
		 return;
	}
}

2. 创建接收函数:

void CMyComportDlg::RecFunc()
{ 
	while(m_IsRecRun)
	{
		//读取串口接收的数据
		m_comport.GetData();
	}
}

在comport.cpp文件中找到GetData()函数:

BOOL CComBase::GetData( PUCHAR pBuff, ULONG nLen,ULONG nTimeOut)
{
	ULONG n;
	DWORD flag;
	DWORD res;
	COMSTAT rst;
	DWORD result;
	DWORD dw;
	result = WaitCommEvent(m_hFile,&flag,&m_tOv);       // 等待读取事件
	dw = WaitForSingleObject(m_tOv.hEvent,1000);    	
	if(dw ==WAIT_OBJECT_0)
	{
		if(flag & EV_RXCHAR)
		{
			ClearCommError(m_hFile,&res,&rst);
			BOOL c = ReadFile(m_hFile,pBuff,nLen,&n,&m_rOv);
			if(!c)
			{
				GetOverlappedResult(m_hFile,&m_rOv,&n,TRUE);
			}
			return TRUE;		
		}
	}
	return FALSE;
}

 修改为:

/*********************传进一个缓冲区,和一个接收变量长度***********************/
BOOL CComBase::GetData( PCHAR pBuff, ULONG& nLen)//xiugai
{
	ULONG n;
	DWORD flag;
	DWORD res;
	COMSTAT rst;
	DWORD result;
	DWORD dw;


	result = WaitCommEvent(m_hFile,&flag,&m_tOv);       // 等待读取事件
	dw = WaitForSingleObject(m_tOv.hEvent,1000);    	
	if(dw ==WAIT_OBJECT_0)
	{
		if(flag & EV_RXCHAR)
		{
			ClearCommError(m_hFile,&res,&rst);
			BOOL c = ReadFile(m_hFile,pBuff,rst.cbInQue,&n,&m_rOv);//xiugai
			if(!c)
			{
				GetOverlappedResult(m_hFile,&m_rOv,&n,TRUE);
			}
            nLen = n;//修改
			return TRUE;		
		}
	}
	return FALSE;
}

同时修改声明:在comport.h

	/*得到串口数据*/
/*	BOOL GetData(PUCHAR pBuff, ULONG nLen, ULONG nTimeOut = 1000);*/
	BOOL CComBase::GetData( PCHAR pBuff, ULONG& nLen)

 定义一个buffer:

CHAR buffer[100];

引用:

ULONG bytes;

void CMyComportDlg::RecFunc()
{ 
	CHAR buff[100] = {0};
	ULONG bytes = 0;
	CString szStrAll = "", szStr = "";
	while(m_IsRecRun)
	{
		//读取串口接收的数据,读了butes个字节
		if(m_comport.GetData(buff,bytes))
		{
			GetDlgItemText(IDC_REC_DATA_EDIT, szStrAll);
			for(int i=0; i<bytes; i++)
			{
				szStr.Format("%c", buff[i]);
				szStrAll += szStr;
			}
			SetDlgItemText(IDC_REC_DATA_EDIT, szStrAll);
		}
	}
}

9.3 HEX接收

IDC_IS_HEX_REC

定义两个成员变量,一个存放数组,一个存放HEX,用以替换szStrAll

    CString m_szHexDisplay;
    CString m_szAsciiDisplay;

清空接收按钮对上述变量清零:

void CMyComportDlg::OnRecButton() 
{
	// TODO: Add your control notification handler code here
	m_RecLength = 0;
				/*	SetDlgItemInt(IDC_SEND_LENGTH, m_SendLength); */
	CString str;
	str.Format("接收字节数:%d",m_RecLength);
    GetDlgItem(IDC_REC_LENGTH)->SetWindowText(str);
    GetDlgItem(IDC_REC_DATA_EDIT)->SetWindowText("");//清空对话框

	m_szHexDisplay = "";
	m_szAsciiDisplay = "";
}

再定义一个变量BOOL m_IsHexDisplay;//用于条件判断

初始化函数中初始化为:m_IsHexDisplay =TRUE;

%x意思是16进制输出(以16进制输出变量地址)

/******************HEX接收函数**********************/
void CMyComportDlg::OnIsHexRec() 
{
	// TODO: Add your control notification handler code here
	if(((CButton*)(GetDlgItem(IDC_IS_HEX_REC)))->GetCheck())
	{
		//显示HEX
		m_IsHexDisplay =TRUE;
		SetDlgItemText(IDC_REC_DATA_EDIT, m_szHexDisplay);
	}else{
		m_IsHexDisplay =FALSE;
		SetDlgItemText(IDC_REC_DATA_EDIT, m_szAsciiDisplay);
		//显示ASCLL
	}
}
void CMyComportDlg::RecFunc()
{ 
	CHAR buff[100] = {0};
	ULONG bytes = 0;
	CString szStr = "";
	while(m_IsRecRun)
	{
		//读取串口接收的数据,读了butes个字节
		if(m_comport.GetData(buff,bytes))
		{
			GetDlgItemText(IDC_REC_DATA_EDIT, m_szHexDisplay);
			for(int i=0; i<bytes; i++)
			{
				szStr.Format("%c", buff[i]);
				m_szAsciiDisplay += szStr;
				szStr.Format("%X", buff[i]);
				m_szHexDisplay += szStr;
			}
			if(m_IsHexDisplay)
				SetDlgItemText(IDC_REC_DATA_EDIT, m_szHexDisplay);	
	    	else
				SetDlgItemText(IDC_REC_DATA_EDIT, m_szAsciiDisplay);
			/***********************接收计数***************************/
			m_RecLength += bytes;//计算数据长度,发送一次累加一次
			
			/*	SetDlgItemInt(IDC_SEND_LENGTH, m_SendLength); */
			CString str;
			str.Format("接收字节数:%d",m_RecLength);
            GetDlgItem(IDC_REC_LENGTH)->SetWindowText(str);
		}
	}
}

定义变量存储数据,不用获取界面的值 

9.4 接收计数

 定义接收成员变量

public:
	void CreateRecThread();
	void SendHex(CString szStr);
	void SendString(CString szStr);
	CComBase m_comport;
    UINT m_SendLength; //4个字节
	UINT m_RecLength;

并将其初始化为零

	m_RecLength = 0;

在void CMyComportDlg::RecFunc()写入:

void CMyComportDlg::RecFunc()
{ 
	CHAR buff[100] = {0};
	ULONG bytes = 0;
	CString szStrAll = "", szStr = "";
	while(m_IsRecRun)
	{
		//读取串口接收的数据,读了butes个字节
		if(m_comport.GetData(buff,bytes))
		{
			GetDlgItemText(IDC_REC_DATA_EDIT, szStrAll);
			for(int i=0; i<bytes; i++)
			{
				szStr.Format("%c", buff[i]);
				szStrAll += szStr;
			}
			SetDlgItemText(IDC_REC_DATA_EDIT, szStrAll);
			/***********************接收计数***************************/
			m_RecLength += bytes;//计算数据长度,发送一次累加一次
			
			/*	SetDlgItemInt(IDC_SEND_LENGTH, m_SendLength); */
			CString str;
			str.Format("接收字节数:%d",m_RecLength);
            GetDlgItem(IDC_REC_LENGTH)->SetWindowText(str);
		}
	}
}

 9.5 清空接收

 

/*********************清空接收按钮*************************/
void CMyComportDlg::OnRecButton() 
{
	// TODO: Add your control notification handler code here
	m_RecLength = 0;
				/*	SetDlgItemInt(IDC_SEND_LENGTH, m_SendLength); */
	CString str;
	str.Format("接收字节数:%d",m_RecLength);
    GetDlgItem(IDC_REC_LENGTH)->SetWindowText(str);
    GetDlgItem(IDC_REC_DATA_EDIT)->SetWindowText("");//清空对话框
}

 其中:GetDlgItem(IDC_REC_DATA_EDIT)->SetWindowText("");//清空对话框

10 添加实时时间

第一步:首先创建text控件,ID改为IDC_CURRENT_TIME不要标题;样式垂直居中+凹陷。

第二步:在BOOL CMyComportDlg::OnInitDialog()开启定时器。

BOOL CMyComportDlg::OnInitDialog()
{
   .....
   InitCtrl();
   InitComport();
   m_SendLength = 0;
   CreateRecThread();//创建串口接收线程函数
   SetTimer(1,1000,NULL);//启动定时器
   .....
}

第三步:

首先右击CMyComportDlg并选择ADD Windows Message Handler

在左侧找到WM_TIMER并双击添加到右侧,再点击确定。此时出现一个关闭事件类,我们在里面添加关闭串口和线程的代码。

 双击OnTimer(UINT nIDEvent)并写入以下代码:

void CMyComportDlg::OnTimer(UINT nIDEvent) 
{
	// TODO: Add your message handler code here and/or call default
	CTime time = CTime::GetCurrentTime();
	CString sTime = time.Format("%Y年:%m月:%d日  %X");
	GetDlgItem(IDC_CURRENT_TIME)->SetWindowText(sTime);
	CDialog::OnTimer(nIDEvent);
}

最终效果:

实时时间测试By罗平生
实时时间测试By罗平生

问题1:发送128显示不正确

HEX发送十进制的128是发送80,但是串口助手HEX接受错误。为FFFFF80,FFFFF代复数,如何解决呢?

在void CMyComportDlg::RecFunc()中加入无符号整形
            for(int i=0; i<bytes; i++)
            {
                szStr.Format("%c", buff[i]);
                m_szAsciiDisplay += szStr;
                UCHAR c = (UCHAR)buff[i];
                szStr.Format("%X", c);
                m_szHexDisplay += szStr;
            }

项目资源下载:

本项目需要的comport程序,请在这里免费下载:

  • 15
    点赞
  • 97
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米杰的声音

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值