S函数的相关概念与写法,直接在帮助文件中搜:【MATLAB S-Functions Create custom blocks defined】、【S-Function Concepts】等
S函数模块可以从下图中拖出来:
图 1
其中S-Function是正宗的S函数模块,旁边还有一个S-Function builder是给新手用的,只要学会了S函数模块,S builder模块自然一看就懂。
使用S函数模块的步骤:①写S函数的.m文件, 并把m文件所在的文件夹加入搜索路径,如下图2所示。②在simulink中拖出S函数,并填上m文件的名字,如果有参数(下文会讲到),也把参数名填上,如下图3所示。然后就可以执行仿真了
图2
图3
下面是最关键的部分,如何写S函数:
写S函数的本质就是写一堆回调函数,C/C++程序员对回调函数应该很熟悉了,既然是回调函数,那写之前必须要知道形参和返回值的格式,这个格式从哪查?直接在matlab命令行执行: edit sfuntmpl,就直接打开了官方提供的模板,连格式都不用查了,直接仿照官方模板把函数体改改盖就行了,方便。
(1)S函数的总函数
把官方模板的代码拷到一个新文件里,并命名为自定义名称,我取的名字是oneOrderModule.m,然后把函数名也改成和文件名相同,我的是这样的:function [sys,x0,str,ts,simStateCompliance] = oneOrderModule(t,x,u,flag, T)
我与官方版不同的是,自己加了一个参数T,如果你还想添加更多参数,直接在形参表里添加就行了。添加的形参的实参值,来自于图3的第二个输入框,这个输入框可填常量,也可以填工作区变量名。
形参:①当前的仿真时间t,单位为秒,该参数可以用来描述变参数系统,例如你想实现【在t>2S时,把系统增益给改掉】这一功能,就可以通过判定t的值来实现;②x为状态列向量;③输入列向量u;④flag为当前状态机的第几步,例如实参送进来的flag=0代表S函数需要初始化,flag=1代表要更新连续状态
返回值:在不同的状态步下(也即flag不同时),返回值的意义是不同的,在模板文件的注释中都讲到了:
例如flag=0时,需要返回:输入输出状态变量等的大小SIZES、状态变量的初值X0等。。。
又如flag=3时,返回值sys代表输出向量Y,也即状态空间表达式第2式,Y=CX+DU
不同flag下返回值得写法,将在下面的各个函数中依次讲解
(2)系统初始化
当我么在函数中检测到flag=0时,意味着simulnk需要我们返回系统初始化的一些信息。
实际上,我们可以直接把flag=0时需要执行的代码直接写在switch-case中,但是为了使程序更清晰,我们也仿照官方模板,在case中调用初始化函数mdlInitializeSizes,我们把代码写在初始化函数中。
function [sys,x0,str,ts,simStateCompliance]=mdlInitializeSizes
sizes = simsizes;
sizes.NumContStates = 1;%连续状态向量的元素数目
sizes.NumDiscStates = 0;%离散状态向量的元素数目
sizes.NumOutputs = 2;%输出向量的元素数目
sizes.NumInputs = 1;%输入向量的元素数目
sizes.DirFeedthrough = 1;%输入是否直接馈通到输出
sizes.NumSampleTimes = 1; % 采样时间矩阵的行数(必须设为>=1),另外列数固定为2,无需设置
sys = simsizes(sizes);%把以上赋值好的结构赋给返回值
x0 = [0];%设置状态向量的初值
str = [];%保留参数,不用管
ts = [0 0];%采样时间设置,必须为mx2维度,其中m为上面sizes.NumSampleTimes的值
simStateCompliance = 'UnknownSimState';
此函数中,按照回调函数的要求,sys的返回要返回一些成员的尺寸大小,我们可以一个个为sys(1)、sys(2)。。等依次赋值,但我们不会这么做,因为官方模板为我们提供了一个辅助数据结构的实体simsizes,直接把他复制出来把成员值改好,再赋值给返回值sys即可。simsizes的成员有6个,其中有2个需要单独讲一下:
①直接馈通标志DirFeedthrough,这个东西实际上就是看看状态空间表达式第2式Y=CX+DU中的D矩阵是否为0矩阵,如果不是,那我们必须把馈通标志设为1。从回调函数来看,只要我们把DirFeedthrough设成了1,那么当flag=3时,系统会把t、u两个参数传进总函数oneOrderModule中,如果DirFeedthrough设成了0,那么当flag=3时,我们在oneOrderModule函数或者mdlOutputs函数中中将无法读到t的值(实际读出来总是0),也无法读到u的值(实际读出来总是一个只含Nan元素的向量)。
②采样时间矩阵的行数NumSampleTimes
这个东西有些复杂,从直观上看,它决定了【采样时间矩阵ts】的行数,ts矩阵是NumSampleTimes行2列的矩阵。ts的每一行均包含一个数据对:[ 采样时间 偏移量 ],这些数据对不是乱填的,可选就这么几种形式:
采样时间可以理解为采样周期,真正的采样时刻=n*周期+偏移量。
对于连续系统,采样时间应设为0,matlab也提供宏CONTINUOUS_SAMPLE_TIME,该宏的值=0。
对于固定步长的离散系统,可以直接设置采样间隔和偏移,形如:[ 0.1 0.02 ]
对于变步长的离散系统,可以设置为:[VARIABLE_SAMPLE_TIME, 0.0],这种参数需要同时把simulink求解器solver设置成变步长的。
继承前一模块的采样点,可以设置为:[INHERITED_SAMPLE_TIME, FIXED_IN_MINOR_STEP_OFFSET或0]。
更多知识点,可以搜索这几个宏名来学习。
(3)连续系统更新、离散系统更新
这两个函数分别为mdlDerivatives和mdlUpdate,如果需要支持自定义的参数,那么直接修改函数的形参表即可,在我的例子中,我附加了一个T参数,那么我的连续系统更新函数是这样的:
function sys=mdlDerivatives(t,x,u, T)
A = [-1/T];
B = [1/T];
sys = A*x+B*u;
如果系统中存在离散状态,那么就在mdlUpdate中写出离散状态空间表达式即可,如果没有离散状态,就直接sys返回空矩阵。
(4)输出函数
function sys=mdlOutputs(t,x,u)
C = [1
1];
D = [0
1];
sys = C * x + D * u;
%sys = [x t];
这个也没啥好说的,描述一下输出向量即可。不过提醒一下,如果使用了 u或t 参数,不要忘记把馈通标志置1,否则在本函数中收不到t和u的实参值。
这个函数描述了两个输出: y(1)=x,y(2)=x+u
下面看一下仿真结果:
和预想的一样,没毛病。