将通道从 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