Main Content

本页翻译不是最新的。点击此处可查看最新英文版本。

将通道从 MDF 文件映射到 Simulink 模型输入端口

此示例说明如何以编程方式从 MDF 文件中映射通道,并通过 Simulink® 模型的输入端口使用其数据。它收集 Simulink 模型的输入端口名称,并将它们与给定 MDF 文件的内容相关联。然后在它们之间创建链接,当模型运行时,该链接会使用来自 MDF 文件的通道数据。

获取模型详细信息

定义示例模型名称并将其打开。

mdlName = "ModelForMDFInput";
open_system(mdlName);

使用 createInputDataset (Simulink) 函数获取关于模型及其输入的整体信息。将 DatasetSignalFormat 选项指定为 "timetable",以将数据集信号元素配置为时间表。

dsObj = createInputDataset(mdlName, "DatasetSignalFormat", "timetable")
dsObj = 
Simulink.SimulationData.Dataset '' with 2 elements

                            Name      BlockPath 
                            ________  _________ 
    1  [2x1 timetable]      triangle  ''       
    2  [1x1 struct   ]      busInput  ''       

  - Use braces { } to access, modify, or add elements using index.

获取模型输入端口名称

此模型既有总线又有单独的输入端口。helperGetMdlInputNames 函数说明如何获取所有模型输入的名称,而不管它们在模型中是如何定义的。

mdlInputNames = helperGetMdlInputNames(mdlName)
mdlInputNames = 4x1 string
    "triangle"
    "pwm"
    "pwm_level"
    "pwm_filtered"

调查 MDF 文件

现在您已有模型的输入端口名称,就可以看到 MDF 文件中存在哪些通道,以便尝试匹配它们。mdfChannelInfo 函数允许快速访问 MDF 文件中存在的可用通道。

mdfName = "CANape.MF4";
channelInfo = mdfChannelInfo(mdfName)
channelInfo=42×13 table
                     Name                     GroupNumber    GroupNumSamples    GroupAcquisitionName    GroupComment    GroupSourceName    GroupSourcePath    DisplayName       Unit                             Comment                         ExtendedNamePrefix    SourceName     SourcePath
    ______________________________________    ___________    _______________    ____________________    ____________    _______________    _______________    ___________    ___________    _________________________________________________    __________________    ___________    __________

    "Counter_B4"                                   1              1993                 10 ms               10 ms          <undefined>          XCPsim             ""         <undefined>    Single bit demo signal (bit from a byte shifting)          XCPsim          <undefined>      XCPsim  
    "Counter_B5"                                   1              1993                 10 ms               10 ms          <undefined>          XCPsim             ""         <undefined>    Single bit demo signal (bit from a byte shifting)          XCPsim          <undefined>      XCPsim  
    "Counter_B6"                                   1              1993                 10 ms               10 ms          <undefined>          XCPsim             ""         <undefined>    Single bit demo signal (bit from a byte shifting)          XCPsim          <undefined>      XCPsim  
    "Counter_B7"                                   1              1993                 10 ms               10 ms          <undefined>          XCPsim             ""         <undefined>    Single bit demo signal (bit from a byte shifting)          XCPsim          <undefined>      XCPsim  
    "PWM"                                          1              1993                 10 ms               10 ms          <undefined>          XCPsim             ""         <undefined>    Pulse width signal from PWM_level and Triangle             XCPsim          <undefined>      XCPsim  
    "PWMFiltered"                                  1              1993                 10 ms               10 ms          <undefined>          XCPsim             ""         <undefined>    Low pass filtered PWM signal                               XCPsim          <undefined>      XCPsim  
    "PWM_Level"                                    1              1993                 10 ms               10 ms          <undefined>          XCPsim             ""         <undefined>    <undefined>                                                XCPsim          <undefined>      XCPsim  
    "Triangle"                                     1              1993                 10 ms               10 ms          <undefined>          XCPsim             ""         <undefined>    Triangle test signal used for PWM output PWM               XCPsim          <undefined>      XCPsim  
    "ampl"                                         2               199                 100ms               100ms          <undefined>          XCPsim             ""         <undefined>    Amplitude of channel 1-3                                   XCPsim          <undefined>      XCPsim  
    "channel1"                                     2               199                 100ms               100ms          <undefined>          XCPsim             ""         <undefined>    FLOAT demo signal (sine wave)                              XCPsim          <undefined>      XCPsim  
    "map1_8_8_uc_measure"                          1              1993                 10 ms               10 ms          <undefined>          XCPsim             ""         <undefined>    8*8 fixed axis,  permanently morphing                      XCPsim          <undefined>      XCPsim  
    "syncArrayStruct.mem_charArray[000]"           2               199                 100ms               100ms          <undefined>          XCPsim             ""         <undefined>    <undefined>                                                XCPsim          <undefined>      XCPsim  
    "syncArrayStruct.mem_charArray[001]"           2               199                 100ms               100ms          <undefined>          XCPsim             ""         <undefined>    <undefined>                                                XCPsim          <undefined>      XCPsim  
    "syncArrayStruct.mem_doubleArray[000]"         2               199                 100ms               100ms          <undefined>          XCPsim             ""         <undefined>    <undefined>                                                XCPsim          <undefined>      XCPsim  
    "syncArrayStruct.mem_doubleArray[001]"         2               199                 100ms               100ms          <undefined>          XCPsim             ""         <undefined>    <undefined>                                                XCPsim          <undefined>      XCPsim  
    "syncArrayStruct.mem_floatArray[000]"          2               199                 100ms               100ms          <undefined>          XCPsim             ""         <undefined>    <undefined>                                                XCPsim          <undefined>      XCPsim  
      ⋮

