Отображение каналов MDF в порты Simulink Модели Входа

В этом примере показано, как программно сопоставить каналы MDF и использовать их данные через входные порты модели Simulink. Он выполняет сбор входа имен портов модели Simulink и коррелирует их с содержимым данного MDF-файла. Затем создаётся редактирование между ними, которая потребляет данные канала, полученные из MDF-файла, когда модель запускается.

Получение сведений о модели

Определите имя модели в качестве примера и откройте его.

mdlName = 'ModelForMDFInput';
open_system(mdlName);

Используйте функцию createInputDataset, чтобы получить общую информацию о модели и ее входах.

dsObj = createInputDataset(mdlName)
dsObj = 
Simulink.SimulationData.Dataset '' with 2 elements

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

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

Получите имена Input port модели

Эта модель имеет и шину, и индивидуума входа порт. The helperGetMdlInputNames функция демонстрирует, как получить имя всех входных параметров модели независимо от того, как они определены в модели.

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

Исследуйте MDF-файл

Теперь, когда у вас есть имена входных портов модели, вы можете увидеть, какие каналы существуют в MDF-файле, чтобы вы могли попытаться соответствовать им. The channelList функция функции MDF позволяет быстро получить доступ к доступным каналам, присутствующим в MDF-файле.

mdfName = 'CANape.MF4';
mdfObj = mdf(mdfName);
mdfChannelInfo = channelList(mdfObj)
mdfChannelInfo=120×9 table
            ChannelName            ChannelGroupNumber    ChannelGroupNumSamples    ChannelGroupAcquisitionName    ChannelGroupComment    ChannelDisplayName    ChannelUnit                     ChannelComment                                      ChannelDescription                 
    ___________________________    __________________    ______________________    ___________________________    ___________________    __________________    ___________    _________________________________________________    ___________________________________________________

    "ampl"                                 2                       199                        100ms                      100ms                   ""            <undefined>    Amplitude of channel 1-3                             "Amplitude of channel 1-3"                         
    "channel1"                             2                       199                        100ms                      100ms                   ""            <undefined>    FLOAT demo signal (sine wave)                        "FLOAT demo signal (sine wave)"                    
    "Counter_B4"                           1                      1993                        10 ms                      10 ms                   ""            <undefined>    Single bit demo signal (bit from a byte shifting)    "Single bit demo signal (bit from a byte shifting)"
    "Counter_B5"                           1                      1993                        10 ms                      10 ms                   ""            <undefined>    Single bit demo signal (bit from a byte shifting)    "Single bit demo signal (bit from a byte shifting)"
    "Counter_B6"                           1                      1993                        10 ms                      10 ms                   ""            <undefined>    Single bit demo signal (bit from a byte shifting)    "Single bit demo signal (bit from a byte shifting)"
    "Counter_B7"                           1                      1993                        10 ms                      10 ms                   ""            <undefined>    Single bit demo signal (bit from a byte shifting)    "Single bit demo signal (bit from a byte shifting)"
    "map1_8_8_uc_measure"                  1                      1993                        10 ms                      10 ms                   ""            <undefined>    8*8 fixed axis,  permanently morphing                "8*8 fixed axis,  permanently morphing"            
    "map1_8_8_uc_measure[0][0]"            1                      1993                        10 ms                      10 ms                   ""            <undefined>    8*8 fixed axis,  permanently morphing                "8*8 fixed axis,  permanently morphing"            
    "map1_8_8_uc_measure[0][1]"            1                      1993                        10 ms                      10 ms                   ""            <undefined>    8*8 fixed axis,  permanently morphing                "8*8 fixed axis,  permanently morphing"            
    "map1_8_8_uc_measure[0][2]"            1                      1993                        10 ms                      10 ms                   ""            <undefined>    8*8 fixed axis,  permanently morphing                "8*8 fixed axis,  permanently morphing"            
    "map1_8_8_uc_measure[0][3]"            1                      1993                        10 ms                      10 ms                   ""            <undefined>    8*8 fixed axis,  permanently morphing                "8*8 fixed axis,  permanently morphing"            
    "map1_8_8_uc_measure[0][4]"            1                      1993                        10 ms                      10 ms                   ""            <undefined>    8*8 fixed axis,  permanently morphing                "8*8 fixed axis,  permanently morphing"            
    "map1_8_8_uc_measure[0][5]"            1                      1993                        10 ms                      10 ms                   ""            <undefined>    8*8 fixed axis,  permanently morphing                "8*8 fixed axis,  permanently morphing"            
    "map1_8_8_uc_measure[0][6]"            1                      1993                        10 ms                      10 ms                   ""            <undefined>    8*8 fixed axis,  permanently morphing                "8*8 fixed axis,  permanently morphing"            
    "map1_8_8_uc_measure[0][7]"            1                      1993                        10 ms                      10 ms                   ""            <undefined>    8*8 fixed axis,  permanently morphing                "8*8 fixed axis,  permanently morphing"            
    "map1_8_8_uc_measure[1][0]"            1                      1993                        10 ms                      10 ms                   ""            <undefined>    8*8 fixed axis,  permanently morphing                "8*8 fixed axis,  permanently morphing"            
      ⋮

