2、设计任务书 设计MFC应用程序,实现图形显示、数据采集和数据通信功能。程序设计可以选用下列两种结构: (1)基于对话框的MFC应用程序; (2)基于文档/视图的MFC应用程序; 设计应用程序界面时,可根据选用的程序结构,通过控件/菜单实现相关的控制功能。 2.1设计任务一:图形显示程序设计 1、根据功能要求设计软件界面,设计图形显示区域的坐标系统和坐标变换公式,要求图形显示区域能够显示不少于200点数据; 2、选用适当的绘图工具(包括画笔、画刷和字体等)和绘图函数绘制图形显示区域; 3、使用随机数函数rand()产生数据,实现多点数据采集功能,每次采集一屏数据,手动刷新; 4、使用随机数函数rand()产生数据,实现单点数据采集功能,每次采集一个数据,引入WM_TIMER消息,实现定时采集并刷新。数据点数超过数据显示区域范围时,曲线自动左移,实现滚动显示; 2.2设计任务二:数据采集程序设计 5、在项目中引入数据源设备DLL库文件; 6、设计数据源设备控制模块,包括设备打开、参数设置和设备关闭等模块; 7、使用数据源设备DLL接口,实现单点数据采集功能,采集正弦波、方波和三角波等信号,每次采集一个数据,引入WM_TIMER消息,实现定时采集并刷新; 2.3设计任务三:串口通信程序设计 8、在项目中引入MSCOMM串口通信控件,设计串口控制功能模块,包括“打开串口”、“关闭串口”、“参数设置”和串口数据接收处理模块; 9、根据MODBUS通信协议,实现软件和模拟流量计的通信接口模块,包括数据包组包、数据包解包和CRC校验等模块; 10、通过串口通信方式,采集模拟流量计的质量流量数据,引入WM_TIMER消息,实现定时采集并显示质量流量参数曲线。
3、软件设计 经过小组讨论,确定了程序整体结构,选择基于对话框的MFC应用程序,应用各种控件实现程序设计要求。 3.1图形显示程序设计 1、根据功能要求设计软件界面,设计图形显示区域的坐标系统和坐标变换公式,要求图形显示区域能够显示不少于200点数据; 设计方案1:坐标系统:逻辑坐标系统;设备坐标系统(屏幕坐标系统、窗口坐标系统、用户区坐标系统)。绘图区域定位坐标为六行十列,绘出一份从(0,0)到(520,320)的矩形网格,网格大小为50*50,坐标功能实现在OnPanint()函数中,通过不断刷新来实现数据的刷新。并且定义了一个m_npoint【】数组来储存数据实现曲线数据点的储存。 2、选用适当的绘图工具(包括画笔、画刷和字体等)和绘图函数绘制图形显示区域; 设计方案2: CPen类的成员函数CreatePen()用于创建画笔,其原型为: BOOL CreatePen (int nPenStyle, int nWidth, COLORREF crColor); 第1个参数是画笔样式,各种虚线只有当线宽为1时有效。第2个参数为线宽,第3个参数为线的颜色,可使用RGB()宏指定。 创建画刷的成员函数的原型为: BOOL CreateSolidBrush ( COLORREF crColor ); 参数crColor指定了画刷的颜色。除此而外,还可以创建一个阴影风格的画刷: BOOL CreateHatchBrush ( int nIndex,COLORREF crColor ); 其中参数nIndex指定了阴影风格。 绘图用到红色、绿色和蓝色三种颜色宽度为3的线。 1)绘制直线 CDC::MoveTo(int x, int y) 将画笔移动到当前位置,即坐标(x, y)处,并没有画线。 CDC::LineTo(int x, int y) 画笔从当前位置绘制一条子线到(x, y)点,但不包含(x, y)点。 (3) 绘制矩形 CDC::Rectangle(int x1, int y1, int x2, int y2) 参数x1、y1表示矩形左上角坐标,参数x2、y2表示矩形右下角坐标。 (6) 绘制椭圆函数 CDC::Ellipse(int x1, int y1, int x2, int y2) 参数x1、y1是绘制椭圆外接矩形左上角的坐标,x2、y2是外接矩形的右下角坐标。当绘制的外接矩形长和宽相同,即绘制的是圆。 3、使用随机数函数rand()产生数据,实现多点数据采集功能,每次采集一屏数据,手动刷新; 设计方案3:图形刷新是绘图过程中必须考虑的重要问题,包括:刷新请求、对刷新请求的响应、刷新方法。当用户区的内容需要刷新时,系统向应用程序消息队列发送WM_PAINT消息。当用户拖动最小化窗口时系统调用此函数取得光标,操作界面为左侧是网格,右侧为多点采集的点击窗口,点击一次即对数据进行采集,点击刷新则刷新一次重新采集数据。
4、使用随机数函数rand()产生数据,实现单点数据采集功能,每次采集一个数据,引入WM_TIMER消息,实现定时采集并刷新。数据点数超过数据显示区域范围时,曲线自动左移,实现滚动显示; 设计方案4:定时器:可以帮助开发者或者用户定时完成某项任务。在使用定时器时,我们可以给系统传入一个时间间隔数据,然后系统就会在每个此时间间隔后触发定时处理程序,实现周期性的自动操作。例如,我们可以在数据采集系统中,为定时器设置定时采集时间间隔为1个小时,那么每隔1个小时系统就会采集一次数据,这样就可以在无人操作的情况下准确的进行操作。 MFC定时器:VS2010编程中,我们可以使用MFC的CWnd类提供的成员函数SetTimer实现定时器功能,也可以使用Windows API函数SetTimer来实现。两者使用方法实际上很类似,但也有不同。CWnd类的SetTimer成员函数只能在CWnd类或其派生类中调用,而API函数SetTimer则没有这个限制,这是一个很重要的区别。 单点采集及显示:在定时器的ontimer()函数中,当数据点没超出显示范围时,利用随机函数rand()产生数据依次储存到m_point[]数组中,在onpaint()函数中,利用ployline()函数连接m_point()中的点,定义一个m_nPointNum变量,从1变到99,因此连接m_nPointNum个点,实现信号的单点采集。 滚动显示:当数据点超出显示区域后,也就是m_nPointNum变量等于100时,m_point[]数组中的每一个数代替后一个数也就是m_Point[k].y = m_Point[k+1].y,然后只剩下一个m_point[99],令其等于新产生的随机变量,实现滚动显示。 单点采集开始后,添加控件函数Invalidate()通知处理程序代码并且刷新窗口,当采集点数超过屏幕时则显示范围左移,以此实现滚动显示。
3.2数据采集程序设计 5、在项目中引入数据源设备DLL库文件; 设计方案5:DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。一个应用程序可使用多个DLL文件,一个DLL文件也可能被不同的应用程序使用,这样的DLL文件被称为共享DLL文件。 lib库:一种是静态lib(static Lib),也就是最常见的lib库,在编译时直接将代码加入程序当中。静态lib中,一个lib文件实际上是任意个obj文件的集合,obj文件是cpp文件编译生成的。 另一种lib包含了函数所在的DLL文件和文件中函数位置的信息(入口),代码由运行时加载在进程空间中的DLL提供。也就是平时编写dll时附带产生的lib,其中Lib只是Dll的附带品,是DLL导出的函数列表文件而已。 共同点:两者都是二进制文件,都是在链接时调用,使用static lib的exe可以直接运行,使用另一种lib的exe需要对应的dll才能运行。 编译之后生成一个mylib.lib的库文件,引用库文件如下: (1)右击工程-->属性(alt+F7)-->C/C++-->附加包含目录--->包含所需要的头文件; (2)右击工程-->属性(alt+F7)-->链接器-->常规--->附加目录--->包含lib库所在的目录; (3)右击工程-->属性(alt+F7)-->输入--->附加依赖项--->lib库名。 这样就能引用该lib内的函数了。 dll的调用: (1)dll的显示调用:前提是我们必须知道函数名、返回值、参数列表。此时不需要相应的头文件,也不需要lib文件。只需要对应的dll就可以了。 这种情况下:HINSTANCE hInstance = LoadLibrary("mydll1.dll");会出现”2 IntelliSense: "const char *" 类型的实参与 "LPCWSTR" 类型的形参不兼容 “问题。解决办法:工程--->属性--->常规-->字符集--->使用多字节字符集就可以了。 (2)dll的隐式调用: 这是相应的lib库就派上用场了,其用法static library用法一致。 总结:显示调用和隐式调用这两种方法各有千秋,显示调用优点是只要接口参数列表没有发生改变,修改了函数实现的细节也没关系,所调用的exe程序不用重新编译只需替换新版的dll就可以。在大的项目中只用比较方便,但调用过程相对复杂需要使用一系列的windows函数还有函数指针等。对于隐式调用和static library用法一致比较容易理解,缺点是只要修改了任意内容相应的exe都需要重新编译。 6、设计数据源设备控制模块,包括设备打开、参数设置和设备关闭等模块; 设计方案6:数据源设备的操作界面为,左侧为数据网格,右侧为包括“设备打开”、“设备关闭”、“数据采集”、“停止”、“方波”、“三角波”、“正弦波”和“刷新”的界面,各个控件皆为按钮控件。设备打开直接调用DLL库中的OpenDevice()函数,设备关闭直接调用DLL库中的CloseDevice()函数,参数设置需要用滚动条的采集点和滚动参数调用到DLL库中的SetSignalPara()函数,实现三个不同信号的参数设置,包括幅值,频率,相位设置。 7、使用数据源设备DLL接口,实现单点数据采集功能,采集正弦波、方波和三角波等信号,每次采集一个数据,引入WM_TIMER消息,实现定时采集并刷新; 设计方案7:三种信号的数据采集均为单点采集,切换采集信号类型的操作方式:设备打开后选择波类型,然后开始数据采集。切换其他波时需先进行“停止”,再清屏刷新,不然最新产生的波形会在之前产生的波形之后显示,点击其他波形后再点击数据采集,从而进行切换采集数据类型的操作。切换数据类型的方法是改变参数m_flag的值,然后通过ontimer()函数生成新的信号并且储存,不断刷新Onpaint()函数实现。 3.3串口通信程序设计 8、在项目中引入MSCOMM串口通信控件,设计串口控制功能模块,包括“打开串口”、“关闭串口”、“参数设置”和串口数据接收处理模块; 设计方案8:MSCOMM控件:属性描述 CommPort 设置并返回通讯端口号。 Settings 以字符串的形式设置并返回波特率、奇偶校验、数据位、停止位。 PortOpen 设置并返回通讯端口的状态。也可以打开和关闭端口。 Input 从接收缓冲区返回和删除字符。 Output 向传输缓冲区写一个字符串。 “启动串口”“关闭串口”“参数设置”均位于操作界面的左侧,“串口数据接收模块”位于操作界面的右上方,各类流量计的参数显示在相应的参数区域内,“实时曲线”则位于操作界面的右下方, 9、根据MODBUS通信协议,实现软件和模拟流量计的通信接口模块,包括数据包组包、数据包解包和CRC校验等模块; 设计方案9:Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。 使用RING BUFF,接收部分就接收不分析数据,然后外面单独解码,可以再加上超时RESET。或者直接用dll或库里的函数的参数,程序调用函数时,直接把通信内容放参数里。 CRC校验原理: 其根本思想就是先在要发送的帧后面附加一个数(这个就是用来校验的校验码,但要注意,这里的数也是二进制序列的,下同),生成一个新帧发送给接收端。当然,这个附加的数不是随意的,它要使所生成的新帧能与发送端和接收端共同选定的某个特定数整除(注意,这里不是直接采用二进制除法,而是采用一种称之为“模2除法”)。到达接收端后,再把接收到的新帧除以(同样采用“模2除法”)这个选定的除数。因为在发送端发送数据帧之前就已通过附加一个数,做了“去余”处理(也就已经能整除了),所以结果应该是没有余数。如果有余数,则表明该帧在传输过程中出现了差错。 模2除法: 模2除法与算术除法类似,但每一位除的结果不影响其它位,即不向上一位借位,所以实际上就是异或。在循环冗余校验码(CRC)的计算中有应用到模2除法。 CRC校验步骤: CRC校验中有两个关键点,一是预先确定一个发送送端和接收端都用来作为除数的二进制比特串(或多项式),可以随机选择,也可以使用国际标准,但是最高位和最低位必须为1;二是把原始帧与上面计算出的除数进行模2除法运算,计算出CRC码。 具体步骤: (1)选择合适的除数; (2)看选定除数的二进制位数,然后再要发送的数据帧上面加上这个位数-1位的0,然后用新生成的帧以模2除法的方式除上面的除数,得到的余数就是该帧的CRC校验码。注意,余数的位数一定只比除数位数少一位,也就是CRC校验码位数比除数位数少一位,如果前面位是0也不能省略; (3)将计算出来的CRC校验码附加在原数据帧后面,构建成一个新的数据帧进行发送;最后接收端在以模2除法方式除以前面选择的除数,如果没有余数,则说明数据帧在传输的过程中没有出错。 先将一个float型转化为内存存储格式的步骤: (1)先将这个实数的绝对值化为二进制格式; (2)将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边; (3)从小数点右边第一位开始数出二十三位数字放入第22到第0位; (4)如果实数是正的,则在第31位放入“0”,否则放入“1”; (5)如果n是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第30位放入“0”; (6)如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。 如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位。 再将一个内存存储的float二进制格式转化为十进制: (1)将第22位到第0位的二进制数写出来,在最左边补一位“1”,得到二十四位有效数字。将小数点点在最左边那个“1”的右边; (2)取出第29到第23位所表示的值n。当30位是“0”时将n各位求反。当30位是“1”时将n增1; (3)将小数点左移n位(当30位是“0”时)或右移n位(当30位是“1”时),得到一个二进制表示的实数; (4)将这个二进制实数化为十进制,并根据第31位是“0”还是“1”加上正号或负号即可。 10、通过串口通信方式,采集模拟流量计的质量流量数据,引入WM_TIMER消息,实现定时采集并显示质量流量参数曲线。 设计方案10: 将采集到的数据中反映质量流量的数据存入一一个BYTE类型的数组,转换为float类型后,进行参数曲线绘制,由于显示区域显示数值最大为300,而采集数据值的范围约为0 3600,所以将采集数据乘系数-0.05并且起点加上310的偏移量,以便于在图形显示区进行显示。
4、软件开发实现 4.1图形显示程序实现代码说明 1、根据功能要求设计软件界面,设计图形显示区域的坐标系统和坐标变换公式,要求图形显示区域能够显示不少于200点数据; 代码段1:本段代码实现了绘制网格线的功能 代码: CPaintDC dc(this); CPen PenGrid; CPen PenLine; CPen *pOldPen = NULL; dc.Rectangle(0,0,520,320); PenGrid.CreatePen(PS_DASH,1,RGB(255,0,0)); pOldPen = dc.SelectObject(&PenGrid); for(int k=0; k<7;k++) // 画方格线 { dc.MoveTo(10,10+50*k); dc.LineTo(510,10+50*k); } for(int k=0; k<11; k++) { dc.MoveTo(10+50*k,10); dc.LineTo(10+50*k,310); } 2、选用适当的绘图工具(包括画笔、画刷和字体等)和绘图函数绘制图形显示区域; 代码段1:本段代码实现了选线型的功能 代码: UpdateData(TRUE);//DDX 控件--〉变量 switch(m_nLineStyle) //选线型 { case 0: PenLine.CreatePen(PS_SOLID,3, RGB(m_Red.GetScrollPos(), m_Green.GetScrollPos(), m_Blue.GetScrollPos())); break; case 1: PenLine.CreatePen(PS_DASH,1, RGB(m_Red.GetScrollPos(), m_Green.GetScrollPos(), m_Blue.GetScrollPos())); break; case 2: PenLine.CreatePen(PS_DOT,1, RGB(m_Red.GetScrollPos(), m_Green.GetScrollPos(), m_Blue.GetScrollPos())); break; } 3、使用随机数函数rand()产生数据,实现多点数据采集功能,每次采集一屏数据,手动刷新; 代码段1:本段代码实现了多点采集数据的功能 代码: void CDemoDlg::OnClickedRefresh() //多点采集 手动刷新 { // TODO: 在此添加控件通知处理程序代码 m_flag=0; for(int k=0; k<100;k++) { m_Point[k].x = 10+5*k; m_Point[k].y = 10+rand()%300; } Invalidate();// 发送WM_PAINT消息 } void CDemoDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)//颜色设置滑动块 { // TODO: 在此添加消息处理程序代码和/或调用默认值 UINT Pos; switch(nSBCode) { case SB_THUMBTRACK://拖动滑动块 pScrollBar->SetScrollPos(nPos); break; case SB_LINELEFT: Pos = pScrollBar->GetScrollPos(); pScrollBar->SetScrollPos(Pos-10); break; case SB_LINERIGHT: Pos = pScrollBar->GetScrollPos(); pScrollBar->SetScrollPos(Pos+10); break; case SB_PAGELEFT: Pos = pScrollBar->GetScrollPos(); pScrollBar->SetScrollPos(Pos-30); break; case SB_PAGERIGHT: Pos = pScrollBar->GetScrollPos(); pScrollBar->SetScrollPos(Pos+30); break; } Invalidate();// 发送WM_PAINT消息 CDialog::OnHScroll(nSBCode, nPos, pScrollBar); } 4、使用随机数函数rand()产生数据,实现单点数据采集功能,每次采集一个数据,引入WM_TIMER消息,实现定时采集并刷新。数据点数超过数据显示区域范围时,曲线自动左移,实现滚动显示; 代码段1:本段代码实现了单点采集数据的功能 代码: void CDemoDlg::OnClickedShownow() //实现单点采集 { m_flag=1; SetTimer(1,50,NULL); m_nRunSta = 1; } void CDemoDlg::OnBnClickedShowstop() //停止采集 { KillTimer(1); m_nRunSta = 0;// TODO: 在此添加控件通知处理程序代码 } void CDemoDlg::OnTimer(UINT nIDEvent) //单点采集定时循环 { if(m_nPointNum<100) { m_Point[m_nPointNum].x = 10+5*m_nPointNum; m_Point[m_nPointNum].y = 10+rand()%300; m_nPointNum++; } else //点数超过屏幕显示范围左移 { for(int k=0; k<99; k++) { m_Point[k].y = m_Point[k+1].y; } m_Point[99].y = 10+rand()%300; } Invalidate(); // 通知刷新窗口 CDialog::OnTimer(nIDEvent); } if(m_flag==1) //单点采集 { dc.Polyline(m_Point,m_nPointNum); }
4.2数据采集程序实现代码说明 5、在项目中引入数据源设备DLL库文件; 代码段1:本段代码实现了引入头文件的功能 代码:#include "MyDLL.h" // DemoDlg.cpp : 实现文件 // #include "stdafx.h" #include "Demo.h" #include "DemoDlg.h" #include "afxdialogex.h" #include "MyDLL.h" #ifdef _DEBUG #define new DEBUG_NEW #endif DLL文件: extern "C" { int OpenDevice();/// 打开设备 /// 返回值:0-成功 其他-失败 void SetSignalPara(int pSigType, double pAmp, double pFreq, double pPhase0);/// 设置信号参数 /// pSigType:信号类型,0-正弦信号;1-方波信号;2-三角波信号 /// pAmp:信号幅值 /// pFreq:信号频率,单位:HZ /// pPhase0:信号初始相位,单位:弧度
void SetSWDuty(double pDuty);/// 设置方波占空比/// pDuty:方波占空比,取值范围0.00-1.00
void SetSampPara(double pSampFreq);/// 设置采样参数 /// pSampFreq:采样频率,单位:点/秒
int ReadOneData(double *pData);/// 采集正弦信号数据(单点) /// pData:数据缓存区地址 /// 返回值:0-成功 其他-失败
int ReadData(int Num, double *pData);/// 采集正弦信号数据(多点) /// Num:数据点数 /// pData:数据缓存区地址 /// 返回值:0-成功 其他-失败
int CloseDevice(void);/// 关闭设备 /// 返回值:0-成功 其他-失败 } 6、设计数据源设备控制模块,包括设备打开、参数设置和设备关闭等模块; 代码段1:本段代码实现了设备打开和关闭的功能 void CDemoDlg::OnClickedRun() { OpenDevice(); AfxMessageBox(_T("设备打开成功")); // TODO: 在此添加控件通知处理程序代码 } void CDemoDlg::OnBnClickedClose() { // TODO: 在此添加控件通知处理程序代码 CloseDevice(); AfxMessageBox(_T("设备关闭成功")); } 代码段2:实现了参数设置功能 //参数设置初始化 m_fuzhi.SetScrollRange(0,150); m_fuzhi.SetScrollPos(150); m_pinlv.SetScrollRange(10,150); m_pinlv.SetScrollPos(10); m_xiangwei.SetScrollRange(0,150); m_xiangwei.SetScrollPos(0);
void CDemoDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) //滚动条拖动处理参数 { // TODO: 在此添加消息处理程序代码和/或调用默认值 UINT Pos; switch(nSBCode) { case SB_THUMBTRACK://拖动滑动块 pScrollBar->SetScrollPos(nPos); break; case SB_LINELEFT: Pos = pScrollBar->GetScrollPos(); pScrollBar->SetScrollPos(Pos-10); break; case SB_LINERIGHT: Pos = pScrollBar->GetScrollPos(); pScrollBar->SetScrollPos(Pos+10); break; case SB_PAGELEFT: Pos = pScrollBar->GetScrollPos(); pScrollBar->SetScrollPos(Pos-30); break; case SB_PAGERIGHT: Pos = pScrollBar->GetScrollPos(); pScrollBar->SetScrollPos(Pos+30); break; } Invalidate();// 发送WM_PAINT消息 CDialog::OnHScroll(nSBCode, nPos, pScrollBar); }
SetSignalPara(0,m_fuzhi.GetScrollPos(),m_pinlv.GetScrollPos(),m_xiangwei.GetScrollPos()) //参数代入到信号类型采集函数中,实现参数的变化呈现
7、使用数据源设备DLL接口,实现单点数据采集功能,采集正弦波、方波和三角波等信号,每次采集一个数据,引入WM_TIMER消息,实现定时采集并刷新; 代码段1:本段代码实现了正弦波、方波和三角波的单点信号采集 代码: void CDemoDlg::OnTimer(UINT nIDEvent) //定时单点采集 { if(m_flag==0) //正弦信号单点采集 { if(m_nPointNum<100) { SetSignalPara(0,1, 30, 0); double dblData[100]; ReadData(100,dblData); m_Point[m_nPointNum].y = 160-150*dblData[m_nPointNum]; m_nPointNum++; } else { for(int k=0; k<99; k++) { m_Point[k].y = m_Point[k+1].y; } SetSignalPara(0,1, 30, 0); double dblData[10000]; ReadData(10000,dblData); m_Point[99].y = 160-150*dblData[m_k]; m_k++; } Invalidate(); // 通知刷新窗口 } else if(m_flag==1) //方波信号单点采集 { if(m_nPointNum<100) { SetSignalPara(1,1, 30, 0); SetSWDuty(0.5); double dblData[100]; ReadData(100,dblData); m_Point[m_nPointNum].y = 160-150*dblData[m_nPointNum]; m_nPointNum++; } else { for(int k=0; k<99; k++) { m_Point[k].y = m_Point[k+1].y; } SetSignalPara(1,1, 30, 0); SetSWDuty(0.5); double dblData[10000]; ReadData(10000,dblData); m_Point[99].y = 160-150*dblData[m_k]; m_k++; } Invalidate(); // 通知刷新窗口 } else if(m_flag==2) //三角波信号单点采集 { if(m_nPointNum<100) { SetSignalPara(2,1,30,0); double dblData[10000]; ReadData(10000,dblData); m_Point[m_nPointNum].y = 160-150*dblData[m_nPointNum]; m_nPointNum++; } else { for(int k=0; k<99; k++) { m_Point[k].y = m_Point[k+1].y; } SetSignalPara(2,1, 30, 0); double dblData[10000]; ReadData(1000,dblData); m_Point[99].y = 160-150*dblData[m_k]; m_k++; } Invalidate(); // 通知刷新窗口 } CDialog::OnTimer(nIDEvent); } // //单点
void CDemoDlg::OnClickedShownow() //数据开始采集 { SetTimer(1,50,NULL); } void CDemoDlg::OnBnClickedShowstop() //停止采集 { KillTimer(1); } dc.Polyline(m_Point,m_nPointNum); //实现单点的曲线绘制 4.3串口通信程序实现代码说明 8、在项目中引入MSCOMM串口通信控件,设计串口控制功能模块,包括“打开串口”、“关闭串口”、“参数设置”和串口数据接收处理模块; 代码段1:本段代码实现了设置串口参数的功能 void CCommDlg::InitComm(int iComport,int bps)
{
m_Comm.SetCommPort(iComport);
m_Comm.SetInBufferSize(1024);
m_Comm.SetOutBufferSize(1024);
if(!m_Comm.GetPortOpen())
{
m_Comm.SetPortOpen(TRUE);
}
m_Comm.SetInputMode(1);
CString strparm;
strparm.Format("%d,n,8,1",bps);
m_Comm.SetSettings(strparm);
m_Comm.SetRThreshold(1);
m_Comm.SetInBufferCount(0);
}
代码段2:本段代码实现了串口开关的功能
void CCommDlg::OnStopreceive()
{
if(m_Comm.GetPortOpen())
{
m_Comm.SetPortOpen(FALSE);
AfxMessageBox("关闭串口成功");
}
else
{
AfxMessageBox("串口未打开 ");
}
}
void CCommDlg::OnStartreceive()
{
CCommApp* pApp = (CCommApp*)AfxGetApp();
UpdateData(TRUE);
pApp->m_nPort = atoi(m_Port);
int n = pApp->m_nPort;
int bps = atoi(m_bps);;
InitComm(n, bps);
if(m_Comm.GetPortOpen())
{
AfxMessageBox("打开串口成功");
}
else
{
AfxMessageBox("打开失败");
}
9、根据MODBUS通信协议,实现软件和模拟流量计的通信接口模块,包括数据包组包、数据包解包和CRC校验等模块;
代码段1:本段代码实现了数据传送的功能
void CCommDlg::OnSend()
{
TODO: Add your control notification handler code here
InitComm();
UpdateData(TRUE);
int Count=m_Send.GetLength();
CByteArray m_Array;
m_Array.RemoveAll();
m_Array.SetSize(Count);
for(int i=0;i<Count;i++)
m_Array.SetAt(i,m_Send);
m_Comm.SetOutput(COleVariant(m_Array));
UpdateData(FALSE);
}
代码段2:本段代码实现了CRC检验功能
WORD CRC16(BYTE *ptr, int len)
{
WORD wcrc = 0xFFFF; // 预置16位crc寄存器,初值全部为1
BYTE temp; // 定义中间变量
int i = 0, j = 0; // 定义计数
for (i = 0; i < len; i++) // 循环计算每个数据
{
temp = *ptr & 0x00FF; // 将八位数据与crc寄存器亦或
ptr++; // 指针地址增加,指向下个数据
wcrc ^= temp; // 将数据存入crc寄存器
for (j = 0; j < 8; j++) // 循环计算数据的
{
if (wcrc & 0x0001) // 判断右移出的是不是1,如果是1则与多项式进行异或。
{
wcrc >>= 1; // 先将数据右移一位
wcrc ^= 0xA001; // 与上面的多项式进行异或
}
else // 如果不是1,则直接移出
{
wcrc >>= 1;//直接移出
}
}
}
return (unsigned int)(wcrc);
}
代码段3:本段代码实现了float字节转换类型功能
static int m_nFLMode1 = 1; //浮点数字节序
float TransferFloat(BYTE *pBytes)
{
float fltRetVal = 0.00;
BYTE *p = (BYTE*)&fltRetVal;
static BYTE nIdx[4][4] = { { 0, 1, 2, 3 }, { 1, 0, 3, 2 }, { 2, 3, 0, 1 }, { 3, 2, 1, 0 } };
for (int k = 0; k < sizeof(float); k++)
{
p[nIdx[m_nFLMode1][k]] = *pBytes++;
}
return fltRetVal;
}
10、通过串口通信方式,采集模拟流量计的质量流量数据,引入WM_TIMER消息,实现定时采集并显示质量流量参数曲线。
代码段1:本段代码实现了绘制网格线的功能
CGdiObject* pOldPen = pDC->SelectObject(pPenLine);
int ipontstart = 10;
rect = CRect(rect.left+ipontstart, rect.top+ipontstart,rect.right-ipontstart,rect.bottom-ipontstart);
for (i = 0; i <= max_nums_x; i++)
{
pDC->MoveTo(ipontstart + 1.0*rect.Width() /max_nums_x * i,ipontstart + rect.Height());
pDC->LineTo(ipontstart + 1.0*rect.Width() /max_nums_x * i,ipontstart);
}
for ( i = 0; i <= max_nums_y ; i++)
{
pDC->MoveTo(ipontstart ,ipontstart +1.0*rect.Height()/max_nums_y*i);
pDC->LineTo(ipontstart+ rect.Width() , ipontstart +1.0*rect.Height()/max_nums_y*i);
}
pOldPen = pDC->SelectObject(pPenBlue);
int lenwith = rect.Width()/max_nums_x;
for ( i = 1; i < m_pointnumshava; i++)
{
int lentemp = m_fIntensity[i-1]/max_ydata*rect.Height();
代码段2:本段代码实现了绘制流量曲线的功能
void CCommDlg::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
if (nIDEvent ==1)
{
//发送命令
if(m_Comm.GetPortOpen() ==FALSE) return;
UCHAR Sendbuf[8] = {0x01, 0x03 ,0x13, 0xBA,0x00,0x0F,0x20,0xAF};
CByteArray m_Array;//CByteArray构造一个空的字节数组
m_Array.RemoveAll();
m_Array.SetSize(sizeof(Sendbuf));
for(int i=0;i<sizeof(Sendbuf);i++)
m_Array.SetAt(i,Sendbuf);
//发送
m_Comm.SetOutput(COleVariant(m_Array));//向缓冲区写数据流
Sleep(500);
UpdateData(TRUE);
//处理显示原始数据
VARIANT m_input;
int count = m_Comm.GetInBufferCount();
if(count< 35) return;
if( count >= 35) count = 35;
UCHAR * porgdata;
if(count > 0)
//得到接收数据
m_input = m_Comm.GetInput();//从缓冲区取走一串字符
porgdata = (unsigned char*)m_input.parray->pvData;
CString strshouw;
m_Receive = "";
for (int i = 0; i < count ; i++)
{
strshouw.Format("%02x ",porgdata);
m_Receive +=strshouw;
}
// 显示数据
UpdateData(FALSE);
if(porgdata[0] != 0x01) return;
if(porgdata[1] != 0x03) return;
//判断校验
WORD crc16 =CRC16(porgdata,33);
WORD crc162;
memcpy(&crc162,porgdata+33,2);
if(crc162 != crc16)
return;
//显示数据
int off = 3;
float retVal1 = TransferFloat(porgdata+off); //质量流量
off+=4;
float retVal2 = TransferFloat(porgdata+off); //体积流量
off+=4;
float retVal3 = TransferFloat(porgdata+off); //密度
off+=4;
float retVal4 = TransferFloat(porgdata+off); //温度
off+=4;
float retVal5 = TransferFloat(porgdata+off); //累积和1
off+=4;
float retVal6 = TransferFloat(porgdata+off); //累积和2
off+=4;
float retVal7 = TransferFloat(porgdata+off); //累积和3
off+=4;
WORD status =0 ;
memcpy(&status,porgdata+off,2);
CString strtemp;
strtemp.Format("%.3f",retVal1);
GetDlgItem(IDC_EDIT1)->SetWindowText(strtemp);
strtemp.Format("%.3f",retVal2);
GetDlgItem(IDC_EDIT2)->SetWindowText(strtemp);
strtemp.Format("%.3f",retVal3);
GetDlgItem(IDC_EDIT3)->SetWindowText(strtemp);
strtemp.Format("%.3f",retVal4);
GetDlgItem(IDC_EDIT4)->SetWindowText(strtemp);
strtemp.Format("%.3f",retVal5);
GetDlgItem(IDC_EDIT5)->SetWindowText(strtemp);
strtemp.Format("%.3f",retVal6);
GetDlgItem(IDC_EDIT6)->SetWindowText(strtemp);
strtemp.Format("%.3f",retVal7);
GetDlgItem(IDC_EDIT7)->SetWindowText(strtemp);
strtemp.Format("%d",status);
GetDlgItem(IDC_EDIT8)->SetWindowText(strtemp);
drawChart(retVal1);
}
CDialog::OnTimer(nIDEvent);
}
5、软件测试
5.1图形显示程序测试
1、根据功能要求设计软件界面,设计图形显示区域的坐标系统和坐标变换公式,要求图形显示区域能够显示不少于200点数据;
2、选用适当的绘图工具(包括画笔、画刷和字体等)和绘图函数绘制图形显示区域;
3、使用随机数函数rand()产生数据,实现多点数据采集功能,每次采集一屏数据,手动刷新;
4、使用随机数函数rand()产生数据,实现单点数据采集功能,每次采集一个数据,引入WM_TIMER消息,实现定时采集并刷新。数据点数超过数据显示区域范围时,曲线自动左移,实现滚动显示;
测试结果如下:
图5.1为初次运行未进行任何操作时软件界面。功能区为数据采集方式设置,分别为单点采集、多点采集,以及随机函数rand()曲线的显示方式,分别为实线、虚线和点线式。下方为曲线颜色设置,初始参数为红色 RGB(255,0,0)
选择单点采集【RGB(255,255,0)】时数据采集如图5.2所示,多点采集【RGB(255,255,0)】时数据采集如图5.3所示,并以多点采集为例,分别改变函数曲线的显示属性。实线显示【RGB(255,0,255)】图5.3,虚线显示【RGB(0,0,255)】图5.4,点线显示【RGB(255,0,0)】图5.5,并通过改变RGB值来改变曲线颜色。
测试结论:图形显示程序初步实现了设计任务中的所有要求,并能方便的改变显示数据的曲线颜色、采样方式等属性。
图5-1
图5-2
图5-3
图5-4
图5-5
5.2数据采集程序测试
5、在项目中引入数据源设备DLL库文件;
6、设计数据源设备控制模块,包括设备打开、参数设置和设备关闭等模块;
7、使用数据源设备DLL接口,实现单点数据采集功能,采集正弦波、方波和三角波等信号,每次采集一个数据,引入WM_TIMER消息,实现定时采集并刷新;
测试结果如下:
图5.6为初次运行未进行任何操作时软件界面。功能区为信号发生设置、采集设置和显示设置。首先打开设备,如图5.7,选择数据采集并分别方波、正弦波和三角波信号,如图5.8-5.10所示,可以根据采集需求切换函数曲线颜色、线型。
测试结论:程序可以实现单点采集功能,并分别采集正弦波、方波和三角波信号。并能实现刷新功能。
图5.6
图5.7
图5.8
图5.9
图5.10
5.3串口通信程序实测试
8、在项目中引入MSCOMM串口通信控件,设计串口控制功能模块,包括“打开串口”、“关闭串口”、“参数设置”和串口数据接收处理模块;
9、根据MODBUS通信协议,实现软件和模拟流量计的通信接口模块,包括数据包组包、数据包解包和CRC校验等模块;
10、通过串口通信方式,采集模拟流量计的质量流量数据,引入WM_TIMER消息,实现定时采集并显示质量流量参数曲线。
测试结果如下:
如图5.11所示,模拟流量计界面分为参数设置、数据显示和开
|