构造表来管理关注的项目

使用一个表将模型输入端口映射到 MDF 通道。

channelTable = table();
channelTable.PortNames = mdlInputNames;
n = size(channelTable.PortNames,1);
channelTable.GroupNumber = NaN(n,1);
channelTable.ChannelName = strings(n,1);
channelTable
channelTable=4×3 table
      PortNames       GroupNumber    ChannelName
    ______________    ___________    ___________

    "triangle"            NaN            ""     
    "pwm"                 NaN            ""     
    "pwm_level"           NaN            ""     
    "pwm_filtered"        NaN            ""     

执行输入端口到通道的匹配

helperReportChannelInfo 函数在 MDF 文件中搜索与模型输入端口名称匹配的通道名称。找到通道后,该通道的详细信息会记录在表中。具体来说,包括文件中给定通道所在的通道组编号及其实际定义的名称。请注意,实际的通道名称与模型端口名称并不完全匹配。在本示例中,通道名称匹配不区分大小写,并忽略下划线字符。该算法可以根据具体应用的匹配标准的需要进行调整。

channelTable = helperReportChannelInfo(channelTable, channelInfo)
channelTable=4×3 table
      PortNames       GroupNumber     ChannelName 
    ______________    ___________    _____________

    "triangle"             1         "Triangle"   
    "pwm"                  1         "PWM"        
    "pwm_level"            1         "PWM_Level"  
    "pwm_filtered"         1         "PWMFiltered"

用通道数据填充 Simulink 数据集对象

先前创建的数据集对象包含单个时间表和由时间表组成的结构体。这使得将数据分配回给它们有些困难。用户需要牢记以下事项:

  • 由于数据集对象具有不同元素(时间表和由时间表组成的结构体),您需要手动管理集合,并确保写入正确的位置。

  • 默认情况下,mdfRead 函数将数据作为物理值读取。然而,感兴趣的一个通道“PMW”包含 ValueToText 转换。为了确保读取原始数值来填充数据集对象,应将 ReadRaw 选项设置为 true

