[Simulink] 从S函数到模块代码生成

整理自《Simulink仿真及代码生成技术入门到精通》

S函数

当Simulink默认提供的模块不能够满足用户的需求时,用户可以通过S函数打造自己的模块,实现自定义的算法或期望的动作。

S函数类型

S函数的类型,按照所支持的功能分类,可以分为

  • Level 1 —— 编写简单的数学算法用来仿真
  • Level 2 —— 所编写的算法需要传递多个输入输出端口且每个端口数据都是多维矩阵时,需要使用该类型

除了仿真时对端口数据类型的要求之外,这两类S函数还有一个区别

  • Level 1的S函数不支持代码生成
  • Level 2的S函数支持代码生成,但如果需要生成代码,需要给S函数编写同名的tlc文件

编写支持目标硬件外设寄存器配置的驱动模块时,C Mex S函数是一个不错的选择

S函数的组成及执行顺序

S函数由几个子方法构成

  • 初始化mdlInitializeSizes()
  • 采样时间设定mdlInitializeSampleTimes()
  • 系统输出mdlOutputs()
  • 仿真结束前终止方法mdlTerminate()
  • mdlRTW()
    其中,加粗部分为S函数能正常仿真的必要方法。

编写S函数

Level1 M S函数

Level1 M S函数支持简单的MATLAB接口及少数S函数的API,是结构简单、功能最少的S函数
<后期补充吧>

Level2 M S函数

<后期补充吧>

C Mex S函数

使用C语言编写S函数成为C Mex S函数,C语言编写的S函数可执行文件为mex文件,是MATLAB环境下的动态链接可执行文件。
C Mex S函数中模块属性的设置和获取,以及数据结构的访问和操作都需要C Mex宏函数来实现。

C Mex S函数的编写:

定义宏

#define S_FUNCTION_NAME  demo
#define S_FUNCTION_LEVEL 2

包含头文件如果需要使用Simluink内建的固定点数据类型,应该包括fixedpoint.h

#include "simstruc.h"

C Mex S函数的各个子方法
其中,mdlRTW方法比较特别,负责传递模块参数到rtw文件中,以便TLC文件在代码生成阶段使用RTW中的记录。
注意:这个的各个子方法都是static子函数,其含义不是指存储方式,而是指函数的作用于仅局限于本文件,而不会被其他S函数所调用

static void mdlInitializeSizes(SimStruct *S){}
static void mdlInitializeSampleTimes(SimStruct *S){}
static void mdlOutputs(SimStruct *S,int_T tid){}
static void mdlTerminate(SimStruct *S){}
static void mdlRTW(SimStruct *S){}

结尾部分

#ifdef MATLAB_MEX_FILE
#include"Simulink.c"//用于仿真
#else
#include"cg_sfun.h"//用于RTW代码生成(非内嵌的S函数)
#endif
mdlInitializeSizes

定义S函数块的基本特性,包括采样时间、连续或离散状态的初始条件以及sizes数组

SimStruct宏函数名作用
ssSetNumSFcnParams设定模块参数个数,第2个参数表示参数个数
ssGetNumSFcnParams获取模块参数的个数
ssGetSFcnParamsCount获取S函数实际拥有的参数个数
ssSetNumInputPorts设置输入端口的个数
ssSetInputPortWidth设置某个输入端口的位数,端口号从0开始,第3个参数是维度
ssSetInputPortDirectFeedThrough设置某个输入端口是狗直接馈入,通过第2个参数制定端口号,第3个参数1表示存在直接馈入,0则相反
ssSetNumOutputPorts设置输出端口的个数
ssSetOutputPortWidth设置某个输出端口的位数,端口号从0开始,第3个参数是维度
ssSetNumContStates设置连续状态变量个数,第2个参数表示连续状态变量个数
ssSetNumDiscStates设置离散状态变量个数,第2个参数表示离散状态变量个数
ssSetNumSampleTimes设置采样时间的个数,第2个参数表示采样时间的个数
ssSetOptions设置S函数的选项
ssSetInputPortDataType设置输入端口的类型

这里说一下ssSetInputPortDataType,默认情况下端口的数据类型为double,对应到C Mex S函数的SimStruct数据结构为SS_DOUBLE

数据类型ID号SimStruct数据类型宏Simulink内建类型
0SS_DOUBLEdouble
1SS_SINGLEsingle
2SS_INT8int8
3SS_UINT8uint8
4SS_INT16int16
5SS_UINT16uint16
6SS_INT32int32
7SS_UINT32uint32
8SS_BOOLEANboolean

使用方法:
ssSetInputPortDataType(S,0, SS_UINT32)

自定义数据类型
一般情况下,自定义数据类型可以通过M语言在workspace中创建Simulink.NumericType和Simulink.AliasType。

% 创建Simulink Numeric Type
% Simulink.NumericType默认是double类型
MyDataType = Simulink.NumericType;
% 设置这个变量是一个类型定义的别名
MyDataType.IsAlias = 1;

之后在C Mex S函数的mdlInitializeSizes子方法中注册,并设置到输入输出端口或工作向量数据类型中去:

