MATLAB Appdesigner实用技巧(三):在App中实现示波器效果/动态波形效果

1. MATLAB App显示动态波形

IMU901上传上来的数据中包含四元数和加速度,每帧数据包按如下格式向上位机发送:

0.00 0.00 0.00 0.00 0.00 0.00 0.00\r\n

每帧数据中,相邻两个数据之间使用空格符隔开,末尾使用\r\n字符作为判断符。在器件运行的过程中,我们可能需要实时观察数据波形来判断设备是否正常运作。这一节,我将详细介绍如何在MATLAB的坐标区中显示动态波形。

1.1 在MATLAB脚本中显示波形

1.回到MATLAB Appdesigner实用技巧(二):MATLAB App建立TCP服务端和下位机通信1.3节中我们创建的MATLAB脚本文件。根据上位机发送上来的数据格式,我们可以以字符串“\r\n”为标准将每组数据分割开。使用如下函数:

regexp(data,'\r\n','split');

可以将数据从“\r\n”字符处分割,形成我们需要的数据组。将1.3中的代码改写如下(此处仅附上需要改写的部分,读者需要对比MATLAB Appdesigner实用技巧(二):MATLAB App建立TCP服务端和下位机通信1.3节中的代码进行修改):

clc; clear; close all

%% 全局变量
global data;
global splitedData;

%% 回调函数
function Receive_Callback(ts,~)
global data;
global splitedData;
    TCPBytesAvailable = get(ts,'NumBytesAvailable');
    if TCPBytesAvailable % 判断是否有数据可读,如果可读则进行下一步读出的操作
        data = read(ts,TCPBytesAvailable,"char"); % 读出的数据类型为char
        disp(data);
        splitedData = regexp(data,'\r\n','split');
    end
end

2.运行上述代码,在工作区找到并点开“data”和“splitedData”变量:

MATLAB Appdesigner实用技巧(三):在App中实现示波器效果/动态波形效果_第1张图片
在这里插入图片描述

可以看到,回调函数中直接读出来的数据为char类型,这个长度实际包含了两组数据;而分割函数将数据分割成了一个1×3的元胞数组。那么,这时我们就需要对分割后的数据做处理。很显然,这个元胞数组的最后一个元素为空,这是我们不需要的,我们仅仅需要前面两个元胞数组存储的数据。那么接下来,我们需要做如下几件事:

  • 首先,将分割出来的每一组数据从字符串类型转化为double类型。使用如下函数
str2num(splitedData{1});

该函数可以将字符串转化为double数组。

  • 判断每一组数据的长度是否为7,如果是则保留,不是则去掉
  • 新建若干变量,用以存储可用数据以及坐标区显示范围。
  • 绘制图像

各个部分需要注意的地方均在下列代码中添加了注释。

完整代码如下:

clc; clear; close all

%% 全局变量
global data;
global splitedData;
global Accx Accy Accz; % 三轴加速度
Accx = 0; Accy = 0; Accz = 0;
% 上面的三轴加速度将作为y轴参与绘图;
% 因此我们需要一个x轴变量来绘图;
% 并且为了实现动态效果,我们需要一个变量来存储坐标区显示的起始值
global XAxis StartValue; 
XAxis = 0; StartValue = 0;
% 自程序运行以来接收到的可用数据的组数
global NumAllData;
NumAllData = 1;

%% 创建TCPServer
address = '192.168.4.2';
port = 8080;
ts = tcpserver(address,port);

%% 加速度图窗
global AccFigure 
AccFigure = figure('Name','Acc','NumberTitle','off');
AccFigure.Color = [1 1 1];
% 加速度Plot句柄。创建句柄后,使用set函数更新数据会提高绘图速度
global AccPlots 
AccPlots = plot(XAxis,Accx,'r',...
    XAxis,Accy,'g', ...
    XAxis,Accz,'b');
legend(AccFigure.Children,{'AccX','AccY','AccZ'});
axis([XAxis XAxis+1000 -2 2]);
AccFigure.Position = [400 200 1200 700];
grid on
grid minor

%% 清空数据并打开回调函数
flush(ts);

configureTerminator(ts,"CR/LF");
configureCallback(ts,"terminator",@Receive_Callback);

