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