C Mex S函数的实例

用一个滤波器实例,其数学模型表述如下:
Y ( t ) = ( U ( t ) − Y ( t − 1 ) ) × L c + Y ( t − 1 ) Y(t) = (U(t) - Y(t-1)) \times L_c + Y(t-1) Y(t)=(U(t)Y(t1))×Lc+Y(t1)

  • U ( t ) U(t) U(t)表示当前采样时刻的输入
  • Y ( t ) Y(t) Y(t)表示当前采样时刻的输出
  • Y ( t − 1 ) Y(t-1) Y(t1)表示的上一个采样时刻的输出值
#define S_FUNCTION_NAME sfun_c_filter
#define S_FUNCTION_LEVEL 2

#include "simstruc.h"

#define COEF_IDX 0
#define COEF(S) mxGetScalar(ssGetSFcnParam(S,COEF_IDX))

/*Function:mdlInitializeSizes*/
static void mdlInitializeSizes(SimStruct *S)
{
	ssSetNumSFcnParams(S,1);
	if (ssGetNumSFcnParams(S)!= ssGetSFcnParamsCount(S)){
		return;
	}
	
	if (!ssSetNumInputPorts(S,1)) return;
	ssSetInputPortWidth(S,0,DYNAMICALLY_SIZED);
	ssSetInputPortDirectFeedThrough(S,0,1);
	
	if (!ssSetNumOutputPorts(S,1)) return;
	ssSetOutputPortWidth(S,0,DYNAMICALLY_SIZED);
	
	ssSetNumDWork(S,1);
	ssSetDWorkWidth(S,0,DYNAMICALLY_SIZED);
	
	ssSetNumSampleTimes(S,1);
	
	ssSetSimStateCompliance(S,USE_DEFAULT_SIM_STATE);
	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 mdlInitializeConditions(SimStruct *S){
	real_T *x = (real_T *) ssGetDWork(S,0);
	x[0] = 0.0;
}

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);
	real_T *x = (real_T *) ssGetDWork(S,0);
	real_T Lc = COEF(S);
	for (i=0;i< width;i++){
		y[i] = (*uPtrs[i] - x[i]) * Lc +x[i];
	}
	for(i=0;i<width;i++){
		x[i] = y[i];
	}
}

static void mdlTerminate(SimStruct *S){}

#ifdef MATLAB_MEX_FILE
#include "simulink.c"
#else
#include "cg_sfun.h"
#endif

mex sfun_c_filter.c

然后建立S-Function模块,并封装Mask,将封装的Paramters与S-Function中的参数设置成一致的
在这里插入图片描述
在这里插入图片描述
建立Simulink模型如下:
在这里插入图片描述
如果这时选择编译的话,会报错,如下:
在这里插入图片描述
这里,Nonline S Function就是指不具有TLC文件,不支持代码生成的S函数。因此会报错。
这里提供两种内联方法:

  • Full inlined 完全内联 ——在TLC的Output子方法中实现具体的算法,明确给出输入/输出关系
  • wrapper inlined 封装内联 —— 在TLC的Output子方法中不是实现具体算法代码,而是规定输入/输出端口变量如何调用已经存在的C代码。

为C MEX S函数配置TLC
在配置TLC文件之前,需要先在sfun_c_filter.c中加入mdlRTW()函数。

void mdlRTW(SimStruct *S)
{
	/*Get Parameters*/
	real_T c_coef = COEF(S);
	/*Write parameter into rtw file*/
	if (!ssWriteRTWParamSettings(S,1,
			SSWRITE_VALUE_DTYPE_NUM, "r_coef", &c_coef, DTINFO(SS_DOUBLE, COMPLEX_NO)))
			return;	
}

模块TLC编写

%implements sfun_c_filter "C"
%% Function :blockTypeSetup
%% Purpose: add some macro defines.
%function BlockTypeSetup(block,system) void

%endfunction

%% Function :Start
%% Purpose: these code will appear at model.c initialization function
%function Start(block,system) Output

%endfunction

%% Function :Outputs
%% Purpose: these code will appear at model.c step function
%function Outputs(block,system) Output

	%assign t_coef = SFcnParamSettings.r_coef
	%assign rollVars = ["U","Y","DWork"]
	%roll sigIdx = RollRegions, lcv = RollThreshold, block, "Roller" ,rollVars
		%assign u = LibBlockInputSignal(0,"",lcv, sigIdx)
		%assign y = LibBlockOutputSignal(0,"",lcv, sigIdx)
		%assign x = LibBlockDWork(dwork,"",lcv, sigIdx)
		/*Calculate the filter result*/
		% <y> = (%<u> - %<x>)*%<t_coef> + %<x>;
		%<x> = <y>;
	%endroll
%endfunction

【未完】

包含头文件的部分,C Mex S函数使用用户自定义数据类型有两种方法:

  • C Mex S函数中使用外部结构体数据类型,保存在head.h文件中。
typedef struct{
	signed long Alpha;
	signed long Beta;
}AlphaBeta;

然后将S函数的头文件部分修改为:

#include "simstruc.h"
#include "head.h"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值