资源描述
VC(MFC)编写串口调试助手
1. 序
确定基本功能:
1.自动寻找串口,并自动添加到下拉框中共选择;
2.有波特率、数据位、停止位、校验位的选择设置;
3.串口打开控制按钮;
4.发送、清除按钮;
5.接收是自动实现的;
6.有定时自动发送功能;
7.有传送文件功能;
8.有状态栏显示,指示串口状态,设置参数和发送接收显示。
下面就一步步实现,本人纯业余,只是记录下来这个学习过程,请勿拍砖。
开发平台Visual C++6.0英文版,电脑是i7-2670Q四核8G内存1G独显的笔记本,装的win7 64位旗舰版,因此VC6兼容不是太好,有些小毛病,不过不影响编写。
2. 创建MFC项目
File -> New -> Projects选择MFC AppWizard(exe),项目名称commassist
选择OK
选中Dialog based,点击Next> 。
默认选项,点击Next> ,
继续默认选项,点击Next> ,如果选中As a statically linked library,生产的EXE可直接在没装VC的机器上运行。可以在项目中进行更改。
选择第二个CCommassistDlg,点击Finish
点击OK。项目创建完毕,进入项目。
删除界面上确定和取消按钮以及静态文字。
3. 创建界面
保存后便可以开始创建界面了。
参考界面
仿照设计的界面,具体添加按钮或编辑框等的布局步骤就不用细说了。
4. 图标修改
在资源视图中选择Icon右键InsertIcon加入打开和关闭的Icon图标或自行绘制,如下图
IDR_MAINFRAME原为MFC提供的图标,这里我直接改成自己的,生成EXE后将会显示这个图标。下面将帮助页面图标也改为自绘图标。
在打开按钮旁边加入自绘的打开和关闭图标:先加入工具条中的Picture,然后选中右键看属性,并如图将Image选为默认的IDI_ICON_CLOSE。如下图
5. 基本设置
下面对各个按钮及编辑框设置进行描述
右键串口对应的Combo Box,ID设置为IDC_COMLIST,Type设置为Drop List,Sort不选择(我系统是WIN7 64位,不选中反而自动排序,至于XP得试试看了,以下的选择相同)。
右键波特率对应的Combo Box,ID设置为IDC_BAUD,Type及Sort同上。
右键数据位对应的Combo Box,ID设置为IDC_BDATA,Type及Sort同上。
右键停止位对应的Combo Box,ID设置为IDC_BSTOP,Type及Sort同上。
右键校验位对应的Combo Box,ID设置为IDC_CAL,Type及Sort同上。
每个下拉框要点击右边的小箭头,然后将其拉长,不然显示不出内容。
接收EDIT框ID设置为IDC_EDIT_RX。
发送EDIT框ID设置为IDC_EDIT_TX。
自动发送时间间隔的EDIT框ID设置为IDC_EDIT_TIMER。
选择文件后面的EDIT框ID设置为IDC_EDIT_FILEPATH。
接收区的十六进制显示的Check Box复选框ID设置为IDC_CHECK_HEXRX。
发送区的十六进制发送的Check Box复选框ID设置为IDC_CHECK_HEXTX。
按钮“打开串口”ID设置为IDC_COMCONTROL。
按钮“清空显示区”ID设置为IDC_BTN_CLRRX。
按钮“手动发送”ID设置为IDC_BTN_HANDSEND。
按钮“清空发送区”ID设置为IDC_BTN_CLRTX。
按钮“自动发送”ID设置为IDC_BTN_AUTOSEND。
按钮“选择文件”ID设置为IDC_BTN_SELCTFILE。
按钮“发送文件”ID设置为IDC_BTN_SENDFILE。
6. 开始写代码
6.1. 基本思路:
因为串口通信部分代码我可能用在以后的单片机上位机上,因此考虑单独形成CPP和H文件,定义为comm.cpp和comm.h。在comm.cpp中编写串口创建、打开、关闭以及串口监听线程(用于自动接收)的代码,同时加入进制转换或显示的函数,这些在comm.h文件中申明,在主对话框中包含comm.h即可。
想修改按钮样式,在网上搜了一圈,结果不轻松,最后确定创建新类来实现。
6.2. 创建自定义按钮类:
View -> Class Wizard选择Add Class -> New,名字MyButton,基类选择CButton。
在头文件 MyButton.h 中加入以下变量和函数定义:
private:
int m_Style; //按钮形状(0-正常,1-当前,2-按下,3-锁定)
bool b_InRect; //鼠标进入标志
CString m_strText; //按钮文字
COLORREF m_ForeColor;//文本颜色
COLORREF m_MouseInColor;//鼠标进入时文本颜色
COLORREF m_BackColor;//背景颜色
COLORREF m_LockForeColor; //锁定按钮的文字颜色
CRect m_ButRect; //按钮尺寸
CFont* p_Font; //字体
void DrawButton(CDC *pDC); //画正常按钮
// 接口函数
public:
MyButton();
void SetText(CString str); //设置文字
void SetForeColor(COLORREF color); //设置文本颜色
void SetBkColor(COLORREF color); //设置背景颜色
void SetTextFont(int FontHight,LPCTSTR FontName); //设置字体
在 MyButton.cpp 的构造函数中初始化变量:
m_Style = 1; //m_Style = 0; //按钮形状风格
b_InRect = false; //鼠标进入标志
m_strText = _T(""); //按钮文字(使用默认文字)
m_ForeColor = RGB(0,0,0); //文字颜色(黑色)
m_MouseInColor = RGB(0,0,255); //鼠标进入时文字颜色(蓝色)
m_BackColor = RGB(230,230,230); //m_BackColor = RGB(243,243,243); //背景色(灰白色)
m_LockForeColor = GetSysColor(COLOR_GRAYTEXT); //锁定按钮的文字颜色
p_Font = NULL; //字体指针
用ClassWizard添加下列消息函数:
PreSubclassWindow();
DrawItem();
onMouseMove();
OnLButtonDown();
OnLButtonUp();
在各函数内加入代码:
void MyButton::PreSubclassWindow()
{
// TODO: Add your specialized code here and/or call the base class
ModifyStyle( 0, BS_OWNERDRAW ); //设置按钮属性为自画式
//PreSubclassWindow()在按钮创建前自动执行,所以我们可以在其中做一些初始工作。
//这里只做了一项工作,就是为按钮设置属性为“自绘”式,这样,用户在添加按钮后,就不需设置“Owner draw”属性了。
CButton::PreSubclassWindow();
}
void MyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
// TODO: Add your code to draw the specified item
CDC *pDC = CDC::FromHandle( lpDrawItemStruct->hDC );
m_ButRect = lpDrawItemStruct->rcItem; //获取按钮尺寸
if( m_strText.IsEmpty() )
GetWindowText( m_strText ); //获取按钮文本
int nSavedDC = pDC->SaveDC();
VERIFY( pDC );
DrawButton( pDC ); //绘制按钮
pDC->RestoreDC( nSavedDC );
}
//DrawItem()函数是一个关键函数,按钮的绘制工作就在这里进行,它的作用相当于对话框中的OnPaint()函数和视图中的OnDraw()函数。
//这里我做了三项工作:获取按钮尺寸、获取按钮文本、绘制按钮。其中绘制工作在自定义函数DrawButton()中完成。以下就是绘制过程:
void MyButton::DrawButton(CDC *pDC)
{
//调整状态
if( m_Style==3 ) m_Style = 0;
if( GetStyle() & WS_DISABLED )
m_Style = 3; //禁止状态
//根据状态调整边框颜色和文字颜色
COLORREF bColor, fColor; //bColor为边框颜色,fColor为文字颜色
switch( m_Style )
{
case 0: bColor = RGB(192,192,192); fColor = m_ForeColor; break; //正常按钮
case 1: bColor = RGB(255,255,255); fColor = m_ForeColor; break; //鼠标进入时按钮
case 2: bColor = RGB(192,192,192); fColor = m_MouseInColor; break; //按下的按钮
case 3: bColor = m_BackColor; fColor = m_LockForeColor; break; //锁定的按钮
}
//绘制按钮背景
CBrush Brush;
Brush.CreateSolidBrush( m_BackColor ); //背景刷
pDC->SelectObject( &Brush );
CPen Pen;
Pen.CreatePen(PS_SOLID, 3, bColor );
pDC->SelectObject( &Pen );
pDC->RoundRect(&m_ButRect,CPoint(10,10)); //画圆角矩形
//绘制按钮按下时的边框
if( m_Style!=2 )
{
CRect Rect;
Rect.SetRect( m_ButRect.left+1, m_ButRect.top+1, m_ButRect.right, m_ButRect.bottom );
pDC->DrawEdge( &Rect, BDR_RAISEDINNER, BF_RECT ); //画边框
}
//绘制按钮文字
pDC->SetTextColor( fColor ); //画文字
pDC->SetBkMode( TRANSPARENT );
pDC->DrawText( m_strText, &m_ButRect, DT_SINGLELINE | DT_CENTER
| DT_VCENTER | DT_END_ELLIPSIS);
//绘制拥有焦点按钮的虚线框
if( GetFocus()==this )
{
CRect Rect;
Rect.SetRect( m_ButRect.left+3, m_ButRect.top+2, m_ButRect.right-3, m_ButRect.bottom-2 );
pDC->DrawFocusRect( &Rect ); //画拥有焦点的虚线框
}
}
//变量 m_Style 表征当前按钮状态,它的取值为:0-正常,1-当前,2-按下,3-锁定。不同状态下按钮的边框颜色和文字颜色有所不同。
//m_Style 的值在鼠标响应函数中进行修改。
//绘制工作主要利用CDC类的绘图函数完成,主要注意在 m_Style 不同取值下表现出来的差别。
void MyButton::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_Style = 2;
Invalidate(); //重绘按钮
CButton::OnLButtonDown(nFlags, point);
}
//OnLButtonDown()函数是单击鼠标左键时的消息函数。这里只是重新绘制按钮,具体的单击响应应该在拥有按钮的对话框或视图中进行。
void MyButton::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
if( !b_InRect || GetCapture()!=this ) //鼠标进入按钮
{
b_InRect = true; //设置进入标志
SetCapture(); //捕获鼠标
m_Style = 2; //m_Style = 1; //设置按钮状态
Invalidate(); //重绘按钮
}
else
{
if ( !m_ButRect.PtInRect(point) ) //鼠标离开按钮
{
b_InRect = false; //清除进入标志
ReleaseCapture(); //释放捕获的鼠标
m_Style = 1; //m_Style = 0; //设置按钮状态
Invalidate(); //重绘按钮
}
}
CButton::OnMouseMove(nFlags, point);
}
//onMouseMove()函数是鼠标移动消息函数,用于判定当前鼠标指针是否在按钮上。b_InRect是个标志,为true表示鼠标指针进入了按钮区域,
//此时要捕获鼠标,让鼠标命令传送给按钮。当鼠标指针离开按钮时,要清除b_InRect标志,并且释放捕获的鼠标,让其它窗口可以接收鼠标命令。
//Invalidate()函数用于更新按钮,它会自动调用DrawItem()函数重新绘制按钮。
//设置条件的目的是仅在鼠标指针进入按钮和离开按钮时更新按钮,这样可以防止鼠标在按钮上移动时发生闪烁。
void MyButton::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
m_Style = 1;
Invalidate(); //重绘按钮
CButton::OnLButtonUp(nFlags, point);
}
//OnLButtonUp()函数是单击鼠标左键后弹起时的消息函数。这里也只是重绘按钮,这样能使按钮在按下和弹起时有所不同,使按钮看上去有动态效果。
//接口函数是用 CMyButton类 定义的按钮修改颜色、字体和按钮文字的接口,由以下函数组成:
//设置按钮文本
void MyButton::SetText(CString str)
{
m_strText = _T("");
SetWindowText(str);
}
//设置文本颜色
void MyButton::SetForeColor(COLORREF color)
{
m_ForeColor = color;
Invalidate();
}
//设置背景颜色
void MyButton::SetBkColor(COLORREF color)
{
m_BackColor = color;
Invalidate();
}
//设置字体(字体高度、字体名)
void MyButton::SetTextFont(int FontHight,LPCTSTR FontName)
{
if ( p_Font ) delete p_Font; //删除旧字体
p_Font = new CFont;
p_Font->CreatePointFont( FontHight, FontName ); //创建新字体
SetFont( p_Font ); //设置字体
}
///由于新字体由 new 生成,必须显式回收,这项工作可以在 CMyButton类 的析构函数中进行:
/*CMyButton::~CMyButton()
{
if ( p_Font ) delete p_Font; //删除字体
}
*/
//这样一个可设置颜色、字体的按钮类就做好了。使用时,先在对话框中放置好按钮,再用 ClassWizard 为按钮添加控制变量,
//并且将变量的类型设置为 CMyButton。之后,可以用该变量调用接口函数设置按钮颜色和字体。
OK,自定义按钮完成。
6.3. 实现过程及代码:
现在可以对按钮,EDIT框等控件添加变量,文字描述麻烦,上图。
comm.cpp编写内容如下
#include "stdafx.h"
#include "commassist.h"
#include "commassistDlg.h"
#include "comm.h"
char ConvertHexChar(char ch);
HANDLE hCom; //串口句柄
CString strcomname; //串口名,如"COM1"
bool ComIsOK; //串口打开状态标识,为真表示已打开,否则未打开
//============自动寻找串口函数=================================
//函数功能:通过扫描注册表来找出当前所有物理串口
//输入参数:无
//返回类型:无
//说 明:若搜索成功,则每搜到一个串口便发送消息通知主对话框,并将串口号以WPARAM传递
void FindComm()
{
//枚举当前系统中的串口
LONG result = 0;
HKEY key = NULL;
result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, //需要打开的主键的名称
"HARDWARE\\DEVICEMAP\\SERIALCOMM", //需要打开的子键的名称,设备串口
0, //保留,必须设置为0
KEY_READ, //安全访问标记,也就是权限
&key); //得到的将要打开键的句柄,当不再需要句柄,
//必须调用 RegCloseKey 关闭它
if( result )
{
AfxMessageBox("无法获取串口,请确认是否安装并连接串口!");
return;
}
TCHAR portname[250]; //串口名
TCHAR data[250];
DWORD portnamelen = 0; //串口名长度
DWORD datalen = 0;
int index = 0;
while(1) //找完COM后跳出
{
portnamelen = 255;
datalen = 255;
result = RegEnumValue(key, //Long,一个已打开项的句柄,或者指定一个标准项名
index++, //Long,欲获取值的索引。注意第一个值的索引编号为零
portname, //String,用于装载位于指定索引处值名的一个缓冲区
&portnamelen, //Long,用于装载lpValueName缓冲区长度的一个变量。
//一旦返回,它会设为实际载入缓冲区的字符数量
NULL, //Long,未用;设为零
NULL, //Long,用于装载值的类型代码的变量
(LPBYTE)data, //Byte,用于装载值数据的一个缓冲区
&datalen); //Long,用于装载lpData缓冲区长度的一个变量。
//一旦返回,它会设为实际载入缓冲区的字符数量
if( result ) break;
//发送消息,WM_USER+1为自定义消息,即找到串口的,并将串口号"COMx"通过WPARAM参数传送给主对话框窗口
//::AfxGetMainWnd()->m_hWnd,获得主对话框句柄
//(WPARAM)(LPCTSTR)data,类型转换
::SendMessage(::AfxGetMainWnd()->m_hWnd,WM_FOUNDCOMM,(WPARAM)(LPCTSTR)data,0);
}
RegCloseKey(key); //调用 RegCloseKey 关闭打开键的句柄
}
//============自动寻找串口函数结束==================
//==========串口打开函数===========================
//功 能:打开串口,将已打开的串口句柄赋值给hCom,给出串口打开状态ComIsOK,完成串口状态设置
//输入参数:波特率,数据位,停止位,校验位
//返回类型:无
void OpenComm(int nBaud, int nData, int nStop, int nCal)
{
hCom = CreateFile ( strcomname, //串口号
GENERIC_READ | GENERIC_WRITE, //允许读或写
0, //独占方式
NULL,
OPEN_EXISTING, //打开而不是创建
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,//重叠方式,用于异步通信
NULL );
if(hCom == INVALID_HANDLE_VALUE)
{
AfxMessageBox("打开COM失败,串口不存在或已被占用!");
ComIsOK = false;
return;
}
ComIsOK = true;
SetCommMask(hCom, EV_TXEMPTY | EV_RXCHAR ); //设置事件掩码,暂时没用上
SetupComm(hCom,1024,1024); //设置输入缓冲区和输出缓冲区的大小都是1024
COMMTIMEOUTS TimeOuts;
//设定读超时
TimeOuts.ReadIntervalTimeout = MAXDWORD;
TimeOuts.ReadTotalTimeoutConstant = 0;
TimeOuts.ReadTotalTimeoutMultiplier = 0;
//设定写超时
TimeOuts.WriteTotalTimeoutConstant = 500;
TimeOuts.WriteTotalTimeoutMultiplier = 100;
if(SetCommTimeouts(hCom,&TimeOuts) == false)
{
CloseHandle(hCom);
ComIsOK = false;
return;
}
//串口属性配置
DCB dcb;
GetCommState(hCom,&dcb);
dcb.BaudRate=nBaud; //dcb.BaudRate=9600; //波特率为9600
dcb.ByteSize=nData; //dcb.ByteSize=8; //每个字节为8位
dcb.StopBits=nStop; //dcb.StopBits=ONESTOPBIT; //1位停止位
dcb.Parity=nCal; //dcb.Parity=NOPARITY; //无奇偶检验位
SetCommState(hCom,&dcb);
PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);
if(SetCommState(hCom,&dcb) == false)
{
CloseHandle(hCom);
ComIsOK = false;
return;
}
return;
}
//==========串口打开函数结束=====================
//==========串口关闭控制函数=====================
void CloseComm()
{
CloseHandle(hCom);
hCom = NULL;
ComIsOK = false;
}
//==========串口关闭控制函数结束==================
//==========串口监听线程函数======================
UINT ThreadFunc(LPVOID pParam)
{
// CCommassistDlg* pdlg = (CCommassistDlg*)pParam; //定义指针指向主对话框
COMSTAT ComStat;
DWORD dwErrorFlags;
while(ComIsOK)
{
DWORD dwBytesRead = 100;
ClearCommError(hCom,&dwErrorFlags,&ComStat);
dwBytesRead = min(dwBytesRead,(DWORD)ComStat.cbInQue);
if(!dwBytesRead)
{
Sleep(10);//continue;//使用continue时,打开串口后CPU占用率非常高
}
else ::SendMessage(::AfxGetMainWnd()->m_hWnd,WM_READCOMM,1,0); //发送消息,已读到
}
return 0;
}
//==========串口监听线程函数结束================
//=================字符串转16进制显示==========
//字符串转16进制显示的函数
//传入参数Data为字符串
//Blank_allow为空格允许标志,为真则代表允许加入空格
//函数返回为CString的结果sResult
CString DisplayCString2Hex(CString Data, bool Blank_allow)
{
CString sResult;
CString sTemp;
int Data_Length;
Data_Length = Data.GetLength();
if (Data_Length == 0) return "";
char *pchar = new char[Data_Length]; //用了new分配内存空间,要记得释放
strncpy(pchar,Data,Data_Length);
for(int i=0; i<Data_Length; i++)
{
sTemp.Format("%02X",pchar[i]);
if(Blank_allow)
{
if(i == Data_Length -1) sResult = sResult + sTemp; //去掉最后一个空格
else sResult = sResult + sTemp+" ";
}
else sResult = sResult + sTemp;
}
delete pchar; //释放内存空间
return sResult;
}
//===============函数结束============================
//=================16进制转字符串======================
//16进制转字符串,输入16进制的字符串,输出转换为16进制码
//传入参数str为字符串,判断输入是否按照16进制格式输入
int ConvertHexC2String(CString str, CByteArray &senddata)
{
//先判断输入字符串是否2个字符一组
int str_Length,iLength;
int hexdata, l_data;
char hstr,lstr;
char cTemp;
str_Length = str.GetLength();
iLength = 0;
senddata.SetSize(str_Length/2); //预先设置数组长度,不设置时,允许有错
char *ppchar = new char[str_Length];
strncpy(ppchar,str,str_Length);
for(int i=0; i<str_Length; )
{
cTemp = ppchar[i];
if(cTemp == ' ')
{
//iLength--;
i++;
continue; //如检测到空格则跳过,继续下一次循环
}
else
{
hstr = ppchar[i]; //取出字符作为16进制高位
i++;
lstr = ppchar[i]; //取出下一个字符作为16进制低位
if(lstr == ' ') //若取出的低位为空格,则不符合16进制2个一组的格式,终止循环
{
AfxMessageBox("请按照16进制每2个字符一组的方式输入",MB_ICONERROR);
break;
}
else
{
hexdata = ConvertHexChar(hstr); //高位转换为相应的0进制
l_data = ConvertHexChar(lstr); //低位转换为相应的10进制
if( (hexdata == -1) || (l_data == -1) )
{
AfxMessageBox("请按照16进制字符要求输入",MB_ICONERROR);
break;
}
else hexdata = hexdata*16 + l_data; //安装16进制方式高位低位合并
senddata[iLength] = (char)hexdata; //int整型数转换为char字符型,并存入数组senddata[]
i++; //进入下一次循环
iLength++; //成功转换一组(2个)字符,记录长度加1
}
}
}
senddata.SetSize(iLength);
delete ppchar;
return iLength;
}
//===============函数结束===========================
char ConvertHexChar(char ch)
{
//将一个字符转换为相应的十六进制
if ((ch >= '0') && (ch <= '9'))
return ch - 48;//0x30;
else if ((ch >= 'A') && (ch <= 'F'))
return ch - 'A' + 10;
else if ((ch >= 'a') && (ch <= 'f'))
return ch - 'a' + 10;
else
return (-1);
}
//=================16进制转字符串显示=====================
//16进制转字符串显示的函数
//传入参数Data为16进制的字符串
//函数返回为CString的结果sResult
CString DisplayHex2CString(CString Data)
{
展开阅读全文