Этот пример показывает вам, как программно сопоставить каналы от 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.
Эта модель имеет и шину и отдельный входной порт. helperGetMdlInputNames
функция демонстрирует, как получить имя всех входных параметров модели независимо от того, как они заданы в модели.
mdlInputNames = helperGetMdlInputNames(mdlName)
mdlInputNames = 4x1 string
"triangle"
"pwm"
"pwm_level"
"pwm_filtered"
Теперь, когда у вас есть имена входного порта модели, вы видите, какие каналы существуют в MDF-файле, таким образом, можно попытаться совпадать с ними. channelList
функция предоставляет быстрый доступ к доступным каналам, существующим в 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 ""
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"
Объект набора данных создал, ранее содержит и один объект timeseries и структуру объектов timeseries. Это делает данные о присвоении назад им несколько сложными. Вещи иметь в виду включают:
При определении TimeSeries
как тип возврата от read
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.
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);
Закройте доступ к 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