MATLAB Appdesigner开发独立桌面App全流程(一):以打开串口功能为例介绍Appdesigner的基本使用
本系列博客仅为学习记录,不定期更新。
1.MATLAB Appdesigner 的基本使用MATLAB目前内置的App开发工具为Appdesigner。Appdesigner使用面向对象的方式进行编写;并且在开发工具中,所有控件的生成代码由开发工具自动生成,用户只需要按照自己的实际需要添加全局变量、回调函数,修改控件属性即可。按照官方文档的说法,GUIDE将会被逐渐移除出MATLAB。因此,我们需要尽快学习并掌握Appdesigner的使用。
1.打开Appdesinger:先打开MATLAB,界面如下所示: 2.点击上图中红色圈圈出的“设计App”,进入如下界面: 3.在第一行,根据需要可以选择新建App,空白模板或者自动调整的分为两栏和三栏的模板。此处我们选择“可自动调整布局的两栏式App”,进入如下界面: 4.左侧方框内为MATLAB开发工具中所附带的控件;中间方框为App视图;右侧方框为各个控件的一些属性以及组件树,我们可以在此处修改各种属性,以达到美化App的目的。点击App视图右上角的“代码视图”,界面如下: 这是还没有添加任何控件和用户代码的App代码,由设计工具自动生成,不需要用户一个个编写属性。
至此,我们就新建了一个.mlapp工程,可以开始使用Appdesigner开发工具开发出符合我们需要的App了。将工程保存到自己想要保存的路径后,开始添加控件和回调函数,以便达到我们的设计目的。下面几小节将介绍部分常见控件的使用以及不同类型回调函数的编写。
2.按钮1.首先,我们需要将按钮控件添加到设计画布上。在左边常用控件里找到“按钮”,鼠标左键按住,将其拖动到画布上以后松开鼠标左键。这样,一个按钮控件就添加好了。 2.如上图所示,当添加好按钮控件后,单击控件,周围会出现一个蓝色框。这个和我们在PPT里常见的文本框性质是相同的,我们可以直接拖拽右下角调整控件的大小;按住整个控件,可以改变其位置。右侧的组件浏览器中,上方为组件树,下方为我们所选中的组件的属性。最常用的属性为“Text”,这个属性代表了控件在图窗中显示的名字。比如此处,我将其命名为“打开串口”,那么在图窗中,这个按钮控件就会显示为“打开串口”: 3.多数情况下,按钮并不会被其他控件调用。但是其他控件,比如坐标区、文本框等,会经常被其他控件的回调函数调用。很多时候,我们需要调用多个坐标区或文本框。因此,为了方便我们在写代码时准确调用其他控件,我们需要养成良好的命名习惯,保证我们在写代码时不会混淆各个控件和变量。那么在此,我们需要给这个按钮变量(注意,按钮显示在图窗上的名字和按钮变量自身的名字并不是一回事)命名。此处,我们给按钮控件命名为“OpenSerialButton”。在控件树中找到刚才创建的按钮变量,如下图所示: 4.双击该控件,我们就可以给控件重命名了。重命名后如下所示: 这样,我们就创建好了一个按钮。
3.下拉框在实际使用中,一个功能可能要对应多个不同的参数输入。这个时候,我们需要使用下拉框控件。
1.和按钮一样,我们在控件树中找到“下拉框”,将其拖拽进画布,如下图所示。 2.打开串口需要设置波特率,在此处,我们设置4个波特率,分别为9600,14400,19200,115200。双击上图中“倒三角”符号的位置,出现如下界面: 3.双击各个“Option”,可以给这些Option命名。单选选中一个Option后,点击右侧的“-”会将其删除,而点击“+”将会新建一个Option。在这里,我们把Options设置成各个波特率: 红色框中的小黑点选中后,代表默认的选项。这个选项将会默认出现在软件图窗上。
4.和按钮组一样,我们把这个下拉框命名为“波特率”,下拉框变量命名为“BaudRateDropDown”: 5.注意橙色框圈出的两个下拉框属性。“ItemData”中有四个元素,分别对应当前选项。比如,现在选中的选项为“115200”,是4项中的第4项,则“Value”属性就是4。这个“ItemData”为了后续编程时调用方便(这一部分将在第8节回调函数详细介绍),我将其手动改为了1,2,3,4(更改的过程中,要这样输入:1,2,3,4——横排输入,不要按回车。否则Value属性调用出来为元胞数组)。默认状态下,“ItemData”缺省,而“Value”的值就是当前选中的选项的值(此例中是115200)。
到这里,我们就创建了一个下拉框控件,并配置好了我们所需要的一些属性。
4.坐标区坐标区在App的开发中是至关重要的。很多时候,我们需要使用坐标区绘制数据、显示图像、显示模型等。因此,我们需要掌握坐标区的使用。
1.同样,在控件树中找到“坐标区”控件,将其拖拽进画布,如下图所示。 2.上图中红色框中为坐标区的名字,而蓝色区中为坐标区的各个属性。可以看到,坐标区具有三个方向的属性:“X、Y、Z”。要调整坐标区是二维坐标区还是三维坐标区,需要在“视角”这一属性中改变。在这里,我们要显示模型,则将坐标区的名字改为“ModelUIAxes”,并调整“视角属性”。效果如下所示。 3.“View”属性中为一个二维向量,分别表示了观察者视角与X轴和Z轴之间的夹角。通过改变这个值,我们可以将坐标区旋转到一个适合的位置,以最好地显示我们的模型。在这里方便起见,就调整为45,45.
这样,我们就创建好了一个坐标区控件。
5.文本框有些时候我们需要使用文本框来显示我们接收到的数据,便于我们调试设备或者查找bug。
1.在控件树中找到“文本区域”控件,将其拖拽进画布,如图所示:
2.注意,文本区域变量的属性名为“Value”。这个是文本区域所显示的字符串。因此,在将收上来的数据显示在文本区域时,要将数据赋给这个属性,即:
app.SerialTextArea.Value = 'Submarine'; % or the following syntax set(app.SerialTextArea,"Value",'Submarine');3.通过上面两个语句(任意一个即可),可以将我们想要的信息显示在文本区域当中。左上角的“Text Area”是一个标签,单击后可以删除,或者在右侧属性栏“标签”里为它命名。在这里,我先把它删除。最后效果如下图所示。 这样,我们就完成了文本区域的添加。
6.选项卡组很多时候我们开发的App功能较多,需要的控件数量较多。但是整个图窗的面积有限,而且如果所有控件都堆在一个画布上,排版美观也很难保证。因此,这时候我们就需要使用选项卡组来帮助我们进行排版。
1.找到选项卡组,将其拖拽进画布: 2.选项卡组的属性较少,一般我们只改变其变量名称即可。在这里,我们暂不修改“TabGroup”,只修改下面两个子Tab的变量名称,分别改为“ModelTab”和“ButtonTab”,分别摆放我们显示模型的坐标区和一些按钮控件。将刚才创建的坐标区和按钮拖拽进两个选项卡,效果如下图所示: 3.双击图窗里Tab组件的文字,可以给这个Tab重命名。在上图里,我分别给两个图窗命名为“模型显示”和“按钮控件”,表明它们的用途。同时注意右侧控件树的变化,坐标区变成了选项卡组的一个子级。单击点击“模型显示”和“按钮组件”,可以在两个选项卡中进行切换: 这样,我们就创建好了一个选项卡组控件。
至此,我们已经创建好了我们所需的控件。接下来,就要进入编写程序的环节,让这些控件在程序运行后真正发挥作用。第7节和第8节将从全局变量和回调函数的角度,详细介绍如何编写程序给App的控件添加功能。
7.全局变量首先考虑一个问题:在某一个控件里产生的数据,该如何被其他控件知道呢?比如说,当我按下某个按钮,按钮中的某个变量被赋值为1;但在另一些时候,另一个控件需要使用这个变量,将其赋值为2。那如何使两个控件都能使用这个变量,或者两个控件之间要进行通信呢?这个时候,就需要全局变量的引入。这一节将介绍在Appdesigner中全局变量的使用。
1.点击代码视图,将界面切换到代码,效果如下图所示: 2.上图中,蓝色方框圈起来的部分就是添加函数和属性的位置。点击“函数”或者“属性”下面的箭头,会显示“公共属性”或“私有属性”。在这里,我们选择“公共属性”和“公共函数”,将其添加到代码当中,结果如下图所示。 在代码框里就会出现一个“properties”和“methods”块。在properties中,我们就可以添加我们的全局变量了。而在methods中,我们需要添加一些非控件的回调函数,此处暂时不表,我们放在第8节中详细说明。
3.这时,我们就可以在properties块中添加我们所需要的全局变量了。在此项目中,我们需要如下变量:
一个串口变量,用来连接串口和读取串口的数据;一个串口名称变量,用来存储串口名称的字符串;一个模型变量,用来存储我们的模型数据;一个波特率变量,用以表明我们所选择的波特率;一个定时器变量,用来创建定时器调度任务。设置好变量名称,效果如下: 要想给这些变量赋一个初始值,按照MATLAB给变量赋初值的语法就可以了。上面三个变量中,只有baud可以赋为浮点数,其他变量的建立需要使用函数和不同的参数,涉及串口变量的使用和导入数据文件。这两部分将在第8节以及后续的文章中详细介绍。这里,给变量赋初值,上面的代码可以改写为:
properties (Access = public) ser; % Serial serialname; model; % To store the model data baud = 115200; % baud rate datetimer; % To show time end这样,我们就添加好了App所需要的全局变量。在读者具体的项目中,读者也可以根据自己的需要添加变量,也可以定义不同的变量类型。
P.S.在后面回调函数的编写中,我可能会添加另外一些变量。在每处新增的变量我都将进行说明。
8.回调函数这一部分将介绍在Appdesigner中不同类型的回调函数的编写。
我们以创建串口变量为例来介绍按钮控件回调函数的编写。
1.Serialportlist函数的使用
在2019b版本中引入serialport函数后,MATLAB不太推荐使用原有的serial函数,而是推荐使用serialport函数。根据官方文档,使用serialport函数至少需要两个参数:portname和baudrate。因此,我们至少需要知道连接到我们电脑上的串口名称和波特率。在MATLAB中,serialportlist函数可以获取与我们电脑上相连的串口名称:
serialname = serialportlist;函数的返回值如下: 该函数将返回一个string类型的数组,是电脑目前所有可用串口的名称。
2.串口类型的查看
串口类型常见的是蓝牙标准串行和CH340,而硬件接上来的串口类型一般为CH340。打开文件资源管理器,找到“此电脑”,鼠标左键单击后,出现如下界面: 然后单击“管理”,弹出如下窗口: 之后,点击“设备管理器”,再点击“端口(COM和LPT)”将其展开,显示电脑目前可用的串口以及类型。可以看到,COM8口为CH340口,这个是和我们的硬件设备相连接的COM口。
3.添加按钮回调
此时,我们就可以针对创建串口变量这一功能,添加按钮的回调函数了。在这里,我引入一个新的按钮,用作查找串口号;新建一个下拉框,用以显示串口号。将这两个控件分别命名为“FindSerialportButton”和“SeriallistDropDown”。
鼠标右键单击按钮,按如下路径左键点击:“回调” → \rightarrow → “添加ButtonPushedFcn回调”,如下图所示。 转到代码视图,我们可以发现自动生成了一个代码块: 4.在这个白色框中,我们就可以编写“查询串口”按钮的功能代码了:
% Button pushed function: FindSerial function FindSerialportButtonPushed(app, event) app.serialname = serialportlist; Devices = app.serialname; num = length(Devices); % 传回设备个数信息 items = cell(1,num); itemsdata = zeros(1,num); for i = 1:num items{i} = [char(Devices(i)),' USB-Serial']; itemsdata(i) = i; end % 还记得上面讲的下拉框ItemsData属性和Items属性的关系吗?在这里,我们把Items属性设置成文本信息,显示串 口号;而ItemsData对应他们的序号,方便我们选中某个之后,其他控件可以知道我们到底选中的是哪个选项。 app.SeriallistDropDown.Items = items; app.SeriallistDropDown.ItemsData = itemsdata; end这段代码一共实现了两个功能:
找到目前电脑上所有可用的串口;将这些串口设置成显示串口号的下拉框的选项。5.点击“运行”,我们就可以测试这段代码的效果了: 可以看到,这个时候,“串口号”这个下拉框里还没有任何内容;当我们点击“查询串口”按钮后,串口号便会显示出电脑上所有的串口号信息。点击下拉框,我们就可以选中我们想要的那个串口号: 这样,一个查询串口号的按钮功能就做好了。
6.“打开串口”按钮功能
当我们找到串口名称、设置好波特率后,就可以打开串口了。那么现在就有了一个问题:“打开串口”这个按钮该如何得知我们选中的是哪个串口呢?这就涉及到不同控件之间的“交流”了。和上面一样,我们先给“打开串口”按钮添加回调函数,然后在代码视图中给按钮添加功能代码:
function OpenSerialButtonPushed(app, event) % 获取当前选中的串口名称 serName = app.serialname(app.SeriallistDropDown.Value); % 获取当前选中的波特率 app.baud = app.BaudRateDropDown.Items{app.BaudRateDropDown.Value}; % 创建串口变量 app.ser = serialport(serName,str2double(app.baud)); end注意:当我们调用Items属性时,注意一下返回值类型。在这里,我们在上面代码块中的第7行代码处添加一个调试断点,如下图所示: 这时候,我们回到命令行窗口,输入如下指令:
app.BaudRateDropDown.Items返回值如下图所示: 可以看到,下拉框的Items属性存储数据是元胞数组的形式。那么每个元胞数组里的数据又是什么形式呢?在命令行中输入如下指令:
ans{1} % 元胞数组内数据的调用要使用{}。元胞数组的具体使用方法请参考官方文档。返回值如下: 可以看到,这个返回了第一个选项,但是是以字符串的形式返回的。因此,我们需要将它转变成double类型才能输入进serialport函数。字符串变成double的函数方法如下:
str2double(app.baud);至此,我们就获得了串口名和波特率两个基本参数,可以用来创建串口了。上述代码便是“打开串口”按钮的功能代码。而上面所讲的也是一个例子,提醒读者注意我们调用其他组件属性时返回值是什么,从而在本控件的代码中做出一定的调整以适应我们所用函数的需要。接下来,我将详细介绍串口回调函数的使用。
9.串口回调1.创建好了串口变量,我们就需要读取和处理串口数据了。在这个项目中,我们使用“Terminator”来判断是否应该读取数据。在下位机传上来的数据包中,每组数据的末尾都添加了“\r\n”字符。那么上位机端,我们就使用这两个字符作为是否该读取数据的标志。在serialport函数后,我们需要添加如下一行代码:
configureTerminator(app.ser,"CR/LF");这段代码的作用是将回调函数触发条件设置为“Terminator”,通俗来说就是读到特定字符时触发回调函数。另外还有一种方式,是在串口中数据量到达固定字节后触发串口回调函数。在这里,我们只详细介绍“Terminator”方式。
2.设置好后,接下来需要添加调用回调函数的代码:
configureCallback(app.ser,"terminator",@app.SerialReceive_Callback);在这里,我们调用的是名为“SerialReceive_Callback”的函数。但是注意,在函数名称前面,要加上“app.”。这是因为回调函数是app这个变量的一个methods,需要用app来调用。具体的细节,读者可以自行参考MATLAB面向对象编程的一些资料,此处不再赘述。
3.回顾一下第7节全局变量,我们在那里不只创建了公共属性,也创建了一个公共函数: 我们需要将串口的回调函数放在这里。在这里,我们实现一个简单的功能:将串口接收到的数据显示在文本框里。回调函数的全部代码如下:
properties (Access = public) ser; % Serial serialname; model; % To store the model data baud = 115200; % baud rate datetimer; % To show time SerialData; end methods (Access = public) function SerialReceive_Callback(app, src,~) SerialBytesAvailable = get(src,'NumBytesAvailable'); if SerialBytesAvailable % 判断是否有数据可读,如果可读则进行下一步读出的操作 app.SerialData = read(src,SerialBytesAvailable,"char"); set(app.SerialTextArea,"Value",app.SerialData); end end end在properties中,我添加了一个新的变量:SerialData,用以存储每一次串口读取的数据。上面代码块中的第16行,实现将串口数据原封不动地显示到我们在第5节创建的文本控件当中。
“开启串口”回调的全部代码为:
% Button pushed function: OpenSerialButton function OpenSerialButtonPushed(app, event) serName = app.serialname(app.SeriallistDropDown.Value); app.baud = app.BaudRateDropDown.Items{app.BaudRateDropDown.Value}; app.ser = serialport(serName,str2double(app.baud)); %% 清除缓冲区 flush(app.ser); %% 设置中断回调 configureTerminator(app.ser,"CR/LF"); configureCallback(app.ser,"terminator",@app.SerialReceive_Callback); end运行App,初始界面如下: 这个时候,文本框中的内容还是我们在画布上设置的“Submarine”,而“串口号”下拉框中也没有内容。按如下流程点击操作:“查询串口” → \rightarrow → 在“串口号”下拉框中选择串口号 → \rightarrow → 在“波特率”下拉框中选择波特率 → \rightarrow → “打开串口”。结果如下图所示: 可以看到,文本框中显示出了下位机发送上来的信息。
至此,我们就完成了接收下位机数据这一功能。
4.关闭串口
在一个任务周期完成之后,我们需要关闭串口。关闭串口的代码如下:
configureCallback(app.ser,"off"); delete(app.ser);第一行代码将回调函数关闭;第二行代码将串口变量删除,释放串口资源,为连接其他串口提供空间。在这里,我将关闭串口的代码写入另一个按钮的回调函数当中,当我们按下按钮后,串口关闭。完整代码如下:
% Button pushed function: CloseSerialButton function CloseSerialButtonPushed(app, event) configureCallback(app.ser,"off"); delete(app.ser); end至此,我们就走完了串口使用的整个流程。读者可以针对自己数据的具体需求,在回调函数中添加自己的处理算法以实现特定的功能。
未完待续…