设计和创建自定义模块
设置工作环境以设计和创建自定义模块
此示例打开目录,其中包含本主题所需的以下文件。
ex_customsat_lib.slx
sldemo_customsat.slx
msfuntmpl.m
custom_sat.m
custom_sat_final.m
customsat_callback.m
customsat_plotcallback.m
custom_sat_plot.m
plotsat.m
如何设计自定义模块
一般情况下,可按照以下过程设计自定义模块:
假设您要创建一个自定义饱和模块,以便基于模块参数或输入信号的值来限制信号的上限和下限。在此模块的第二个版本中,您希望能在仿真完成后绘制饱和界限。以下教程将指导您如何设计这些模块。库 ex_customsat_lib
中包含两个版本的自定义饱和模块。
示例模型 sldemo_customsat
使用此模块的基本版本。
定义自定义模块行为
首先定义自定义模块的功能和限制。在此示例中,模块支持以下功能:
打开和关闭饱和上限或下限。
通过模块参数设置上限和/或下限。
使用输入信号设置上限和/或下限。
它还存在以下限制:
达到饱和的输入信号必须为标量。
输入信号和饱和界限的数据类型都必须为双精度。
不需要生成代码。
确定自定义模块的类型
根据自定义模块的功能,模块实现需要支持以下功能:
多个输入端口
相对简单的算法
没有连续或离散系统状态
因此,本教程使用 2 级 MATLAB® S-Function 实现自定义模块。MATLAB S-Function 支持多个输入,而且因为算法很简单,所以更新图或仿真模型时不会产生很大的开销。要了解与其他类型的自定义模块相比 MATLAB S-Function 提供了哪些不同的功能,请参阅比较自定义模块功能。
参数化 MATLAB S-Function
首先定义 S-Function 参数。此示例需要四个参数:
第一个参数指示如何设置饱和上限。此界限可以关闭、通过模块参数进行设置,或者通过输入信号进行设置。
第二个参数是饱和上限的值。仅当通过模块参数设置饱和上限时才使用此值。如果使用了此参数,您应该能够在仿真过程中更改此参数的值,也就是说,此参数是可调的。
第三个参数指示如何设置饱和下限。此界限可以关闭、通过模块参数进行设置,或者通过输入信号进行设置。
第四个参数是饱和下限的值。仅当通过模块参数设置饱和下限时才使用此值。与饱和上限一样,此参数在使用时也是可调的。
第一个和第三个 S-Function 参数代表模式,它们必须转换为 S-Function 能够识别的值。因此,可为饱和上限和下限模式定义以下值:
1
表示关闭饱和界限。2
表示通过模块参数设置饱和界限。3
表示通过输入信号设置饱和界限。
编写 MATLAB S-Function
定义 S-Function 参数和功能后,即可编写 S-Function。可以将模板 msfuntmpl.m
作为起始点来编写 2 级 MATLAB S-Function。您可以在文件 custom_sat.m
中找到自定义饱和模块的完整版本。在继续本教程之前,请打开此文件。
此 S-Function 将修改 S-Function 模板,如下所示:
setup
函数基于为饱和上限和下限模式输入的值来初始化输入端口数。如果通过输入信号设置饱和界限,此方法将为模块添加输入端口。然后,setup
方法指示有四个 S-Function 参数并设置参数可调性。最后,此方法注册在仿真过程中使用的 S-Function 方法。function setup(block) % The Simulink engine passes an instance of the Simulink.MSFcnRunTimeBlock % class to the setup method in the input argument "block". This is known as % the S-function block's run-time object. % Register original number of input ports based on the S-function % parameter values try % Wrap in a try/catch, in case no S-function parameters are entered lowMode = block.DialogPrm(1).Data; upMode = block.DialogPrm(3).Data; numInPorts = 1 + isequal(lowMode,3) + isequal(upMode,3); catch numInPorts=1; end % try/catch block.NumInputPorts = numInPorts; block.NumOutputPorts = 1; % Setup port properties to be inherited or dynamic block.SetPreCompInpPortInfoToDynamic; block.SetPreCompOutPortInfoToDynamic; % Override input port properties block.InputPort(1).DatatypeID = 0; % double block.InputPort(1).Complexity = 'Real'; % Override output port properties block.OutputPort(1).DatatypeID = 0; % double block.OutputPort(1).Complexity = 'Real'; % Register parameters. In order: % -- If the upper bound is off (1) or on and set via a block parameter (2) % or input signal (3) % -- The upper limit value. Should be empty if the upper limit is off or % set via an input signal % -- If the lower bound is off (1) or on and set via a block parameter (2) % or input signal (3) % -- The lower limit value. Should be empty if the lower limit is off or % set via an input signal block.NumDialogPrms = 4; block.DialogPrmsTunable = {'Nontunable','Tunable','Nontunable', ... 'Tunable'}; % Register continuous sample times [0 offset] block.SampleTimes = [0 0]; %% ----------------------------------------------------------------- %% Options %% ----------------------------------------------------------------- % Specify if Accelerator should use TLC or call back into % MATLAB script block.SetAccelRunOnTLC(false); %% ----------------------------------------------------------------- %% Register methods called during update diagram/compilation %% ----------------------------------------------------------------- block.RegBlockMethod('CheckParameters', @CheckPrms); block.RegBlockMethod('ProcessParameters', @ProcessPrms); block.RegBlockMethod('PostPropagationSetup', @DoPostPropSetup); block.RegBlockMethod('Outputs', @Outputs); block.RegBlockMethod('Terminate', @Terminate); %end setup function
CheckParameters
方法验证在 2 级 MATLAB S-Function 模块中输入的值。function CheckPrms(block) lowMode = block.DialogPrm(1).Data; lowVal = block.DialogPrm(2).Data; upMode = block.DialogPrm(3).Data; upVal = block.DialogPrm(4).Data; % The first and third dialog parameters must have values of 1-3 if ~any(upMode == [1 2 3]); error('The first dialog parameter must be a value of 1, 2, or 3'); end if ~any(lowMode == [1 2 3]); error('The first dialog parameter must be a value of 1, 2, or 3'); end % If the upper or lower bound is specified via a dialog, make sure there % is a specified bound. Also, check that the value is of type double if isequal(upMode,2), if isempty(upVal), error('Enter a value for the upper saturation limit.'); end if ~strcmp(class(upVal), 'double') error('The upper saturation limit must be of type double.'); end end if isequal(lowMode,2), if isempty(lowVal), error('Enter a value for the lower saturation limit.'); end if ~strcmp(class(lowVal), 'double') error('The lower saturation limit must be of type double.'); end end % If a lower and upper limit are specified, make sure the specified % limits are compatible. if isequal(upMode,2) && isequal(lowMode,2), if lowVal >= upVal, error('The lower bound must be less than the upper bound.'); end end %end CheckPrms function
ProcessParameters
和PostPropagationSetup
方法处理 S-Function 参数调优。function ProcessPrms(block) %% Update run time parameters block.AutoUpdateRuntimePrms; %end ProcessPrms function function DoPostPropSetup(block) %% Register all tunable parameters as runtime parameters. block.AutoRegRuntimePrms; %end DoPostPropSetup function
Outputs
方法基于 S-Function 参数设置和输入信号来计算模块的输出。function Outputs(block) lowMode = block.DialogPrm(1).Data; upMode = block.DialogPrm(3).Data; sigVal = block.InputPort(1).Data; lowPortNum = 2; % Initialize potential input number for lower saturation limit % Check upper saturation limit if isequal(upMode,2), % Set via a block parameter upVal = block.RuntimePrm(2).Data; elseif isequal(upMode,3), % Set via an input port upVal = block.InputPort(2).Data; lowPortNum = 3; % Move lower boundary down one port number else upVal = inf; end % Check lower saturation limit if isequal(lowMode,2), % Set via a block parameter lowVal = block.RuntimePrm(1).Data; elseif isequal(lowMode,3), % Set via an input port lowVal = block.InputPort(lowPortNum).Data; else lowVal = -inf; end % Assign new value to signal if sigVal > upVal, sigVal = upVal; elseif sigVal < lowVal, sigVal=lowVal; end block.OutputPort(1).Data = sigVal; %end Outputs function
将自定义模块放置到库中
库允许您与其他用户共享您的自定义模块,轻松更新自定义模块副本的功能,以及将适用于某个特定工程的模块收集到一起。如果您的自定义模块需要总线作为接口,您可以通过在数据字典中创建总线对象并将字典附加到库来与库用户共享总线对象(请参阅Link Data Dictionary to Custom Libraries)。
此示例将自定义饱和模块放置到一个库中。
在 Simulink® 编辑器中,在仿真选项卡中,选择新建 > 库。
从 User-Defined Functions 库中,将一个 2 级 MATLAB S-Function 模块拖放到您的新库中。
用文件名
saturation_lib
保存您的库。双击该模块以打开其 Function 的“模块参数”对话框。
在 S-Function 名称字段中,输入 S-Function 的名称。例如,输入
custom_sat
。在参数字段中输入2,-1,2,1
。点击确定。
至此,您已创建一个可与其他用户共享的自定义饱和模块。
通过添加自定义用户界面,您可以让此模块更方便使用。
为自定义模块添加用户界面
您可以利用 Simulink 的封装功能,为自定义模块创建一个模块对话框。模块封装还允许您添加端口标签,以指示哪些端口对应于输入信号和饱和界限。
打开包含您刚创建的自定义模块的库
saturation_lib
。右键点击 2 级 MATLAB S-Function 模块,然后选择封装 > 创建掩膜。
在图标绘制命令框的图标和端口窗格中,输入
port_label('input',1,'uSig')
,然后点击应用。此命令将默认端口标记为未饱和的输入信号。
在参数和对话框窗格中,添加四个参数,它们对应于四个 S-Function 参数。对于每个新参数,将一个弹出式控件或编辑控件拖放到对话框部分,如下表所示。将每个参数拖放到“参数”组中。
类型 提示 名称 计算 可调 弹出选项 回调 popup
上边界: upMode
✓ 无限制
输入限制作为参数
使用输入参数进行限制
customsat_callback('upperbound_callback', gcb)
edit
上限: upVal
✓ ✓ 不适用 customsat_callback('upperparam_callback', gcb)
类型 提示 名称 计算 可调 弹出选项 回调 popup
下边界: lowMode
✓ 无限制
输入限制作为参数
使用输入参数进行限制
customsat_callback('lowerbound_callback', gcb)
edit
下限: lowVal
✓ ✓ 不适用 customsat_callback('lowerparam_callback', gcb)
MATLAB S-Function 脚本
custom_sat_final.m
包含封装参数回调。在工作文件夹中查找custom_sat_final.m
,以定义此示例中的回调。此 MATLAB 脚本有两个输入参量。第一个输入参量是一个字符向量,指示哪个封装参数调用了回调。第二个输入参量是关联的 2 级 MATLAB S-Function 模块的句柄。下图显示了在封装编辑器中完成的参数和对话框窗格。
在初始化窗格中,选中允许库模块修改其内容复选框。此设置允许 S-Function 更改模块上的端口数。
在文档窗格中执行以下操作:
在封装类型字段中,输入
Customized Saturation
在封装描述字段中,输入
Limit the input signal to an upper and lower saturation value set either through a block parameter or input signal.
点击确定。
要将 S-Function 参数映射到封装参数,请右键点击 2 级 MATLAB S-Function 模块,然后选择封装 > 查看封装内部。
将 S-Function 名称字段更改为
custom_sat_final
,并将参数字段更改为lowMode,lowVal,upMode,upVal
。下图显示更改之后的 Function 的“模块参数”对话框。
点击确定。保存并关闭库,以退出编辑模式。
重新打开库,然后双击自定义饱和模块,以打开封装参数对话框。
要创建更复杂的用户界面,可在封装的模块上放置一个 MATLAB 图形用户界面。模块 OpenFcn
调用 MATLAB 图形用户界面,它通过调用 set_param
基于用户界面上的设置来修改 S-Function 模块参数。
编写封装回调
函数 customsat_callback.m
包含自定义饱和模块封装参数对话框的封装回调代码。此函数通过调用 feval
来调用与每个封装参数对应的局部函数。
下面的局部函数基于选择的饱和上限模式来控制饱和上限字段的可见性。回调首先通过使用属性名称 MaskValues
调用 get_param
来获取所有封装参数的值。如果回调只需要一个封装参数的值,它可以使用具体的封装参数名称(例如 get_param(block,'upMode')
)来调用 get_param
。因为此示例需要两个封装参数值,所以它使用 MaskValues
属性以减少对 get_param
的调用。
然后,回调通过使用属性名称 MaskVisbilities
调用 get_param
来获取封装参数的可见性。此调用返回一个字符向量元胞数组,指示每个封装参数的可见性。回调基于选择的饱和上限模式更改封装可见性的值,然后更新端口标签文本。
最后,回调使用 set_param
命令更新模块的 MaskDisplay
属性,以标记模块的输入端口。
function customsat_callback(action,block) % CUSTOMSAT_CALLBACK contains callbacks for custom saturation block % Copyright 2003-2007 The MathWorks, Inc. %% Use function handle to call appropriate callback feval(action,block) %% Upper bound callback function upperbound_callback(block) vals = get_param(block,'MaskValues'); vis = get_param(block,'MaskVisibilities'); portStr = {'port_label(''input'',1,''uSig'')'}; switch vals{1} case 'No limit' set_param(block,'MaskVisibilities',[vis(1);{'off'};vis(3:4)]); case 'Enter limit as parameter' set_param(block,'MaskVisibilities',[vis(1);{'on'};vis(3:4)]); case 'Limit using input signal' set_param(block,'MaskVisibilities',[vis(1);{'off'};vis(3:4)]); portStr = [portStr;{'port_label(''input'',2,''up'')'}]; end if strcmp(vals{3},'Limit using input signal'), portStr = [portStr;{['port_label(''input'',',num2str(length(portStr)+1), ... ',''low'')']}]; end set_param(block,'MaskDisplay',char(portStr));
最后一次调用 set_param
会在 MATLAB S-Function custom_sat.m
中调用 setup
函数。因此,可以修改 setup
函数,以便基于封装参数值而不是基于 S-Function 参数值来设置输入端口数。对 setup
函数的此类更改可使 2 级 MATLAB S-Function 模块上的端口数与封装参数对话框中显示的值保持一致。
修改后的 MATLAB S-Function custom_sat_final.m
包含下面的新 setup
函数。如果您是按照本教程中的步骤进行操作的,请打开此文件。
%% Function: setup =================================================== function setup(block) % Register original number of ports based on settings in Mask Dialog ud = getPortVisibility(block); numInPorts = 1 + isequal(ud(1),3) + isequal(ud(2),3); block.NumInputPorts = numInPorts; block.NumOutputPorts = 1; % Setup port properties to be inherited or dynamic block.SetPreCompInpPortInfoToDynamic; block.SetPreCompOutPortInfoToDynamic; % Override input port properties block.InputPort(1).DatatypeID = 0; % double block.InputPort(1).Complexity = 'Real'; % Override output port properties block.OutputPort(1).DatatypeID = 0; % double block.OutputPort(1).Complexity = 'Real'; % Register parameters. In order: % -- If the upper bound is off (1) or on and set via a block parameter (2) % or input signal (3) % -- The upper limit value. Should be empty if the upper limit is off or % set via an input signal % -- If the lower bound is off (1) or on and set via a block parameter (2) % or input signal (3) % -- The lower limit value. Should be empty if the lower limit is off or % set via an input signal block.NumDialogPrms = 4; block.DialogPrmsTunable = {'Nontunable','Tunable','Nontunable','Tunable'}; % Register continuous sample times [0 offset] block.SampleTimes = [0 0]; %% ----------------------------------------------------------------- %% Options %% ----------------------------------------------------------------- % Specify if Accelerator should use TLC or call back into % MATLAB script block.SetAccelRunOnTLC(false); %% ----------------------------------------------------------------- %% Register methods called during update diagram/compilation %% ----------------------------------------------------------------- block.RegBlockMethod('CheckParameters', @CheckPrms); block.RegBlockMethod('ProcessParameters', @ProcessPrms); block.RegBlockMethod('PostPropagationSetup', @DoPostPropSetup); block.RegBlockMethod('Outputs', @Outputs); block.RegBlockMethod('Terminate', @Terminate); %endfunction
custom_sat_final.m
中的局部函数 getPortVisibility
使用饱和界限模式构造一个标志,该标志被传递回 setup
函数。setup
函数使用此标志确定必需的输入端口数。%% Function: Get Port Visibilities ======================================= function ud = getPortVisibility(block) ud = [0 0]; vals = get_param(block.BlockHandle,'MaskValues'); switch vals{1} case 'No limit' ud(2) = 1; case 'Enter limit as parameter' ud(2) = 2; case 'Limit using input signal' ud(2) = 3; end switch vals{3} case 'No limit' ud(1) = 1; case 'Enter limit as parameter' ud(1) = 2; case 'Limit using input signal' ud(1) = 3; end
使用模块回调添加模块功能
ex_customsat_lib
中的 User-Defined Saturation with Plotting 模块使用模块回调为原来的自定义饱和模块添加功能。此模块提供了一个选项,可在仿真结束时绘制饱和界限。以下步骤说明如何修改原来的自定义饱和模块以创建这个新模块。
在封装参数对话框中添加一个复选框,用于打开和关闭绘图选项。
右键点击
saturation_lib
中的 2 级 MATLAB S-Function 模块,然后选择封装 + 创建掩膜。在封装编辑器的参数窗格中,添加具有以下属性的第五个封装参数。
提示 名称 类型 可调 类型选项 回调 绘制饱和界限 plotcheck
复选框 否 不适用 customsat_callback('plotsaturation',gcb)
点击确定。
为新的复选框编写回调。此回调初始化一个结构体,以便在仿真过程中将饱和界限值存储到 2 级 MATLAB S-Function 模块
UserData
中。MATLAB 脚本customsat_plotcallback.m
中包含这个新回调以及旧回调的各修改版本,以处理新的封装参数。如果您是按照本示例中的步骤进行操作的,请打开customsat_plotcallback.m
并复制其中的局部函数,以覆盖customsat_callback.m
中原有的局部函数。%% Plotting checkbox callback function plotsaturation(block) % Reinitialize the block's userdata vals = get_param(block,'MaskValues'); ud = struct('time',[],'upBound',[],'upVal',[],'lowBound',[],'lowVal',[]); if strcmp(vals{1},'No limit'), ud.upBound = 'off'; else ud.upBound = 'on'; end if strcmp(vals{3},'No limit'), ud.lowBound = 'off'; else ud.lowBound = 'on'; end set_param(gcb,'UserData',ud);
如果适用,请更新 MATLAB S-Function
Outputs
方法以存储饱和界限,如在新的 MATLAB S-Functioncustom_sat_plot.m
中那样。如果您是按照本示例中的步骤进行操作的,请复制custom_sat_plot.m
中的Outputs
方法,以覆盖custom_sat_final.m
中原有的Outputs
方法。%% Function: Outputs =================================================== function Outputs(block) lowMode = block.DialogPrm(1).Data; upMode = block.DialogPrm(3).Data; sigVal = block.InputPort(1).Data; vals = get_param(block.BlockHandle,'MaskValues'); plotFlag = vals{5}; lowPortNum = 2; % Check upper saturation limit if isequal(upMode,2) upVal = block.RuntimePrm(2).Data; elseif isequal(upMode,3) upVal = block.InputPort(2).Data; lowPortNum = 3; % Move lower boundary down one port number else upVal = inf; end % Check lower saturation limit if isequal(lowMode,2), lowVal = block.RuntimePrm(1).Data; elseif isequal(lowMode,3) lowVal = block.InputPort(lowPortNum).Data; else lowVal = -inf; end % Use userdata to store limits, if plotFlag is on if strcmp(plotFlag,'on'); ud = get_param(block.BlockHandle,'UserData'); ud.lowVal = [ud.lowVal;lowVal]; ud.upVal = [ud.upVal;upVal]; ud.time = [ud.time;block.CurrentTime]; set_param(block.BlockHandle,'UserData',ud) end % Assign new value to signal if sigVal > upVal, sigVal = upVal; elseif sigVal < lowVal, sigVal=lowVal; end block.OutputPort(1).Data = sigVal; %endfunction
编写函数
plotsat.m
以绘制饱和界限。此函数提取 2 级 MATLAB S-Function 模块的句柄,并使用此句柄检索模块的UserData
。如果您是按照本教程中的步骤进行操作的,请将plotsat.m
保存到您的工作文件夹中。function plotSat(block) % PLOTSAT contains the plotting routine for custom_sat_plot % This routine is called by the S-function block's StopFcn. ud = get_param(block,'UserData'); fig=[]; if ~isempty(ud.time) if strcmp(ud.upBound,'on') fig = figure; plot(ud.time,ud.upVal,'r'); hold on end if strcmp(ud.lowBound,'on') if isempty(fig), fig = figure; end plot(ud.time,ud.lowVal,'b'); end if ~isempty(fig) title('Upper bound in red. Lower bound in blue.') end % Reinitialize userdata ud.upVal=[]; ud.lowVal=[]; ud.time = []; set_param(block,'UserData',ud); end
右键点击 2 级 MATLAB S-Function 模块,然后选择属性。“模块属性”对话框随即打开。在回调窗格中,修改
StopFcn
以调用绘图回调,如下图所示,然后点击确定。