|
汽车零部件采购、销售通信录 填写你的培训需求,我们帮你找 招募汽车专业培训老师
全文约3500字,你将看到以下内容:
构建Step函数的接口函数
MATLAB Utilities代码的集成
共用代码的集成生成代码的最终打包导出
autoMBD最近发布了《autoMBD原创技术文章合集》
合集包含156页丰富的MBD入门基础和MBDT硬件支持包的使用还包含基于MBD的电机控制算法开源项目——AMBD-MC合集配备了丰富的视频讲解
和大量的模型、文档和软件资源
如何获取请参考@所有读者:autoMBD发布《autoMBD原创技术文章合集》。
本篇文章继续介绍MBD的Simulink使用技巧,主要包括如何将Simulink生成的代码与外部已有的代码进行集成。
点击以下链接,可以查看MBD的Simulink使用技巧系列的往期文章:
MBD的Simulink使用技巧①:Simulink代码生成的基本概念MBD的Simulink使用技巧②:详解代码生成中的模型与代码MBD的Simulink使用技巧③:虚拟子系统与原子子系统的代码生成MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程
MBD的Simulink使用技巧⑤:详解自动代码生成的配置与优化
MBD的Simulink使用技巧⑥:代码生成目标配置工具
特别提示:在本篇文章中使用到的PI控制器示例模型和接口函数示例代码,可以在autoMBD资源库的“临时资源分享”文件夹中找到(资源序号为tA25和tA26)。资源库链接的获取可以在《autoMBD原创技术文章合集》中找到(见文章开头)。
1 构建Step函数的接口函数
Step函数的集成方法在以往的文章中介绍过数次。在《MBD的Simulink使用技巧④:详解生成代码的结构与代码的生成流程》一文中,详细介绍了模型生成的三个入口函数(Model Entry-Point Functions):
要集成Simulink生成的代码,最基本的方式就是合理调用这些入口函数,一般情况下,Step函数需要周期性被调用。
在文章合集的《第十七篇 把模型嵌入代码》中,详细展示集成Step函数的基本步骤。更多关于集成的细节可以阅读该文章,这里不再赘述。(获取文章合集的方法见文章开头)
这里仅展示最终集成的代码,如下所示(Motor_ISR(void)是周期中断服务函数,在Step函数的前后分别设置输入数据和输出数据):
voidMotor_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
Individual Element Code Mappings - From autoMBD
然后在“Code Mapping - C”的窗口中,找到Inports下的两个输入端口,将两个端口的存储类(Storage Class)设置为“GetSet”,同时配置Headerfile为自己构建的接口函数的头文件名,本例中设置为:
设置输入端口存储类 - 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接口函数:
在生成的代码中,是没有这三个接口函数的具体实现的,只在“模型名_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占空比 */voidset_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);voidset_PI_Ctrl(real_T PI_Ctrl);
#endifTips:上述示例代码仅展示接口函数如何构建,要使用这些接口函数,还需要完整的包含底层代码的工程。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函数中调用很多Solver和Time相关的的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.h和rtw_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
生成的压缩包中,文件结构层级变得简洁了许多,并且只包含源代码,没有其他任何无关文件,非常方便后续集成开发的使用。
如果你觉得文章对你有帮助,欢迎关注,我会持续更新更多原创文章
与我交流讨论
由于新开通的公众号均不支持留言,欢迎给我发私信,我会及时回复的
。
版权归autoMBD所有,转载请注明作者和来源。
|
|