分享

MBD的Simulink使用技巧⑦:自动生成代码的集成方法

 伊伊爸 2022-11-26 发布于湖北
全文约3500字,你将看到以下内容:
  • 构建Step函数的接口函数

  • MATLAB Utilities代码的集成

  • 共用代码的集成
  • 生成代码的最终打包导出

autoMBD最近发布了《autoMBD原创技术文章合集》

合集包含156页丰富的MBD入门基础
和MBDT硬件支持包的使用
还包含基于MBD的电机控制算法开源项目——AMBD-MC
合集配备了丰富的视频讲解

和大量的模型、文档和软件资源

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

本篇文章继续介绍MBD的Simulink使用技巧,主要包括如何将Simulink生成的代码与外部已有的代码进行集成。

点击以下链接,可以查看MBD的Simulink使用技巧系列的往期文章:

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

1  构建Step函数的接口函数

Step函数的集成方法在以往的文章中介绍过数次。在《MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程》一文中,详细介绍了模型生成的三个入口函数(Model Entry-Point Functions):

  • 模型初始化函数

  • 模型Step函数

  • 模型终止函数

要集成Simulink生成的代最基本的方式就是合理调用这些入口函数,一般情况下,Step函数需要周期性被调用

在文章合集的《第十七篇 把模型嵌入代码》中,详细展示集成Step函数的基本步骤。更多关于集成的细节可以阅读该文章,这里不再赘述。获取文章合集的方法见文章开头)

这里仅展示最终集成的代码,如下所示(Motor_ISR(void)是周期中断服务函数,在Step函数的前后分别设置输入数据和输出数据):

