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 下拉菜单指定默认数据
初始化波特率默认为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;
因为在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
运行结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/c842012ba6584d3e6355398c8c0b4d1b.png)
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, "未打开");
}
![打开按钮测试](https://i-blog.csdnimg.cn/blog_migrate/6bcf612179da90e0aef8eaef3d48bccc.gif)
/*****************************设置串口参数部分代码解释*********************************/
说明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罗平生](https://i-blog.csdnimg.cn/blog_migrate/c0af0648d15687a5dc9fef2cd90d5ca8.gif)
其次将发送文本框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罗平生](https://i-blog.csdnimg.cn/blog_migrate/a6a834af5fc8d610db87bc4f548c53d8.gif)
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罗平生](https://i-blog.csdnimg.cn/blog_migrate/d10cda136fe1ece28a365578561fb21e.gif)
6.3 发送功能清空设计
将清空接收按钮改成IDC_CLEAR_SENDLENGTH
/*********************清空按钮*************************/
void CMyComportDlg::OnClearSendlength()
{
// TODO: Add your control notification handler code here
m_SendLength = 0;
SetDlgItemInt(IDC_SEND_LENGTH, 0);
}
效果展示:
![串口字节统计清空测试](https://i-blog.csdnimg.cn/blog_migrate/0a61996793869822136c7c1db1f7cff4.gif)
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罗平生](https://i-blog.csdnimg.cn/blog_migrate/20855f532cf1920dd6e3e883a688bf69.gif)
剩下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罗平生](https://i-blog.csdnimg.cn/blog_migrate/8ad2541f39078f5444b06d620b79e2a2.gif)
问题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程序,请在这里免费下载: