产线调试最怕的就是“数据悄悄越限,人还没反应过来”。传感器传来的温度、压力、电机电流如果突破了安全阈值,GUI不仅要标红,还得立刻弹警报、记日志,最好连蜂鸣器都能响一下。下面这套代码我直接在MATLAB里跑过,不用打开App Designer画界面,复制粘贴到脚本里就能用,适合你调试阶段快速验证逻辑。
先说怎么跑:把下面的代码保存成 SensorAlarmMonitor.m(文件名必须和函数名一致),在MATLAB命令行输入 SensorAlarmMonitor 回车,面板就出来了。
function SensorAlarmMonitor()
% 创建主窗口
fig = uifigure('Name', '产线传感器监控面板', 'Position', [100 100 700 500]);
% 实时曲线区
ax = uiaxes(fig, 'Position', [50 220 550 220], ...
'XLabel', '采样次数', 'YLabel', '传感器数值');
ylim(ax, [0 120]); grid(ax, 'on');
% 上下限输入
uilabel(fig, 'Text', '下限:', 'Position', [50 180 60 25]);
editMin = uieditfield(fig, 'numeric', 'Value', 20, 'Position', [110 180 80 25]);
uilabel(fig, 'Text', '上限:', 'Position', [220 180 60 25]);
editMax = uieditfield(fig, 'numeric', 'Value', 90, 'Position', [280 180 80 25]);
% 状态与当前值显示
statusLabel = uilabel(fig, 'Text', '状态: 等待启动', ...
'Position', [400 180 200 25], 'FontWeight', 'bold');
currentValueLabel = uilabel(fig, 'Text', '当前值: --', 'Position', [50 140 200 25]);
% 控制按钮
startBtn = uibutton(fig, 'push', 'Text', '开始采集', ...
'Position', [50 100 100 30], ...
'ButtonPushedFcn', @(~,~) startMonitoring());
stopBtn = uibutton(fig, 'push', 'Text', '停止', ...
'Position', [160 100 100 30], ...
'ButtonPushedFcn', @(~,~) stopMonitoring());
% 状态容器(嵌套函数共享变量)
app.dataHistory = [];
app.sampleCount = 0;
app.plotHandle = [];
app.alarmAlert = [];
app.editMin = editMin;
app.editMax = editMax;
app.statusLabel = statusLabel;
app.currentValueLabel = currentValueLabel;
app.ax = ax;
% 定时器:模拟产线采样节拍
app.timerObj = timer('Period', 0.5, 'ExecutionMode', 'fixedRate', 'TimerFcn', @updateData);
%% 核心逻辑函数
function startMonitoring()
lo = app.editMin.Value;
hi = app.editMax.Value;
% 参数保护:防止手滑输错导致判断崩溃
if isnan(lo) || isnan(hi) || lo >= hi
uialert(fig, "请检查上下限设置:下限必须小于上限且为有效数字。", ...
"参数错误", 'Icon', 'warning');
return;
end
app.statusLabel.Text = '状态: 采集中...';
app.statusLabel.TextColor = [0 0.6 0];
app.dataHistory = [];
app.sampleCount = 0;
cla(app.ax); hold(app.ax, 'on');
app.plotHandle = plot(app.ax, [], [], 'LineWidth', 2, 'Color', [0 0.4 0.8]);
ylim(app.ax, [0 120]);
drawnow;
start(app.timerObj);
end
function stopMonitoring()
stop(app.timerObj);
app.statusLabel.Text = '状态: 已停止';
app.statusLabel.TextColor = [0.5 0.5 0.5];
end
function updateData(~, ~)
app.sampleCount = app.sampleCount + 1;
% ===== 此处替换为真实传感器读取 =====
% 调试期用“带呼吸感的噪声”模拟温度/压力信号
newVal = 60 + randn*8 + 5*sin(app.sampleCount/10);
% ====================================
app.dataHistory = [app.dataHistory, newVal];
app.currentValueLabel.Text = sprintf('当前值: %.2f', newVal);
set(app.plotHandle, 'YData', app.dataHistory);
xlim(app.ax, [1 max(1, app.sampleCount)]);
drawnow limitrate; % 降低CPU占用,长时运行不卡
lo = app.editMin.Value;
hi = app.editMax.Value;
% 越限判断
if newVal < lo || newVal > hi
triggerAlarm(newVal, lo, hi);
else
app.statusLabel.Text = '状态: 采集中...';
app.statusLabel.TextColor = [0 0.6 0];
% 恢复正常时清除旧警报
if ~isempty(app.alarmAlert) && isvalid(app.alarmAlert)
delete(app.alarmAlert);
app.alarmAlert = [];
end
end
end
function triggerAlarm(val, lo, hi)
app.statusLabel.Text = sprintf('⚠ 越限报警! 当前: %.2f (范围: %.1f~%.1f)', val, lo, hi);
app.statusLabel.TextColor = [0.8 0 0];
alarmMsg = sprintf('传感器数据越限!\n当前值: %.2f\n允许范围: %.1f ~ %.1f\n请立即检查产线设备。', val, lo, hi);
% 防重复弹窗:先删旧的,再建新的
if ~isempty(app.alarmAlert) && isvalid(app.alarmAlert)
delete(app.alarmAlert);
end
app.alarmAlert = uialert(fig, alarmMsg, '越限警报', ...
'Icon', 'error', 'Position', [350 250 300 120]);
beep; % 系统提示音
disp(['[', datetime(now, 'Format', 'HH:mm:ss.SSS'), '] 报警触发: ', num2str(val)]);
end
%% 窗口关闭清理
fig.CloseRequestFcn = @(~,~) cleanup();
function cleanup()
stop(app.timerObj);
delete(app.timerObj);
if ~isempty(app.alarmAlert) && isvalid(app.alarmAlert)
delete(app.alarmAlert);
end
close(fig);
end
end
代码跑起来之后,咱们不照本宣科,直接拆成你在产线上最关心的几件事。
传感器数据是怎么“流”进来的
调试阶段你大概率还没接上真实的PLC或传感器,所以先用 randn 加正弦扰动模拟一条“有呼吸感”的数据。真实产线对接时,把 updateData 里那段注释掉的 newVal = ... 替换成串口读取、TCP/IP读取或者Modbus读寄存器就行。代码里保留了 sampleCount 和 dataHistory 数组,这样曲线能实时滚动,不会把内存撑爆。如果你通道多了,改成 dataHistory = [dataHistory; newVal] 做矩阵存储,或者用 circularBuffer 对象更稳。
越限判断为什么放在 drawnow limitrate 后面
很多新手会把判断写在更新曲线之前,结果弹窗和曲线不同步。这里先刷新UI,再判限,报警时状态栏会瞬间切红,警报框贴着当前画面弹出来。limitrate 比默认的 drawnow 更省CPU,产线长时间跑的时候不会让电脑风扇起飞。
警报弹窗的“防骚扰”设计
注意看 triggerAlarm 函数里的删除逻辑。传感器越限往往不是一瞬间的事,可能连续十几拍都超标。如果不删旧弹窗直接新建,GUI会被消息框淹没,最后卡死。这段代码每次触发前先清掉上一个警报,保证屏幕上永远只有一个主警报。同时用 beep 配合红色状态栏,视觉听觉双保险。实际产线里,你可以把 beep 换成调用外部声光报警器,或者发WebSocket/MQTT通知到车间看板。
怎么把这套东西接到真设备上
调试到中期,你肯定想换成真实数据。MATLAB支持几种主流方式,挑一个最贴合你产线的:
- 串口通信:
s = serialport("COM3", 115200); data = read(s, 1, "double"); - TCP/IP:
t = tcpclient("192.168.1.100", 5025); data = read(t, 4, "single"); - Modbus TCP:用
modbus.tcp.client读保持寄存器后换算工程单位。
接上真实数据后,记得在 updateData 开头加个 try-catch,网络抖动时别让整个GUI崩掉。产线调试不是实验室,断线重连是常态。
数据记录和追溯
报警不能只弹一下就算完。真实产线要追责、要分析趋势,所以我在代码末尾用 disp 打了时间戳日志。实际部署时,建议改成写入 .csv 或 SQLite:
csvFile = "sensor_alarm_log.csv";
if ~exist(csvFile, "file")
writematrix(["时间戳,传感器编号,当前值,下限,上限,报警类型"], csvFile);
end
row = [datetime(now, "Format","yyyy-MM-dd HH:mm:ss.SSS"), "S01", newVal, lo, hi, "OVER_LIMIT"];
writematrix(row, csvFile, "WriteMode", "append");
这样MES系统或者质量工程师一查就能定位是哪条产线、哪个时间段出的问题。
调参和排雷经验
- MATLAB版本低于 R2019a 的话,
uialert、serialport可能不支持,换成msgbox和serial对象即可,判断逻辑完全一样。 - 定时器周期别设太短。
0.5秒对大多数温度/压力传感器够用,如果设成0.01秒,UI刷新和判断会互相抢线程,反而漏报。 - 上下限输入框做了数值校验了吗?代码里已经补了一行保护,防止有人手滑输负数或下限大于上限。
- 如果你想让警报“必须确认才能消”,可以把
triggerAlarm里的uialert换成msgbox('确认无误后关闭', '报警', 'modal'),操作员点确定才继续采集。不过长期无人值守产线不建议这么干,容易误停。
这套面板现在是个单文件原型,但你完全可以把它当“骨架”往下长肉。加通道选择下拉框、报警阈值自动学习、历史曲线对比、甚至把MATLAB GUI嵌进LabVIEW或Web看板里,都是顺理成章的事。调试的时候遇到具体传感器协议或者报警逻辑卡住,随时把数据格式和产线环境甩过来,咱们接着往下调。