Main Content

Map Channels from MDF Files to Simulink Model Input Ports

This example shows you how to programmatically map channels from MDF files and consume their data via input ports of a Simulink® model. It performs the gathering of input port names of a Simulink model and correlates them to the content of a given MDF file. A linkage between them is then created which consumes channel data sourced from the MDF file when the model runs.

Acquire Model Details

Define the example model name and open it.

mdlName = "ModelForMDFInput";
open_system(mdlName);

Use the createInputDataset (Simulink) function to obtain overall information about the model and its inputs. Specify the DatasetSignalFormat option as "timetable" to configure dataset signal elements as timetables.

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.

Obtain Model Input Port Names

This model has both a bus and an individual input port. The helperGetMdlInputNames function demonstrates how to get the name of all the model inputs regardless of how they are defined in the model.

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

Investigate the MDF File

Now that you have the input port names of the model, you can see what channels exist in the MDF file so you can attempt to match them. The mdfChannelInfo function allows quick access to the available channels present in an MDF file.

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  
      ⋮

Construct a Table to Manage Items of Interest

Use a table to map the model input ports to MDF channels.

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            ""     

Perform Input Port to Channel Matching

The helperReportChannelInfo function searches the MDF file for channel names that match the model input port names. When found, the details of the channel are recorded in the table. Specifically, the channel group number where the given channel is in the file and its actual defined name. Note that the actual channel names are not exact matches to the model port names. In this example, the channel name matching is performed case-insensitive and ignores the underscore characters. This algorithm can be adapted as needed based on application-specific matching criteria.

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"

Populate the Simulink Dataset Object with Channel Data

The dataset object created earlier contains both a single timetable and a structure of timetables. This makes assigning data back to them somewhat challenging. Things to keep in mind include:

  • Because the dataset object has dissimilar elements (a timetable and a structure of timetables), you need to manually manage the collection and make sure you are writing to the correct location.

  • The mdfRead function reads data as physical values by default. However, one channel of interest "PMW" contains ValueToText conversion. To make sure the raw numeric values are read to populate the dataset object, the ReadRaw option should be set to 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.

Enable the Dataset as Input to the Simulink Model

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

Run the Model

Upon executing the model, note that channel data from the MDF file properly maps to the designated input ports and plots through Simulink as expected.

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

Helper Functions

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