MATLAB GUI制作——转换mdf/mf4格式为mat格式

前言: 作为一名汽车软件开发或测试工程师,经常会碰到各种分析困难的问题,这就需要将测试数据导出至simulink中进行mil仿真,看看到底是哪里出了问题?Canape软件支持将mdf或mf4格式数据转换为mat格式的数据,但对于采集的长时间数据在导出过程中,想要保持一定的采样精度和连续性,经常会碰到导出报错“内存不足”、导出数据失败。这就需要想另外的办法,怎样保持采样精度也能保证连续性?本文将介绍怎样将mdf/mf4格式的数据保持采样精度和连续性导出为mat格式。

长时间的数据导出报错“内存不足”

准备工具: MATLAB 2016B、CANape。

正文: 如果需要将mdf/mf4格式数据保存为mat格式,我们需要知道mdf/mf4格式文件的数据结构是什么样的?MATLAB 2016B中针对mdf/mf4格式文件有一个专门的函数可用于打开此文件,函数名为“mdf( )”。本文以CANape软件示例的一个mdf文件为例,首先用CANape打开一个mdf文件,查看文件有哪些信号?示例文件在CANape安装目录下,如本机为:D:\Program Files (x86)\Vector\Examples\Example\sinus.mdf。

示例文件的信号列表

查看信号SinusPhase0,就是一个正弦波。

查看完信号列表后,再用MATLAB打开对应的文件,看看在哪些地方能找到对应的信号。

用mdf( )函数打开示例mdf文件

打开后,看到文件的数据结构如下,在ChannelNames中能找到对应的信号名。

mdf文件数据结构
ChannelNames查找到对应的信号

通过read( )函数读取对应信号,并使用plot( )函数画图,发现信号与CANape中曲线能完美对应上。

read( )函数读取信号
plot对应信号

了解了mdf数据文件结构后,我们就开始使用脚本实现自动化读取想要的信号了。

制作完成的GUI界面如下:

通过如下代码,可以实现打开并读取mdf数据文件。

[FileName, PathName] = uigetfile('*.mf4;*.mdf', '选择一个mdf或mf4文件');
app.FilePath.Value = strcat(PathName, FileName);
app.DataFile = mdf(app.FilePath.Value);

在读取文件后,我们需要判断选取的文件是否是一个有效的mdf或mf4文件。

FileName = app.FilePath.Value;
FileNameLen = length(FileName);
if ~(strcmpi(FileName(FileNameLen-3:1:FileNameLen), '.mdf') || strcmpi(FileName(FileNameLen-3:1:FileNameLen), '.mf4'))
     msgbox('无效mdf或mf4文件,请重新选择数据文件!');
end

读取并判断文件的有效性后,我们接下来就是读取所有需要的信号。我们将所有需要的信号以空格字符或“;”作为分隔符,将信号进行分割。如“SinusPhase0;SinusPhase90;SinusPhase180;SinusPhase270;”

%切割信号名序列
Variables = split(app.SignalName.Value, {' ',';'});
VariablesNum = length(Variables);

信号分割完成后,就是对各个信号进行罗列查找了。

VariableName(countFind) = read(app.DataFile, VariableNamei, char(Variables(i)), 'OutputFormat', 'timeseries');

为了了解到转换的进度,可通过添加数字进度,进行转换进度的展示。

%转换进度
percent = min(i/VariablesNum*100, 99);
percentStr = strcat(num2str(percent), '%');
app.StartMake.Text = strcat('转换进度:', percentStr);

所有信号查找完成后,需要将信号统一时间刻度,然后保存为mat格式。

eval(strcat(VariableName(i).Name, ' = interp1(VariableName(i).Time, VariableName(i).Data, t, ''linear'', ''extrap'');'));
save(char(DataName), VariableName(i).Name, '-append');

如果想要将所有缺失的信号展示出来,可通过如下代码实现。

if countMiss>=1
      fid = fopen('missSignalName.txt','w');
      fprintf(fid, '缺失的信号如下:');
      fprintf(fid, '\n');
      for i = 1:countMiss
            fprintf(fid, char(Variables(missVariable(i))));
            fprintf(fid, ';');
      msgbox('该数据文件缺失部分信号,详见同目录下missSignalName.txt!');
