综述
在实际的工程应用中,例如通信系统仿真,是多人进行的(每个人负责编写一个模块),大计算量的,同时要求编写出来的模块具有可移植性和可重用性,由于现代的复杂通信系统构造非常的庞大,如何调试各自独立的模块,观察输入输出,对于减小开发周期至关重要。
基于以上的要求提出了以下解决问题的原则:
1. 使用C语言编写模块代码,获得比高级语言(例如Matlab)高得多的移植性和重用性能,方便移植到嵌入式操作系统甚至是构造专用的集成电路芯片。
2. 支持网络计算。利用多台计算机同时工作,提高系统的计算性能。缩短仿真时间,缩短开发周期。
3. 独立调试模块的能力。这同样是为了缩短开发的周期。
4. 观察数据的能力。
假设多人编写一个通信系统,每个人分别写各自的模块,每个模块都要具有读写文件和联网的功能,将会非常的繁琐复杂,容易出错,不能将所有的精力放在真正需要花时间的模块上面。本文给出了一个实现方案,使用同一个程序调用不同的模块,实现联网,显示,读写文件的功能,模块的可重用的性能也因此大大提高。
调用程序的实现
DataProbe作为一个这样的调用程序,其基本思想如下图所示:
图1 基本思想
图 2 模型的数据
其中A,B,C,D是数据缓存区,控制程序从输入获得的数据首先放在了A中,判断是否将其移动到B中,模型从B中获得数据,运行完的后的输出放在C中。控制程序判断是否将将C中的数据移动到D中准备输出。
图 3 模型的输入输出及运行流程
通过查看图3,可以大致知道模型的运行情况。特别的,考虑这样的情况:缓存区A中的数据已经更新,但B中数据还没有被模型利用来进行计算,此时不能更新B;由于D中的数据没有发送完毕,即使C中的数据已经更新了,还是不能复制到D中。这些导致模型无法运行,因为一旦运行,产生的输出就会覆盖掉还没有来得及发送掉的保存在C中的数据。所以,必须设定运行标志位,并查询输入输出状态。实现的细节就不在这里讨论了。
DataProbe除了能够对模型进行操作之外,另一个功能就是数据的观察和分析。
对于静态的存储在文件中的数据,通过简单的设置和拖拽(选中数据文件,按住鼠标左键不放,拖动到DataProbe窗口,然后释放鼠标左键)就能够对数据的情况有个大致的了解。
对于模型产生的多种多样的数据提供了多种观察方式。还可以比较多路信号,在“锁定显示”选中的情况下,在只需要简单的双击想观察的参数,如果参数的类型能够添加到当前显示的图形中就可以显示多路信号。
模型的构造
对于使用者来说,更重要的是编写能够被软件调用的模块,DataProbe程序会调用模块中的以下函数:
1. QuerySettings。参数设置函数,用来在加载模块的时候进行参数设置。
2. AllocateMemory。申请内存函数。一般地,动态分配内存的语句应该放在这。
3. QueryList。查询模型的输入输出参数信息。
4. IniModel。初始化模型。
5. RunModel。运行模块。
6. EndModel。结束运行模块。
7. FreeMemory。释放内存,和AllocateMemory中的分配内存的函数是一一对应的关系。
其中只有QueryList和RunModel这两个函数是必须的,其余的函数可以不写或者为空,DataProbe 函数如果发现缺乏这些函数,会使用自带的函数代替,这些自带的函数实际上什么事也没有做。这几个函数是按顺序调用的,除了IniModel,RunModel,EndModel函数可能运行多次之外,其余的函数仅运行一次。在加载完模块后,先运行QuerySettings函数进行参数设置,然后调用AllocateMemory分配内存,每次按下DataProbe程序的“运行模块”按钮,就会调用IniModel初始化模块,然后在定时,输入输出等条件满足时,多次调用RunModel,按下“停止运行”按钮后会执行EndModel函数。当卸载模块时,会调用FreeMemory函数。
包含模型函数的源文件必须包含model.h头文件,这头文件包括函数的原型的定义,宏定义和几个特殊函数(参看附录A),在model.h中的“来源类型/输出方式定义”,“数据类型定义”和“观察数据方式”定义将会在QuerySettings和QueryList中使用,这两个函数的入口参数比较多,现在介绍这两个函数的典型写法,先介绍QuerySettings:
int QuerySettings(int at, char **name, char **note, unsigned long *datatype,
unsigned long *datalen, void **pdata)
{
switch(at)
{
case 0:
*name = "名称1";
*note = "说明1";
*datatype = 类型1;
*datalen = 长度1;
*pdata = 变量地址1;
return TRUE;
case 1:
*name = "名称2";
*note = "说明2";
*datatype = 类型2;
*datalen = 长度2;
*pdata = 变量地址2;
return TRUE;
…..
default:
return FALSE;
}
}
DataProbe程序第一次调用这个函数时会将参数的“at”的值设置为0,如果返回值为TRUE(在model.h中TRUE定义为1,FALSE定义为0),DataProbe将“at”的值增加1,继续调用这个函数,直到函数的返回值为FALSE。这样DataProble就获得了所有待设置属性的信息。参看下图来理解这些参数的意义:
图 4 模型参数设置时弹出的对话框
最后一个参数是变量的地址,这个变量是在模型DLL中的全局变量,将在其他函数中使用。附录B中给出了一个伪随机序列产生模块的函数,具体的实现放在了PN.C中了,因为不是说明问题的关键,没有在附录中给出。另一个函数QueryList基于同样的原理。函数的原型是:
int QueryList(int at, char **name, unsigned long *sourcetype, char **sourcename, unsigned long *datatype, unsigned long *datalen, int *input, unsigned long *viewertype, void **pdata);
其中的sourcetype的值用附录A中的“来源类型/输出方式定义”宏表示,sourcename可以是个文件名,也可以是网络地址和端口号,根据sourcetype来确定。datatype的值用附录A中的“数据类型定义”宏表示,目前只支持所列的数据类型。datalen数表示的不是数据的字节数而是特定类型的数据个数,这一点要特别注意。input只有两个值,TRUE和FALSE,TRUE表示输入,FALSE表示输出。viewertype表示数据的观察方式,使用值已经在附录A中的“观察数据方式”中定义。一个参数可能有多种观察方式,使用 “|” (或)连接。下表列出了数据观察方式的说明:
diViewerNull | 无可视化方案 |
|
diViewerTxt | 文本文件 | sourcetype只能是diIOFile,sourcename为文件名 |
diViewerImage | 图形文件 | sourcetype只能是diIOFile,sourcename为文件名 |
diViewerBin | 二进制图形 | 只能分辨0,1信号 |
diViewerCode | 码型图 | 能分辨多进制信号,可以为负数 |
diViewerCon | 连续图,示波器 | 适用于浮点数 |
diViewerFFT | 频谱分析 | 1000个点的频谱分析,周期图法,-п到+п之间 |
diViewerStar | 星座图 | 两个相连的数表示一个点,特别适合复数表示 |
diViewerEye | 眼图 | 眼图的周期要自己根据实际设定 |
模块是使用C语言编写的DLL(动态链接库),DataProbe负责模块的加载,初始化,调用,暂停,停止,卸载等操作,在模块空闲时从输入接口获得数据,模块运算完成后向输出接口发送数据,管理数据观察窗口等等。DataProbe本身是使用VC++6.0编写的,包含多种类(类,是C++中的概念,是一组相关数据和函数的结合),按功能分为以下几种:
1. 缓存区控制。负责数据的输入输出缓存,还具有文件读写的功能。
2. 模块数据控制。对模块的输入输出数据进行更新,维护,保存属性和设置的类。
3. 图形显示。根据数据或文件显示图形,对不同种图形显示的切换。
4. 对话框。对话框是程序和用户交互的平台,在主对话框中对模块实行操作。
5. 数学计算。拥有频谱分析等函数。
6. 网络连接。基于CAsyncSocket类。
下面将详细讨论主对话框中对模块的操作:
1. 加载。选择菜单中“模型”下的“加载”项加载模块。由于计算机中存在多个DLL,这些DLL可能是DataProbe的模型也可能不是,如果是的话单从DLL的名称中无法判断它的作用,在加载之前显示DLL的作用是一个解决办法。
2. 参数的设置。如果有的话在加载后就会弹出参数设置窗口。
3. 卸载。在加载模块之前,如果发现已经加载过模块了,会自动卸载加载过的模块,也可以选择菜单中“模型”下的“卸载”项手动卸载模块。
4. 输入输出设置。模块加载完毕后,点击主界面下的“参数信息”栏中的参数列表,对参数的输入输出设置后更新。
5. 运行。所有的参数设置完成后,按下“运行模块”按钮,执行模块。
6. 链接。如果输入输出中包含文件和网络地址和端口,程序将首先检查文件和网络的链接状态。确认无误后才可能运行。对于输出到文件的设置,还需指定输出文件的最大允许长度。
7. 运行设置。每隔一定的时间,如果满足参数的输入输出条件,就运行模块一次。时间间隔可以选择菜单中的“设置”下的“定时”项进行设置。
8. 停止。按下“运行模块”后,按钮会变成“停止运行”,再次按下后停止模块的运行。
9. 暂停。在模块运行的过程中,可以暂时停止模块的运行。如果是多台计算机同时计算,停止的时间不要超过所有计算机中等待数据超时时间最短的那个时间间隔。
10. 恢复。按下“暂停”按钮之后按钮会变成“恢复”,再次按下就能重新启动被暂停的模型的运行。
为了更进一步的说明模型的运行方式,看下图: