设计带有波形显示和幅度,频率测量的测试应用程序。要求波形数据和测量数据来自uvats。提交测试后截图,波形显示代码截图,测量代码截图。
分析:题目要求实现三个功能——波形显示、幅度测量和频率测量,其中波形、幅度和频率都来自于DSO.所以我们需要通过VISA接口,用指令的方式从仪器获取相关的数据。由于需要通过GUI显示结果,前端我们需要Graph控件、Message控件和Button控件,分别用于显示波形,显示测量结果,控制GUI是否显示数据。由于在这里Graph控件画图和VISA通信耗时很久,如果我们使用单线程编程来完成这道题目的话,可以实现基本功能,但是GUI响应各种事件,比如单击、拖动窗口等事件,都会有不能忍受的延迟。
另外:白嫖作业不是好习惯,本文的主要目的是点拨。有细节上的问题可以私聊我QQ.
根据仪器编程手册,我们要获取到波形数据,需要用到的指令为:
:WAVeform:DATA?
这个简单,我们只需要通过相关的函数将这个命令发送给仪器就行了。
但是我们接受到的信息是怎样的呢?通过查询仪器编程手册,可以得到这样的结论:
帧头里面包含了两个信息:1.仪器发送了多少数据;2.仪器发送的数据是多少字节的。
如上图,我们发送:WAV:DATA?指令收到的帧头是#41000,其中蕴藏的含义为:1.一个数据由4个byte构成,那么我们的每一个接收指针则需要指向至少32bit的内存。2.这一帧数据发送了1000个波形数据,那么我们需要用长度为1000的数组来装下这些数据头。所以我们需要接收两次,一次接收帧头(6个bit),一次接收数据。
在这里,每个波形数据传回来的数字并不是直接的电压值,而是和测量的电压值成倍数关系的很大的整数值(但我不知道这个整数的意义,一旦研究出这个整数是什么了,一定会第一时间告诉读者的),所以我们要乘上一个数,将这个大整数转换为电压。
Warning: 在这里,由于老师提供的uviScanf函数可能会引起General Protection的错误,所以在这里建议用uviRead函数来实现接收功能。
根据仪器编程手册,获得幅度和频率的指令分别为:
:MEASure:VAMPlitude?
:MEASure:FREQuency?
由于发送这两个指令,仪器返回的都是单值,没有太大的执行难度,只要用%lf(双精度浮点)格式接收即可。
Warning:在这里建议用老师给的uviScanf函数接收.
多线程编程主要分为三个步骤:
在这里用到了这个函数:
int CVIFUNC CmtNewThreadPool (int maxNumThreads, CmtThreadPoolHandle *poolHandle);
第一个输入参数为线程池中最大的线程数,第二个输入参数为线程池句柄,这个句柄需要自己在代码中定义,其中CmtThreadPoolHandle可以替换为int。返回值为0则说明线程池创建成功。
e.g.
int pool_handle;
CmtNewThreadPool(3,&pool_handle);
首先我们需要写好辅助线程函数,比如在本次作业中,我将绘制波形和仪器通信函数都放在了辅助线程中。由于辅助线程函数在运行完之后就不会被自动重新调用,所以我们需要把相关代码放在while(1)中。格式大致如下:
e.g.
int CVICALLBACK Thread1 ()
{
//需要定义的变量
while(1)
{
//相关操作...
}
return 0;
}
我们还需要告诉线程池,哪个函数是它需要调度的线程函数,在这里我们用到这个函数:
int CVIFUNC CmtScheduleThreadPoolFunction
(CmtThreadPoolHandle poolHandle, CmtThreadFunctionPtr threadFunction,
void *threadFunctionData, CmtThreadFunctionID *threadFunctionID);
poolHandle即位线程池句柄,threadFunction为线程函数的函数名,threadFunctionData为函数的返回值传向的变量的指针,如果不关系返回值可以输入NULL,CmtThreadFunctionID为线程ID,在调用该函数之前定义即可,数据类型CmtThreadFunctionID依然可以用int替换。使用示例如下:
e.g.
int CVICALLBACK Thread1 ();
/*...*/
int pool_handle;
int thread_funcID;
CmtNewThreadPool(3,&pool_handle);
CmtScheduleThreadPoolFunction (pool_handle, Thread1, NULL, &thread_funcID);
在结束程序的时候,同时也需要将线程池丢弃(没学过操作系统,要丢弃的原因是不是和打开文件后要关闭文件一样是为了把占用的内存释放?求解释)。
相关函数为:
int CVIFUNC CmtDiscardThreadPool (CmtThreadPoolHandle poolHandle);
输入参数poolHandle为要丢弃的线程池的句柄。该函数在RunUserInterface()函数后面运行,即保证UIR关闭后,线程池也将被丢弃。
GUI是这次作业的三个部分中最简单的一个部分了,所以放在了最后一个讲,同时也不准备讲怎么用Graph、Button什么的,大家肯定都会,在这里讲点好玩点的。(这一部分在划水)
用CVI打开你的工程文件,在Build选项卡中选择Target Setting.
然后在Application Icon File中,选择你希望修改为的图标文件,在这里我用pop子的头像作为图标。
Warning:在这里,必须用*.ico文件作为图标文件。百度上可以搜索到一些在线将图片文件转换为ico文件的网站,在这里就不细说了。
用CVI打开工程中的uir文件,在右侧边栏的PANEL的属性栏中的Title and Titlebar Appearance组中找到Title这一选项,将其修改为你希望你的GUI所拥有的标题名就行了。这里我就叫Eden’s GUI(不想动脑筋。
同时,修改相关的属性设置,还可以让你的GUI尺寸不会发生改变,也可以禁用“最大化”按钮,让你的GUI变得更酷一点。