MBD的Simulink使用技巧⑤:详解自动代码生成的配置与优化方法

您所在的位置:网站首页 simulink添加标签 MBD的Simulink使用技巧⑤:详解自动代码生成的配置与优化方法

MBD的Simulink使用技巧⑤:详解自动代码生成的配置与优化方法

2022-11-30 02:33| 来源: 网络整理| 查看: 265

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