在结构体中组织相关的模块参数定义
如果您在模型中使用 MATLAB® 数值变量设置模块参数值,大型模型可能会累积很多变量,从而增加模型的维护工作,导致变量名称长度增加。
更好的做法是将这些参数值组织成结构体。每个结构体是一个变量,结构体中的每个字段存储一个数字参数值。您可以为结构体、子结构体和字段赋予有意义的名称,以指示每个值的目的。
使用结构体可以:
减少必须维护的工作区变量数。
避免工作区变量之间发生名称冲突。
您不能在同一个作用域(例如,基础工作区)中创建两个同名的变量。创建结构体时,您必须为每个字段提供一个名称,但不同的结构体可以包含同名的字段。因此,您可以将每个结构体和子结构体作为一个命名空间,防止同一作用域中的字段名称相互冲突或与其他变量名称冲突。
从逻辑上对模块参数值进行分组。例如,使用嵌套的结构体清楚地标识每个子系统或引用模型使用的参数值。
如果您使用封装参数或模型参量将参数值传递给系统的组件,可以使用结构体以减少必须维护的封装参数或模型参量的数量。您可以传递一个结构体变量,而不是传递多个变量。
有关创建和操作 MATLAB 结构体的基本信息,请参阅结构体。有关在模型中设置模块参数值的基本信息,请参阅设置模块参数值。
要使用结构体初始化总线信号,请参阅指定总线元素的初始条件。
创建并使用参数结构体
此示例说明如何在模型中创建并使用参数结构体。
示例模型 f14
使用基础工作区中的多个变量来设置模块参数值。例如,当您打开模型时,它会在基础工作区中创建变量 Zw
、Mw
和 Mq
。要将这些变量组织成一个结构体变量,请执行以下操作:
在命令提示符下,打开示例模型。
f14
在命令提示符下,创建参数结构体
myGains
。通过使用目标变量的值设置字段值。myGains.Zw = Zw; myGains.Mw = Mw; myGains.Mq = Mq;
在模型资源管理器中,在模型层次结构窗格中点击基础工作区。在目录窗格中,右键点击变量
Mq
并选择查找使用位置。在选择系统对话框中,点击节点 f14,然后点击确定。当提示更新图时,点击确定。
在目录窗格中,右键点击标有 Gain1 的模块所对应的行,然后选择属性。Gain1 模块对话框随即打开。
将增益参数的值从
Mq
更改为myGains.Mq
,然后点击确定。在目录窗格中,右键点击 Transfer Fcn.1 模块所对应的行,然后选择属性。
将分母系数参数的值从
[1,-Mq]
更改为[1,-myGains.Mq]
,然后点击确定。在模型层次结构窗格中,点击基础工作区。使用查找使用位置定位使用变量
Mw
和Zw
的模块。在模块对话框中,根据下表所示替换对变量名称的引用。变量名称 替换名称 Mw
myGains.Mw
Zw
myGains.Zw
清除旧变量。
clear Zw Mw Mq
现在,修改后的每个模块参数使用 myGains
结构体的一个字段。每个结构体字段的数值等于您清除的对应变量的值。
您可以迁移模型以使用单个参数结构体,而不是多个工作区变量。
将数据类型信息存储在字段值中
要使用结构体或结构体数组来组织使用非 double
数据类型的参数值,您可以在创建结构体时显式指定数据类型。当您创建结构体时,请使用定型的表达式(如 single(15.23)
)指定字段值。
myParams.Gain = single(15.23);
如果以后要更改字段值,您必须记得再次显式指定类型。如果不指定类型,字段值将使用 double
数据类型:
myParams.Gain = 15.23;
% The field 'Gain' now uses the data type 'double' instead of 'single'.
要保留类型指定,您可以使用下标赋值为字段指定新值:
% Assign value of type 'single'. myParams.Gain = single(15.23); % Assign new value while retaining type 'single'. myParams.Gain(:) = 11.79;
要匹配定点数据类型,请使用 fi
(Fixed-Point Designer) 对象设置字段值。
通过创建参数对象来控制字段数据类型和特征
Simulink.Parameter
对象允许您将模块参数的值与其数据类型分离。如果您使用参数对象存储结构体或结构体数组,则可以创建一个 Simulink.Bus
对象作为整个结构体的数据类型。
您可以使用总线对象和参数对象显式控制以下内容:
每个字段的数据类型。使用这种方法时,您不需要记得使用定型表达式或下标赋值来设置字段值。
每个字段的复/实性、维度和单位。
每个字段的最小值和最大值(如果该字段代表可调参数值)。
整个结构体的形状。结构体的形状是指字段的数量、名称和层次结构。
结构体在您从模型中生成的代码中的可调性。
创建一个参数结构体
myParams
。myParams = struct(... 'SubsystemA',struct(... 'Gain',15.23,... 'Offset',89,... 'Init',0.59),... 'SubsystemB',struct(... 'Coeffs',[5.32 7.99],... 'Offset',57,... 'Init1',1.76,... 'Init2',2.76)... );
使用函数
Simulink.Bus.createObject
创建代表结构体和子结构的Simulink.Bus
对象。Simulink.Bus.createObject(myParams)
因为
myParams
包含两个唯一子结构体,所以该函数创建三个Simulink.Bus
对象:一个名为slBus1
,代表父结构体myParams
;一个名为SubsystemA
,代表子结构体SubsystemA
;一个名为SubsystemB
,代表子结构体SubsystemB
。将总线对象
slBus1
重命名为myParamsType
。myParamsType = slBus1; clear slBus1
将结构体
myParams
存储在Simulink.Parameter
对象中。myParams = Simulink.Parameter(myParams);
参数对象的
Value
属性包含该结构体。将参数对象的数据类型设置为总线对象
myParamsType
。myParams.DataType = 'Bus: myParamsType';
打开类型编辑器以查看总线对象。
typeeditor
在表中,展开名为
SubsystemA
的总线对象。然后,根据下图设置数据类型。(可选)更改名为
SubsystemB
的总线对象的数据类型。
参数对象 myParams
存储参数结构体。该参数对象的数据类型为总线对象 myParamsType
。在进行仿真和代码生成之前,该参数对象会将字段值转换为您在总线对象中指定的数据类型。
要使用这些字段之一设置模块参数值,请指定表达式,例如 myParams.SubsystemB.Init1
。
要在命令提示符下访问字段值,请使用参数对象的 Value
属性。因为总线对象控制字段数据类型,所以您不需要使用定型表达式来设置字段值。
myParams.Value.SubsystemA.Gain = 12.79;
总线对象会严格控制字段特征和结构体的形状。例如,如果您将二元素字段 myParams.SubsystemB.Coeffs
的值设置为三元素数组,则当您设置模块参数值时,模型将产生错误。要更改字段的维度,请修改总线对象 SubsystemB
中的元素 Coeffs
。
要在创建总线对象之后操作它们,请参阅创建 Simulink 总线对象和保存 Simulink 总线对象。
将字段数据类型与信号数据类型匹配
假设您使用字段 myParams.SubsystemA.Gain
为 Gain 模块中的增益参数设置值。如果您希望该字段的数据类型与该模块的输出信号的数据类型匹配,则不能依赖上下文相关数据定型(请参阅Context-Sensitive Data Typing)。请考虑使用 Simulink.AliasType
或 Simulink.NumericType
对象来设置字段和信号的数据类型。如果您不使用数据类型对象,则每次更改信号的数据类型时,都必须记得更改字段的数据类型。
在 MATLAB 命令行窗口中,创建一个表示数据类型
single
的Simulink.AliasType
对象。myType = Simulink.AliasType; myType.BaseType = 'single';
在 Gain 模块对话框中,在信号属性选项卡上,将输出数据类型设置为
myType
。在 MATLAB 命令行窗口中,打开类型编辑器。
typeeditor
选择名为
SubsystemA
的总线对象。然后,将名为Gain
的元素的数据类型设置为myType
。
现在,Gain 模块的输出信号和结构体字段 myParams.SubsystemA.Gain
都使用您通过 myType
的 BaseType
属性指定的数据类型。
有关数据类型对象的详细信息,请参阅 Simulink.AliasType
和 Simulink.NumericType
。
管理结构体变量
要创建、修改和检查其值为结构体的变量,可以使用变量编辑器。有关详细信息,请参阅以交互方式修改结构体和数组变量。
通过创建嵌套结构体定义参数层次结构
要进一步组织模块参数值,可以创建一个嵌套的结构体层次结构。
例如,假设您在模型中创建了名为 SubsystemA
和 SubsystemB
的子系统。您使用变量(例如 Offset_SubsystemA
和 Offset_SubsystemB
)设置子系统中的模块参数值。
Gain_SubsystemA = 15.23; Offset_SubsystemA = 89; Init_SubsystemA = 0.59; Coeffs_SubsystemB = [5.32 7.99]; Offset_SubsystemB = 57; Init1_SubsystemB = 1.76; Init2_SubsystemB = 2.76;
创建一个参数结构体,其中每个子系统对应一个子结构体。使用现有变量的值设置字段值。
myParams = struct(... 'SubsystemA',struct(... 'Gain',Gain_SubsystemA,... 'Offset',Offset_SubsystemA,... 'Init',Init_SubsystemA),... 'SubsystemB',struct(... 'Coeffs',Coeffs_SubsystemB,... 'Offset',Offset_SubsystemB,... 'Init1',Init1_SubsystemB,... 'Init2',Init2_SubsystemB)... );
一个结构体变量 myParams
就包含了子系统中模块的所有参数信息。因为每个子结构体相当于一个命名空间,所以您可以多次定义 Offset
字段。
要使用子结构体 SubsystemB
中的 Offset
字段作为模块参数的值,请在模块对话框中以表达式 myParams.SubsystemB.Offset
的形式指定该参数值。
将多个参数结构体组成一个数组
要组织具有类似特征的参数结构体,您可以创建一个值为结构体数组的变量。这种方式有助于您参数化包含一种算法的多个实例的模型,例如使用模型参量的库子系统或引用模型。
假设您在模型中创建两个相同的子系统。
假设每个子系统中的模块需要三个数值来设置参数值。为这两个结构体创建一个数组以存储这些值。
myParams(1).Gain = 15.23; myParams(1).Offset = 89; myParams(1).Init = 0.59; myParams(2).Gain = 11.93; myParams(2).Offset = 57; myParams(2).Init = 2.76;
该数组中的每个结构体存储一个子系统的三个参数值。
要设置其中一个子系统中的模块参数值,请指定一个表达式,引用该数组中一个结构体的一个字段。例如,使用表达式 myParams(2).Init
。
组织可重用组件和迭代算法的参数值
您还可以在 For Each Subsystem 模块中对结构体数组进行分区。当一个模型重复执行某算法(例如,对某个向量信号迭代执行该算法)时,使用这种方法有助于您组织工作区变量。有关示例,请参阅Repeat an Algorithm Using a For-Each Subsystem。
如果您使用模型参量为引用模型的多个实例指定不同的参量值,则您可以使用结构体数组来组织模型参量值。在引用模型工作区中,创建一个结构体变量并将模型配置为使用该结构体作为模型参量。使用结构体的字段设置模型中的模块参数值。然后,在基础工作区或父模型所链接到的数据字典中创建一个结构体数组。在父模型中,使用该数组中的每个结构体作为一个 Model 模块中的模型参量的值。该数组中的每个结构体存储引用模型的一个实例的参数值。
模型 sldemo_mdlref_datamngt
包含封装的引用模型 sldemo_mdlref_counter_datamngt
的三个实例(封装的 Model 模块)。基础工作区变量 IC1
、IC2
、Param1
和 Param2
是值为结构体的 Simulink.Parameter
对象。父模型使用这些变量设置 Model 模块上的封装参数值。由于 IC1
与 IC2
、Param1
与 Param2
在结构上相同,所以您可以将这四个结构体组合成两个结构体数组。
打开父模型示例。
openExample('sldemo_mdlref_datamngt')
该模型在基础工作区中创建四个
Simulink.Parameter
对象。打开示例引用模型。
sldemo_mdlref_counter_datamngt
模型工作区定义两个值为结构体的模型参量
CounterICs
和CounterParams
。模型中的模块使用这些结构体的字段设置参数值。在模型
sldemo_mdlref_datamngt
中,打开模型数据编辑器(在建模选项卡上,点击模型数据编辑器)。在模型数据编辑器中,检查参数选项卡。在模型中,点击其中一个 Model 模块。
模型数据编辑器突出显示与所选 Model 模块上两个封装参数对应的行。该模块使用封装参数来设置由引用模型
sldemo_mdlref_counter_datamngt
定义的两个模型参量的值。每个 Model 模块使用基础工作区中四个参数对象的不同组合来设置参量值。在模型数据编辑器的值列中,点击其中一个单元格开始编辑相应的封装参数(例如
IC1
)的值。在参数值的旁边,点击操作按钮 ,然后选择打开。将打开该参数对象的属性对话框。在属性对话框中,在值框的旁边,点击操作按钮,然后选择打开变量编辑器。
变量编辑器显示参数对象存储一个结构体。
Param2
和IC2
中的结构体与Param1
和IC1
中的结构体具有相同的字段,但字段值不同。在命令提示符下,将这四个参数对象组合成两个值为结构体数组的参数对象。
% Create a new parameter object by copying Param1. Param = Param1.copy; % Use the structure in Param2 as the second structure in the new object. Param.Value(2) = Param2.Value; % The value of Param is now an array of two structures. % Delete the old objects Param1 and Param2. clear Param1 Param2 % Create a new parameter object by copying IC1. % Use the structure in IC2 as the second structure in the new object. IC = IC1.copy; IC.Value(2) = IC2.Value; clear IC1 IC2
在父模型中,在模型数据编辑器中,使用值列根据表替换封装参数的值
旧值 新值 Param1
Param(1)
IC1
IC(1)
Param2
Param(2)
IC2
IC(2)
每个 Model 模块使用数组 IC
中的一个结构体来设置模型参量 CounterICs
的值。类似地,每个模块使用 Param
中的一个结构体来设置 CounterParams
的值。
强制结构体数组中的一致性
一个结构体数组中的所有结构体都必须具有相同的字段层次结构。层次结构中的每个字段必须在整个数组中具有相同的特征。您可以使用参数对象和总线对象来强制结构体之间保持这种一致性。
要使用参数对象来表示参数结构体数组,请将对象的值设置为结构体数组:
% Create array of structures. myParams(1).Gain = 15.23; myParams(1).Offset = 89; myParams(1).Init = 0.59; myParams(2).Gain = 11.93; myParams(2).Offset = 57; myParams(2).Init = 2.76; % Create bus object. Simulink.Bus.createObject(myParams); myParamsType = slBus1; clear slBus1 % Create parameter object and set data type. myParams = Simulink.Parameter(myParams); myParams.DataType = 'Bus: myParamsType';
要使用这些字段之一设置模块参数值,请指定表达式,例如 myParams(2).Offset
。
要在命令提示符下访问字段值,请使用参数对象的 Value
属性。
myParams.Value(2).Offset = 129;
创建常量值信号结构体
您可以使用结构体在 Constant 模块中创建一个能够传输多个数值常量的总线。有关详细信息,请参阅 Constant。有关总线的信息,请参阅合成接口规范。
迁移到参数结构体之前的注意事项
迁移模型以使用参数结构体之前,需要找出目标模型和其他模型中使用您要替换的变量的所有模块。
例如,假设一个模型中有两个模块使用工作区变量
myVar
。如果您创建具有myVar
字段的结构体myParams
,并且只将其中一个模块的参数值设置为myParams.myVar
,另一个模块继续使用变量myVar
。如果您删除myVar
,模型会生成错误,因为另一个模块还需要这个删除的变量。要找出使用某个变量的所有模块,请执行以下操作:
打开可能使用该变量的所有模型。如果这些模型在一个模型引用层次结构中,您可以只打开顶层模型。
在模型数据编辑器或模型资源管理器的目录窗格中,右键点击该变量并选择查找使用位置。模型资源管理器将显示使用该变量的所有模块。
您只能在打开的模型中查看是否使用了该变量。在迁移到参数结构体之前,请打开可能使用目标变量的所有模型。有关如何确定模型中是否使用某个变量的详细信息,请参阅查找使用特定变量的模块。
也可以避免删除
myVar
。但是,如果您更改myParams.myVar
结构体字段的值,还必须记得更改myVar
的值以保持匹配。您可以将多个单独的变量或参数对象(例如
Simulink.Parameter
)组合成一个结构体,存储在单个变量或参数对象中(要组合参数对象,请参阅将现有参数对象组合成一个结构体)。但是,生成的变量或对象作为一个实体存在。因此,您不能为该结构体中的各个字段应用不同的代码生成设置(如存储类)。
将现有参数对象组合成一个结构体
当您使用参数对象设置模块参数值时(例如,为了应用存储类),要将这些对象组合成一个结构体,请执行以下操作:
创建一个 MATLAB 结构体并将其存储在某个变量中。要设置字段值,请使用每个现有参数对象存储的参数值。
将该变量转换为参数对象。创建一个
Simulink.Bus
对象并将其作为参数对象的数据类型(请参阅通过创建参数对象来控制字段数据类型和特征)。选择要对生成的参数对象应用的存储类。您只能选择一个存储类,它将应用于整个结构体。
将参数元数据(例如,现有参数对象的
Min
和Max
属性)传输给总线对象中Simulink.BusElement
对象的对应属性。
例如,假设您有三个单独的参数对象。
coeff = Simulink.Parameter(17.5); coeff.Min = 14.33; coeff.DataType = 'single'; coeff.StorageClass = 'ExportedGlobal'; init = Simulink.Parameter(0.00938); init.Min = -0.005; init.Max = 0.103; init.DataType = 'single'; init.StorageClass = 'Model default'; offset = Simulink.Parameter(199); offset.DataType = 'uint8'; offset.StorageClass = 'ExportedGlobal';
创建结构体变量。
myParams.coeff = coeff.Value; myParams.init = init.Value; myParams.offset = offset.Value;
将该变量转换为参数对象。
myParams = Simulink.Parameter(myParams);
创建一个总线对象并将其作为参数对象的数据类型。
Simulink.Bus.createObject(myParams.Value); paramsDT = copy(slBus1); myParams.DataType = 'Bus: paramsDT';
将元数据从旧参数对象传输给总线对象中的总线元素。
% coeff paramsDT.Elements(1).Min = coeff.Min; paramsDT.Elements(1).DataType = coeff.DataType; % init paramsDT.Elements(2).Min = init.Min; paramsDT.Elements(2).Max = init.Max; paramsDT.Elements(2).DataType = init.DataType; % offset paramsDT.Elements(3).DataType = offset.DataType;
为了帮助您编写执行此传输操作的脚本,您可以使用
properties
函数查找总线元素和旧参数对象都具有的公共属性。要列出结构体字段以便对它们进行迭代,请使用fieldnames
函数。将存储类应用于参数对象。
myParams.StorageClass = 'ExportedGlobal';
现在,您可以使用 myParams
(而不是旧参数对象)的字段来设置模块参数值。
生成的代码中的参数结构体
您可以配置参数结构体,使其以结构体和结构体数组的形式出现在生成的代码中。有关使用参数结构体生成代码的信息,请参阅Organize Data into Structures in Generated Code (Simulink Coder)。
有关参数结构体的限制
一个结构体数组中的所有结构体都必须具有相同的字段层次结构。层次结构中的每个字段必须在整个数组中具有相同的特征:
字段名称
数值数据类型,如
single
或int32
。复/实性
维度
假设您定义一个数组,其中包含两个结构体。
paramStructArray = ... [struct('sensor1',int16(7),'sensor2',single(9.23)) ... struct('sensor1',int32(9),'sensor2',single(11.71))];
您不能在模块参数中使用任何这些字段,因为字段
sensor1
在每个结构体中使用不同的数据类型。参数结构体不支持在生成的代码中使用上下文相关数据定型。如果代码中的参数结构体可调,则该结构体的字段使用您通过定型表达式或
Simulink.Bus
对象指定的数值数据类型。如果您不使用定型表达式或Simulink.Bus
对象,该结构体的字段将使用double
数据类型。如果结构体或其任何子结构体的字段具有空值(设置为
[]
),则代码生成器不会保留该结构体的可调性。
为查找表打包共享断点和表数据
当您在查找表模块之间共享数据时,请考虑使用 Simulink.LookupTable
和 Simulink.Breakpoint
对象来存储数据和对数据进行分组,而不是使用结构体。这种方法通过清楚地将数据标识为查找表的一部分,并将断点数据与表数据显式关联,从而提高了模型的可读性。请参阅为查找表打包共享断点和表数据。
根据现有 C 代码中的结构体类型创建参数结构体
您可以创建符合现有 C 代码定义的 struct
类型定义的参数结构体。使用此方法可以:
将现有 C 代码替换为 Simulink® 模型。
集成现有 C 代码以便在 Simulink 中进行仿真(例如,通过使用代码继承工具)。有关示例,请参阅Integrate C Function Whose Arguments Are Pointers to Structures。
生成可与现有 C 代码一起编译成一个应用程序的 C 代码 (Simulink Coder™)。有关示例,请参阅Exchange Structured and Enumerated Data Between Generated and External Code (Embedded Coder)。
在 MATLAB 中,可将参数结构体存储在参数对象中,并使用总线对象作为数据类型(请参阅通过创建参数对象来控制字段数据类型和特征)。要根据您的 C 代码 struct
类型创建总线对象,请使用 Simulink.importExternalCTypes
函数。