end

通过以上主要的函数接口和代码,即可实现将mdf/mf4格式数据转换为mat格式。

如需具体代码,可留言联系。

应各位知友的要求,本帖将源代码贴上。

classdef CANape_DataFormat_Conversion < matlab.apps.AppBase
    % Properties that correspond to app components
    properties (Access = public)
        mdftomatUIFigure  matlab.ui.Figure
        FilePath          matlab.ui.control.EditField
        mdfmf4Label       matlab.ui.control.Label
        SelectFile        matlab.ui.control.Button
        StartMake         matlab.ui.control.Button
        Label_2           matlab.ui.control.Label
        SignalName        matlab.ui.control.TextArea
        Lamp              matlab.ui.control.Lamp
        Label_3           matlab.ui.control.Label
        signalPeriod      matlab.ui.control.NumericEditField
        msLabel           matlab.ui.control.Label
    properties (Access = public)
        DataFile; % Description
    methods (Access = private)
        % Callback function: FilePath, SelectFile
        function SelectA2LFile(app, event)
            [FileName, PathName] = uigetfile('*.mf4;*.mdf', '选择一个mdf或mf4文件');
            app.FilePath.Value = strcat(PathName, FileName);
            app.DataFile = mdf(app.FilePath.Value);
        % Button pushed function: StartMake
        function StartMakeButtonPushed(app, event)
            app.StartMake.Enable = 'off';
            FileName = app.FilePath.Value;
            FileNameLen = length(FileName);
            if ~(strcmpi(FileName(FileNameLen-3:1:FileNameLen), '.mdf') || strcmpi(FileName(FileNameLen-3:1:FileNameLen), '.mf4'))
                msgbox('无效mdf或mf4文件,请重新选择数据文件!');
                app.Lamp.Color = [1,0,0];
                %设定待保存文件名
                FileNameSplit = split(app.FilePath.Value, '\');
                tempName = char(FileNameSplit(length(FileNameSplit)));
                DataName = strcat(tempName(1:length(tempName)-4), '.mat');
                %切割信号名序列
                Variables = split(app.SignalName.Value, {' ',';'});
                VariablesNum = length(Variables);
                %逐个查找信号名并保存至文件
                countMiss = 0;
                countFind = 0;
                for i = 1:VariablesNum
                    if strcmp(Variables(i), '')==0
                        flag = 0;
                        for VariableNamei = 1:length(app.DataFile.ChannelNames)
                            for VariableNamej = 1:length(app.DataFile.ChannelNames{VariableNamei})
                                if strcmp(Variables(i), app.DataFile.ChannelNames{VariableNamei}{VariableNamej})==1
                                    flag = 1;
                                    break;
                            if flag==1
                                break;
                        if VariableNamei==length(app.DataFile.ChannelNames) && flag==0
                            countMiss = countMiss + 1;
                            missVariable(countMiss) = i;
                                countFind = countFind + 1;
                                VariableName(countFind) = read(app.DataFile, VariableNamei, char(Variables(i)), 'OutputFormat', 'timeseries');
                                %转换进度
                                percent = min(i/VariablesNum*100, 99);
                                percentStr = strcat(num2str(percent), '%');
                                app.StartMake.Text = strcat('转换进度:', percentStr);
                            catch
                                countFind = countFind - 1;
                                countMiss = countMiss + 1;
                                missVariable(countMiss) = i;
                % 数据统一时间
                if countFind>0
                    if app.signalPeriod.Value/1000.0 > max(VariableName(1).Time)
                        msgbox('设定的信号周期超过了信号的最大时间,请重新设定合适的信号周期!');
                        return;
                    t = [0:app.signalPeriod.Value/1000.0:max(VariableName(1).Time)]';
                    save(char(DataName), 't');
                    for i = 1:countFind
                        eval(strcat(VariableName(i).Name, ' = interp1(VariableName(i).Time, VariableName(i).Data, t, ''linear'', ''extrap'');'));
                        save(char(DataName), VariableName(i).Name, '-append');
                app.StartMake.Text = strcat('转换进度:', '100%');
                if countMiss>=1
                    fid = fopen('missSignalName.txt','w');
                    fprintf(fid, '缺失的信号如下:');
                    fprintf(fid, '\n');
                    for i = 1:countMiss
                        fprintf(fid, char(Variables(missVariable(i))));
                        fprintf(fid, ';');
                    msgbox('该数据文件缺失部分信号,详见同目录下missSignalName.txt!');
                msgbox('数据格式转换完成!');
                app.StartMake.Text = '开始转换';
                app.StartMake.Enable = 'on';
                app.Lamp.Color = [0,1,0];
    % App initialization and construction
    methods (Access = private)
        % Create UIFigure and components
        function createComponents(app)
            % Create mdftomatUIFigure
            app.mdftomatUIFigure = uifigure;
            app.mdftomatUIFigure.Position = [100 100 640 480];
            app.mdftomatUIFigure.Name = 'mdf to mat';
            app.mdftomatUIFigure.Resize = 'off';
            setAutoResize(app, app.mdftomatUIFigure, true)
            % Create FilePath
            app.FilePath = uieditfield(app.mdftomatUIFigure, 'text');
            app.FilePath.ValueChangedFcn = createCallbackFcn(app, @SelectA2LFile, true);
            app.FilePath.Position = [169 437 370 22];
            % Create mdfmf4Label
            app.mdfmf4Label = uilabel(app.mdftomatUIFigure);
            app.mdfmf4Label.HorizontalAlignment = 'right';
            app.mdfmf4Label.FontWeight = 'bold';
            app.mdfmf4Label.Position = [17 441 153 15];
            app.mdfmf4Label.Text = '待处理的mdf或mf4文件:';
            % Create SelectFile
            app.SelectFile = uibutton(app.mdftomatUIFigure, 'push');
            app.SelectFile.ButtonPushedFcn = createCallbackFcn(app, @SelectA2LFile, true);
            app.SelectFile.FontWeight = 'bold';
            app.SelectFile.Position = [558 437 69 22];
            app.SelectFile.Text = '选择';
            % Create StartMake
            app.StartMake = uibutton(app.mdftomatUIFigure, 'push');
            app.StartMake.ButtonPushedFcn = createCallbackFcn(app, @StartMakeButtonPushed, true);
            app.StartMake.FontSize = 14;
            app.StartMake.FontWeight = 'bold';
            app.StartMake.Position = [152 22 432 32];
            app.StartMake.Text = '开始转换';
            % Create Label_2
            app.Label_2 = uilabel(app.mdftomatUIFigure);
            app.Label_2.HorizontalAlignment = 'right';
            app.Label_2.FontWeight = 'bold';
            app.Label_2.Position = [17 402 109 15];
            app.Label_2.Text = '待转换的信号名:';
            % Create SignalName
            app.SignalName = uitextarea(app.mdftomatUIFigure);
            app.SignalName.Position = [27 70 600 323];
            % Create Lamp
            app.Lamp = uilamp(app.mdftomatUIFigure);
            app.Lamp.Position = [596 28 20 20];
            % Create Label_3
            app.Label_3 = uilabel(app.mdftomatUIFigure);
            app.Label_3.HorizontalAlignment = 'right';
            app.Label_3.FontWeight = 'bold';
            app.Label_3.Position = [17 31 57 15];
            app.Label_3.Text = '信号周期';
            % Create signalPeriod
            app.signalPeriod = uieditfield(app.mdftomatUIFigure, 'numeric');
            app.signalPeriod.Limits = [1 10000];
            app.signalPeriod.Position = [76 27 37 22];
            app.signalPeriod.Value = 5;
            % Create msLabel
            app.msLabel = uilabel(app.mdftomatUIFigure);
            app.msLabel.Position = [116 31 25 15];
            app.msLabel.Text = 'ms';
    methods (Access = public)
        % Construct app
        function app = CANape_DataFormat_Conversion()
            % Create and configure components
            createComponents(app)
            % Register the app with App Designer
            registerApp(app, app.mdftomatUIFigure)
            if nargout == 0
                clear app
        % Code that executes before app deletion
        function delete(app)