% 这里pause的效果是,按下键盘上任意键,代码从这一行继续运行。
% 如果不加这一行,则程序将直接运行到configureCallback(ts,"off");即关闭回调函数
% 回调函数将不会运行
pause;

configureCallback(ts,"off");

%% 回调函数
function Receive_Callback(ts,~)
global data;
global splitedData;
global Accx Accy Accz;
global XAxis StartValue;
global NumAllData;
global AccPlots;
global AccFigure;
NumUsefullData = 0; % 每次回调函数读出的可用数据组数
    TCPBytesAvailable = get(ts,'NumBytesAvailable');
    if TCPBytesAvailable % 判断是否有数据可读,如果可读则进行下一步读出的操作
        data = read(ts,TCPBytesAvailable,"char"); % 读出的数据类型为char
        disp(data);
        splitedData = regexp(data,'\r\n','split');
        L = length(splitedData);
        if isempty(splitedData)
            return;
        else
        	usefullData = cell(L,1);
            for i = 1:L
            	% 如果数组长度为7,则认为此组为可用组,可用数据组数+1
                if length(str2num(splitedData{i})) == 7
                    NumUsefullData = NumUsefullData + 1;
                    usefullData{NumUsefullData} = str2num(splitedData{i});
                end
            end
            acx = zeros(NumUsefullData,1);
            acy = acx; acz = acx;
            for j = 1:NumUsefullData
                acx(j) = usefullData{j}(5);
                acy(j) = usefullData{j}(6);
                acz(j) = usefullData{j}(7);
            end
            % 更新x轴的数据信息
            XAxis = [XAxis;transpose(NumAllData:NumAllData+NumUsefullData-1)];
            % 更新三轴加速度(y轴)的数据信息
            Accx = [Accx;acx];
            Accy = [Accy;acy];
            Accz = [Accz;acz];
            % 更新自运行以来所有接收到的可用数据组数
            NumAllData = NumAllData + NumUsefullData;
            % 设置坐标区x轴的显示范围始终是自StartValue开始的1000个数据
            AccFigure.Children(2).XLim = [StartValue, StartValue+1000];
            % 使用set函数更新在脚本开头创建的plot句柄的数据,
            % 这样就不需要每次都使用plot画图,否则运行一段时间后程序巨卡无比
            set(AccPlots(1),"XData",XAxis,"YData",Accx);
            set(AccPlots(2),"XData",XAxis,"YData",Accy);
            set(AccPlots(3),"XData",XAxis,"YData",Accz);
            drawnow;
            % 如果数据组大于1000个,则更改横坐标轴的起始值,
            % 在原来的起始值基础上加上每次回调函数读出来的可用数据组数
            % 这样用来实现动态效果
            if XAxis(end) >= StartValue+1000
                StartValue = StartValue + NumUsefullData;
            end
        end
    end
end

运行上述代码,效果如下:
MATLAB Appdesigner实用技巧(三):在App中实现示波器效果/动态波形效果_第2张图片
可以看到,在坐标区显示出了我们的波形,并且随着数据量的增加,坐标区的起始值发生了改变,也就做出了示波器的效果。

1.2 在App的坐标区中显示波形

有了上面的测试经验,我们将代码移植到App中即可。关于Appdesigner的基本使用,读者可参考笔者MATLAB Appdesigner开发独立桌面App全流程(一):以打开串口功能为例介绍Appdesigner的基本使用系列博客。移植过程中需要注意以下几点:

  • 调用公共属性时,需要在属性名前加app.。在App中,公共属性起到类似全局变量的作用,所以不需要写”global“。具体可参考上述文章的第8和9节。
  • plot函数可以指定坐标区,在我们创建的坐标区控件中绘图
  • 调用回调函数时,需要在回调函数名称前面添加“@app.“。在本例中,如果在App中调用回调函数,则语法格式如下:
configureCallback(app.ts,"terminator",@app.TCPReceive_Callback);

至此,我们就在App中实现了示波器效果。笔者已写好的App中,运行效果如下:

MATLAB Appdesigner实用技巧(三):在App中实现示波器效果/动态波形效果_第3张图片

你可能感兴趣的:(MATLAB,matlab,开发语言,ui)