中国汽车工程师之家--聚集了汽车行业80%专业人士 

论坛口号:知无不言,言无不尽!QQ:542334618 

本站手机访问:直接在浏览器中输入本站域名即可 

  • 145查看
  • 0回复

[Simulink] MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程

[复制链接]


该用户从未签到

发表于 2-3-2024 09:09:36 | 显示全部楼层 |阅读模式

汽车零部件采购、销售通信录       填写你的培训需求,我们帮你找      招募汽车专业培训老师


全文约3000字,你将看到以下内容:


    生成代码的结构

    模型到代码的生成的流程

    结束语
autoMBD最近发布了《autoMBD原创技术文章合集》
合集包含156页丰富的MBD入门基础和MBDT硬件支持包的使用还包含基于MBD的电机控制算法开源项目——AMBD-MC合集配备了丰富的视频讲解
和大量的模型、文档和软件资源

如何获取请参考@所有读者:autoMBD发布《autoMBD原创技术文章合集》。

本篇文章继续介绍Simulink生成代码相关的内容,之前的内容可查看往期文章(点击可以直接跳转):

    MBD的Simulink使用技巧①:Simulink代码生成的基本概念MBD的Simulink使用技巧②:详解代码生成中的模型与代码
    MBD的Simulink使用技巧③:虚拟子系统与原子子系统的代码生成


阅前提示:本篇文章使用到的PI控制器示例模型,可以在autoMBD资源库的“临时资源分享”文件夹中找到该模型(资源序号tA22)。资源库链接的获取可以在《autoMBD原创技术文章合集》中找到(见文章开头)。

1  生成代码的结构

先复习一下,在《MBD的Simulink使用技巧②:详解代码生成中的模型与代码》中提到,一般情况下(默认配置且模型不复杂),生成的代码包含以下七个文件:


    ert_main.c模型名.c模型名.h模型名_private.h模型名_types.hrtwtypes.h
    模型名_data.c(开启Parameters可调会生成)

这么多的文件中,算法模型的核心应用代码是在“模型名.c”中实现的,其他文件更多的是实现数据结构定义、数据类型定义、变量声明、函数声明等功能。关于模型中的数据与代码中的数据定义、声明之间的详细对应关系,可以查看《MBD的Simulink使用技巧②:详解代码生成中的模型与代码》。

模型生成的应用代码,其结构由三部分组成,这三部分代码也被称为模型入口函数(Model Entry-Point Functions),具体如下:


    模型初始化函数

    模型Step函数
    模型终止函数

以示例PI控制器示例模型(资源序号tA22)为例,它生成的入口函数如下所示:

/* 模型入口函数(Model Entry-Point Functions)声明 生成于“模型名.h”*//* Model entry point functions */externvoidautoMBD_example_PI_noSubs_initialize(void);externvoidautoMBD_example_PI_noSubs_step(void);externvoidautoMBD_example_PI_noSubs_terminate(void);1.1 模型初始化函数顾名思义,模型初始化函数是用来初始化模型中的数据的,大多数情况下指的是状态变量的初始化。初始化函数在调用Step函数之前调用一次,以完数据的初始化。如果没有数据需要初始化,则可以不调用。打开PI控制器示例模型,如下所示。该模型中的离散积分模块包含状态变量,是需要初始化的。
MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程w1.jpg

PI控制器:离散积分模块包含状态变量 - From autoMBD
点击“Generate Code”生成代码,查看初始化函数:/* 默认情况:模型初始化函数 生成于“模型名.c” *//* Model initialize function */void autoMBD_example_PI_noSubs_initialize(void){/* (no initialization code required) */}但是为什么没有任何变量初始化的内容呢?这是因为离散积分模块的状态变量,初始值默认设置为“零”。默认情况下,初始值为零的变量初始化不会生成初始化代码。这里我们修改离散积分器的初始值为100,如下图所示:
MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程w2.jpg

修改离散积分器的初始值为100 - From autoMBD
重新生成代码,可以看到离散积分器的状态变量的初始化已经有了,如下所示:
/* 有非零变量初始化:模型初始化函数 生成于“模型名.c” *//* Model initialize function */voidautoMBD_example_PI_noSubs_initialize(void){/* InitializeConditions for DiscreteIntegrator: '<Root>/Discrete-Time Integrator' */  autoMBD_example_PI_noSubs_DW.DiscreteTimeIntegrator_DSTATE = 100.0;}
当然,用户也可以通过修改配置,关闭零初始值变量的优化选项,让初始值为零的数据初始化也生成代码,修改配置的方法如下所示:

MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程w3.jpg

不勾选零初始化数据优化选项 - From autoMBD

