SimulinkS-Function编程(C语言)与模块封装技术1.S-Function概念S

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Simulink S-Function 编程(C语言)与模块封装技术
1.S-Function概念
S-Function(System function)是Simulink模块的计算机语言描述。

可以用M、C/C++、Ada、Fortran 语言以MEX(Matlab Executable,MATLAB可执行文件,在Windows系统中就是其为DLL)文件的形式编写。

S-Function以特殊的方式与Simulink方程求解器交互。

这种交互和Simulink内建模块的做法非常相似。

S-Function模块可以是连续、离散或者混合系统。

通过S-Function,用户可以将自己的模块加入Simulink模型中。

从而可以实现用户自定义的算法或者与硬件设备交互等。

2.S-Function工作机制
2.1 Simulink模块的数学描述
Simulink模块包括一系列输入、状态和输出。

输出是采样时间、输入、模块状态的函数。

下面的方程描述了输入、输出和状态的数学关系。

2.2 仿真过程
Simulink模型的执行按下述几个步骤。

首先是初始化阶段。

在这一阶段Simulink将库模块集合到模型,传播宽度、数据类型和采样时间,评估模块参数,确定模块执行顺序,分配内存。

然后是仿真阶段。

此时Simulink进入一个仿真循环,循环的每次执行对应一个仿真步。

在每个仿真步,Simulink按初始化阶段确定的顺序执行各个模块。

对每个模块,Simulink计算模块在当前采样时间的状态、微分和输出。

这将持续到仿真结束。

图1描述了Simulink的仿真过程。

图1 Simulink执行仿真的步骤
2.3 S-function的回调(Callback)方法
S-function包括一系列的回调方法,用以执行每个仿真步骤所需的任务。

在一个模型的仿真过程中,每个仿真步骤,Simulink将调用各S-function的适当方法。

S-function执行的方法包括:
●初始化:在首次仿真循环中执行。

Simulink初始化S-function。

在这一步骤中Simulink
将:
-初始化SimStruct,这是一种Simulink结构,包含了S-function的信息。

-设置输入输出端口的个数和数据宽度。

-设置模块的采样次数。

-分配存储区域和数组长度。

●计算下一采样点:如果定义了一个可变采样步长的模块,这一步将计算下一次采样
点,也就是计算下一步长。

●计算在主要时间步中的输出:这一步结束之后,模块的输出端口在当前时间步是有
效的。

●更新主要时间步中的离散状态:所有的模块在该回调方法中,必须执行一次每次时
间步都要执行的活动,比如为下一次仿真循环更新离散状态。

●积分:这用于具有连续状态的或者(和)具有非采样过零的模型。

如果用户的
S-function具有连续状态,Simulink在最小采样步长调用S-function的输出和微分部分。

这也是Simulink之所以能计算S-function的状态。

如果用户S-function(仅针对C MEX)具有非采样过零,Simulink在最小采样步长调用S-function的输出和微分部分,这样可以确定过零点。

2.4 S-Function中几个主要的子函数
mdlInitializeSizes:用于获取输入端口和输出端口的数量、端口宽度、以及S-function 所需的任何其它对象(诸如状态数量)等有关信息。

mdlInitializeSampleTimes:设置S-function 的采样时间。

mdlStart:该函数在模型开始时执行且仅执行一次,像电机上电这种操作,在整个仿真过程中只需上一次电,就可以放在这里。

mdlStart:在每个采样时间步长内,Simulink计算块的输出。

使用SimStruct 的一个宏:InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs ( S,0 );来获取输入信号。

该宏返回一个向量的指针,必须使用*uPtrs[ i ] 来访问它。

使用了SimStruct 的一个宏:real_T *y = ssGetOutputPortRealSignal (S,0 );
来访问输出信号。

该宏返回一个包含了输出向量的指针。

S-function 使用int_T width = ssGetOutputPortWidth ( S,0 ) 来获取通过块传递的信号宽度。

最后,S-function 采用循环通过输入来计算输出。

mdlTerminate:执行仿真结束时的任务。

3.编写C语言的S函数
3.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实现的回调方法。

C MEX-file可以访问并修改Simulink内部用来存储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 MA TLAB_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。

