Main Content

本页的翻译已过时。点击此处可查看最新英文版本。

在结构体中组织相关的模块参数定义

如果您在模型中使用 MATLAB® 数值变量设置模块参数值,大型模型可能会累积很多变量,从而增加模型的维护工作,导致变量名称长度增加。

更好的做法是将这些参数值组织成结构体。每个结构体是一个变量,结构体中的每个字段存储一个数字参数值。您可以为结构体、子结构体和字段赋予有意义的名称,以指示每个值的目的。

使用结构体可以:

  • 减少必须维护的工作区变量数。

  • 避免工作区变量之间发生名称冲突。

    您不能在同一个作用域(例如,基础工作区)中创建两个同名的变量。创建结构体时,您必须为每个字段提供一个名称,但不同的结构体可以包含同名的字段。因此,您可以将每个结构体和子结构体作为一个命名空间,防止同一作用域中的字段名称相互冲突或与其他变量名称冲突。

  • 从逻辑上对模块参数值进行分组。例如,使用嵌套的结构体清楚地标识每个子系统或引用模型使用的参数值。

如果您使用封装参数或模型参数将参数值传递给系统的组件,可以使用结构体以减少必须维护的封装参数或模型参数的数量。您可以传递一个结构体变量,而不是传递多个变量。

有关创建和操作 MATLAB 结构体的基本信息,请参阅结构体。有关在模型中设置模块参数值的基本信息,请参阅设置模块参数值

要使用结构体初始化总线信号,请参阅指定总线信号的初始条件

创建并使用参数结构体

此示例说明如何在模型中创建并使用参数结构体。

示例模型 f14 使用基础工作区中的多个变量来设置模块参数值。例如,当您打开模型时,它会在基础工作区中创建变量 ZwMwMq。要将这些变量组织成一个结构体变量,请执行以下操作:

  1. 在命令提示符下,打开示例模型。

    f14

  2. 在命令提示符下,创建参数结构体 myGains。通过使用目标变量的值设置字段值。

    myGains.Zw = Zw;
    myGains.Mw = Mw;
    myGains.Mq = Mq;

  3. 在模型资源管理器中,在 Model Hierarchy 窗格中点击 Base Workspace。在 Contents 窗格中,右键点击变量 Mq 并选择 Find Where Used

  4. Select a system 对话框中,点击节点 f14,然后点击 OK。当提示更新图时,点击 OK

  5. Contents 窗格中,右键点击标有 Gain1 的模块所对应的行,然后选择 Properties。Gain1 模块对话框随即打开。

  6. Gain 参数的值从 Mq 更改为 myGains.Mq,然后点击 OK

  7. Contents 窗格中,右键点击 Transfer Fcn.1 模块所对应的行,然后选择 Properties

  8. Denominator coefficients 参数的值从 [1,-Mq] 更改为 [1,-myGains.Mq],然后点击 OK

  9. Model Hierarchy 窗格中,点击 Base Workspace。使用 Find Where Used 定位使用变量 MwZw 的模块。在模块对话框中,根据下表所示替换对变量名称的引用。

    变量名称替换名称
    MwmyGains.Mw
    ZwmyGains.Zw

  10. 清除旧变量。

    clear Zw Mw Mq

现在,修改后的每个模块参数使用 myGains 结构体的一个字段。每个结构体字段的数值等于您清除的对应变量的值。

您可以迁移模型以使用单个参数结构体,而不是多个工作区变量。有关示例,请参阅Migration to Structure Parameters

将数据类型信息存储在字段值中

要使用结构体或结构体数组来组织使用非 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 对象作为整个结构体的数据类型。

您可以使用总线对象和参数对象显式控制以下内容:

  • 每个字段的数据类型。使用这种方法时,您不需要记得使用类型化表达式或下标赋值来设置字段值。

  • 每个字段的复/实性、维度和单位。

  • 每个字段的最小值和最大值(如果该字段代表可调参数值)。

  • 整个结构体的形状。结构体的形状是指字段的数量、名称和层次结构。

  • 结构体在您从模型中生成的代码中的可调性。

  1. 创建一个参数结构体 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)...
    );

  2. 使用函数 Simulink.Bus.createObject 创建代表结构体和子结构的 Simulink.Bus 对象。

    Simulink.Bus.createObject(myParams)
    

    因为 myParams 包含两个唯一子结构体,所以该函数创建三个 Simulink.Bus 对象:一个名为 slBus1,代表父结构体 myParams;一个名为 SubsystemA,代表子结构体 SubsystemA;一个名为 SubsystemB,代表子结构体 SubsystemB

  3. 将总线对象 slBus1 重命名为 myParamsType

    myParamsType = slBus1;
    clear slBus1

  4. 将结构体 myParams 存储在 Simulink.Parameter 对象中。

    myParams = Simulink.Parameter(myParams);

    参数对象的 Value 属性包含该结构体。

  5. 将参数对象的数据类型设置为总线对象 myParamsType

    myParams.DataType = 'Bus: myParamsType';

  6. 打开总线编辑器以查看总线对象。

    buseditor

  7. Model Hierarchy 窗格中,点击 SubsystemA 节点。在 Contents 窗格中,根据下图设置字段数据类型。

  8. (可选)设置子结构体 SubsystemB 的字段数据类型。

