基于MATLAB App Designer的串口RS485 Modbus上位机
1. 前言
上学那会儿MATLAB还没有App Designer,记得当时开发了一个基于MATLAB的计算器。最近,由于项目需要一个简单的上位机做监控,结合自身实际,拟定了以下三种方案:
- PyQt5:结合QT和Python的GUI开发库,结合QSS可以做出漂亮的界面
- Streamlit:基于Python的web应用开发库,简单易上手,本来是用来做数据科学的快速展示的,在本地上拿来搞个界面也不错,颜值也很高
- App Deisgner:MATLAB出品,控件外观可自定义的东西不多,不过借助MATLAB强大的toolbox,数据通讯、采集、控制、分析非常方便
本文采用第三种开发,另外两种后续有时间再写。
使用的MATLAB版本为2018b,关于modbus协议请自行百度。
2. 界面框架
首先应该对界面大体框架进行简单设计,如下图所示
串口连接配置:设置要连接的串口,配置波特率
数据显示:读寄存器显示
控制:写寄存器控制
3. 设计过程
3.1 创建界面
如下图所示
打开界面后,往界面拖入控件修改基础属性并进行布局得到的界面如下
可以从组件浏览器看到界面的组件
完成界面设计后,就可以开始代码编写
3.2 代码编写
3.2.1 私有属性
点击代码视图->属性->私有属性,建立以下几个属性:
CurrentSerial % 当前选择的串口
ModbusSerialRTU % modbus对象
TreadFlag % 关闭停止标志
SwitchStateMsg % 开关状态消息
SwitchList % 开关对象列表
SwitchTagList % 开关对象的标签,用于event查找
SliderMsg % 滑块消息
3.2.1 初始化回调函数
右击组件浏览器中的app.modbusFigure,选择回调->添加StartupFcn回调,在自动生成的startupFcn回调方法中,添加以下代码:
app.SerialPortSelectDropDown.Items = seriallist; % 获取计算机串口号列表并写入
app.TreadFlag = false;
app.ModbusSerialRTU = 0;
% 两个开关的初始化
app.SwitchStateMsg = zeros(1,2); % 开关消息数组初始化为0
app.SwitchList = [app.Switch1, app.Switch2]; % 设置开关列表
app.SwitchTagList = ["switch1", "switch2"]; % 开关标签列表,主要用于event事件查找
for i = 1:2 % 初始化开关标签
app.SwitchList(i).Tag = app.SwitchTagList(i);
end
第一行调用matlab的seriallist命令获取计算机当前可用串口号,并将其写入串口号选择下拉列表。
3.2.2 刷新按钮回调
刷新按钮用于手动刷新当前可用的串口号。右击组件浏览器的app.RefreshButton,选择回调->添加RefreshButton回调,添加以下代码
app.PortSelectDropDown.Items = seriallist; % 获取计算机串口号列表并写入下拉框
3.2.3 连接按钮回调
连接按钮将尝试创建modbus对象,若创建成功则进入监控程序,并禁止使用连接按钮,若创建失败则在状态标签处进行显示。
try
app.ModbusSerialRTU = modbus('serialrtu', app.PortSelectDropDown.Value, ...
'BaudRate', str2double(app.BaudSelectDropDown.Value), 'Timeout', 1);
app.TreadFlag = true;
catch
app.ModbusSerialRTU = 0;
app.StatusLabel.Text = 'WARNING: modbus创建失败';
app.TreadFlag = false;
end
if app.TreadFlag == true
app.LinkButton.Enable = false;
main(app, event); % 进入监控
end
3.2.4 关闭按钮回调
关闭按钮主要功能是将app.TreadFlag设置位false,从而控制退出main(app, event),同时重新使能连接按钮
app.ModbusSerialRTU = 0;
app.TreadFlag = false;
app.LinkButton.Enable = true;
3.2.5 开关回调
GUI有两个开关,可以分别设置回调函数。此处,将两个开关的回调函数设置为相同,首先选择开关1,添加回调函数,然后选择开关2右击选择已有的回调函数定位到开关1回调函数即可。
if event.Source.Tag == app.SwitchTagList(1) % 此处演示如何判断开关触发源
fprintf('开关1被按下了');
app.StatusLabel.Text = '开关1被按下了';
else
fprintf('开关2被按下了')
app.StatusLabel.Text = '开关2被按下了';
end
if strcmp(app.Switch1.Value, 'On')
app.SwitchStateMsg(1) = 1;
else
app.SwitchStateMsg(1) = 0;
end
if strcmp(app.Switch2.Value, 'On')
app.SwitchStateMsg(2) = 1;
else
app.SwitchStateMsg(2) = 0;
end
% 当开关很多的时候可以这么写
% for i = 1:length(app.SwitchList)
% app.SwitchStateMsg(i) = strcmp(app.SwitchList(i).Value, 'On');
% end
3.2.6 滑动条回调
滑动条用于设置输出电压,其在回调函数中写入
app.SliderMsg = event.Value;
3.2.7 modbus轮询函数
完成所有控件的回调后,点击函数->添加私有函数,命名为main(app, event),该函数在用户点击连接后将轮询读写寄存器
methods (Access = private)
function main(app, event)
fprintf('modbus轮询\r\n');
while true
try
Rpm = read(app.ModbusSerialRTU, 'holdingregs', 33, 4, 1, 'uint16'); % 从机地址位1
pause(0.1);
catch
app.StatusLabel.Text = '保持寄存器读取失败';
Rpm = zeros(1, 4);
end
app.UITable.Data = Rpm(1:4);
try
write(app.ModbusSerialRTU, 'coils', 54, app.SwitchStateMsg, 2); % 从机地址为2
pause(0.1)
catch
app.StatusLabel.Text = '线圈写入失败';
end
try
write(app.ModbusSerialRTU, 'holdingregs', 11, round(app.SliderMsg*1000), 3, 'uint16'); % 从机地址为3
pause(0.1)
catch
app.StatusLabel.Text = '保持寄存器写入失败';
end
if app.TreadFlag == false
fprintf('退出主线程\r\n');
app.UITable.Data = zeros(1, 4);
break;
end
end
end
4 测试
首先用Virtual SerialPort创建虚拟串口对COM1和COM2,使用ModSim32模拟3个从机,地址分别为1,2,3。运行app,通讯成功。