模块化开发中,模型集成和代码集成是很多工程师非常关心的问题。
常见的代码集成方式有两种,一是单元级模型上生成代码,在代码上做集成,一是模型集成之后,再去生成集成级别的代码。无论采用哪种方式,模型级别的集成测试都是非常有必要的。
也就是说,如果采用第一种方式,那么模型集成测试之后,再回到单元模型上生成代码;而对于第二种方式,则在模型集成测试之后,直接在集成模型上生成代码。
模型集成测试怎么做?或者说为了做集成测试,我们怎么把单元模型集成到一起?目前可能已经很少有人再有这样的疑问,大多数模型开发工程师都知道可以通过模型引用的方式将单元模型引用后在集成模型中进行集成,如下图所示,图中使用了模型引用模块,引用了3个单元模型Unit1、Unit2、Unit3,然后在集成级别上按照输入、输出关系将3个模块连接在一起。
三个单元模型之间有输入、输出关系,那就要考虑模型之间需要传递的数据怎么去定义,比如,Unit1的输出,同时是Unit2和Unit3的输入,这两个信号,应该放在哪里定义?模型中的参数该定义在哪里?我们来看以下几种方式:
方式一:单元模型独立生成代码
以Unit1为例,模型中有3个输入,两个输出,以及参数k1、k2,查表函数中还有两个参数TableData1d、BreakPoint1d。
这里将信号和参数定义在两个不同的数据字典中,信号定义在u1dd.sldd,参数定义在param_dd.sldd,对于输入信号,StorageClass设置为ImportedExtern,输出信号的StorageClass设置为ExportedGlobal。
于是代码中就有了对输出信号对应变量的定义:
以及对于输入信号的外部声明:
参数被指定到swc_params.c文件中定义:
这种方式下,对于同一个信号,比如unit1_output_signal1,需要在Unit1的数据字典u1dd.sldd和Unit2的数据字典u2dd.sldd以及Unit3的数据字典u3dd.sldd中定义相应的信号对象,不同的是,unit1_output_signal1作为Unit1的输出信号,StorageClass被定义为ExportedGlobal,而在Unit2和Unit3的数据字典中,StorageClass要被定义成ImportedExtern,这样可以保证变量unit1_output_signal1只被定义一次,不会出现重定义问题。
并且,如果按照单元模型的数据字典中只定义相应单元模块输出变量的规则,在模块化开发中,模块间变量定义问题上不再有沟通成本。
对于参数,可能会有一些麻烦,比如,参数k1,可能用于不同的单元模型,而在哪个模块定义,在哪个模块引用,就是比较麻烦的事情,这里直接把参数指定到统一的文件中,比如swc_params.c,多个单元模块会分别生成这样的文件,可以使用合并工具对这些模块做合并处理。这不一定是最好的方式,但看上去这样做可以避免重定义问题,也可以省去沟通成本。缺点也很明显,违背了修改自动生成代码的基本原则。如果网友更好的方案,也欢迎留言分享。
另外,对于这种方式下生成的函数 Unit1_step()、Unit2_step()、Unit3_step()在调用的时候没有约束,可以放在中断服务程序或者任务中调用,无需考虑中断或者任务之间的中断和抢占行为。
方式二:模型集成,然后生成集成级别的代码
不得不说,这种方式是最省心的方式,不过,省心的前提是单元模块的调度简单,可以通过模型完全实现,模块与模块之间不存在中断和抢占行为。
如上图示,只需在集成模型上对最外层的输入、输出和模型中的参数做定义。
从集成模型的级别上生成了SWC_step()函数,里面有对Unit1、Unit2和Unit3的调用。SWC的最外层输入input_signal1、input_signal2、input_signal3,这里同样设置成外部定义,SWC的最外层输出,在这个集成级别的模型上做了定义,output_signal1、output_signal2。
对于单元模型之间传递的信号,在函数调用之前,被定义成局部变量,分别为rtb_Model_o1、rtb_Model_o2、rtb_Model2,从模型上可以看出,这也就是Unit2的3个输入信号对应的变量。
假如Unit1、Unit2、Unit3三个模块之间的调度关系就如模型描述的那样,不再有中断或者抢占,那么,对这部分算法的调用就只需要调用SWC_step()函数即可。
而如果Unit1、Unit2、Unit3分别被分配不同优先级的中断服务程序中,它们之间只是从数据上有这样的数据传递关系,该怎么办?我们来看方式三。
方式三:模型集成,使用生成的单元模型代码
如果是三个单元模型对应代码之间有抢占或者中断关系,那么方式二中的SWC_step()显然没法直接使用,我们需要把Unit1、Unit2、Unit3对应的函数分别放到3个中断服务程序或者任务中调用,显然它们之间不能再像方式二中通过局部变量传递数据,它们之间需要有全局变量来传递数据。
为减少单元模型对集成模型的依赖,信号对象unit1_output_signal1和unit1_output_signal2的StorageClass设置为ExportToFile,并指定相应的文件名。
而对于Unit2和Unit3,它们的输入信号是Unit1的输出,已经在Unit1模型中做了定义,所以这两个模块的输入无需定义,如下图:
集成模型生成的代码如下:
从生成的SWC_step()函数来看,Unit1、Unit2、Unit3的传递的参数都是全局变量,当然,如前所述,SWC_step()函数不会被集成到软件中,我们只需要使用Unit1()、Unit2()、Unit3()。
以Unit2为例,被引用模型生成的代码包含Unit2.c、Unit2.h、Unit2_private.h、Unit2_types.h四个文件,分析这四个文件不难发现,这四个文件对集成级别模型SWC对应的代码并无依赖。
我们再看Unit2的输出信号对应的变量定义,这两个数据被定义在unit2_output_signals.c文件中,文件中有这样一行代码
#include “SWC_types.h”
显然这个文件对SWC_types.h文件有依赖,SWC_types.h文件内容如下:
这里仅仅定义了一个宏,并不依赖其他文件。
也就是说,我们在对单元模型对应代码的集成中,可以丢弃SWC.c、SWC.h、SWC_private.h等文件,但是,需要SWC_types.h文件。
----------------------------------------------
对应方式二和方式三,除第一次需要整个集成模型做代码生成之外,后续的模型变更可以基于单元模型开展,代码生成也可以在单元模型上生成,不过,不能使用Ctrl+B的方式去生成,而是需要专门的命令行生成,命令行如下(以Unit1模型为例):
model_name = 'Unit1';
slbuild(model_name,'ModelReferenceRTWTargetOnly');
再对上述内容做一点补充,每个模型对应一个数据字典(sldd文件),参数专门定义了一个数据字典。数据字典中,对应模型的输出被定义到指定的文件,参数也被指定到指定的文件。数据字典之间可以相互引用。