全文约3500字,你将看到以下内容:
代码生成的配置概述
代码生成的配置选项
代码生成的配置方法小结
autoMBD最近发布了《autoMBD原创技术文章合集》
合集包含156页丰富的MBD入门基础和MBDT硬件支持包的使用还包含基于MBD的电机控制算法开源项目——AMBD-MC合集配备了丰富的视频讲解
和大量的模型、文档和软件资源
如何获取请参考@所有读者:autoMBD发布《autoMBD原创技术文章合集》。
经过忙碌的九月、十月,终于可以回到更新文章的状态。本篇文章将介绍如何在Simulink中,对模型生成的代码进行配置和优化。点击以下链接,可以查看往期文章:
MBD的Simulink使用技巧①:Simulink代码生成的基本概念MBD的Simulink使用技巧②:详解代码生成中的模型与代码MBD的Simulink使用技巧③:虚拟子系统与原子子系统的代码生成
MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程
1 代码生成的配置概述
当我们对模型设置不同的配置选项时,会对模型生成的代码产生不同的影响。评价代码优化情况,有以下几个维度:
否影响调试性(Debugging)
否影响可追踪性(Traceability)
否影响执行效率(Efficiency)
否影响安全预防(Safety precaution)
调试性指的是调试模型生成代码的编译过程,这里不做深入介绍。
可追踪性指的是模型和代码之间的映射关系是否易于追踪,例如:如果代码优化水平设置的比较高,代码和模型之间可能会没有明显的一一对应关系,这会不利于追踪。
执行效率包括生成的代码所占用的RAM、ROM空间大小,以及处理器执行生成代码的效率,这一般是我们最关心的优化目标。
安全预防指的是代码是否具备防止执行错误的情况发生,例如:除零检查、溢出检查等。
安全预防一般与效率是矛盾的,因为安全检查需要消耗存储空间和执行时间。有些代码优化选项会移除掉这些安全检查,开发者一般根据需求来决定,是模型来保证安全,还是开发者自己来保证安全。
下面的链接可以查看MathWorks官方对全部配置选项和相应的影响的总结。
MathWorks:模型配置参数建议配置总结https://ww2.mathworks.cn/help/ecoder/ref/parameter-reference.html?s_tid=srchtitle_recommended%20settings%20summary_1
Tips:由于自动生成代码的配置选项较多,文章中仅对常用的配置选项进行简单介绍,具体的功能和作用读者自行在实践中练习和体验。
Tips:不同的Simulink版本,配置界面可能会有一些细微区别,本文是基于2020b版本进行展示的。
2 代码生成的配置选项
2.1 静态代码的度量报告
在生成代码时,可以配置生层静态代码的度量报告,报告详细列举了代码量的信息(文件数量、代码行数等)。不同的代码优化配置选项,生成的代码量是不一样的,可以作为代码优化的一个参考指标。
Tips:读者可以通过对比某一优化选项开启和关闭时,代码度量报告的区别,来了解该优化选项的效果。也可以直接对比生成的代码,查看该优化选项的具体作用。
打开静态代码度量报告的方法如下所示:
打开静态代码的度量报告 -
From autoMBD
静态代码的度量报告 -
From autoMBD
2.2 优化不必要代码
生成的代码中,有时候包括一些不影响功能的不必要代码,可以优化去掉。这些代码包括:
数据初始化代码(Data initialization)
对于浮点数的零初始化过程,可以配置使用memset()函数,或者直接var=0.0赋值。初始化的数据非常多时,后者会花费更多的存储空间和执行时间。如下图所示:
配置浮点数零初始化方法 -
From autoMBD
还可以选择移除零初始化代码,因为一般变量的内存区域初始值已经为零,不需要再初始化,这种配置是最高效的。如下图所示:
移除零初始化代码 -
From autoMBD
如果终止代码中没有必要代码,那么可以移除,如下所示:
移除不必要的终止代码 -
From autoMBD
整型数据的溢出回环(Integer wrapping)
假设这样一种情况:将浮点数float 80000.0转换为16位无符号整型数据unsigned short。很显然这里发生了数据溢出,因为16位无符号整型数据最大值为65535。在C语言中,溢出的位会被忽略,只保留低16位数据,float 80000.0转换为16位无符号整型数据的结果为unsigned short 14464。如下C语言中示例结果:
C语言中的数据回环 -
From autoMBD
但在Simulink中,有些配置选项会添加软件处理过程,来保证这个过程的正确性。如下所示的数据转换模型和生成的代码:
数据转换模型 -
From autoMBD
/* Model step function */voidfloat2int_step(void){ real_T tmp;
/* DataTypeConversion: '<Root>/Data Type Conversion' incorporates: * Constant: '<Root>/Constant' */ tmp = fmod(floor(float2int_P.Constant_Value), 65536.0);
/* Outport: '<Root>/y' incorporates: * DataTypeConversion: '<Root>/Data Type Conversion' */ float2int_Y.y = (int16_T)(tmp < 0.0 ? (int32_T)(int16_T)-(int16_T)(uint16_T) -tmp : (int32_T)(int16_T)(uint16_T)tmp);}一般情况下,这部分的代码也是不必要的,可以去掉(默认是去掉的),将这一过程交给编译器来实现,去掉的方式如下:
移除数据回环代码 -
From autoMBD
配置移除数据回环代配置选项后,生成的代码如下:
/* Model step function */voidfloat2int_step(void){/* Outport: '<Root>/y' incorporates: * Constant: '<Root>/Constant' * DataTypeConversion: '<Root>/Data Type Conversion' */ float2int_Y.y = (int16_T)floor(float2int_P.Constant_Value);}
代数运算异常保护代码(Arithmetic exceptions)
代数运算异常最常见的就是除零错误,Simulink可以生成相应的代码进行除零检查,但需要消耗额外的执行时间和存储空间,影响代码效率。
如下所示的除法模型和生成的代码:
除法模型 -
From autoMBD
/* Model step function */void divide_step(void){ /* Outport: '<Root>/y' incorporates: * Inport: '<Root>/In1' * Inport: '<Root>/In2' * Product: '<Root>/Divide' */ divide_Y.y = divid_U.In2 == 0U ? MAX_uint32_T : divide_U.In1 / divide_U.In2;}可以看到,一个简单的除法操作,被添加了“0判断条件”,这会消耗额外的资源。在保证不会有除零的情况发生的前提下,可以去掉这部分代码,配置选项如下:
移除代数异常检查代码 -
From autoMBD
配置异常代数运算异常检查代码后,除法模型新生成的代码如下:
/* Model step function */void divide_step(void){ /* Outport: '<Root>/y' incorporates: * Inport: '<Root>/In1' * Inport: '<Root>/In2' * Product: '<Root>/Divide' */ divide_Y.y = divide_U.In1 / divide_U.In2;}
无效的模块(Unnecessary blocks)
在有些情况下,模型中的有些模块永远也得不到执行,例如下图所示的模型:
不被执行的无效模块 -
From autoMBD
模型中的加法运算永远得不到执行,因为Switch的判断条件(>0)始终是上面乘法的路径是有效的。
我们应当避免搭建这种无效的模型,但也可以通过配置,使无效的模型不生成代码,配置方法如下:
移除的无效模块 -
From autoMBD
配置移除无效模型后,上述模型生成的代码如下:
/* Model step function */void autoMBD_example_defaultConfig_step(void){ /* Outport: '<Root>/Out2' incorporates: * Inport: '<Root>/In1' * Inport: '<Root>/In2' * Product: '<Root>/Product' * Trigonometry: '<Root>/Trigonometric Function' */ autoMBD_example_defaultConfig_Y.Out2 = sin(autoMBD_example_defaultConfig_U.In1 * autoMBD_example_defaultConfig_U.In2);}可以看到生成的代码中,只有乘法的路径,加法的模块没有生成任何代码。
2.3 优化变量的使用形式
Simulink模型支持非常多的数据类型,包括浮点数、复数、变步长信号等,但其中有一部分并不是代码所需要的,可以将其去掉(一般仅保留浮点数即可):
移除不必要的数据支持 -
From autoMBD
关于生成代码中变量的形式,信号存储复用(Signal store reuse)是比较重要的优化配置选项。如下图所示,默认情况下全部复用功能都是打开的:
信号存储复用的配置选项 -
From autoMBD
在《MBD的Simulink使用技巧②:详解代码生成中的模型与代码》中曾提到:模型中的信号(Signals)有两种:外部信号和内部信号。对于内部信号,具有分叉点的信号线会生成局部变量,变量名为“rtb_
信号名”。
实际上内部信号还包括模块信号,接下来将介绍如何配置生成模块信号。
还是以下图的模型为例:
示例模型 -
From autoMBD
信号存储复用的配置选项全部打开时(即默认配置),生成的代码如下所示,可以看到生成的代码非常简洁,只有一行就完成了所有操作,并没有任何的中间变量。
/* Model step function */void autoMBD_example_defaultConfig_step(void){ /* Outport: '<Root>/Out2' incorporates: * Inport: '<Root>/In1' * Inport: '<Root>/In2' * Product: '<Root>/Product' * Trigonometry: '<Root>/Trigonometric Function' */ autoMBD_example_defaultConfig_Y.Out2 = sin(autoMBD_example_defaultConfig_U.In1 * autoMBD_example_defaultConfig_U.In2);}
当信号存储复用的选项全部关闭时,那么生成的代码每一行只会执行一个操作,并且每一个操作得到的结果都会存储在一个变量当中或者输出。生成的代码如下:
/* Model step function */voidautoMBD_example_defaultConfig_step(void){/* Product: '<Root>/Product' incorporates: * Inport: '<Root>/In1' * Inport: '<Root>/In2' */ autoMBD_example_defaultConfig_B.Product = autoMBD_example_defaultConfig_U.In1 * autoMBD_example_defaultConfig_U.In2;
/* Switch: '<Root>/Switch' */ autoMBD_example_defaultConfig_B.Switch = autoMBD_example_defaultConfig_B.Product;
/* Outport: '<Root>/Out2' incorporates: * Trigonometry: '<Root>/Trigonometric Function' */ autoMBD_example_defaultConfig_Y.Out2 = sin (autoMBD_example_defaultConfig_B.Switch);}可以看到,原本的一行代码被分解成了三个操作:乘法操作、传值操作、sin运算。同时还新增了一个变量:
模型名_B。该变量是一个全局变量,被用来存储模块的变量值。这种方式执行效率降低,但执行的逻辑是最清晰的,易于阅读。
模型名_B的定义如下,包含Product和Switch两个元素:/* Block signals (default storage) */typedefstruct { real_T Product; /* '<Root>/Product' */ real_T Switch; /* '<Root>/Switch' */} B_autoMBD_example_defaultConf_T;
如果把信号存储复用的选项配置为如下:
打开Enable local block output -
From autoMBD
此时生成的代码如下:
/* Model step function */voidautoMBD_example_defaultConfig_step(void){ real_T rtb_Product; real_T rtb_Switch; real_T rtb_TrigonometricFunction;
/* Product: '<Root>/Product' incorporates: * Inport: '<Root>/In1' * Inport: '<Root>/In2' */ rtb_Product = autoMBD_example_defaultConfig_U.In1 * autoMBD_example_defaultConfig_U.In2;
/* Switch: '<Root>/Switch' */ rtb_Switch = rtb_Product;
/* Trigonometry: '<Root>/Trigonometric Function' */ rtb_TrigonometricFunction = sin(rtb_Switch);
/* Outport: '<Root>/Out2' */ autoMBD_example_defaultConfig_Y.Out2 = rtb_TrigonometricFunction;}可以看到,原来的全局变量“
模型名_B”已经不见了,取而代之的是局部变量“rtb
_模块名”。所以可以准确控制模块变量是全局变量还是局部变量,不同的需求下,可能会选择不同的变量形式。信号存储复用的选项还有其他的组合方式,这里不再继续展示,读者可以自行测试验证。单看某一个配置项,似乎没有多大影响。但当模型变得复杂时,某一个配置项能对代码产生非常大的影响。总体而言,信号存储复用的各个选项对代码生成有着较大的影响,特别是代码效率,具体怎么来配置需要更加需求来定。在不清楚各个选项的具体用途情况下,稳妥的方法是全部保持默认配置(全打开),执行效率最高,但可读性和可追踪性较差;如果已经清楚了各个选项的具体作用,则可以根据需求来控制特定的变量生成方式。
3 代码生成的配置方法小结到这里,Simulink代码生成过程中,常用的的配置方法和优化方法已经简单介绍完毕。读者可能更关心的是,如何在实际中使用这些配置选项。但很遗憾,并没有一个固定的定式来回答这个问题。大多数情况下,官方默认配置即可满足要求。但也存在一些需要修改配置的才能实现的需求,这就要具体情况具体分析。实际上除了这些配置选项,还有其他的方法和途径来控制代码生成,例如存储类(Store class),这会在后续的文章中进行介绍。读者或许发现还有一些配置选项没有讲到。文章中主要介绍的是常用到的配置选项,未提及的部分,建议保持默认配置即可。读者也可以自行测试和验证其他的配置选项。读者或许也能发现,上文中讲解的代码生成的各个配置选项,分布有点杂乱无序,没有集中在一起。大部分在Optimization标签下面,但其他标签(例如Simulation target和Interface)下面也存在一些对代码生成有影响的配置选项。对于不熟悉的人,配置时还是会感到困惑。
MathWorks官方或许也发现了这个问题,所以他们提供了一个专门的配置工具来追踪不同的配置选项,这个在下一期中进行介绍,欢迎持续关注哦
。
如果你觉得文章对你有帮助,欢迎关注,我会持续更新更多原创文章
与我交流讨论
由于新开通的公众号均不支持留言,欢迎给我发私信,我会及时回复的
。
版权归autoMBD所有,转载请注明作者和来源。