4. 编写C语言的S函数
4.1 C MEX-file S-function简介
定义了S-function模块的C MEX-file必须在仿真过程中向Simulink提供模型信息。在仿真中Simulink、ODE求解器、MEX-file协作完成指定任务。这些任务包括:定义初始条件和模块特性,计算微分、离散状态和输出。
Simulink与C MEX-file S-function模块的交互仍是通过S-function的回调方法。每个回调方法执行一个预定义的,实现仿真所需功能的任务。S-function可以执行任何其实现的任务。一系列C MEX-file S-function实现的回调方法,都远大于M-file S-function中的。与M-file S-function不同的是,C MEX-file可以访问并修改Simulink内部用来存储S-function信息的数据结构。更多的回调方法和对Simulink内部数据结构的访问能力,使得C MEX-file S-function可以实现更丰富的模块特性,如处理矩阵信号和多种数据类型。
C MEX-file S-function只需实现Simulink定义的回调方法的一个小子集即可。如果不实现某个回调方法,相应的功能将被省略掉。这有利于快速开发简单的模块。
通常C MEX-file S-function的形式如下:
#define S_FUNCTION_NAME your_sfunction_name_here
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
static void mdlInitializeSizes(SimStruct *S)
{
}
<additional S-function routines/code>
static void mdlTerminate(SimStruct *S)
{
}
#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX-file? */
#include "simulink.c" /* MEX-file interface mechanism */
#else
#include "cg_sfun.h" /* Code generation registration function */
#endif
mdlInitializeSizes是Simulink与S-function交互时调用的第一个方法。随后Simulink将调用其他S-function方法(都以mdl开头)。仿真结束时,Simulink调用mdlTerminate。
注意:与M-file S-function不同,C MEX-file S-function回调方法不是每个都具有flag参数。这是因为,Simulink仿真时直接在适当的时间调用每个回调方法。
4.2 自动建立S-function模块
S-Function Builder是通过规范定义和用户提供的C代码建立S-function的Simulink模块。S-Function Builder还用作普通的S-function在Simulink模型中的包装。
通过S-Function Builder建立S-function。
1. 将MATLAB当前目录设置到需要建立S-function的目录。
2. 创建新的Simulink模型。
3. 从Simulink User-Defined Functions library中将S-Function Builder拖入新建的ulink模型。
图4.
4. 双击模块打开S-Function Builder对话框。
图5.
5. 输入所需信息和用户代码。(详见下节)
6. 如果还未设置mex编译器,用mex –setup在MATLAB命令行设置。
7. 点Build按钮,启动建立过程。Simulink建立MEX文件实现指定的S-function,并存放在当前目录
8. 保存包含S-Function Builder模块的模型。
部署生成的S-Function
要在其他
模型中使用生成的S-Function,首先必须检查生成的S-Function所在的目录是否在MATLAB路径中。然后把S-Function Builder模块从创建它的模型复制到目标模型并设置其参数。
S-Function Builder如何建立S-Function
4.3 S-Function Builder对话框
4.4 基本的C MEX-file S-function实例
本节介绍一个C MEX-file S-function实例:timestwo,实现输出信号放大为输入信号的2倍。以下是模型图:
图6.
S-function timestwo的回调方法如图7所示:
图7.
以下是timestwo.c文件的代码:
#define S_FUNCTION_NAME timestwo
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
static void mdlInitializeSizes(SimStruct *S)
{
ssSetNumSFcnParams(S, 0);
if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) {
return; /* Parameter mismatch will be reported by Simulink */
}
if (!ssSetNumInputPorts(S, 1)) return;
ssSetInputPortWidth(S, 0, DYNAMICALLY_SIZED);
ssSetInputPortDirectFeedThrough(S, 0, 1);
if (!ssSetNumOutputPorts(S,1)) return;
ssSetOutputPortWidth(S, 0, DYNAMICALLY_SIZED);
ssSetNumSampleTimes(S, 1);
ssSetOptions(S,
SS_OPTION_WORKS_WITH_CODE_REUSE |
SS_OPTION_EXCEPTION_FREE_CODE |
SS_OPTION_USE_TLC_WITH_ACCELERATOR);
}
static void mdlInitializeSampleTimes(SimStruct *S)
{
ssSetSampleTime(S, 0, INHERITED_SAMPLE_TIME);
ssSetOffsetTime(S, 0, 0.0);
ssSetModelReferenceSampleTimeDefaultInheritance(S);
}
static void mdlOutputs(SimStruct *S, int_T tid)
{
int_T i;
InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
real_T *y = ssGetOutputPortRealSignal(S,0);
int_T width = ssGetOutputPortWidth(S,0);
for (i=0; i<width; i++)
{
*y++ = 2.0 *(*uPtrs[i]);
}
}
static void mdlTerminate(SimStruct *S)
{
}
#ifdef MATLAB_MEX_FILE /* Is this file being compiled as a MEX-file? */
#include "simulink.c" /* MEX-file interface mechanism */
#else
#include "cg_sfun.h" /* Code generation registration function */
#endif
这个实例包括3部分:宏定义和头文件;回调方法的实现;Simulink (或 Real-Time Workshop)接口。
宏定义和头文件
示例程序中包含两个宏定义:
#define S_FUNCTION_NAME timestwo
#define S_FUNCTION_LEVEL 2
第一个指定S-function的名字,第二个指示该S-function是Level-2格式的。
然后是包含头文件"simstruc.h",在其中定义了SimStruct数据结构和MATLAB应用程序接口(API)函数。SimStruct是Simulink用于保持S-function信息的数据结构,"simstruc.h"中还有用于MEX文件设置或获取SimStruct属性值的宏定义。
回调方法的实现
mdlInitializeSizes:
Simulink调用mdlInitializeSizes方法查询S-function模块
的输入输出端口数,端口容量,以及S-function所需的其他对象(如,状态个数等)。
Timestwo实现的mdlInitializeSizes方法指定了下面信息
● 零参数:
意味着S-function对话框的参数框必须为空。否则,Simulink将报告参数不匹配。
● 一个输入和一个输出端口:
输入输出端口的宽度可以动态变化。Simulink将把所有输入信号乘以2作为输出信号的结果。注意,在这种情况下(一个输入一个输出端口),Simulink对S-function宽度的默认处理是输入和输出宽度相等。
● 一个采样时间
例子timestwo在mdlInitializeSampleTimes中指定实际的采样时间。
● 代码无异常处理
指定无异常处理的代码可以加速S-function的执行。作这项指定必须谨慎。通常,如果用户S-function与MATLAB没有交互,指定无异常处理的代码是安全的。
mdlInitializeSampleTimes:
Simulink调用mdlInitializeSampleTimes设置S-function的采样时间。每当timestwo模块的前驱模块执行一次,timestwo模块就执行一次,所以timestwo模块采用继承的采样时间。
mdlOutputs
Simulink在每个时间步调用mdlOutputs计算模块的输出。Timestwo模块的mdlOutputs方法实现了将输入信号乘以2,将结果写到输出信号。
Timestwo模块的mdlOutputs方法使用了SimStruct中的宏:
InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
来实现对输入信号的访问。这个宏返回一个指针向量,必须通过*uPtrs[i]来访问。
Timestwo模块的mdlOutputs方法还使用了
real_T *y = ssGetOutputPortRealSignal(S,0);
来访问输出信号。这个宏返回一个包含模块输出的数组。
S-function使用
int_T width = ssGetOutputPortWidth(S,0);
来得到通过该模块的信号宽度。最后S-function循环通过输入得到输出。
mdlTerminate
执行仿真结束最后的任务。这是一个强制的S-function过程。不过由于timestwo模块不需要任何终止操作,所以其函数为空。
Simulink/Real-Time Workshop 接口
在S-function的最后,下面的特定代码将该例子关联到Simulink或者Real-Time Workshop:
#ifdef MATLAB_MEX_FILE
#include "simulink.c"
#else
#include "cg_sfun.h"
#endif
生成timestwo实例
在命令行键入:
mex timestwo.c
mex命令将把timestwo.c编译、链接为一个可由Simulink动态加载执行的模块。这是一个动态链接库文件,在Window中,是个dll文件。
4.5 C MEX S-functions模板
Simulink提供了C MEX S-functions模板。<matlabroot>/simulink/src/sfuntmpl_basic.c提供了C MEX S-functions的常用功能方法。<matlabroot>/simulink/src/sfuntmpl_doc.c则描述了C MEX S-functions的全部实现方法。
S-function源代码文件的要求
用户S-function要访问SimStruct结构,则必须在文件头部具备下列宏定义和头文
件:
#define S_FUNCTION_NAME your_sfunction_name_here
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
这里your_sfunction_name_here指得是用户S-function的模块名。这些声明使S-function能访问SimStruct结构。
当被编译为MEX文件时,matlabroot/simulink/include/simstruc.h将包括下面头文件:
Header File
Description
matlabroot/extern/include/tmwtypes.h
General data types, e.g., real_T
matlabroot/extern/include/mex.h
MATLAB MEX-file API routines
matlabroot/extern/include/matrix.h
MATLAB MEX-file API routines
当被编译为MEX文件时,matlabroot/simulink/include/simstruc.h将包括下面头文件:
Header File
Description
matlabroot/extern/include/tmwtypes.h
General types, e.g., real_T
matlabroot/rtw/c/libsrc/rt_matrx.h
Macros for MATLAB API routines
用户C MEX S-function文件尾部必须包括下面声明:
#ifdef MATLAB_MEX_FILE /* Is this being compiled as MEX-file? */
#include "simulink.c" /* MEX-file interface mechanism */
#else
#include "cg_sfun.h" /* Code generation registration func */
#endif
这些声明确保针对用户具体应用选择适当的代码:
● 当被编译为MEX文件的时候,simulink.c文件将被包含。
● 当被编译为关联了实时工具箱(Real-Time Workshop)的独立程序时,cg_sfun.件将被包含。
SimStruct结构
C头文件matlabroot/simulink/include/simstruc.h定义了Simulink数据结构和访问SimStruct的宏定义。SimStruct封装了所有模型和S-function相关的数据。
Simulink模型具有一个与之关联的SimStruct结构,而模型中的每个S-function模块也具有自己的SimStruct结构。这些SimStruct组成一个目录树,模型的SimStruct为根,S-function模块的SimStruct为孩子。
编译C S-function
S-function可以以下列三种方式编译:
● MATLAB_MEX_FILE - 编译为用于Simulink的MEX文件。
● RT - 通过Real-Time Workshop生成代码,成为一个具有固定步长求解器的实时应用程序。
● NRT -通过Real-Time Workshop生成代码,成为一个具有变步长求解器的非实时应用程序。
4.6 Simulink和C MEX S-functions的互操作
本节从过程和数据两方面的观点来介绍Simulink和C MEX S-functions的交互。
过程观点
下图描述了Simulink调用S-function回调方法的顺序。实线框表示该回调函数在初始化和(或)仿真循环的每个时间步都被调用;虚线框表示该回调函数可能在初始化和(或)仿真循环的每个时间步被调用。
图8.
图9.
数据观点
S-function模块具有输入、输出信号,参数,内部状态,加上其他普通的工作区间。通常模块的输入输出信号都从模块的I/O向量读写。输入还可来自:
● 根输入模块的外部输入。
● 本地磁盘,如果没有输入信号连接或者是落地的数据。
输出也可以写到根输出模块的外部输出。除了输入、输出信号,S-functions还具有:
● 连续状态。
● 离散状态。
● 其他工作区间,如实型、整形或指针工作向量。
利用S-function的对话框还可以实现S-function模块的参数化。
下图显示了这些不同类型数据的相互关系:
图10.
S-function的mdlInitializeSizes方法可以设置不同信号和向量占用的空间。S-function访问信号有两种方式:通过指针或者通过毗邻的输入。
通过指针访问信号
在仿真循环中,可通过下面语句访问输入信号:
InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,portIndex);
uPtrs是一个指针数组,其中portIndex从0开始,每个端口都有一个。要访问信号的元素,必须使用*uPtrs[element]
如图所示:
图11.
注意,输入数组的指针可能会指向内存中毗邻的位置。类似的,可以通过下面函数得到输出信号:
real_T *y = ssGetOutputPortSignal(S,outputPortIndex);
访问毗邻的输入信号
一个S-function的mdlInitializeSizes方法:ssSetInputPortRequiredContiguous,可用来指定输入信号必须占用毗邻的内存空间。如果输入是连续的,其他方法可以通过ssGetInputPortSignal来访问输入。
访问单个端口的输入信号
图11.显示了输入指针数组可以指向非毗邻的模块I/O向量。特定端口的输出信号形成一个连续的向量。所以,假定输入输出端口的宽度相同,正确的访问输入元素,写输出元素的方法是采用下面这样的代码:
int_T element;
int_T portWidth = ssGetInputPortWidth(S,inputPortIndex);
InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,inputPortIndex);
real_T *y = ssGetOutputPortSignal(S,outputPortIdx);
for (element=0; element<portWidth; element++) {
y[element] = *uPtrs[element];
}
这里常犯的一个错误是用指针运算来访问输入信号。例如,将
real_T *u = *uPtrs; /* Incorrect */
正好置于uPtrs的初始化下面,并且在循环内部采用:
*y++ = *u++; /* Incorrect */
这有可能导致非法访问内存,从而MEX文件和Simulink冲突,这取决于用户模型如何建立。
可以通过传递一个复制的信号到S-function的输入端口,来验证S-function是否正确的访问输入信号。如下图:
图12.
4.7 编写回调方法
5. 编写C++语言的S函数
C++ S-functions与创建C S-functions基本类似。本节只讨论不同部分。
5.1 代码形式
C++ S-functions的代码形式与C S-functions基本一致。区别在于要告诉C++编译器,要按照C惯例编译回调方法。这是由于Simulink仿真引擎假定回调方法都遵从C惯例。
为了使编译器按照C惯例编译回调方法,要将实现回调方法的C++代码包装在extern "C"声明中。C++版本的S-functions例子:matla
broot/simulink/src/sfun_counter_cpp.cpp,说明了如何利用extern "C"声明实现编译器生成回调方法。
/* File : sfun_counter_cpp.cpp
* Abstract:
*
* Example of an C++ S-function which stores an C++ object in
* the pointers vector PWork.
*
* Copyright 1990-2000 The MathWorks, Inc.
*
*/
#include "iostream.h"
class counter {
double x;
public:
counter() {
x = 0.0;
}
double output(void) {
x = x + 1.0;
return x;
}
};
#ifdef __cplusplus
extern "C" { // use the C fcn-call standard for all functions
自定义函数怎么用c语言#endif // defined within this scope
#define S_FUNCTION_LEVEL 2
#define S_FUNCTION_NAME sfun_counter_cpp
/*
* Need to include simstruc.h for the definition of the SimStruct and
* its associated macro definitions.
*/
#include "simstruc.h"
/*====================*
* S-function methods *
*====================*/
/* Function: mdlInitializeSizes ===============================================
* Abstract:
* The sizes information is used by Simulink to determine the S-function
* block's characteristics (number of inputs, outputs, states, etc.).
*/
static void mdlInitializeSizes(SimStruct *S)
{
/* See sfuntmpl_doc.c for more details on the macros below */
ssSetNumSFcnParams(S, 1); /* Number of expected parameters */
if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) {
/* Return if number of expected != number of actual parameters */
return;
}
ssSetNumContStates(S, 0);
ssSetNumDiscStates(S, 0);
if (!ssSetNumInputPorts(S, 0)) return;
if (!ssSetNumOutputPorts(S, 1)) return;
ssSetOutputPortWidth(S, 0, 1);
ssSetNumSampleTimes(S, 1);
ssSetNumRWork(S, 0);
ssSetNumIWork(S, 0);
ssSetNumPWork(S, 1); // reserve element in the pointers vector
ssSetNumModes(S, 0); // to store a C++ object
ssSetNumNonsampledZCs(S, 0);
ssSetOptions(S, 0);
}
/* Function: mdlInitializeSampleTimes =========================================
* Abstract:
* This function is used to specify the sample time(s) for your
* S-function. You must register the same number of sample times as
* specified in ssSetNumSampleTimes.
*/
static void mdlInitializeSampleTimes(SimStruct *S)
{
ssSetSampleTime(S, 0, mxGetScalar(ssGetSFcnParam(S, 0)));
ssSetOffsetTime(S, 0, 0.0);
}
#define MDL_START /* Change to #undef to remove function */
#if defined(MDL_START)
/* Function: mdlStart =======================================================
* Abstract:
* This function is called once at start of model execution. If you
* have states that should be initialized once, this is the place
* to do it.
*/
static void mdlStart(SimStruct *S)
{
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论