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

文章目录

  • S函数
    • S函数类型
    • S函数的组成及执行顺序
    • 编写S函数
      • Level1 M S函数
      • Level2 M S函数
      • C Mex S函数
        • mdlInitializeSizes
        • C Mex 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内建类型
0 SS_DOUBLE double
1 SS_SINGLE single
2 SS_INT8 int8
3 SS_UINT8 uint8
4 SS_INT16 int16
5 SS_UINT16 uint16
6 SS_INT32 int32
7 SS_UINT32 uint32
8 SS_BOOLEAN boolean

使用方法:
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
mex sfun_c_filter.c

然后建立S-Function模块,并封装Mask,将封装的Paramters与S-Function中的参数设置成一致的
[Simulink] 从S函数到模块代码生成_第1张图片
[Simulink] 从S函数到模块代码生成_第2张图片
建立Simulink模型如下:
[Simulink] 从S函数到模块代码生成_第3张图片
如果这时选择编译的话,会报错,如下:
[Simulink] 从S函数到模块代码生成_第4张图片
这里,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*/
		%  = (% - %)*% + %;
		% = ;
	%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"

你可能感兴趣的:(Simulink,嵌入式系统,Simulink,TLC,S函数)