文章目录
前言
Target Language Compiler(TLC)
C MEX S-Function模块
编写TLC文件
生成代码
Tips
分析和应用
总结
前言
见《开箱报告,Simulink Toolbox库模块使用指南(一)——powergui模块》
见《开箱报告,Simulink Toolbox库模块使用指南(二)——MATLAB Fuction模块》
见《开箱报告,Simulink Toolbox库模块使用指南(三)——Simscape 电路仿真模块》
见《开箱报告,Simulink Toolbox库模块使用指南(四)——S-Fuction模块》
见《开箱报告,Simulink Toolbox库模块使用指南(五)——S-Fuction模块(C MEX S-Function)》
Target Language Compiler(TLC)
目标语言编译器(Target Language Compiler)代码生成器的重要组成部分。Mathworks官方Help对该部分内容的说明如下所示。
在使用Simulink自动生成代码时,Library中自带的模块可以顺利的生成代码,但是如果用户在Model中用到了自己开发的C MEX S-Function模块,Simulink就不知道这个模块如何生成代码了。TLC文件的作用就是,告诉Simulink自己想把C MEX S-Function模块生成一些什么样的代码,以及如何与Model中的其他内容互联融合。TLC及模型代码的生成过程如下图所示:
本文继续以DFT算法为例,介绍如何编写一个TLC文件,将C MEX S-Function模块生成代码。
C MEX S-Function模块
DFT算法的原理讲解和C MEX S-Function模块的开发在上一篇文章中已经完成了,见《开箱报告,Simulink Toolbox库模块使用指南(五)——S-Fuction模块(C MEX S-Function)》。到这里仅仅是在Simulink中仿真时可以使用这样一个算法模块,本文是要把他生成C代码。由于算法中涉及了4个状态变量,对应到C语言中就要定义一组全局变量,这在TLC文件中实现会稍微麻烦一些。为了简化该过程,让大家更好地理解TLC,笔者对原有的C MEX S-Function模块进行了一些调整,将全局变量的定义放到了模块外面。如下图所示:
DFT_CMexSfunc.c中对应代码的调整如下:
//增加一个输入端口if (!ssSetNumInputPorts(S, 2)) return;ssSetInputPortWidth(S, 0, 1);ssSetInputPortWidth(S, 1, 4); //新增的输入端口有4个信号//增加一个输出端口if (!ssSetNumOutputPorts(S, 2)) return; ssSetOutputPortWidth(S, 0, 1);ssSetOutputPortWidth(S, 1, 4); //新增的输处端口有4个信号
DFT_CMexSfunc.c调整后需要用mex命令重新编译,如下图所示:
编写TLC文件
在Model的Workspace文件夹下,新建一个DFT_CMexSfunc.tlc文件,编写tlc代码。写好后的完整内容如下,各部分代码的解释,以注释形式标注在对应位置。
%implements "DFT_CMexSfunc" "C"//与C MEX S-Function模块相对应 %% Function: Outputs%function Outputs(block, system) Output//定义一个输出函数 %assign u = LibBlockInputSignal(0,"","",0)//获取输入信号%assign u_count = LibBlockInputSignal(1,"","",0)%assign u_t = LibBlockInputSignal(1,"","",1)%assign u_cos_integ = LibBlockInputSignal(1,"","",2)%assign u_sin_integ = LibBlockInputSignal(1,"","",3) %assign y = LibBlockOutputSignal(0,"","",0) //获取输出信号%assign y_count = LibBlockOutputSignal(1,"","",0)%assign y_t = LibBlockOutputSignal(1,"","",1)%assign y_cos_integ = LibBlockOutputSignal(1,"","",2)%assign y_sin_integ = LibBlockOutputSignal(1,"","",3)/%下面是要为C MEX S-Function模块生成的代码%/if(% < 5e3)//为了降低TLC复杂度,将常量L的值5e3直接写出来{% = % + %*cos(2*3.14 * 50*%);//将常量Freq的值50直接写出来% = % + %*sin(2*3.14 * 50*%);% = % + 1/10e3; //将常量Fs的值10e3直接写出来% = % + 1;}else if(% == 5e3){% = sqrt((%/L*2)^2 + (%/L*2)^2); //将过程变量real和imag用对应公式直接写出来% = % + 1;//避免无效运行消耗资源}else{} %endfunction//结束函数定义
DFT_CMexSfunc.tlc文件保存在对应的路径下即可,不需要做额外的编译操作。
生成代码
C MEX S-Function模块调整后对应的完整模型如下:
点击代码生成按钮,可以看到如下过程提示:
点击打开报告按钮,可以看到如下生成报告:
点击左侧的sfucdemo.c超链接,可以看到如下生成的代码,其中30行到140行是该模型主要功能的代码,40行到53行是与我们C MEX S-Function模块直接相关的代码。
File: sfucdemo.c1/*2 * sfucdemo.c3 *4 * Code generation for model "sfucdemo".5 *6 * Model version: 1.457 * Simulink Coder version : 9.4 (R2020b) 29-Jul-20208 * C source code generated on : Sun Sep 10 14:44:22 20239 *10 * Target selection: grt.tlc11 * Note: GRT includes extra infrastructure and instrumentation for prototyping12 * Embedded hardware selection: Intel->x86-64 (Windows64)13 * Code generation objectives: Unspecified14 * Validation result: Not run15 */1617#include "sfucdemo.h"18#include "sfucdemo_private.h"1920/* Block signals (default storage) */21B_sfucdemo_T sfucdemo_B;2223/* Block states (default storage) */24DW_sfucdemo_T sfucdemo_DW;2526/* Real-time model */27static RT_MODEL_sfucdemo_T sfucdemo_M_;28RT_MODEL_sfucdemo_T *const sfucdemo_M = &sfucdemo_M_;2930/* Model step function */31void sfucdemo_step(void)32{33/* Selector: '/Selector2' incorporates:34 *Constant: '/Constant2'35 *UnitDelay: '/Output'36 */37sfucdemo_B.Selector2 =38sfucdemo_ConstP.Constant2_Value[sfucdemo_DW.Output_DSTATE];3940/* S-Function (DFT_CMexSfunc): '/S-Function3' */41if (sfucdemo_B.Memory[0] < 5e3) {42sfucdemo_B.SFunction3_o2[2] = sfucdemo_B.Memory[2] + sfucdemo_B.Selector2*43cos(2*3.14 * 50*sfucdemo_B.Memory[1]);44sfucdemo_B.SFunction3_o2[3] = sfucdemo_B.Memory[3] + sfucdemo_B.Selector2*45sin(2*3.14 * 50*sfucdemo_B.Memory[1]);46sfucdemo_B.SFunction3_o2[1] = sfucdemo_B.Memory[1] + 1/10e3;47sfucdemo_B.SFunction3_o2[0] = sfucdemo_B.Memory[0] + 1;48} else if (sfucdemo_B.Memory[0] == 5e3) {49sfucdemo_B.SFunction3_o1 = sqrt((sfucdemo_B.Memory[2]/L*2)^2 +50(sfucdemo_B.Memory[3]/L*2)^2);51sfucdemo_B.SFunction3_o2[0] = sfucdemo_B.Memory[0] + 1;52} else {53}5455/* Selector: '/Selector3' incorporates:56 *Constant: '/Constant3'57 *UnitDelay: '/Output'58 */59sfucdemo_B.Selector3 =60sfucdemo_ConstP.Constant3_Value[sfucdemo_DW.Output_DSTATE];6162/* S-Function (DFT_CMexSfunc): '/S-Function4' */63if (sfucdemo_B.Memory1[0] < 5e3) {64sfucdemo_B.SFunction4_o2[2] = sfucdemo_B.Memory1[2] + sfucdemo_B.Selector3*65cos(2*3.14 * 50*sfucdemo_B.Memory1[1]);66sfucdemo_B.SFunction4_o2[3] = sfucdemo_B.Memory1[3] + sfucdemo_B.Selector3*67sin(2*3.14 * 50*sfucdemo_B.Memory1[1]);68sfucdemo_B.SFunction4_o2[1] = sfucdemo_B.Memory1[1] + 1/10e3;69sfucdemo_B.SFunction4_o2[0] = sfucdemo_B.Memory1[0] + 1;70} else if (sfucdemo_B.Memory1[0] == 5e3) {71sfucdemo_B.SFunction4_o1 = sqrt((sfucdemo_B.Memory1[2]/L*2)^2 +72(sfucdemo_B.Memory1[3]/L*2)^2);73sfucdemo_B.SFunction4_o2[0] = sfucdemo_B.Memory1[0] + 1;74} else {75}7677/* Switch: '/FixPt Switch' incorporates:78 *Constant: '/FixPt Constant'79 *Sum: '/FixPt Sum1'80 *UnitDelay: '/Output'81 */82if ((uint16_T)(sfucdemo_DW.Output_DSTATE + 1U) > 4999) {83/* Update for UnitDelay: '/Output' incorporates:84 *Constant: '/Constant'85 */86sfucdemo_DW.Output_DSTATE = 0U;87} else {88/* Update for UnitDelay: '/Output' */89sfucdemo_DW.Output_DSTATE++;90}9192/* End of Switch: '/FixPt Switch' */9394/* Memory: '/Memory' */95sfucdemo_B.Memory[0] = sfucdemo_DW.Memory_PreviousInput[0];9697/* Memory: '/Memory1' */98sfucdemo_B.Memory1[0] = sfucdemo_DW.Memory1_PreviousInput[0];99100/* Update for Memory: '/Memory' */101sfucdemo_DW.Memory_PreviousInput[0] = sfucdemo_B.SFunction3_o2[0];102103/* Update for Memory: '/Memory1' */104sfucdemo_DW.Memory1_PreviousInput[0] = sfucdemo_B.SFunction4_o2[0];105106/* Memory: '/Memory' */107sfucdemo_B.Memory[1] = sfucdemo_DW.Memory_PreviousInput[1];108109/* Memory: '/Memory1' */110sfucdemo_B.Memory1[1] = sfucdemo_DW.Memory1_PreviousInput[1];111112/* Update for Memory: '/Memory' */113sfucdemo_DW.Memory_PreviousInput[1] = sfucdemo_B.SFunction3_o2[1];114115/* Update for Memory: '/Memory1' */116sfucdemo_DW.Memory1_PreviousInput[1] = sfucdemo_B.SFunction4_o2[1];117118/* Memory: '/Memory' */119sfucdemo_B.Memory[2] = sfucdemo_DW.Memory_PreviousInput[2];120121/* Memory: '/Memory1' */122sfucdemo_B.Memory1[2] = sfucdemo_DW.Memory1_PreviousInput[2];123124/* Update for Memory: '/Memory' */125sfucdemo_DW.Memory_PreviousInput[2] = sfucdemo_B.SFunction3_o2[2];126127/* Update for Memory: '/Memory1' */128sfucdemo_DW.Memory1_PreviousInput[2] = sfucdemo_B.SFunction4_o2[2];129130/* Memory: '/Memory' */131sfucdemo_B.Memory[3] = sfucdemo_DW.Memory_PreviousInput[3];132133/* Memory: '/Memory1' */134sfucdemo_B.Memory1[3] = sfucdemo_DW.Memory1_PreviousInput[3];135136/* Update for Memory: '/Memory' */137sfucdemo_DW.Memory_PreviousInput[3] = sfucdemo_B.SFunction3_o2[3];138139/* Update for Memory: '/Memory1' */140sfucdemo_DW.Memory1_PreviousInput[3] = sfucdemo_B.SFunction4_o2[3];141142/* Matfile logging */143rt_UpdateTXYLogVars(sfucdemo_M->rtwLogInfo, (&sfucdemo_M->Timing.taskTime0));144145/* signal main to stop simulation */146{/* Sample time: [0.001s, 0.0s] */147if ((rtmGetTFinal(sfucdemo_M)!=-1) &&148!((rtmGetTFinal(sfucdemo_M)-sfucdemo_M->Timing.taskTime0) >149sfucdemo_M->Timing.taskTime0 * (DBL_EPSILON))) {150rtmSetErrorStatus(sfucdemo_M, "Simulation finished");151}152}153154/* Update absolute time for base rate */155/* The "clockTick0" counts the number of times the code of this task has156 * been executed. The absolute time is the multiplication of "clockTick0"157 * and "Timing.stepSize0". Size of "clockTick0" ensures timer will not158 * overflow during the application lifespan selected.159 * Timer of this task consists of two 32 bit unsigned integers.160 * The two integers represent the low bits Timing.clockTick0 and the high bits161 * Timing.clockTickH0. When the low bit overflows to 0, the high bits increment.162 */163if (!(++sfucdemo_M->Timing.clockTick0)) {164++sfucdemo_M->Timing.clockTickH0;165}166167sfucdemo_M->Timing.taskTime0 = sfucdemo_M->Timing.clockTick0 *168sfucdemo_M->Timing.stepSize0 + sfucdemo_M->Timing.clockTickH0 *169sfucdemo_M->Timing.stepSize0 * 4294967296.0;170}171172/* Model initialize function */173void sfucdemo_initialize(void)174{175/* Registration code */176177/* initialize non-finites */178rt_InitInfAndNaN(sizeof(real_T));179180/* initialize real-time model */181(void) memset((void *)sfucdemo_M, 0,182sizeof(RT_MODEL_sfucdemo_T));183rtmSetTFinal(sfucdemo_M, 10.0);184sfucdemo_M->Timing.stepSize0 = 0.001;185186/* Setup for data logging */187{188static RTWLogInfo rt_DataLoggingInfo;189rt_DataLoggingInfo.loggingInterval = NULL;190sfucdemo_M->rtwLogInfo = &rt_DataLoggingInfo;191}192193/* Setup for data logging */194{195rtliSetLogXSignalInfo(sfucdemo_M->rtwLogInfo, (NULL));196rtliSetLogXSignalPtrs(sfucdemo_M->rtwLogInfo, (NULL));197rtliSetLogT(sfucdemo_M->rtwLogInfo, "tout");198rtliSetLogX(sfucdemo_M->rtwLogInfo, "");199rtliSetLogXFinal(sfucdemo_M->rtwLogInfo, "");200rtliSetLogVarNameModifier(sfucdemo_M->rtwLogInfo, "rt_");201rtliSetLogFormat(sfucdemo_M->rtwLogInfo, 0);202rtliSetLogMaxRows(sfucdemo_M->rtwLogInfo, 0);203rtliSetLogDecimation(sfucdemo_M->rtwLogInfo, 1);204rtliSetLogY(sfucdemo_M->rtwLogInfo, "");205rtliSetLogYSignalInfo(sfucdemo_M->rtwLogInfo, (NULL));206rtliSetLogYSignalPtrs(sfucdemo_M->rtwLogInfo, (NULL));207}208209/* block I/O */210(void) memset(((void *) &sfucdemo_B), 0,211sizeof(B_sfucdemo_T));212213/* states (dwork) */214(void) memset((void *)&sfucdemo_DW, 0,215sizeof(DW_sfucdemo_T));216217/* Matfile logging */218rt_StartDataLoggingWithStartTime(sfucdemo_M->rtwLogInfo, 0.0, rtmGetTFinal219(sfucdemo_M), sfucdemo_M->Timing.stepSize0, (&rtmGetErrorStatus(sfucdemo_M)));220221/* InitializeConditions for UnitDelay: '/Output' */222sfucdemo_DW.Output_DSTATE = 0U;223224/* InitializeConditions for Memory: '/Memory' */225sfucdemo_DW.Memory_PreviousInput[0] = 0.0;226227/* InitializeConditions for Memory: '/Memory1' */228sfucdemo_DW.Memory1_PreviousInput[0] = 0.0;229230/* InitializeConditions for Memory: '/Memory' */231sfucdemo_DW.Memory_PreviousInput[1] = 0.0;232233/* InitializeConditions for Memory: '/Memory1' */234sfucdemo_DW.Memory1_PreviousInput[1] = 0.0;235236/* InitializeConditions for Memory: '/Memory' */237sfucdemo_DW.Memory_PreviousInput[2] = 0.0;238239/* InitializeConditions for Memory: '/Memory1' */240sfucdemo_DW.Memory1_PreviousInput[2] = 0.0;241242/* InitializeConditions for Memory: '/Memory' */243sfucdemo_DW.Memory_PreviousInput[3] = 0.0;244245/* InitializeConditions for Memory: '/Memory1' */246sfucdemo_DW.Memory1_PreviousInput[3] = 0.0;247}248249/* Model terminate function */250void sfucdemo_terminate(void)251{252/* (no terminate code required) */253}254
人工检查上述自动生成的C代码,可以实现该Simulink模型设计的功能。
至此,可以证明该TLC文件可以较好地生成C MEX S-Fuction模块的自动代码。
Tips
TLC的特殊性在于,它本身是一种编程语言,具有文本类编程语言的大部分特点,同时它要实现的功能又是控制C或C++另一种文本语言代码的生成,所以TLC的开发必须熟练掌握它特有的语法结构,常见的一些基础语法如下。
1、%,TLC指令开始的标志符。
2、%implements,一个模块的TLC文件要执行的第一条指令,不可省略。
3、%function,声明一个函数,要配合%endfunction使用。
4、%assign,创建变量。
5、函数LibBlockInputSignal(portIdx, “”,””,sigIdx),返回模块的输入信号,portIdx和sigIdx都从0开始计数。
6、函数LibBlockOutputSignal(portIdx, “”,””,sigIdx),返回模块的输出信号。
7、函数LibBlockParameterValue(param, elIdx),返回模块的参数值。
8、,TLC表达式的开始和结束。
9、%%和/% %/,注释。
分析和应用
本文上述内容中看到,TLC实现了C MEX S-Fuction模块的代码生成,但是进一步仔细研究发现,Library中自带的模块的代码生成也是由TLC实现的,甚至生成代码的总体结构也是由TLC实现的,这些模块的TLC文件就存放在Matlab的系统路径ProgramFiles\Matlab2020b\rtw\c\tlc下。
所以说Simulink的自动代码生成过程,并不是完全固定死的,当我们有特定需求时,可以通过调整TLC文件的内容来实现的。这样就给了代码开发工程师们在代码生成方面的灵活度和自由度,为Simulink的自动代码生成提供了无限可能。
总结
以上就是本人在使用TLC时,一些个人理解和分析的总结,首先介绍了TLC的背景知识,然后展示它的使用方法,最后分析了该模块的特点和适用场景。
后续还会分享另外几个最近总结的Simulink Toolbox库模块,欢迎评论区留言、点赞、收藏和关注,这些鼓励和支持都将成文本人持续分享的动力。
另外,上述例程使用的Demo工程,可以到笔者的主页查找和下载。
版权声明,原创文章,转载和引用请注明出处和链接,侵权必究!