Составьте таблицу для управления интересующими вас элементами

Используйте таблицу, чтобы сопоставить входные порты модели с каналами MDF.

channelTable = table();
channelTable.PortNames = mdlInputNames;
n = size(channelTable.PortNames,1);
channelTable.ChGrpNum = NaN(n,1);
channelTable.ChNameActual = strings(n,1);
channelTable
channelTable=4×3 table
      PortNames       ChGrpNum    ChNameActual
    ______________    ________    ____________

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

Выполните Input port каналу

The helperReportChannelInfo функция ищет в MDF-файле имена каналов, которые совпадают с именами входных портов модели. При обнаружении детали канала записываются в таблицу. В частности, номер группы каналов, где данный канал находится в файле, и его фактическое определенное имя. Обратите внимание, что фактические имена каналов не совпадают с именами портов модели. В этом примере соответствие имен каналов выполняется без учета регистра и игнорирует символы подчеркивания. Этот алгоритм может быть адаптирован по мере необходимости на основе специфичных для приложения критериев соответствия.

channelTable = helperReportChannelInfo(channelTable, mdfChannelInfo)
channelTable=4×3 table
      PortNames       ChGrpNum    ChNameActual 
    ______________    ________    _____________

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

Заполните объект набора данных Simulink данными канала MDF

Объект набора данных, созданный ранее, содержит как один объект timeseries, так и структуру объектов timeseries. Это делает присвоение данных им несколько сложным. Вещи, которые нужно иметь в виду:

  • При указании 'TimeSeries' в качестве типа возврата от функции чтения MDF необходимо вызвать чтение отдельно для каждого канала.

  • Поскольку объект набора данных имеет разнородные элементы (скалярные timeseries и скалярная структура объектов timeseries), вам нужно вручную управлять коллекцией и убедиться, что вы записываете в правильное место.

for ii = 1:dsObj.numElements
    switch ii
        case {1} % [1x1 timeseries], triangle
            % Read the input port data from the MDF-file one channel at a time.
            mdfData = read(mdfObj, channelTable.ChGrpNum(ii), channelTable.ChNameActual(ii), 'OutputFormat', 'TimeSeries');
            % Populate the dataset object.
            dsObj{ii} = mdfData;
            
        case {2} % [1x1 struct], busInput
            for jj = 1:numel(fieldnames(dsObj.getElement(ii)))
                % Read the input port data from the MDF-file one channel at a time.
                mdfData = read(mdfObj, channelTable.ChGrpNum(jj+1), channelTable.ChNameActual(jj+1), 'OutputFormat', 'TimeSeries');
                % Populate the dataset object.
                dsObj{ii}.(channelTable.PortNames{jj+1}) = mdfData;                
            end
    end
end
dsObj
dsObj = 
Simulink.SimulationData.Dataset '' with 2 elements

                             Name      BlockPath 
                             ________  _________ 
    1  [1x1 timeseries]      Triangle  ''       
    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)
set_param(mdlName, 'SimulationCommand', 'start');

Закройте файл

Закройте доступ к MDF-файлу путем удаления его переменной из рабочей области.

clear mdfObj

Вспомогательные функции

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 stand-alone input port going into it. To drive an input port that expects a bus means you need to supply 
% the signals as timeseries objects 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

% 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.
busInportIdx = 1;
for idx = 1:numElements
    if isStruct(idx)
        % Get names of signals from a bus input port.
        inPortsBus(busInportIdx, :) = string(fieldnames(dsObj.getElement(idx)));
    else
        % Get signal name from a non-bus input port.
        inPorts(idx) = string(dsObj.getElement(idx).Name);
    end
end

mdlInputNames = [inPorts, inPortsBus]';
end


function channelTableOut = helperReportChannelInfo(channelTableIn, mdfChannelInfo)
% 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(mdfChannelInfo.ChannelName,'_'));

% 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.ChGrpNum = mdfChannelInfo{(inPortidx), {'ChannelGroupNumber'}};
channelTableOut.ChNameActual = mdfChannelInfo{(inPortidx), {'ChannelName'}};
end