再重新生成代码,新的初始化函数如下所示:
/* 不开启零初始化优化选项:模型初始化函数 生成于“模型名.c” *//* Model initialize function */voidautoMBD_example_PI_noSubs_initialize(void){/* Registration code */
/* initialize error status */  rtmSetErrorStatus(autoMBD_example_PI_noSubs_M, (NULL));
/* states (dwork) */  (void) memset((void *)&autoMBD_example_PI_noSubs_DW, 0,sizeof(DW_autoMBD_example_PI_noSubs_T));
/* external inputs */  (void)memset(&autoMBD_example_PI_noSubs_U, 0, sizeof               (ExtU_autoMBD_example_PI_noSub_T));
/* external outputs */  autoMBD_example_PI_noSubs_Y.PI_Ctrl = 0.0;
/* InitializeConditions for DiscreteIntegrator: '<Root>/Discrete-Time Integrator' */  autoMBD_example_PI_noSubs_DW.DiscreteTimeIntegrator_DSTATE = 100.0;}可以看到初始化函数多了很多内容,所有零初始值变量和非零初始值变量都被保留了下来。并且对于特殊结构体的变量初始化,使用的是C的标准库函数memset()实现的。不过不建议关闭零初始值变量的优化选项,一般变量的内存区域初始值已经为零,不需要再初始化,关闭该优化选项会降低代码的执行效率。
1.2 模型Step函数
Step函数即模型本身,Step函数实现模型算法的一个步长运算。由于嵌入式系统一般是实时系统,且步长固定,所以Step函数要放在一个周期中断ISR里,被周期性的调用运算。

调用Step函数的周期中断ISR要满足以下两点:


    周期中断ISR的步长要和模型中设置的步长保存一致;
    在一个周期内,必须保证所有的运算任务能够完成。

Step函数和模型的运算功能是完全相同的,读者可以自行查看PI控制器示例模型生成的Step函数,与PI控制器模型做对比,了解它是如何实现模型的运算过程的。

Tips:用户完全不用担心生成的代码功能和模型不一致,把Step函数当作模型本身即可。

在ert_main.c的注释中,详细介绍了Step函数的使用方法,大多数开发者可能不太习惯阅读注释,但ert_main.c中的注释值得详细阅读。ert_main.c中关于Step函数的代码,如下所示:
/* 详细阅读注释 了解Step函数的使用方法 *//* * Associating rt_OneStep with a real-time clock or interrupt service routine * is what makes the generated code "real-time".  The function rt_OneStep is * always associated with the base rate of the model.  Subrates are managed * by the base rate from inside the generated code.  Enabling/disabling * interrupts and floating point context switches are target specific.  This * example code indicates where these should take place relative to executing * the generated code step function.  Overrun behavior should be tailored to * your application needs.  This example simply sets an error status in the * real-time model and returns from rt_OneStep. */voidrt_OneStep(void);voidrt_OneStep(void){static boolean_T OverrunFlag = false;
/* Disable interrupts here */
/* Check for overrun */if (OverrunFlag) {    rtmSetErrorStatus(autoMBD_example_PI_noSubs_M, "Overrun");return;  }
  OverrunFlag = true;
/* Save FPU context here (if necessary) *//* Re-enable timer or interrupt here *//* Set model inputs here */
/* Step the model */  autoMBD_example_PI_noSubs_step();
/* Get model outputs here */
/* Indicate task complete */  OverrunFlag = false;
/* Disable interrupts here *//* Restore FPU context here (if necessary) *//* Enable interrupts here */}
/* Attach rt_OneStep to a timer or interrupt service routine with * period 0.001 seconds (the model's base sample time) here.  The * call syntax for rt_OneStep is * *  rt_OneStep(); */ert_main.c中给出是Step函数的一种使用示范,它将Step函数进行了一层封装形成rt_OneStep()函数,添加了错误状态检查功能。用户可以参考它,也可以不参考,直接使用Step函数。
在《autoMBD原创技术文章合集》的《第十七篇 把模型嵌入代码》中,详细展示了如何在自己的代码中集成Step函数。获取文章合集的方法见文章开头。

1.3 模型终止函数

模型终止函数在完全退出Step函数后,调用一次。但终止函数在绝大多数情况下都是空的,如下所示:
/* 默认终止函数 生成于“模型名.c”*//* Model terminate function */voidautoMBD_example_PI_noSubs_terminate(void){/* (no terminate code required) */}用户也可以在终止函数中添加自定义执行代码,方法如下所示:

MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程w4.jpg