for ii = 1:dsObj.numElements
    switch class(dsObj.getElement(ii))
        case 'timetable'
            % Read the input port data from the MDF-file one channel at a time.
            mdfData = mdfRead(mdfName, Channel=channelTable.ChannelName(ii), ReadRaw=true);
            % Populate the dataset object.
            dsObj{ii} = mdfData{1};
        
        % For a port that accepts a bus, the data to be loaded must be arranged in a struct
        % that matches the structure of the bus object attached to the input port.
        case 'struct'
            names = fieldnames(dsObj.getElement(ii));
            for jj = 1:numel(names)
                % Find row index of this signal name in the channel table.
                rowIdx = find(channelTable.PortNames == names(jj));
                % Read the input port data from the MDF-file one channel at a time.
                mdfData = mdfRead(mdfName, Channel=channelTable.ChannelName(rowIdx), ReadRaw=true);
                % Populate the dataset object.
                dsObj{ii}.(channelTable.PortNames{rowIdx}) = mdfData{1};                
            end
    end
end
dsObj
dsObj = 
Simulink.SimulationData.Dataset '' with 2 elements

                               Name      BlockPath 
                               ________  _________ 
    1  [1993x1 timetable]      ''        ''       
    2  [1x1 struct      ]      busInput  ''       

  - Use braces { } to access, modify, or add elements using index.

将数据集用作 Simulink 模型的输入

set_param(mdlName, "LoadExternalInput", "on");
set_param(mdlName, "ExternalInput", "dsObj");

运行模型

执行模型时,请注意来自 MDF 文件的通道数据正确地映射到了指定的输入端口,并按预期通过 Simulink 进行了绘制。

open_system(mdlName);
bp = find_system(mdlName, "BlockType", "Scope");
open_system(bp);
pause(1)
sim(mdlName, "TimeOut", 10);

辅助函数

function mdlInputNames = helperGetMdlInputNames(mdlName)
% helperGetMdlInputNames Find input port names of a Simulink model.
%
% This function takes in the name of a Simulink model and returns the names of each model input. This specific model has 
% both a bus and a standalone input port going into it. To drive an input port that expects a bus means you need to supply 
% the signals as timetables in a struct that matches the structure of the bus object attached to the input port.

% Test to see if the model is currently loaded in memory.
isLoaded = bdIsLoaded(matlab.lang.makeValidName(mdlName));

% If the model is not open then load it.
if ~isLoaded
    load_system(mdlName);
end

dsObj = createInputDataset(mdlName);
numElements = dsObj.numElements;
isStruct = zeros(1, numElements);

% Check to see if any of the elements in the returned dataset object are
% structs. If they are, assume they are for an input port that accepts a bus.
for elementIdx = 1:numElements
    isStruct(elementIdx) = isa(dsObj.getElement(elementIdx),"struct");
end

% Initialize an empty string array to hold names of all input ports. If
% your model has a very large number of inputs, consider preallocating the
% string array for performance.
mdlInputNames = string.empty;
for idx = 1:numElements
    if isStruct(idx)
        % Get names of signals from a bus input port.
        mdlInputNames = [mdlInputNames; string(fieldnames(dsObj.getElement(idx)))]; %#ok<AGROW> 
    else
        % Get signal name from a non-bus input port.
        mdlInputNames = [mdlInputNames; string(dsObj.getElement(idx).Name)]; %#ok<AGROW> 
    end
end

end
function channelTableOut = helperReportChannelInfo(channelTableIn, channelInfo)
% channelTableOut Reports if a channel is present in a set of channel names.

% Assign the output data.
channelTableOut = channelTableIn;

% Remove underscores and make everything lowercase for matching.
inPortChannelNames = lower(erase(channelTableIn.PortNames,"_"));
mdfChannelNames = lower(erase(channelInfo.Name,"_"));

% Match the input channel names to the channel names in the MDF-file.
[~, inPortIdx] = ismember(inPortChannelNames, mdfChannelNames);

% Assign the relevant information back to the channel table.
channelTableOut.GroupNumber = channelInfo.GroupNumber(inPortIdx);
channelTableOut.ChannelName = channelInfo.Name(inPortIdx);

end