注意:Simulink仿真时直接在适当的时间调用每个回调方法,利用C写的S-Function仿真步骤如图2所示:
图2 利用C写的S-Function执行步骤
3.2 利用S-Function模板创建DLL
1.新建一个文件夹,比如F:\test。

2.打开MA TLAB,选择当前目录使其指向第一步中新建文件夹,打开simulink,选择User-Defined Functions/S-Function Examples/C-files S-Function//Basic C-MEX Template,根据所定义模块的功能,将该自动生成的文件重命名,并存入你指定的文件夹中。

2
1
图3
3
图4
4
图5
5
图6
3.对图6所示的文件进行改写,首先将圈中的sfuntmpl_basic.c改一个名字,比如改为MyOn。

然后添加控制卡的.h和.lib文件。

(由于MA TLAB编译器寻找所需的.h和.lib文件是从当前文件夹找,所以,我们得事先把控制所包含的.h文件,.lib文件以及相关的dll放入MyOn文件所在文件夹中)
#include "adt8940a1.h"
#pragma comment(lib, "8940A1.lib")
然后在mdlStart(SimStruct *S)函数中添加
adt8940a1_initial(); //控制卡初始函数
write_bit(0,0,1);//给伺服电机上电
4.改写好MyOn.c文件后,将文件后缀改为cpp(因为我的编译器采用的是c++的语法对程序进行编译的)
5.在MA TLAB的Command Window中输入mex MyOn.cpp
如果之前没有进行编译器的选择,则选择一个编译器,我们这里选择VC++选项;
如果之前就选择过编译器,那么Command Window中会出现让用户再次输入的>>。

此时,我们打开MyOn.cpp文件所在的文件夹就可以看到出现了一个MyOn.dll文件。