在终止函数中添加自定义执行代码 - From autoMBD
新生成的终止函数如下所示:/* 添加自定义终止函数执行内容 *//* Model terminate function */voidautoMBD_example_PI_noSubs_terminate(void){/* user code (Terminate function Body) */
/* Just a test */double temp = 0;  temp++;}
2  模型到代码的生成的流程
当用户点击”Generate Code“之后,模型是怎么一步一步变成C代码的呢?这里将对这一过程进行一个简单的介绍。

从模型到代码的生成过程,可以用下面的流程图表示:

MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程w5.jpg

模型到代码生成流程 - From autoMBD

从上图可以看到,模型生成代码可以分为三个阶段:


    模型编译

    代码生成
    可执行文件生成(非必须)

2.1 模型编译

模型编译的输入即为模型本身,通过一个代码生成翻译器(Code generation interpreter),对模型中的模块、信号、参数、Stateflow等进行检查,输出一个后缀名为rtw的文件:模型.rtw。

该文件是一个中间文件,独立于编程语言,描述了相应模型文件中的模块、输入、输出、参数、状态、存储以及其他模型组件和属性。是目标语言编译器(Target Language Compiler,TLC)的输入。

在默认情况下,该rtw文件会在生成代码后被自动清除,但也可以修改配置使其保留下来,方法如下:

MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程w6.jpg

修改配置保留rtw文件 - From autoMBD

修改配置后,在模型同级目录的“模型名_ert_rtw”文件夹内,可以找到该文件:

MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程w7.jpg

生成的rtw文件 - From autoMBD

读者可以尝试阅读该文件,了解该文件的结构和内容。该文件具体的语法规则,可以在MATLAB帮助文档中搜索关键词“rtw”,阅读相关的说明文档,这里不再深入介绍。

2.2 代码生成

上一阶段生成的rtw文件,作为系统目标文件(即ert.tlc脚本,在《MBD的Simulink使用技巧①:Simulink代码生成的基本概念》中介绍了它的作用)的输入文件,通过TLC编译器,以及Simulink内置的TLC函数库,将该rtw文件中的所有信息逐个编译成目标语言(C语言)程序,输出生成的代码。

换句话说,TLC编译器执行的是ert.tlc脚本,该脚本的输入为rtw文件,输出为生成的C代码。

特别提示一点,除了系统目标文件的ert.tlc脚本,有的模块还有自己的模块tlc脚本。系统目标文件描述的是整个模型如何生成代码,用户一般不能修改该文件,而模块自己的tlc脚本则描述了该模块如何生成代码。

所以,要创建一个自己的模块,并且能在Simulink中自动生成代码,学习TLC是必不可少的。由于目前我对TLC掌握的很少,这里不再深入介绍TLC,将来有机会再单独开一个系列详细介绍TLC。

2.3 可执行文件生成

该阶段是一个可选项,不是必须的步骤。该阶段即对生成的C代码进行编译,生成可执行文件,或者二进制文件。该过程也可以由用户来定义,从而适配不同的编译器、调试器。

由于MATLAB并不是IDE,它的代码调试功能很弱,所以生成的可执行文件一般只有下载的功能,这里不做进一步介绍。

3  结束语

为了收集读者对《autoMBD原创技术文章合集》的反馈,特地举行反馈征集活动。在8月31日之前,所有读者可以向作者反馈阅读和使用《autoMBD原创技术文章合集》的好评、差评、想法、感受或建议,参与到活动中。

反馈征集活动的规则如下:


    所以读者可以私信autoMBD作者,获取《autoMBD原创技术文章合集》全文;
    即日起,到2022-08-31为止,读者可以通过autoMBD的账号(知乎账号或微信公众号任选其一),向autoMBD作者反馈关于《文章合集》的阅读和使用的想法、感受和建议,反馈的内容不限,长度不限;每一位参与反馈的读者,都可以获得免费获得无水印的《autoMBD原创技术文章合集》,无需赞赏;
    在活动结束的三日内,将从反馈信息大于30字的读者中,随机抽取一位读者(随机抽取的过程录制视频并公开),赠送价值99元的米家保温杯弹盖版。

特别提示,最终奖品米家保温杯弹盖版是MATLAB的纪念版。是我上次参加MATLAB  EXPO活动时,由MathWorks官方赠送的,瓶身刻有MathWorks的Logo。

米家保温杯弹盖版的官图如下所示:

MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程w8.jpg
希望读者能踊跃参与,给予我有价值的反馈信息,与我一同改进和提高《autoMBD原创技术文章合集》的质量。

快速发帖

您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|手机版|小黑屋|Archiver|汽车工程师之家 ( 渝ICP备18012993号-1 )

GMT+8, 22-11-2024 21:05 , Processed in 0.340732 second(s), 30 queries .

Powered by Discuz! X3.5

© 2001-2013 Comsenz Inc.