尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Simulink与App Designer实时通信:构建交互式控制界面

Simulink与App Designer实时通信:构建交互式控制界面 做控制仿真的人大概都经历过这样的尴尬模型在 Simulink 里跑得好好的可一旦想让别人看结果、调参数就只能把整个 MATLAB 窗口甩给对方或者截图发过去。更麻烦的是当你打算做一个演示系统、虚拟实验台或者上位机界面时Simulink 自带的 Scope 根本不够用——没有交互控件、没有参数面板、没有好看的界面布局。于是很多人会去搜“Simulink 如何和 APP 实时通信”“如何用 App Designer 显示 Simulink 仿真结果”然后发现资料非常零散有的讲了一半就断掉有的只演示了仿真结束后一次性导入数据根本没有实现真正的“实时”。实际上Simulink 与 MATLAB App Designer 的实时通信并不是一个黑科技而是由三个技术点组合出来的常规工程能力第一模型侧要能持续把数据输出来第二App 侧要有定时器按固定频率去拉取数据并刷新曲线第三两者之间要有一个共同的数据通道比如基础工作区Base Workspace、信号对象Simulink.Signal或外部模式External Mode。把这三点想清楚你就能用 App Designer 搭出一个堪比专业上位机的控制界面而且在纯软件环境下就能跑通不需要额外硬件。这篇文章会从核心概念开始讲清楚三种常见的实时通信方式然后以“二阶系统增益调参 实时曲线”为例子给出可以复制的完整代码和配置最后整理常见报错和工程最佳实践。读完你不仅能照抄代码还能知道出了问题该往哪里查。1. 为什么需要 Simulink 和 APP 实时通信很多初学者会困惑我仿真完了把yout数据拿到 App 里画个图不就实现通信了吗为什么还要专门研究“实时”区别在于使用场景。如果只是写报告、出论文图那一次仿真完再绘图完全够用。可如果你在做下面这些事一次性数据传输是远远不够的参数交互调试希望拖动一个滑块模型里的增益、PID 参数立刻变化曲线马上响应通过观察实时趋势来快速调参。这在控制算法整定、教学演示中非常常见。硬件在环或半实物仿真Simulink 模型连接真实硬件或上位机App 作为监控面板需要每几十毫秒刷新一次状态和曲线。产品原型与验收演示你的模型编译或仿真跑在后台界面展示给别人看不能让人看到 MATLAB 编辑器里的一堆模块而是看到一个设计感足够好的独立界面。多模型并行监控同时跑多个 Simulink 仿真实例每个实例的状态需要实时汇集到一个界面中。传统做法是先用sim命令跑完整个仿真然后在回调函数里把数据一次性绘制。这种方式的问题非常明显——仿真结束前页面上一个点都没有参数改一版要重新跑一遍才能看效果。调试周期长、交互感差本质上还是一个“批处理程序”而不是一个“实时系统”。真正需要的机制是模型持续运行App 侧持续刷新用户操作能反过来影响运行中的模型。这就是本文要解决的完整问题。2. Simulink 与 App Designer 实时通信的三种实现路径从通信架构上看Simulink 和 App 之间的实时交互有三个层次理解这三个层次你就能根据场景选技术方案。2.1 基于基础工作区 定时器轮询这是最常用、最容易上手的方案适合纯 MATLAB 环境下的实时演示。实现思路是Simulink 模型运行时把输出数据写入基础工作区例如tout和yout。App Designer 中创建一个timer定时器每隔 0.05 秒读取一次基础工作区里的最新数据。拿到数据后调用plot(app.UIAxes, t, y)刷新曲线。这个方案的优点是简单直观不依赖额外工具链缺点是不够“硬实时”定时器的最小刷新周期受 MATLAB 事件循环影响通常在 10ms 以上。对控制仿真教学、算法演示来说完全够用但如果你需要微秒级同步就不适合了。2.2 基于函数回调 仿真命令控制这是第一种方案的升级版也是本文重点演示的方案。它的关键在于不用sim命令把仿真一次性锁死而是用set_param(model, SimulationCommand, start)启动仿真让后台运行再通过模型回调比如OutputFcn在每个仿真输出步更新工作区数据。这样 App 侧既能读数据又能反过来修改模型参数例如拖动滑块时修改工作区变量K再调用set_param(model, SimulationCommand, update)让 Simulink 重新读取变量。这个方案具备真正的双向交互能力参数可以先改后跑、边跑边改非常适合“控制面板式”App。2.3 基于外部模式外部模式External Mode是 Simulink 的传统实时通信模式常用于把模型部署到真实硬件上例如 Arduino、树莓派或嵌入式目标机。在这种模式下Simulink 模型在目标硬件上实时运行App 或 Simulink 界面以固定步长实时采集数据。配置大体是set_param(mdl, SimulationMode, external); set_param(mdl, SolverType, Fixed-step); set_param(mdl, FixedStep, 0.01); set_param(mdl, SimulationCommand, connect); set_param(mdl, SimulationCommand, start);外部模式的优势是实时性强是硬件在环方案的正统路径缺点是需要对应的目标支持包配置复杂度高纯软件仿真时不一定能直接用。所以本文先讲前两种方案最后再补充外部模式的适用场景。3. Simulink 与 App Designer 实时通信的核心概念在进入实操前有几个概念必须先讲清楚否则后面代码会出现一堆莫名其妙的报错。3.1 工作区中的tout和youtSimulink 默认可以把仿真时间和输出端口数据记录到基础工作区。tout是时间向量yout是输出数据。自 R2016a 之后yout默认是一个Simulink.SimulationData.Dataset对象而不是单纯的矩阵。取值时要特别注意y yout{1}.Values.Data; % Dataset 对象取第一个信号的数据 t tout;很多新手直接拿yout当矩阵用然后出现维度错误原因就在这里。3.2 模型输出回调OutputFcnOutputFcn是 Simulink 模型的回调函数之一在每个仿真输出步Output Step结束时被调用。我们可以利用它把最新数据同步到基础工作区。配置方式是set_param(mdl, OutputFcn, updateFromSim(plant_demo));这句话的意思是每个仿真步结束时执行updateFromSim(plant_demo)这个函数。这个函数需要自己写并且必须放在 MATLAB 搜索路径中。它的作用是把tout和yout从基础工作区读取出来然后转换成 App 绘图方便使用的普通变量。3.3 定时器timerMATLAB 自带timer对象可以在 App 中创建一个定时器周期性地执行某个函数。例如app.timer timer(... TimerFcn, (~,~) app.refreshPlot(), ... Period, 0.05, ... ExecutionMode, fixedSpacing);Period单位是秒0.05 表示每 50ms 刷新一次。ExecutionMode设为fixedSpacing表示每次回调结束后固定间隔触发比fixedRate更容易避免回调堆积。3.4 信号对象Simulink.Signal如果你需要在 Simulink 模型之外定义一个全局信号可以让模型中的Data Store Memory或Goto From标签与基础工作区中的Simulink.Signal对象关联。mySig Simulink.Signal; mySig.CoderInfo.StorageClass ExportedGlobal; mySig.DataType double; mySig.InitialValue 0; assignin(base, simOutSig, mySig);这样App 侧可以直接读写simOutSig模型内部也能使用同一个信号对象数据共享会非常方便尤其在后续做 C 代码生成时特别有用。4. 环境准备本文示例基于以下环境版本不同可能有细节差异但思路通用环境项建议配置MATLABR2020a 及以上版本App Designer 已经比较成熟Simulink基础模块库即可工具箱不需要额外工具箱操作系统Windows / macOS / Linux 均可建议安装好 MATLAB 后先运行ver确认Simulink已经出现在工具箱列表里。如果用的是 MATLAB Online同样支持上述功能。5. 完整实现Simulink 模型 App Designer 实时绘图下面我们用一个小项目把整个流程走通。项目目标是搭建一个二阶系统模型通过 App Designer 界面控制输入增益 K并实时显示系统输出曲线。5.1 创建 Simulink 模型新建一个模型命名为plant_demo.slx并搭建以下结构In1输入模块输入信号 u。Gain增益模块增益参数设置为K。Transfer Fcn传递函数模块分子为[1]分母为[1 2 5]也就是一个典型的二阶振荡系统。Out1输出模块输出 y。模型结构等价于u —— K —— 1/(s^2 2s 5) —— y把传递函数分母写成[1 2 5]对应自然频率 √5、阻尼比 1/√5模型稳定且动态效果明显。然后在 MATLAB 命令窗口先初始化参数K 1.5; assignin(base, K, K); load_system(plant_demo);这一步很重要Gain 参数填的是变量名K而不是数字所以必须先把K写入基础工作区Simulink 才能解析。5.2 配置模型输出记录为了让模型在运行过程中持续输出数据需要开启输出端口记录。在模型初始化脚本或 App 启动回调中执行mdl plant_demo; set_param(mdl, SaveOutput, on); set_param(mdl, OutputSaveName, yout); set_param(mdl, SaveTime, on); set_param(mdl, TimeSaveName, tout); set_param(mdl, StopTime, 30);运行后tout和yout会不断被写入基础工作区。注意set_param设置的OutputSaveName和TimeSaveName必须和后续读取的变量名一致。5.3 编写数据同步函数新建一个 MATLAB 函数文件updateFromSim.m内容如下% updateFromSim.m % 每个仿真输出步被 Simulink OutputFcn 调用将最新数据同步到基础工作区 function updateFromSim(mdl) try % 从基础工作区读取 Simulink 写入的输出数据 t evalin(base, tout); yv evalin(base, yout); % yout 可能是 Dataset 对象也可能是矩阵统一转成矩阵 if isa(yv, Simulink.SimulationData.Dataset) yData yv{1}.Values.Data; else yData yv; end % 再存回基础工作区后续 App 定时器直接读取 assignin(base, lastTData, t); assignin(base, lastYData, yData); catch % 第一个输出步之前数据可能不存在静默处理即可 end end把updateFromSim.m放到 MATLAB 当前目录或搜索路径下。注意OutputFcn是在 Simulink 执行引擎中被调用的这个函数必须能被 MATLAB 自动找到否则仿真开始后立刻报“未定义函数”。5.4 新建 App Designer 界面在 MATLAB 命令行输入appdesigner创建一个空白 App然后手动添加以下控件控件类型名称 Tag作用按钮StartButton启动仿真按钮StopButton停止仿真滑块KSlider实时调整增益 K坐标轴UIAxes实时显示输出曲线标签StatusLabel显示当前运行状态布局建议左侧放参数面板和按钮右侧大面积放坐标轴。滑块范围设成 0.1 到 5初始值 1.5。5.5 编写 App 回调逻辑切到 App Designer 的“代码视图”在properties (Access private)中加入运行状态字段properties (Access private) isRunning false; % 标记仿真是否在运行 timer % 定时器对象 end然后添加启动按钮回调function StartButtonPushed(app, event) if app.isRunning return; end mdl plant_demo; % 1. 保证模型已加载 load_system(mdl); % 2. 写入初始参数 K app.KSlider.Value; assignin(base, K, K); % 3. 配置输出记录 set_param(mdl, SaveOutput, on); set_param(mdl, OutputSaveName, yout); set_param(mdl, SaveTime, on); set_param(mdl, TimeSaveName, tout); set_param(mdl, StopTime, 30); % 4. 清空旧数据避免串数据 evalin(base, clear tout yout lastTData lastYData); % 5. 设置输出回调 set_param(mdl, OutputFcn, updateFromSim(plant_demo)); % 6. 启动仿真非阻塞方式 set_param(mdl, SimulationCommand, start); % 7. 启动定时器定期刷新绘图 app.timer timer(... TimerFcn, (~,~) app.refreshPlot(), ... Period, 0.05, ... ExecutionMode, fixedSpacing); start(app.timer); app.isRunning true; app.StatusLabel.Text 运行中; end停止按钮回调function StopButtonPushed(app, event) if ~app.isRunning return; end mdl plant_demo; set_param(mdl, SimulationCommand, stop); if ~isempty(app.timer) isvalid(app.timer) stop(app.timer); delete(app.timer); end app.isRunning false; app.StatusLabel.Text 已停止; end滑块回调实现运行中实时调参function KSliderValueChanging(app, event) app.K event.Value; if app.isRunning % 更新基础工作区变量 assignin(base, K, app.K); % 通知 Simulink 重新读取工作区变量 set_param(plant_demo/ Gain, Gain, K); set_param(plant_demo, SimulationCommand, update); end end注意滑块控件的 Tag 如果是KSliderValueChangingFcn回调里的event.Value是滑块拖动过程中的连续值如果想要滑块松手后才更新可以改用ValueChangedFcn。绘制刷新函数function refreshPlot(app) try t evalin(base, lastTData); y evalin(base, lastYData); if ~isempty(t) ~isempty(y) plot(app.UIAxes, t, y, b-, LineWidth, 1.5); xlabel(app.UIAxes, Time (s)); ylabel(app.UIAxes, Output y); grid(app.UIAxes, on); drawnow limitrate; end catch % 数据尚未生成时静默等待 end end5.6 信号对象方式的变体如果你希望数据不通过tout/yout传递而是使用信号对象作为全局共享变量可以改用这种方式在模型中加入一个Data Store Memory模块双击配置“信号名称”为simOutSig“数据源”选择“基础工作区中的 Simulink.Signal 对象”。然后在模型内部通过Data Store Write和Data Store Read读写这个信号。MATLAB 侧创建信号对象并写入初值sig Simulink.Signal; sig.CoderInfo.StorageClass ExportedGlobal; sig.DataType double; sig.InitialValue 0; assignin(base, simOutSig, sig);之后 App 侧读取数据时直接使用evalin(base, simOutSig)。这种方式更接近嵌入式开发的全局数据交互思路为后续学习代码生成和硬件部署打基础。6. 运行结果与效果验证运行 App 后点击“启动”按钮预期效果如下。6.1 正常效果模型开始运行状态标签变为“运行中”。坐标轴上开始出现一条从 0 时刻向右增长的曲线。曲线每隔约 50ms 刷新一次视觉上是平滑的真实时曲线。拖动滑块调整 K 值曲线形态会发生变化例如增大 K 会让输出幅值变大。点击“停止”仿真停止曲线保留在坐标系中不会立刻消失。这里的判断标准很简单曲线是否随时间持续增长、是否在拖动参数时发生变化。如果满足说明 Simulink 和 App 的实时链路是通的。6.2 检查链路是否工作如果界面没有曲线按以下顺序检查% 在命令窗口查看数据是否产生 evalin(base, whos lastTData lastYData) % 查看最新时间点数值 evalin(base, lastTData(end))如果lastTData存在且不断变大说明数据链路没问题问题在绘图刷新环节如果lastTData不存在说明OutputFcn没有被调用或者模型没有启动。6.3 验证参数实时修改运行过程中可以在命令窗口查看% 查看当前 K 值 evalin(base, K)然后拖动滑块再次执行上述命令如果 K 值跟着变同时模型输出变化说明参数交互链路是通的。7. 常见问题与排查思路下面是这个项目最常见的几个问题和排查方案基本覆盖了新手会踩的大部分坑。问题现象可能原因排查方式解决方案点击启动后模型不运行模型名称写错或未加载执行load_system(plant_demo)检查是否报错确认模型文件在当前路径文件名与回调中一致曲线不刷新定时器未启动或OutputFcn未写入数据检查lastTData是否存在启动回调中创建 timer确认updateFromSim.m在搜索路径报错Undefined function updateFromSim回调函数不在 MATLAB 路径执行which updateFromSim把函数文件放到当前目录或addpathyout维度不对没有把 Dataset 对象转成矩阵检查yout{1}.Values.Data在updateFromSim中做类型判断转换滑块拖动后曲线无变化工作区变量更新后 Simulink 没有刷新检查K值是否变化执行set_param(mdl, SimulationCommand, update)外部模式连接失败求解器不是固定步长查看 SolverType 设置改为 Fixed-step 并设置合理的 FixedStep模拟运行时 App 卡死使用了阻塞式sim命令运行完整仿真拆分为 start OutputFcn timer改用非阻塞的SimulationCommand方式停止后再启动出现数据残留上一次运行的输出未清空检查tout/yout启动前执行clear tout yout lastTData lastYData7.1 最容易忽视的坑回调函数路径OutputFcn里的函数名在 Simulink 引擎中运行它不依赖 App Designer 的上下文。如果你把函数写成 App 的私有方法Simulink 可能找不到因为OutputFcn是按字符串形式执行的不会自动访问 App 的方法。最稳妥的做法是创建一个独立的.m文件放在 MATLAB 路径下函数只用基础工作区和全局数据不依赖任何 App 对象。这是一个非常重要但容易忽略的工程细节。7.2 最容易搞混的概念Dataset 对象yout在较新版本 MATLAB 中是Simulink.SimulationData.Dataset对象。直接对它调用plot或者cellfun都可能报错。遇到“索引超出数组边界”或“未定义操作符”这类问题第一步先检查yout的类型再决定如何取数。建议在数据同步函数里统一完成转换避免在绘制函数里反复判断。8. 最佳实践与工程建议8.1 命名规范建议项目文件统一命名Simulink 模型xxx_demo.slx或xxx_model.slx数据同步函数updateFromSim.mApp DesignerxxxApp.mlapp工作区变量统一前缀例如lastTData、lastYData、simOutSig规范的命名能减少回调函数中字符串拼接的出错率。尤其是set_param里的模块路径例如plant_demo/ Gain模块名和路径之间最好不要有容易混淆的额外空格。8.2 数据刷新频率设计timer的刷新周期不是越小越好。刷新太快绘图回调频繁调用plot会拖慢整个 App甚至导致界面卡死。刷新太慢曲线看起来像“跳变”实时感差。推荐经验值模型仿真步长FixedStep设为 0.01 到 0.05 秒。App 定时器刷新周期设为仿真步长的 2 到 5 倍例如 0.05 秒。绘制时尽量使用drawnow limitrate避免强制刷新所有图形对象。这样既能保证曲线平滑又能让界面保持流畅。8.3 异常处理与资源释放仿真启动后如果 App 窗口被关闭定时器可能残留导致 MATLAB 后台一直刷新。建议在 App 的CloseRequestFcn或delete回调中主动停止仿真并清理定时器function UIFigureCloseRequest(app, event) if app.isRunning set_param(plant_demo, SimulationCommand, stop); end if ~isempty(app.timer) isvalid(app.timer) stop(app.timer); delete(app.timer); end delete(app.UIFigure); end这段代码虽然不是每个项目都必需但在长期运行或频繁开关窗口的场合能避免很多隐性问题。8.4 模型备份与安全边界如果在一个正式项目里改 Simulink 模型的参数建议先备份原始模型文件。每次通过set_param修改模块参数前可以先读取原值保存到一个变量里便于回滚。originalGain get_param(plant_demo/ Gain, Gain);尤其在生产环境中改错一个参数可能导致整个仿真结果不可信。养成保存原值和备份模型的习惯能省很多事。8.5 版本兼容建议MATLAB R2018b 之前App Designer 的实时绘图写法可能需要调整。Simulink.SimulationData.Dataset在 R2016a 之后是默认格式。外部模式在较新版本中建议配合对应硬件的支持包使用。如果读者用的是旧版本可以优先考虑把yout的存储格式改成矩阵。在模型配置中输出类型选择“Array”这样yout直接就是矩阵能避免很多兼容问题。8.6 从软件仿真到硬件在环的迁移当你熟悉了 App Designer 和 Simulink 的实时通信逻辑后下一步可以尝试把模型部署到真实硬件上。外部模式是走向硬件在环的必经之路它和本文前两种方案的核心思路一致都是“模型持续运行 数据实时回传 界面实时刷新”不同的是数据通道从工作区变成了串口、UDP 或 TCP/IP模型运行目标从本地变成了硬件。9. 总结Simulink 和 App Designer 的实时通信本质上就是把“计算引擎”和“交互界面”解耦。Simulink 负责跑模型App Designer 负责展示曲线和接收操作指令数据通过工作区变量、信号对象或外部模式这三个通道来沟通。本文给出的方案重点解决了两件事一是让 Simulink 在后台持续运行用OutputFcn把最新数据同步出来二是让 App 通过timer周期性地读取并绘制配合set_param(...,SimulationCommand,update)实现运行中实时调参。这套方法不需要额外硬件只需 MATLAB Simulink 就能跑通适合课程设计、算法演示和快速原型验证。建议你在自己电脑上照着代码敲一遍先跑通最简单的“二阶系统 增益调节”示例再把它扩展到你自己的模型上。遇到问题优先按第 7 节的排查表逐项检查大部分问题都出在变量名不一致、回调函数不在路径、Simulink.SimulationData.Dataset类型转换这三类原因上。如果你准备往嵌入式方向走下一步可以重点研究外部模式、Simulink Coder 代码生成和信号对象StorageClass的配置。你会发现今天学到的实时通信思路正是上位机与下位机完整链路的基础。建议收藏这篇文章做项目时按步骤对照排查能省下不少搜资料的时间。
返回列表