void Motor_ISR (void){ /*Read ADC*/ ADC_DRV_GetChanResult(INST_ADCONV1,0,&adcResult[0][0]);/*V_REFSH*/ ADC_DRV_GetChanResult(INST_ADCONV1,1,&adcResult[0][1]);/*Pot*/ ADC_DRV_GetChanResult(INST_ADCONV1,2,&adcResult[0][2]);/*iA*/ ADC_DRV_GetChanResult(INST_ADCONV1,3,&adcResult[0][3]);/*Temp*/ ADC_DRV_GetChanResult(INST_ADCONV2,0,&adcResult[1][0]);/*V_REFSL*/ ADC_DRV_GetChanResult(INST_ADCONV2,1,&adcResult[1][1]);/*uDC*/ ADC_DRV_GetChanResult(INST_ADCONV2,2,&adcResult[1][2]);/*iB*/ ADC_DRV_GetChanResult(INST_ADCONV2,3,&adcResult[1][3]);/*iDC*/
/*Set model inputs here*/ FOC_Ctrl_CodeModel_U.ADCinput[0]=adcResult[0][2]; FOC_Ctrl_CodeModel_U.ADCinput[1]=adcResult[1][2]; FOC_Ctrl_CodeModel_U.ADCinput[2]=adcResult[1][3]; FOC_Ctrl_CodeModel_U.ADCinput[3]=adcResult[1][1]; FOC_Ctrl_CodeModel_U.ADCinput[4]=adcResult[0][1]; FOC_Ctrl_CodeModel_U.FaultSwitch=faultSwitch; FOC_Ctrl_CodeModel_U.MotorSwitch=motorSwitch;
/*Calculate one-step for the model*/ rt_OneStep();
/*Get model output here*/ pwmDuty[0]=(uint16_t)(FULL_DUTY*FOC_Ctrl_CodeModel_Y.DUTY[0]); pwmDuty[1]=(uint16_t)(FULL_DUTY*FOC_Ctrl_CodeModel_Y.DUTY[1]); pwmDuty[2]=(uint16_t)(FULL_DUTY*FOC_Ctrl_CodeModel_Y.DUTY[2]); 28 /*Set PWM duty*/ FTM_DRV_FastUpdatePwmChannels(INST_FLEXTIMER_PWM1, 3, pwmChannels, pwmDuty, true);
/*Clear FTM flag*/ FTM_DRV_ClearStatusFlags(3, FTM_TIME_OVER_FLOW_FLAG | FTM_RELOAD_FLAG);}
模型不复杂,模型的输入、输出数据不多的情况下,上述集成方法是有效可行的。当模型变得复杂时,上述的方法就显得不太灵活,且效率不高。
这时候构建专门的接口函数实现数据的传递,或执行输入、输出的一些特定的动作(读ADC数据,或输出PWM信号,等,是一个较为常用且高效的方法
接口函数具体执行的内容需要开发者自己构建,但如何快速方便地将开发者构建的接口函数与模型生成的代码(Step函数)集成在一起呢?

在Simulink中,可以通过存储类(Storage Class)来实现这样的需求。

Tips:autoMBD目前还尚未系统介绍存储类,不了解存储类的读者也不用担心,后续会专门介绍存储类的相关概念。这里把它当作一种控制代码生成的方法即可。

下面以PI控制器示例模型为例,向读者展示如何通过存储类,为输入输出端口构建专门的接口函数。

图片

PI控制器示例模型 - From autoMBD

该模型想必很多读者都比较熟悉了,打开该模型后,首先在APPS找到Embedded Coder工具,在Embedded Coder标签页中,点击Code Interface下拉菜单中的Individual Element Code Mappings选项,此时Property Inspector窗口出现,如下所示:

图片

打开Embedded Coder工具 - From autoMBD

图片

IndividuaElement Code Mappings - From autoMBD

然后在“Code Mapping - C”的窗口中,找到Inports下的两个输入端口,将两个端口的存储类(Storage Class)设置为“GetSet”,同时配置Headerfile为自己构建的接口函数的头文件名,本例中设置为:

  • autoMBD_example_IO.h
图片

设置输入端口存储类 - From autoMBD

对Outports执行同样的操作,将存储类设置为“GetSet”,将Headerfile设置与输入相同。这里要注意,输出端口的Identifier不能为空,需要设置一个合适的名字,如下所示:

图片

设置输出端口存储类 - From autoMBD

设置完成后,点击“Build”生成代码,此时Step函数如下:

/* Model step function */void autoMBD_example_PI_noSubs_step(void){  real_T rtb_Err;
/* Sum: '<Root>/Sum2' incorporates: * Inport: '<Root>/Feedback' * Inport: '<Root>/Req_Ctrl' */ rtb_Err = get_Req_Ctrl() - get_Feedback();
/* Outport: '<Root>/PI_Ctrl' incorporates: * DiscreteIntegrator: '<Root>/Discrete-Time Integrator' * Gain: '<Root>/Kp' * Sum: '<Root>/Sum1' */ set_PI_Ctrl(2.0 * rtb_Err + autoMBD_example_PI_noSubs_DW.DiscreteTimeIntegrator_DSTATE);
/* Update for DiscreteIntegrator: '<Root>/Discrete-Time Integrator' incorporates: * Gain: '<Root>/Ki' */ autoMBD_example_PI_noSubs_DW.DiscreteTimeIntegrator_DSTATE += 3.0 * rtb_Err * 0.001;}
可以看到,输入端口Req_Ctrl和Feedback变成了Get接口函数
  • get_Req_Ctrl()
  • get_Feedback()

输出端口PI_Ctrl变成了Set接口函数
  • set_PI_Ctrl()
在生成的代码中,是没有这三个接口函数的具体实现的,只在“模型名_private.h”文件中包含了我们所设置的头文件,如下所示:
/* Includes for objects with custom storage classes. */#include 'autoMBD_example_IO.h'
前文提到接口函数具体执行的内容需要开发者自己构建,这里给出上述接口函数的一个实现示例,供读者参考:
/**************************************** * 文件名  :autoMBD_example_IO.c  * 作者    :autoMBD  * 创建时间:2022-11-20 ****************************************/
#include 'autoMBD_example_IO.h'#include 'Cpu.h'#include 'rtwtypes.h'/* 在这里添加必要的头文件 */
/* get_Req_Ctrl接口函数,返回Req_Ctrl的值 */real_T get_Req_Ctrl (void){ static real_T Req_Ctrl = 0; Req_Ctrl += 1; return Req_Ctrl;}
/* get_Feedback接口函数,返回Feedback的值 */real_T get_Feedback (void){ real_T Feedback; /*Read ADC*/ ADC_DRV_GetChanResult(0,0,&Feedback); return Feedback;}
/* set_PI_Ctrl接口函数,根据输入参数,设置PWM占空比 */void set_PI_Ctrl (real_T PI_Ctrl){ FTM_DRV_FastUpdatePwmChannels(0, 1, 0, PI_Ctrl, true);}
接口函数的头文件如下:
/**************************************** * 文件名 :autoMBD_example_IO.h * 作者 :autoMBD * 创建时间:2022-11-20 ****************************************/#ifndef AUTOMBD_EXAMPLE_IO_H#define AUTOMBD_EXAMPLE_IO_H
real_T get_Req_Ctrl (void);real_T get_Feedback (void);void set_PI_Ctrl (real_T PI_Ctrl);
#endif
Tips:上述示例代码仅展示接口函数如何构建,要使用这些接口函数,还需要完整的包含底层代码的工程。
Tips:上述模型和示例代码可以在autoMBD资源库的“临时资源分享”文件夹中找到该模型,资源序号tA25。
用户可以自由构建接口函数的实现内容和执行的任务,只要接口函数的名称一致,并且和模型生成的代码放在同一个工程内(生成的代码中已经自动包含了用户设置的头文件),Step函数就可以直接使用接口函数,而不需要开发者额外的任何操作
这种使用接口函数的集成方式,在复杂模型和大量数据输入输出的场景下,会十分高效和实用。

2  MATLAB Utilities代码的集成

在有的情况下,模型生成的代码,并不全部来自于模型。

部分代码是由MathWorks官方预先写好的,存放在MATLAB的安装目录下,模型生成的代码只是调用这些代码中的函数来实现功能。

这部分MathWorks官方预先写好的源代码就是MATLAB Utilities代码特别是当模型中使用了Simulink的特定功能时,可能会使用到MATLAB Utilities相关代码,例如连续模型求解器、AI算法、图像处理算法等

读者可以在MATLAB安装目录下的这个位置找到MATLAB Utilities代码:

/* MATLAB Utilities代码路径 */~/MATLAB/simulink/include~/MATLAB/simulink/src
图片

MATLAB Utilities代码位置 - From autoMBD

下面以连续模型为例,向读者展示如何使用MATLAB Utilities相关代码。

使用到的模型依然是PI控制器模型,不过对该模型进行了修改,将原来的离散积分模块改为了连续积分模块,如下所示:

图片

PI控制器连续模型 - From autoMBD

同时配置模型支持连续时间(continuous time),如下所示:

图片

配置模型支持连续时间 - From autoMBD

Tips:上述连续模型可以在autoMBD资源库的“临时资源分享”文件夹中找到该模型,资源序号tA26。

Tips:不建议在嵌入式系统中使用连续模型,本次例程仅展示MATLAB Utilities相关代码的使用方法。

使用Embedded Coder重新生成代码,对比原本离散积分器生成的较为简单的代码,连续模型生成的代码变得非常复杂,如下所示(只截取了部分代码片段):

/* 连续积分模块生成的代码 *//* Model step function */void autoMBD_example_PI_Continous_step(void){ real_T rtb_Err; if (rtmIsMajorTimeStep(autoMBD_example_PI_Continous_M)) { /* set solver stop time */ rtsiSetSolverStopTime(&autoMBD_example_PI_Continous_M->solverInfo, ((autoMBD_example_PI_Continous_M->Timing.clockTick0+1)* autoMBD_example_PI_Continous_M->Timing.stepSize0)); } /* end MajorTimeStep */
/* Update absolute time of base rate at minor time step */ if (rtmIsMinorTimeStep(autoMBD_example_PI_Continous_M)) { autoMBD_example_PI_Continous_M->Timing.t[0] = rtsiGetT (&autoMBD_example_PI_Continous_M->solverInfo); }
/* Sum: '<Root>/Sum2' incorporates: * Inport: '<Root>/Feedback' * Inport: '<Root>/Req_Ctrl' */ rtb_Err = autoMBD_example_PI_Continous_U.Req_Ctrl - autoMBD_example_PI_Continous_U.Feedback;
/* Outport: '<Root>/PI_Ctrl' incorporates: * Gain: '<Root>/Kp' * Integrator: '<Root>/Integrator' * Sum: '<Root>/Sum1' */ autoMBD_example_PI_Continous_Y.PI_Ctrl = 2.0 * rtb_Err + autoMBD_example_PI_Continous_X.Integrator_CSTATE;
/* Gain: '<Root>/Ki' */ autoMBD_example_PI_Continous_B.Ki = 3.0 * rtb_Err; if (rtmIsMajorTimeStep(autoMBD_example_PI_Continous_M)) { rt_ertODEUpdateContinuousStates(&autoMBD_example_PI_Continous_M->solverInfo);
/* Update absolute time for base rate */ /* The 'clockTick0' counts the number of times the code of this task has * been executed. The absolute time is the multiplication of 'clockTick0' * and 'Timing.stepSize0'. Size of 'clockTick0' ensures timer will not * overflow during the application lifespan selected. */ ++autoMBD_example_PI_Continous_M->Timing.clockTick0; autoMBD_example_PI_Continous_M->Timing.t[0] = rtsiGetSolverStopTime (&autoMBD_example_PI_Continous_M->solverInfo);
{ /* Update absolute timer for sample time: [0.001s, 0.0s] */ /* The 'clockTick1' counts the number of times the code of this task has * been executed. The resolution of this integer timer is 0.001, which is the step size * of the task. Size of 'clockTick1' ensures timer will not overflow during the * application lifespan selected. */ autoMBD_example_PI_Continous_M->Timing.clockTick1++; } } /* end MajorTimeStep */}
可以看到,Step函数中调用很多SolverTime相关的的API,这些API相关代码并不在生成的代码中。它们使用的便是MATLAB Utilities代码,在“模型名.h”头文件中,可以看到对MATLAB Utilities头文件的引用:
/* 连续积分模块生成的代码 *//* '模型名.h'中引用'rtw_continuous.h'和'rtw_solver.h' */
#ifndef autoMBD_example_PI_Continous_COMMON_INCLUDES_#define autoMBD_example_PI_Continous_COMMON_INCLUDES_#include 'rtwtypes.h'#include 'rtw_continuous.h'#include 'rtw_solver.h'#endif

上述代码中头文件rtw_continuous.hrtw_solver.h可在MATLAB安装目录下找到源文件。

要使用MATLAB Utilities相关代码,必须在代码中包含相应的头文件;同时在编译时,也要将源文件的路径添加到编译路径中,不然工程找不到相应文件,编译会报错。

3  共用代码的集成

实际开发过程中,我们可能会创建非常多不同的模型。细心的读者可能会发现,文章展示的很多示例模型都会生成相同头文件rtwtypes.h

该文件内容相同,是共用的代码,多模型开发时只需要生成一个即可。在不同情况下,还可能存在其他的共用代码或文件。

可以通过配置“Shared location”功能,让所有共用代码生成在相同的位置,配置的方法如下所示:

图片

配置Shared Location - From autoMBD

配置好之后,共用代码生成的位置会改到“slprj/ert/_sharedutils/“目录下:

图片

共用代码位置 - From autoMBD

4  生成代码的最终打包导出

文章至此,已经介绍了多个方面的代码集成方法。

还有一个问题未能解决:生成的代码存储位置比较混乱无序,生成的C代码源文件还和其他无关代码放在一起;特别是当使用了MATLAB Utilities代码和共用代码后,有多个位置都存放了代码。

在Simulink中,提供了代码打包导出的功能,可以将所有生成的代码打包放在一起,包括MATLAB Utilities代码和共用代码。使用方法如下:

图片

代码打包导出 - From autoMBD

生成的Zip压缩包中的内容如下:

图片

压缩包中的代码 - From autoMBD

生成的压缩包中,文件结构层级变得简洁了许多,并且只包含源代码,没有其他任何无关文件,非常方便后续集成开发的使用。


图片

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多