Simulink之——S函数
在电气工程专业的毕业设计,硕士论文以及课程报告中都少不了simulink模型仿真。而除了直接使用simulink模型库中的元器件之外,还有一种比较看起来高大上的S函数法来建造属于自己的模型。今天打算简单的介绍一下simulink中的S-函数相关内容,主要是以下几点:
-
什么是S函数?
-
S-函数的工作方式
-
如何在模型中使用S函数
- 一个例子分析
一.什么是S函数?
S-函数是系统函数(System Function)的简称,在Simulink中用 非图形化的方式 来描述一个模块。一个完整的S-函数结构体系包含了 描述一个动态系统 所需要的全部能力。使用S-函数用户可以向Simulink模型中添加自己的模块,可以自由选择使用MATLAB、C、C++等语言来创建自己的模块。
二. S-函数的工作方式
若要创建属于自己的S-函数,必须先知道S-函数的工作方式,而要理解S-函数的工作方式,先得了解simulink仿真模型的过程。
Simulink 中的每个模块都有三个基本元素:输入向量、状态向量和输出向量。分别表示为 u , x 和 y 。它们的关系如下所示:
在 Simulink 模块的三个元素中,
状态向量是最
重要的
,也是三者之中最灵活的一个概念。在 Simulink 中状态向量可以分为
连续状态、离散状态或两者的结合
。输入、输出及状态的关系可以用状态方程描述:
Simulink 在仿真时,将上述方程对应不同的仿真阶段,它们分别是计算模块的输出、更新离散状态、计算连续状态的微分。在仿真开始和结束,还包括 初始化和结束仿真 两个阶段。在每个阶段,Simulink都 反复地调用 模块。
为了深入了解 S-函数的工作原理,还需了解一个概念:仿真循环(Simulation loop)。
一个仿真循环就是由仿真阶段按一定顺序组成的执行序列。对于每个模块,经过一次仿真循环就是一个仿真步长, 而在同一个仿真步长中, 模型中各模块的仿真按照事先排好的顺序依次执行。
在仿真开始时Simulink 首先对模型进行初始化,此阶段不属于仿真循环的部分。在所有模块都初始化后,模块进入仿真循环, 在仿真循环的每个阶段,Simulink 都要调用模块或者 S函数 。
在调用模型中的S函数时,Simulink会调用用户定义的S函数的例程来实现每个仿真阶段要完成的任务。这些任务包括:
一、 初始化 :仿真开始前,Simulink 在这个阶段初始化S函数,完成的主要工作包括:
1 、初始化包含S函数所有信息的结构体SimStruct;
2、确定输入输出端口的数目和大小;
3、确定模块的采样时间;
4、分配内存和 Sizes 数组。
二、 计算下一个采样时刻 。如果模型使用变步长求解器,那么就需要在当前仿真步长内确定下一个采样点的时间,也即下一个仿真步长的大小;
三、 计算输出 :计算所有输出端口的输出值。
四、 更新离散状态 :此例程在每个仿真步长处都要执行一次,为当前时间的仿真循环更新离散状态;
五、 数值积分 :这个阶段只有模块具有连续状态和非采样过零点时才会存在。如果S函数存在连续状态,Simulink 就在细化的小时间步长中调用S函数的输出 mdlOutputs和微分 (mdlDerivatives)例程。如果存在非采样过零点,Simulink将调用 S函数中的输出 (mdlOutputs)和过零检测(mdlZeroCrossngs)例程,以定位过零点。
三. 如何在模型中使用S函数?
在编写M文件的S函数时,可以使用M文件的
S函数模板 sfuntmpl.m
文件。该文件包含了所有的S函数的例程, 包含1个主函数和6个子函数。 在主函数程序使用一个多分支语句 (Switch-case)根据标志将执行流程转移到相应的例程函数。主函数的参数 Flag 标志值是由系统(Simulink 引擎)调用时给出的。
打开模板文件的方法由两种,用户可以在 MATLAB 命令窗口中键入:
>> edit sfuntmpl
或者双击 User-defined Function \S-function Examples\M-file S-functions\Leveal-1 M-fileS-functions1\
Leveal-1 M-file template
模块。
%主函数
function [sys,x0,str,ts] = sfuntmpl(t,x,u,flag)
switch flag,
case 0,
[sys,x0,str,ts]=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
error(['Unhandled flag = ',num2str(flag)]);
end % 主函数结束,下面是各个子函数,即各个仿真例程
% 初始化例程子函数:提供状态、输入、输出、采样时间数目和初始状态的值。
function [sys,x0,str,ts]=mdlInitializeSizes
sizes = simsizes; % 生成 sizes 数据结构
sizes.NumContStates = 0; % 连续状态数,缺省为 0
sizes.NumDiscStates = 0; % 离散状态数,缺省为 0
sizes.NumOutputs = 0; % 输出量个数,缺省为 0
sizes.NumInputs = 0; % 输入量个数,缺省为 0
sizes.DirFeedthrough = 1; % 有无直接馈入,有取 1,无取 0,缺省为 1
sizes.NumSampleTimes = 1; % 采样时间个数,至少取 1
sys = simsizes(sizes); % 返回 sizes 数据结构所包含的信息
x0 = []; % 设置初始状态的值
str = []; % 保留变量,置为空矩阵,请忽略
ts = [0 0]; % 采样时间:[采样周期 偏移量],采样时间取 0 表示为连续系统
% 计算导数例程子函数:
计算连续状态的导数
,用户需在此例程输入连续状态方程。
% 该子函数可以不存在。
function sys=mdlDerivatives(t,x,u)
sys = []; % sys 表示连续状态导数
% 状态更新例程子函数:计算离散状态的更新。
% 用户除了需在此输入离散状态方程外,还可以输入其它每个仿真步长都有必要执行的代码。
%
该子函数可以不存在。
function sys=mdlUpdate(t,x,u)
sys = []; % sys 表示下一个离散状态,即 x(k+1)
% 计算输出例程子函数: 计算模块输出。
该子函数必须存在
,用户在此输入系统的输出方程。
function sys=mdlOutputs(t,x,u)
sys = []; % sys 表示系统输出 y
% 计算下一个采样时间,
只有变采样时间系统才调用此仿真例程
。
function sys=mdlGetTimeOfNextVarHit(t,x,u)
sampleTime = 1; % 设置下一次的采样时间是 1s 以后
sys = t + sampleTime; % sys 表示下一个采样时间点
% 仿真结束调用的例程函数:
用户需在此输入结束仿真所需要的必要工作
。
function sys=mdlTerminate(t,x,u)
sys = [];
我们可以看到主函数包含 四个输出参数 :
sys 数组返回某个子函数,它的 含义随着调用子函数的不同而不同 ;
x0为所有状态的初始化向量;
str 是保留参数,总是一个空矩阵;
Ts 是返回系统采样时间。
主函数的四个输入参数分别是采样时间 t,状态 x,输入 u 和仿真流程控制标志变量 flag。
输入参数后面还可以
附加一系列用户仿真需要的参数
。编写用户自己的S函数时,应将函数名sfuntmpl 改为 S-function
模块中设置的函数名
。
可能大家已经发现一个
令人困惑
的问题:不论在哪个仿真阶段,例程子函数的返回变量都是 sys。要搞清楚这个问题,还要
回到 Simulink 如何调用S函数上来
。前面讲过,Simulink 在每个仿真步长的仿真循环中的每个仿真阶段都要调用S函数。在调用时,Simulink 不但根据所处的仿真阶段为 flag 传入不同的值,还会
为返回变量 sys 指定不同的角色
。即是说尽管是相同的 sys 变量,但在不同的仿真阶段其意义是不相同的,这种变化由 Simulink 自动完成。
四.一个例子分析
光说不练假把式,下面就具体举个例子。
对于一个积分器,状态方程为:
\dot{x} =uy=x
为了验证仿真结果,先用数学知识解出 y=\int_{0}^{t} u(t)dt+y(0)
当u(t)=2时,y=2t+y(0)
在这个例子中有一个输入,一个输出,一个连续状态变量。需要编写mdlDeruvatives子函数,目的是把状态变量x的导数通过sys返回。simulink仿真模型如下:
将MATLAB工作当前路径改为积分器m函数和模所在路径下,点击运行。此时会出现一个错误:告诉我们没有定义函数或者变量:'initial_state'
查看我们的S函数的m文件:
变量:'initial_state'是我们自己定义的一个变量,是一个额外的参数,这些参数必须在S函数的第一行的输入参数中列出。此时双击S函数模块,在参数表中写入初值y(0)=5。
这里只是举了一个很简单的例子,实际的课题研究中可能涉及到的研究对象要更加复杂,这里只是抛砖引玉,希望给初学者一些帮助。