参数对象 myParams 存储参数结构体。该参数对象的数据类型为总线对象 myParamsType。在进行仿真和代码生成之前,该参数对象会将字段值转换为您在总线对象中指定的数据类型。

要使用这些字段之一设置模块参数值,请指定表达式,例如 myParams.SubsystemB.Init1

要在命令提示符下访问字段值,请使用参数对象的 Value 属性。因为总线对象控制字段数据类型,所以您不需要使用类型化表达式来设置字段值。

myParams.Value.SubsystemA.Gain = 12.79;

总线对象会严格控制字段特征和结构体的形状。例如,如果您将二元素字段 myParams.SubsystemB.Coeffs 的值设置为三元素数组,则当您设置模块参数值时,模型将产生错误。要更改字段的维度,请修改总线对象 SubsystemB 中的元素 Coeffs

要在创建总线对象之后操作它们,请参阅创建并指定 Simulink.Bus 对象保存 Simulink.Bus 对象

将字段数据类型与信号数据类型匹配

假设您使用字段 myParams.SubsystemA.GainGain 模块中的 Gain 参数设置值。如果您希望该字段的数据类型与该模块的输出信号的数据类型匹配,则不能依赖上下文相关数据类型指定(请参阅Context-Sensitive Data Typing)。请考虑使用 Simulink.AliasTypeSimulink.NumericType 对象来设置字段和信号的数据类型。如果您不使用数据类型对象,则每次更改信号的数据类型时,都必须记得更改字段的数据类型。

  1. 在命令提示符下创建一个 Simulink.AliasType 对象,代表数据类型 single

    myType = Simulink.AliasType;
    myType.BaseType = 'single';

  2. Gain 模块对话框中,在 Signal Attributes 选项卡上,将 Output data type 设置为 myType

  3. 在命令提示符下,打开总线编辑器。

    buseditor

  4. Model Hierarchy 窗格中,选择总线对象 SubsystemA。在 Contents 窗格中,将字段 Gain 的数据类型设置为 myType

现在,Gain 模块的输出信号和结构体字段 myParams.SubsystemA.Gain 都使用您通过 myTypeBaseType 属性指定的数据类型。

有关数据类型对象的详细信息,请参阅 Simulink.AliasTypeSimulink.NumericType

管理结构体变量

要创建、修改和检查其值为结构体的变量,可以使用 Variable Editor。有关详细信息,请参阅以交互方式修改结构体和数组变量

通过创建嵌套结构体定义参数层次结构

要进一步组织模块参数值,可以创建一个嵌套的结构体层次结构。

