定义:S-函数是 System function 系统函数的简称,是指采用非图形化(即计算机语言,而非Simulink系统模块)的方式描述的功能模块。在 MATLAB 中,用户除了可以使用MATLAB 代码编写 S-函数以外,还可以使用 C、C++、FORTRAN 或 Ada 语言编写 S-函数,只不过用这些语言编写程序时需要用编译器生成动态连接库(DLL)文件,然后在Simulink 中直接调用。
使用s-函数的一般步骤:
一、在系统的 Simulink 仿真框图中添加 S-function 模块,并进行正确的设置;
二、创建 S-函数源文件。创建 S-函数源文件的方法有多种。用户可以按照 S-函数的语法格式自行编写代码,但是这样做很麻烦,且容易出错。Simulink 在 S-function Examples 模型库中为用户提供了针对不同语言的很多 S-函数模板和例子,用户可以根据自己的需要修改相应的模板或例子即可完成 S-函数源文件的编写工作;
三、在系统的 Simulink 仿真框图中按照定义好的功能连接输入输出端口。
这里需要说明的是,S-function 模块中 S-函数名称必须和用户建立的 S-函数源文件的名称完全相同,S-function 模块中的 S-函数参数列表必须按照 S-函数源文件中的参数顺序赋值,且参数之间需要用逗号隔开。另外,用户也可以使用子系统封装技术对 S-函数进行封装,这样做的好处是可以增强系统模型的可读性。
常用的s-function主要有M文件和C MEX文件两种。M 文件 S-函数由于具有易于编写和理解的特点,在仿真计算中得到了广泛的应用。但是它有一些缺点:首先,M 文件 S-函数使得每个仿真步都必须激活 MATLAB 解释器,以致仿真速度变慢;其次,当需要利用 RTW 从 Simulink 框图生成实时代码时,框图中不能含有 M 文件 S-函数。而 C MEX S-函数不仅运算速度快,而且可以用来生成独立的仿真程序。现已的 C 语言编写的程序还可以方便地通过包装程序结合至 C MEX S-函数中。C MEX S-函数结合了 C 语言的优势,可以实现对操作系统和硬件的访问,实现与串口或网络的通信,编写设备驱动程序等。
一:M文件编写的s函数
附上mathworks公司的模板(在matlab的命令行窗口输入edit Sfuntmpl.m)
function [sys,x0,str,ts,simStateCompliance] = sfuntmpl(t,x,u,flag)
switch flag,
case 0,
[sys,x0,str,ts,simStateCompliance]=mdlInitializeSizes;
case 1,
sys=mdlDerivatives(t,x,u);
case 2,
sys=mdlUpdate(t,x,u);
case 3,
sys=mdlOutputs(t,x,u);
case 4,
sys=mdlGetTimeOfNextVarHit(t,x,u);
case 9,
sys=mdlTerminate(t,x,u);
otherwise
DAStudio.error('Simulink:blocks:unhandledFlag', num2str(flag));
end
function [sys,x0,str,ts,simStateCompliance]=mdlInitializeSizes
sizes = simsizes;
sizes.NumContStates = 0;
sizes.NumDiscStates = 0;
sizes.NumOutputs = 0;
sizes.NumInputs = 0;
sizes.DirFeedthrough = 1;
sizes.NumSampleTimes = 1; % at least one sample time is needed
sys = simsizes(sizes);
x0 = [];
str = [];
ts = [0 0];
simStateCompliance = 'UnknownSimState';
function sys=mdlDerivatives(t,x,u)
sys = [];
function sys=mdlUpdate(t,x,u)
sys = [];
function sys=mdlOutputs(t,x,u)
sys = [];
function sys=mdlGetTimeOfNextVarHit(t,x,u)
sampleTime = 1; % Example, set the next hit to be one second later.
sys = t + sampleTime;
function sys=mdlTerminate(t,x,u)
sys = [];
对上述模板进行解析:
function [sys,x0,str,ts,simStateCompliance] = sfuntmpl(t,x,u,flag)
先讲输入与输出变量的含义:t是采样时间,x是状态变量,u是输入(是做成simulink模块的输入),flag是仿真过程中的状态标志(以它来判断当前是初始化还是运行等);sys输出根据flag的不同而不同(下面将结合flag来讲sys的含义),x0是状态变量的初始值(可根据需要对其进行赋值),str是保留参数(一般都是在初始化中将它置空),ts是一个1×2的向量,ts(1)表示采样周期,ts(2)表示偏移量。
ps:如果是人为地自定义一些参量作为输入,则需要将模块改为:
function [sys,x0,str,ts,simStateCompliance] = sfuntmpl(t,x,u,flag,a,b,c) %a,b,c即为定义的输入变量
之后是switch,case模块:
根据flag的值判断执行哪一步。
case 0,
[sys,x0,str,ts,simStateCompliance]=mdlInitializeSizes; %该模块为初始化模块。在代码中,找到相应的执行模块:
function [sys,x0,str,ts,simStateCompliance]=mdlInitializeSizes
sizes = simsizes; %用于设置模块参数的结构体用simsizes来生成
sizes.NumContStates = 0; %模块连续状态变量的个数
sizes.NumDiscStates = 0; %模块离散状态变量的个数
sizes.NumOutputs = 0; %模块输出变量的个数
sizes.NumInputs = 0; %模块输入变量的个数
sizes.DirFeedthrough = 1; ;%模块是否存在直接贯通(直接贯通我的理解是输入能 直接控制输出)
sizes.NumSampleTimes = 1; % 模块的采样时间个数,至少是一个
sys = simsizes(sizes); %设置完后赋给sys输出
x0 = []; %根据需要对状态变量赋初值
str = []; %一般都设置为空
ts = [0 0]; %若系统为连续系统,则第一空为0;若第一空不为0,则该值为采样时间。 若第二空为0,表示整个系统无偏移,即为从初始时刻0开始动作。
simStateCompliance = ‘UnknownSimState’;
case 1,
sys=mdlDerivatives(t,x,u); %该模块为连续状态的微分模块。
在代码中,找到相应的执行模块:
function sys=mdlDerivatives(t,x,u)
sys = [];
此时要计算连续状态的微分。如果设置的连续状态变量个数为0,此处置空就可以了。
case 2,
sys=mdlUpdate(t,x,u); %该模块为离散转态的计算模块
在代码中,找到相应的执行模块:
function sys=mdlUpdate(t,x,u)
sys = [];
case 3,
sys=mdlOutputs(t,x,u); %该模块为计算输出的模块
在代码中,找到相应的执行模块:
function sys=mdlOutputs(t,x,u)
sys = [];
case 4,
sys=mdlGetTimeOfNextVarHit(t,x,u); %表示要计算下一次采样的时间。只有在离散采样系统中才有用
在代码中,找到相应的执行模块:
function sys=mdlGetTimeOfNextVarHit(t,x,u)
sampleTime = 1; % Example, set the next hit to be one second later.
sys = t + sampleTime; %对于连续系统,只需将sys置空就可以
case 9,
sys=mdlTerminate(t,x,u); %该模块为结束模块
在代码中,找到相应的执行模块:
function sys=mdlTerminate(t,x,u)
sys = []; %一般直接置空就可以了
进一步的学习内容可以参考
https://blog.csdn.net/weixin_42736130/article/details/89084225
https://blog.csdn.net/weixin_42736130/article/details/89178868
https://blog.csdn.net/dm12mail/article/details/66974956
二:C语言编写的s函数
C语言编写的s函数,需要先编译成可以在MATLAB内运行的二进制代码,然后才可以使用。
编译方法为:mex .c文件
C语言模板的打开方式:在matlab的命令行窗口中输入edit sfuntmpl_basic.c
同样附上mathworks公司的模板
#define S_FUNCTION_NAME sfuntmpl_basic
#define S_FUNCTION_LEVEL 2
#include "simstruc.h"
static void mdlInitializeSizes(SimStruct *S)
{
ssSetNumSFcnParams(S, 0); /* 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, 1)) return;
ssSetInputPortWidth(S, 0, 1);
ssSetInputPortRequiredContiguous(S, 0, true); /*direct input signal access*/
ssSetInputPortDirectFeedThrough(S, 0, 1);
if (!ssSetNumOutputPorts(S, 1)) return;
ssSetOutputPortWidth(S, 0, 1);
ssSetNumSampleTimes(S, 1);
ssSetNumRWork(S, 0);
ssSetNumIWork(S, 0);
ssSetNumPWork(S, 0);
ssSetNumModes(S, 0);
ssSetNumNonsampledZCs(S, 0);
ssSetSimStateCompliance(S, USE_DEFAULT_SIM_STATE);
ssSetOptions(S, 0);
}
static void mdlInitializeSampleTimes(SimStruct *S)
{
ssSetSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME);
ssSetOffsetTime(S, 0, 0.0);
}
#define MDL_INITIALIZE_CONDITIONS /* Change to #undef to remove function */
#if defined(MDL_INITIALIZE_CONDITIONS)
static void mdlInitializeConditions(SimStruct *S)
{
}
#endif /* MDL_INITIALIZE_CONDITIONS */
#define MDL_START /* Change to #undef to remove function */
#if defined(MDL_START)
static void mdlStart(SimStruct *S)
{
}
#endif /* MDL_START */
static void mdlOutputs(SimStruct *S, int_T tid)
{
const real_T *u = (const real_T*) ssGetInputPortSignal(S,0);
real_T *y = ssGetOutputPortSignal(S,0);
y[0] = u[0];
}
#define MDL_UPDATE /* Change to #undef to remove function */
#if defined(MDL_UPDATE)
static void mdlUpdate(SimStruct *S, int_T tid)
{
}
#endif /* MDL_UPDATE */
#define MDL_DERIVATIVES /* Change to #undef to remove function */
#if defined(MDL_DERIVATIVES)
static void mdlDerivatives(SimStruct *S)
{
}
#endif /* MDL_DERIVATIVES */
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
对上述模板进行分析。为了更好地解释清楚,此处举一个例子:
static void mdlInitializeSizes(SimStruct S)
{
ssSetNumSFcnParams(S, 0); / 不含用户参数,设置为零 /
if (ssGetNumSFcnParams(S) != ssGetSFcnParamsCount(S)) {
return; / 若参数不匹配,Simulink 将发出警告 /
}
ssSetNumContStates(S, 2); / 系统有两个连续状态*/
ssSetNumDiscStates(S, 0); /* 系统无离散状态*/
if (!ssSetNumInputPorts(S, 1)) return; /* 如果输入端口数不为 1,则返回*/
/* S-functions 块只有一个输入端口,当需要多个输入时,必须使用 mux 模块把需要输入的信号合
成一个向量*/
ssSetInputPortWidth(S, 0, 2); /* 输入信号宽度为 2 /
ssSetInputPortDirectFeedThrough(S, 0, 1); / 设置馈通标志为 1 /
if (!ssSetNumOutputPorts(S, 1)) return; / 如果输出端口数不为 1,则返回*/
ssSetOutputPortWidth(S, 0, 2); /* 输出信号宽度为 2 /
ssSetNumSampleTimes(S, 1); / 1 个采样时间 /
ssSetNumRWork(S, 0); / 不使用工作向量 */
ssSetNumIWork(S, 0);
ssSetNumPWork(S, 0);
ssSetNumModes(S, 0);
ssSetNumNonsampledZCs(S, 0);
ssSetOptions(S, SS_OPTION_EXCEPTION_FREE_CODE);
}
static void mdlInitializeSampleTimes(SimStruct *S)
{
ssSetSampleTime(S, 0, CONTINUOUS_SAMPLE_TIME);
/*连续系统的采样时间设置为 0,等同于 ssSetSampleTime(S, 0, 0) */
ssSetOffsetTime(S, 0, 0.0);
}
#define MDL_INITIALIZE_CONDITIONS
static void mdlInitializeConditions(SimStruct S)
{
real_T x0 = ssGetContStates(S); / 获得指向连续状态的指针/
int_T lp;
for (lp=0;lp<2;lp++) {
x0++=0.0; / 各状态初值设置为 0 */
}
}
static void mdlOutputs(SimStruct *S, int_T tid)
{ /*获得指向输出向量、连续状态向量和输入端口的指针 */
real_T *y = ssGetOutputPortRealSignal(S,0);
real_T x = ssGetContStates(S);
InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
UNUSED_ARG(tid); / not used in single tasking mode /
/ y=Cx+Du 输出方程 */
y[0]=C[0][0]*x[0]+C[0][1]*x[1]+D[0][0]*U(0)+D[0][1]*U(1);
y[1]=C[1][0]*x[0]+C[1][1]*x[1]+D[1][0]*U(0)+D[1][1]*U(1);
}
#define MDL_DERIVATIVES
static void mdlDerivatives(SimStruct *S)
{
real_T dx = ssGetdX(S); / 获得指向状态导数向量的指针 */
real_T x = ssGetContStates(S);
InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
/ xdot=Ax+Bu 状态方程 */
dx[0]=A[0][0]*x[0]+A[0][1]*x[1]+B[0][0]*U(0)+B[0][1]*U(1);
dx[1]=A[1][0]*x[0]+A[1][1]*x[1]+B[1][0]*U(0)+B[1][1]*U(1);
}
static void mdlTerminate(SimStruct S)
{
UNUSED_ARG(S); / unused input argument */
}
#ifdef MATLAB_MEX_FILE /* 是否编译成 MEX 文件 ? /
#include “simulink.c” / 包含 MEX 文件的接口机制 /
#else
#include “cg_sfun.h” / 代码生成注册函数 */
#endif
似乎很难吧。However,在这个社会中好像万事万物都有后门可走:s-function builder
用户只需在 S-function Builder 界面中的相应位置写入所需的信息和代码即可。S-function Builder会自动生成 C MEX S-函数源文件。用户只要单击 Build 按钮,S-function Builder 就会自动编译,自动生成用户所需的 MEX 文件。
第一步:先在名称栏中定义s函数的名称,之后将上图中的离散状态量个数,离散状态初值,连续状态变量个数,连续状态初值等信息按需要填上;
第二步:设置数据属性选项卡中的设置输入、输出的数据类型、信号维数等信息,设置用户参数的名称、数据类型等。
第三步:在这调用所需的库文件
第四步:在 Outputs、Continues Derivatives 和 Discrete Update 选项卡中分别填入输出方程、连续状态方程、离散状态方程以及其它用户定制的代码。
第五步:单击build键 is ok.