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