例如,假设您在模型中创建了名为 SubsystemASubsystemB 的子系统。您使用变量(例如 Offset_SubsystemAOffset_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 模块)。基础工作区变量 IC1IC2Param1Param2 是值为结构体的 Simulink.Parameter 对象。父模型使用这些变量设置 Model 模块上的封装参数值。由于 IC1IC2Param1Param2 在结构上相同,所以您可以将这四个结构体组合成两个结构体数组。

  1. 打开父模型示例。

    sldemo_mdlref_datamngt

    该模型在基础工作区中创建四个 Simulink.Parameter 对象。

  2. 打开示例引用模型。

    sldemo_mdlref_counter_datamngt

    模型工作区定义两个值为结构体的模型参数 CounterICsCounterParams。模型中的模块使用这些结构体的字段设置参数值。

  3. 在模型 sldemo_mdlref_datamngt 中,打开模型数据编辑器(在 Modeling 选项卡上,点击 Model Data Editor)。在模型数据编辑器中,检查 Parameters 选项卡。

  4. 在模型中,点击其中一个 Model 模块。

    模型数据编辑器突出显示与所选 Model 模块上两个封装参数对应的行。该模块使用封装参数来设置由引用模型 sldemo_mdlref_counter_datamngt 定义的两个模型参数的值。每个 Model 模块使用基础工作区中四个参数对象的不同组合来设置参数值。

  5. 在模型数据编辑器的 Value 列中,点击其中一个单元格开始编辑相应的封装参数(例如 IC1)的值。在参数值的旁边,点击操作按钮 ,然后选择 Open。将打开该参数对象的属性对话框。

  6. 在属性对话框中,在 Value 框的旁边,点击操作按钮,然后选择 Open Variable Editor

    Variable Editor 显示参数对象存储一个结构体。Param2IC2 中的结构体与 Param1IC1 中的结构体具有相同的字段,但字段值不同。

  7. 在命令提示符下,将这四个参数对象组合成两个值为结构体数组的参数对象。

    % 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

  8. 在父模型中,在模型数据编辑器中,使用 Value 列根据表替换封装参数的值

    旧值新值
    Param1Param(1)
    IC1IC(1)
    Param2Param(2)
    IC2IC(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,模型会生成错误,因为另一个模块还需要这个删除的变量。

    要找出使用某个变量的所有模块,请执行以下操作:

    1. 打开可能使用该变量的所有模型。如果这些模型在一个模型引用层次结构中,您可以只打开顶层模型。

    2. 在模型数据编辑器或模型资源管理器的 Contents 窗格中,右键点击该变量并选择 Find Where Used。模型资源管理器将显示使用该变量的所有模块。

    您只能在打开的模型中查看是否使用了该变量。在迁移到参数结构体之前,请打开可能使用目标变量的所有模型。有关如何确定模型中是否使用某个变量的详细信息,请参阅查找使用特定变量的模块

    也可以避免删除 myVar。但是,如果您更改 myParams.myVar 结构体字段的值,还必须记得更改 myVar 的值以保持匹配。

  • 您可以将多个单独的变量或参数对象(例如 Simulink.Parameter)组合成一个结构体,存储在单个变量或参数对象中(要组合参数对象,请参阅将现有参数对象组合成一个结构体)。但是,生成的变量或对象作为一个实体存在。因此,您不能为该结构体中的各个字段应用不同的代码生成设置(如存储类)。

将现有参数对象组合成一个结构体

当您使用参数对象设置模块参数值时(例如,为了应用存储类),要将这些对象组合成一个结构体,请执行以下操作:

  1. 创建一个 MATLAB 结构体并将其存储在某个变量中。要设置字段值,请使用每个现有参数对象存储的参数值。

  2. 将该变量转换为参数对象。创建一个 Simulink.Bus 对象并将其作为参数对象的数据类型(请参阅通过创建参数对象来控制字段数据类型和特征)。

  3. 选择要对生成的参数对象应用的存储类。您只能选择一个存储类,它将应用于整个结构体。

  4. 将参数元数据(例如,现有参数对象的 MinMax 属性)传输给总线对象中 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';

  1. 创建结构体变量。

    myParams.coeff = coeff.Value;
    myParams.init = init.Value;
    myParams.offset = offset.Value;

  2. 将该变量转换为参数对象。

    myParams = Simulink.Parameter(myParams);

  3. 创建一个总线对象并将其作为参数对象的数据类型。

    Simulink.Bus.createObject(myParams.Value);
    paramsDT = copy(slBus1);
    
    myParams.DataType = 'Bus: paramsDT';

  4. 将元数据从旧参数对象传输给总线对象中的总线元素。

    % 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 函数。

  5. 将存储类应用于参数对象。

    myParams.StorageClass = 'ExportedGlobal';

现在,您可以使用 myParams(而不是旧参数对象)的字段来设置模块参数值。

生成的代码中的参数结构体

您可以配置参数结构体,使其以结构体和结构体数组的形式出现在生成的代码中。有关使用参数结构体生成代码的信息,请参阅Organize Data into Structures in Generated Code (Simulink Coder)

有关参数结构体的限制

  • 用于设置模块参数的字段值必须为数值或枚举类型。字段的值可以为实数或复数标量、向量或多维数组。

  • 如果一个结构体的任何字段的值是多维数组,则您不能在仿真过程中调整任何字段的值。

  • 一个结构体数组中的所有结构体都必须具有相同的字段层次结构。层次结构中的每个字段必须在整个数组中具有相同的特征:

    • 字段名称

    • 数值数据类型,如 singleint32

    • Complexity

    • Dimensions

    假设您定义一个数组,其中包含两个结构体。

    paramStructArray = ...
    [struct('sensor1',int16(7),'sensor2',single(9.23)) ...
     struct('sensor1',int32(9),'sensor2',single(11.71))];

    您不能在模块参数中使用任何这些字段,因为字段 sensor1 在每个结构体中使用不同的数据类型。

  • 参数结构体不支持在生成的代码中根据上下文确定数据类型。如果代码中的参数结构体可调,则该结构体的字段使用您通过类型化表达式或 Simulink.Bus 对象指定的数值数据类型。如果您不使用类型化表达式或 Simulink.Bus 对象,该结构体的字段将使用 double 数据类型。

为查找表打包共享断点和表数据

当您在查找表模块之间共享数据时,请考虑使用 Simulink.LookupTableSimulink.Breakpoint 对象来存储数据和对数据进行分组,而不是使用结构体。这种方法通过清楚地将数据标识为查找表的一部分,并将断点数据与表数据显式关联,从而提高了模型的可读性。请参阅为查找表打包共享断点和表数据

根据现有 C 代码中的结构体类型创建参数结构体

您可以创建符合现有 C 代码定义的 struct 类型定义的参数结构体。使用此方法可以:

在 MATLAB 中,可将参数结构体存储在参数对象中,并使用总线对象作为数据类型(请参阅通过创建参数对象来控制字段数据类型和特征)。要根据您的 C 代码 struct 类型创建总线对象,请使用 Simulink.importExternalCTypes 函数。

相关主题