Matlab硬件支持包开发流程
最近在做基于模型设计的开发,不仅仅要做MBD还要做配套的硬件支持包,即MCU对应的底层驱动,查阅了很多资料后发现,这方面的资料实在是太少了,自己整理了一些硬件支持包的开发流程,和大家分享一下。
目录
2 S-Function
2.1 C-MEX S-Function
2.1.1 C MEX S函数的构成
2.1.2 C MEX S函数的编译
3 TLC语言
3.1 TLC语言作用
3.2 TLC的语法
3.2.1基本语法
3.2.2 常用指令
3.3 为S函数编写TLC文件
3.3.1 C MEX S函数获取GUI参数的宏
3.3.2 C MEX S函数的mdlRTW函数
3.3.3 TLC文件的构成
1 基于模型开发的设计流程
基于模型开发的设计流程一般分为以下两种方式:
- 代码生成+手动集成。通过集成开发环境生成驱动代码,然后配合Simulink自动生成的C语言代码,手动编译下载到MCU中,利用IDE调试。
- 全代码生成,需要配合开发硬件支持包TSP(Target Support Package),不需要用集成开发环境,纯Simulink代码生成。
设计TSP前提:C语言、C-MEX S-Function、TLC语言等。
来自: https://zhuanlan.zhihu.com/p/138008476
2 S-Function
Simulink中的系统函数(System Function),是用来描述某个模块特性及功能的函数,当Simulink库提供的模块不足以满足需求时,可以通过S函数自己创建模块。S函数支持C语言等手写和自动生成的方式,可以利用Simulink模块自动生成,也可以用自定义工具写完代码连接到S函数中。
S函数可以分为两类:①直接运行的M S函数(用M语言描述的S函数)。②编译为Mex文件后执行的C MEX S-Function。第一类S函数可以分为两类,下表:
S函数分类
分类 | 区别 |
Level 1 M S | 输入输出端口最多为1且数据维数固定 |
Level 2 M S | 输入输出端口可以为多个,数据维数可以动态改变 |
C Mex S | 可以包含已有的C算法,并在Output子方法中调用既有C函数。 |
S函数可以编写算法或者支持某个目标硬件的驱动模块。例如,编写简单的数学算法等用来仿真时,只需要Level 1 M S函数;所编写算法需要传递多个输入输出端口且每个端口数据都是多维(矩阵)时,需要使用Level 2 M S函数;如果要编写支持目标硬件外设寄存器配置的驱动模块,则需要使用C-MEX S-Function(C语言描述的S函数), 但是需要编写同名的TLC文件。
2.1 C-MEX S-Function
用C语言编写的S函数。它的可执行文件为MEX文件,是Matlab环境下的动态链接可执行文件,当模型运行时,S函数模块通过文件名寻找对应的M文件或者MEX文件来调用执行。
需求 | 建议 |
使用M或C语言的S-Function但是不知道如何手写 | 使用Simulink提供的S-Function builder生成 |
编写S-Function用于生成嵌入式代码 | 编写C-MEX S-Function并提供与 S-Function同名的TLC文件 |
将算法或者部门核心模块保密,仅提供使用而不能看到源代码 | 编写C-MEX S-Function,将C文件通过mex XXX.C格式编译,仅提供mexw32/64 |
S函数包括一组输入、一组状态和一组输出。
模型中的变量包括:
时间量t:随着Simulink解算器运行而记录当前时间。
输入量u:通常为前一个模块的输出量,当作为信号源时,可以没有输入量。
状态量x:根据系统性质分为连续系统中的微分量和离散系统中的差分量,通过前后不同时刻的输入值计算得到。
输出量y:可以没有输出量。
S函数执行的流程 | |
1: | 初始化模型参数 |
2: | 计算下一个采样时间点 |
3: | for 0→仿真终止时间 |
4: | 计算模块的输出 |
5: | 更新离散状态量 |
6: | if (此模型带有连续状态模块) |
7: | here:计算微分 |
8: | 计算模块的输出; |
9: | if (精度未达标) |
10: | goto here |
11: | endif |
12: | 过零检测 |
13: | 计算下一个采样时间点 |
14: | endif |
15: | endfor |
16: | 执行仿真终止动作 |
仿真运行时,模型首先要对模块进行初始化,这个过程包括模块的实例化、输入/输出端口、信号维度、端口数据类型及采样时间等的确定,模块参数的获取及个数检查,并决定模块的执行顺序等。
实例化:Simulink标准库中提供的模块类似于C++等面向对象语言中的一个类,每当模块被拷贝或拖曳到模型中时,就相当于创建了这个类的一个对象,继承了这个类的属性并载入了默认的构造函数方法对其参数、端口等各个属性进行了初始化工作。
信号维度:一根信号线传递的数据不仅可以是标量,也可以是一个向量或矩阵,一个模块的输出端口将具有这个数据维度的信号传递给相连的信号线然后再传递给下一个模块的输入端口,这些都是需要在初始化阶段确定下来的。
端口数据类型:模块的输入/输出数据是浮点数还是固定点数,是8位、16位、32为或64位,有无符号,内建类型或者用户自定义类型,这些也在初始化阶段指定。
采样时间:对于Simulink模型来说,解算器中的一个步长决定了整个模型最小的采样时间间隔。
模型中模块的执行顺序:当众多模块同时存在于一个模型中时,Simulink是有明确的顺序优先度的。
设计S函数时,需要调用S函数的子方法,子方法的名称与功能如下表:
C MEX S函数子方法 | 子方法功能 |
mdllnitializeSizes | 模块属性初始化 |
mdIDerivatices | 更新连续状态变量的微分值 |
mdllnitializeConditions | 初始化工作向量的状态值 |
mdlOutputs | 计算模块的输出 |
mdlSetWorkWidths | 当S函数模块存在于使能子系统中时,每次子系统被使能均进行工作向量属性的初始化工作 |
mdlStart | 在仿真开始时初始化工作向量及状态变量的属性 |
mdITerminate | 在仿真终上时所调用的子方法 |
mdlUpdate | 更新离散状态变量的子方法 |
mdlRtw | 将S函数中获取到的GUI参数变量值写入到rtw文件中以使TLC文件用来生成代码 |
2.1.1 C MEX S函数的构成
(1)首先必须定义的两个宏:C Mex S函数名及C Mex S函数的等级。
- #define S_FUNCTION_NAME two
- #define S_FUNCTION_LEVEL 2
(2)头文件包含部分C Mex S函数核心数据结构simstruct类型的声明及其他库文件,根据使用需要也可以包含其他头文件。
- #include <math.h>
- #include "simstruc.h"
(3)参数对话框访问宏函数的定义。包括常用宏变量TRUE/FALSE的定义,C MexS函数模块通过这些宏能够方便地获取参数对话框各个控件中的值,以方便在之后的各个子方法中使用,但是这个部分不是必需的。
(4)紧接着定义C Mex S函数的各个子方法。需要注意,这些子方法都是static子函数,被static修饰函数的作用域仅局限于本文件,而不会被其他S函数所调用。
- static void mdlInitializeSizes(SimStruct *S){}
- static void mdlInitializeSampleTimes(SimStruct *S){}
- static void mdlOutputs(SimStruct *S, int_T tid){}
- ......
(5)S函数必须根据使用情况(编译为Mex文件用来仿真还是用于自动代码生成)包含必要的源文件和头文件,从而将该S函数文件与Simulink或Simulink Coder产品进行连接。
- //编译C文件时用到,表示S函数是否正在编译为mex文件
- #ifdef MATLAB_MEX_FILE /*Is this file being compiled as a MEXfile*/
- //包含mex文件需要的接口机制,以此将S函数的C文件编译为mex文件
- #include "simulink.c" /* MEX-file interface mechanism */
- #else
- //想要自动生成代码,调用Simulink Coder工具箱
- #include "cg_sfun.h" /* Code generation registration function */
- #endif
2.1.2 C MEX S函数的编译
编译型语言编写的程序执行之前,需要一个专门的编译过程,把程序编译成为机器语言的文件,比如exe文件或mexw32/mexw64文件,运行时不用重新编译,直接使用编译的结果就行了(exe文件),所以程序执行效率高,这也是使用C MEX S函数仿真速度快的原因。解释性语言的程序没有编译这道工序,在运行程序时进行翻译,例如M语言,专门有一个解释器能够直接执行程序,每个语句都是执行的时候才编译,效率比较低。
C语言是编译性语言,与直接解释执行的M语言不同,需要通过编译器编译为可执行文件之后,才能被Simulink执行。为了将C语言文件编译为mex文件,需在PC上安装 编译器,在Matlab的CommandWindow中输人mex -setup可查看是否安装了编译器,最后通过mex XXX.c对S函数进行编译。
3 TLC语言
Simulink模型在Simulink Coder和Embedded Coder的支持下可以生成嵌入式C语言代码,应用于MCU、DSP等芯片。模型生成代码需要靠系统目标文件与模块目标文件的支持。这两个等级的目标文件都是由TLC(Target Language Compiler,目标语言编译器)进行语言转换的TLC文件。
Similink自动代码生成的流程为:使用Embedded Coder或Simulink Coder将.mdl或.slx文件转换为.rtw文件,然后利用TLC文件将.rtw文件转换为C语言代码,转换过程中,系统会按照TLC文件的描述自动生成代码。
3.1 TLC语言作用
(1)支持模型针对通用或特定目标硬件的代码生成功能;
(2)为S函数模块提供代码生成功能,可以自己增加支持代码生成的模块;
(3)在代码生成过程中,生成不依赖 S函数模块的自定义过程代码。
3.2 TLC的语法
TLC是一种以单个%开头的关键字为命令,空格之后跟参数的脚本语言,自身包含了流控制语法、内建函数、关键字和常用命令。
3.2.1基本语法
(1)[text|%<expression>]*
- <LibBlockOutputSignal(0, "", lcv, i)>
text表示字符串,将文本展开到输出流中。在%<>之中的是TLC变量,通过%<>作用将变量的执行结果显示到输出流中。
(2)%keyword [arguement1,arguement2,...]
- % assign Str = "Hello World"
%keyword表示TLC语言中的命令符,[arguement1, arguement2, ...]则表示这个命令符所操作的参数。
%warning、%error、%trace命令可以将其后的变量或字符串的内容输出。
例如:
- % warning Simulink Coder
输出为:
- >> tlc test.tlc
- Warning: Simulink User
3.2.2 常用指令
注释:
单行注释:%%双百分号
多行注释:/%***%/
变量内容扩展:
通过%<>操作符将其内容扩展到输出流中。
- %% test.tlc
- %assign input1 = 3
- %assign input2 = 5
- %warning %<input1> + %<input2> = %<input1 + input2>
输出:
- >> tlc text.tlc
- Warning: 3 + 5 = 8
条件分支:
% if expression
% elseif expression
% else
% endif
例:判断给定值是否为1
- %% test.tlc
- %assign var = 2
- %if ISEQUAL(var,1)
- %warning evering is OK.
- %else
- %warning var should be 1 but now there is something wrong.
- %endif
输出:
- >> tlc text.tlc
- Warning: var should be 1 but now there is something wrong.
3.3 为S函数编写TLC文件
带有参数的S函数首先从GUI上获取用户的输人作为参数,通过C语言的宏将GUI控件上的值读人S函数,再通过S函数的子方法mdlRTW将参数值写人到模型的.rtw文件中,使得TLC文件能够获取这些参数的值,最终生成C语言代码。代码生成流程及参数数据的流向如下图。
代码生成流程图
3.3.1 C MEX S函数获取GUI参数的宏
GUI制作的参数对话框作为S函数的最前端能够提供方便简洁的操作方式。常用的 Simulink Mask控件包括Edit、popup、radiobutton、check-box和pushbutton等。不同控件所容纳的数据类型不同,Edit中可以输入数值也可以输人字符串,从popup/radiobutton中既可以提取表示所选项目的编号也可以提取所选项目的字符串内容。可以根据使用场景不同,有选择地提取数据类型。在C MEX S函数中以Edit为例:
(1)获取Edit中的数值
- #define PARAM(S)(mxGetScalar(ssGetSFcnParam(s, g_param)))
g_param为Edit等控件的参数在GUI控件中的索引号,首先通过ssGetSFcnParam宏函数获取指向Edit控件中参数的数值,再使用mxGetScalar宏函数获取指针指向地址的数值。
(2)获取Edit中的数组
- #define PARAM(S)mxGetNumberOfElements(ssGetSFcnParam(s, g_param))
在使用ssGetSFcnParam宏函数获取Edit控件的指针之后,使用 mxGetNumberOfEle ments获取此数组的数据长度,以传递相同长度的数据到rtw文件。
(3)获取Edit中的字符串
- #define PARAM(S)(ssGetSFcnParam(s, g_param))
仅在宏定义中获取指向参数的指针,在S函数的mdIRTW函数中再进行字符串传递。
3.3.2 C MEX S函数的mdlRTW函数
mdlRTW函数是专为支持代码生成的S函数设计的,不支持仅用于仿真的C MEX S函数,它的功能是传递S函数的参数数据到rtw文件。它必须被包含在以下这个预处理语句中:
- #ifdefined(MATLAB_MEX_FILE)
- #define MDL_RTW
- static viod mdlRTW(SimStruct *S){}
- ...
- #ifend
函数内实现两个作用:①通过定义的宏获取GUI上的数据;②将这些输入写入到rtw文件中。
3.3.3 TLC文件的构成
TLC文件是具体实现一个S函数如何生成代码的,与S函数的构成类似,TLC文件也由多个执行顺序有先后的子函数构成。
子函数 | 输出 | 功能说明 |
BlockInstanceSetup (block, system) | 不产生输出 | 同种类的模块存在多个时,每一模块都会执行一次此函数,可以将模块共同的操作或特例的操作写入此函数中 |
Start (block, system) | 产生输出 | 模块中仅执行一次的函数,内部代码会生成到model_initialize()函数中,通常将模型各变量、状态或硬件外设初始化的代码写在此函数中,因为他们不需要重复执行 |
Outputs (block, system) | 产生输出 | 用于编写模块计算输出的代码,并将其生成到model_step()函数中 |
Terminate (block, system) | 产生输出 | 此函数用于自定义代码用,可以存储数据,释放内存,复位硬件寄存器等操作,此函数内的代码将生成到MdlTerminate()中 |
TLC文件并非必须包括上面所有子函数,根据需要选择其中部分函数。每个子函数都有两个参数:block和system,分别表示当前函数所属的模块名和该模块所属的子系统名。每一个S函数的TLC文件都拥有相同的函数名,即使多个模块的TLC文件同时存在也不会出现访问冲突,在执行时才由TLC决定调用哪个模块的子函数。
产生输出的函数不需指定,即可将内部代码创建成文件流生成到特定的位置;不产生输出的函数中则需要用户编写代码创建文件流再指定其生成的位置。产生输出的函数在其承数参数列表后有output标注,不产生输出的函数参数列表后则以void标注,如:
- %function BlockTypeSetup(block,system)void
- %function Start(block,system)Output
每个模块TLC文件都以%implements命令开头,其后注明S函数名及生成代码的目标语言。如:
- %implements test "C"
在TLC文件结尾需要补上结束命令:
- %endfunction
如有错误还请大家多多指教。
参考书籍:
《基于模型的设计-MCU篇》 刘杰 北京航空航天大学出版社;
我不喝甜水儿: 可以自己生成的话,是不是不需要对cmex和tlc进行学习了呢
我不喝甜水儿: 哦哦哦这两天学习了一下这方面内容,使用LCT生成mex和tlc 文件之后,进行封装就是硬件支持包所提供的模块了吗
我爱吃烤冷面1: hello你好,目前我没有看到过开源的硬件支持包,大部分比较复杂的硬件支持包都是用C语言写成函数,再使用matlab的工具进行编译,生成s-function,mex文件等,一般厂商发布只会发布s-function,mex文件和tlc文件,源代码一般不会发布,虽然生成的s-fucntion也是c语言的,但是看起来十分晦涩难懂,感觉手写s-function不太现实哈哈哈
我不喝甜水儿: 请问有开源的硬件支持包吗?个人对模块内部的代码比较感兴趣
我爱吃烤冷面1: 不需要Matlab官方识别,打包好压缩包可以直接嵌入到simulink的library中,但是商用肯定得先购买正版的Matlab才可以