6.在simulink窗口下,新建一个mdl文件(文件名为PowerOn),向其中添加一个S-Function 模块,双击模块,将其中的S-Function Name改为dll的文件名(不加后缀,也就是MyOn),然后将该mdl文件保存(注意:这里mdl的文件名不能与sfunction的名字相同,也就是说不能为MyOn,否则会出现命名冲突。


图7 名字需相同的地方
7. 点击菜单栏上的小三角形,进行仿真,可以听到吱吱的声音,表明电机已经上电。

S-Function 的功能执行成功!
但是,可以看到,在MA TLAB 的Command Window 中出现了四个警告:
Warning: The model 'Poweron' does not have continuous states, hence using the solver 'V ariableStepDiscrete' instead of the solver 'ode45' specified in the Configuration Parameters dialog.
Warning: Input port 1 of 'Poweron/S-Function' is not connected.
Warning: Output port 1 of 'Poweron/S-Function' is not connected.
Warning: Using a default value of 0.2 for maximum step size. The simulation step size will be limited to be less than this value.
对于第一个警告,电机菜单中的Simulation ,选择Configuration Parameter 。

将Solver 改为discrete ;对于第四个警告,也是在configuration Parameter 中改变Max Step Size 为0.1即可;
对于第二和第三个警告,因为利用模板生成的s-function 它默认有一个输入,一个输出,而这里不需要输入输出,所以,可在mdlInitializeSizes 函数中,将定义输入输出的语句
if (!ssSetNumInputPorts(S, 1)) return;
ssSetInputPortWidth(S, 0, 1);
ssSetInputPortRequiredContiguous(S, 0, true);
ssSetInputPortDirectFeedThrough(S, 0, 1);
if (!ssSetNumOutputPorts(S, 1)) return;
ssSetOutputPortWidth(S, 0, 1);
都去掉。

如果输入输出多于一个的话,比如,输入有两个端口,则修改以下三句
if (!ssSetNumInputPorts(S, 1)) return;
ssSetInputPortWidth(S, 0, 1);
ssSetInputPortRequiredContiguous(S, 0, true);
为:
int i;
if (!ssSetNumInputPorts(S, 2)) return;
for(i = 0;i < 2; i++)
{
ssSetInputPortWidth(S, i, DYNAMICALLY_SIZED);
ssSetInputPortDirectFeedThrough(S, i, 1);
}
其中:
void ssSetNumInputPorts(SimStruct *S, int_T nInputPorts)该函数的第一个参数为模块的一个数据结构(一般都不用改,为S),第二个参数nInputPorts指定模块有几个输入接口;
void ssSetInputPortWidth(SimStruct *S, int_T port, int_T width) 该函数的第一个参数为模块的一个数据结构(一般都不用改,为S),第二个参数为接口的编号(从0开始,比如,有两个接口的话,那么编号只能是0和1),第三个参数指定第二个参数所指定接口的数据宽度
void ssSetInputPortDirectFeedThrough(SimStruct *S, int_T port, int_T dirFeed) 该函数的第一个参数为模块的一个数据结构(一般都不用改,为S),其中第二个参数为接口的编号,第三个参数为用于指定接口的输入数据是否能在mdlOutputs或者mdlGetTimeOfNextV arHit 中调用,0表示不能调用,1表示能调用。

输出的调整可按输入的调整方法进行。

8.为了能让电机运动,我们还需要编写相关的运动模块,定义该模块为mov,即用mov替换掉图7中圈内部分。

将该模块存入名为move的mdl文件中。

参照1~7步,在mdlStart(SimStruct *S)函数中添加:
set_startv(0, 1, 10000);//设置初始速度,10000
set_speed (0, 1, 10000);//设置驱动速度,10000
pmove(0, 1, 20000);////定量驱动,20000个脉冲量
除了在程序中实现设置初始速度,驱动速度,驱动脉冲量等,我们也可以设置该模块具有三个输入接口,没有输出接口
根据第七部中的方法,在mdlInitializeSizes函数中,将
if (!ssSetNumInputPorts(S, 1)) return;
ssSetInputPortWidth(S, 0, 1);
ssSetInputPortRequiredContiguous(S, 0, true);
替换为:
int i;
if (!ssSetNumInputPorts(S, 3)) return;
for(i = 0;i < 3; i++)
{
ssSetInputPortWidth(S, i, DYNAMICALLY_SIZED);
ssSetInputPortDirectFeedThrough(S, i, 1);
}
定义输入接口为三个。


if (!ssSetNumOutputPorts(S,1)) return;
ssSetOutputPortWidth(S, 0, DYNAMICALLY_SIZED);
注释掉,从而使模块没有输出端口。

在mdlOutputs函数中,添加
InputRealPtrsType uPtrs = ssGetInputPortRealSignalPtrs(S,0);
long startv,speed, steps;
startv = *uPtrs[0];
speed = *uPtrs[1];
steps = *uPtrs[2];
set_startv(0, 1, starv);
set_speed(0,1, speed);
pmove(0,1,steps);
利用mex指令生成所需的DLL后,在S-Function中调用该模块时,可发现如图8所示,模块具有三个接口
图8 上电模块
三个接口没有任何标注,这样我们就不清楚道理那个接口该接起始速度、哪个接脉冲总量。

为此,我们可以在图8所示的模块上单击右键,然后选择Mask S-Function,在图9中填入圈中所示字符,
图9 Mask S-Function对话框
即可发现图8变为了图10所示的样子
图10 Run模块
这样,用户就知道该怎样连线了。

图11为利用该模块形成的mdl文件
图11 添加输入的Run模块
10. 想要关闭电机时,需要一个电机断电模块,定义该模块为stop,即用stop替换掉图7中圈内部分。

将该模块放入名为PowerOff的mdl文件中
同1~7步,在mdlStart(SimStruct *S)函数中添加:
write_bit(0,0,0);//给伺服断电
4.GUI界面
为了方便用户的使用,编写一个简单的GUI界面,并将上述三个模块整合到其中。

1.在MA TLAB的Command Window中输入guide,采用默认设置,点击ok;
2.在界面上添加三个按钮,将其分别命名为“伺服上电”、“伺服断电”、“运动”;
3.分别为按钮添加回调函数,以伺服上电为例:
(1)选中“伺服上电”按钮;
(2)右键选择V iew Callbacks/Callback;
(3)在函数中添加“sim(‘PowerOn’);”
在伺服断电回调函数中添加“sim('powerOff');”,在运动回调函数中添加“sim('move');”4.将文件存在F:\shukonghuatai\develop files。

点击运行即可。

问见界面如图12所示:
图12 GUI界面。

相关文档
最新文档