ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Unity与西门子PLC通信:基于S7.net的输送机数字孪生仿真实践

Unity与西门子PLC通信:基于S7.net的输送机数字孪生仿真实践 简介一套输送机Unity仿真与博图通信资源面向工业自动化与Unity开发者使用Unity 2020.3.15f2搭建三维场景并通过C#与S7NetPlus库连接西门子S7系列PLC实现虚拟输送机与TIA Portal V16中PLC程序的实时双向数据交互适用于设备调试、产线验证和工业培训等场景。压缩包共846个文件、约51.68MB包含Unity工程常用的场景、预制体、FBX模型、C#脚本、材质与PNG/EXR纹理以及TIA Portal V16的AP16工程文件文件类型划分清晰便于按模块理解整体架构。目前已有377人学习下载。借助S7NetPlus封装的通信功能开发者不用深入底层协议即可读写PLC数据结合博途项目中的控制逻辑可完整复现加热输送机从启停、运行监控到状态反馈的联调流程。这套工程既能帮助工程师快速搭建虚拟调试环境也能为学生学习Unity与工业自动化融合开发提供可运行的参考模板便于在此基础上进行二次功能扩展。1. 输送机Unity仿真与博图通信的定位先把数字孪生的通信链路说清楚一条传送带在博途里跑得再好也只是梯形图上的波形把它放进 Unity 场景里动起来才显露出数字孪生那层价值。这个标题里的组合——Unity 2020.3.15f2 做表现、TIA Portal V16博途做逻辑、S7.net 在 C# 侧做协议桥——是西门子生态里比较省事的一条链路不需要额外硬件不需要 OPC 网关PLC 侧只开一个 PUT/GET场景里的辊筒、皮带和物料就能跟随 DB 块里的位和字实时动作。下面按这条链路把每个环节的参数和坑讲清楚博途侧动哪几个开关、Unity 侧脚本怎么组织、通信表怎么定义、跑不通时先查哪里。适合做输送线虚拟调试、设备数字孪生演示的工程师手头正好有 1200/1500 加 PLCSIM 的话照着做一遍就能通。2. S7.net 在 Unity 与博途通信里的选型理由和 TIA Portal V16 侧的准备2.1 为什么选 S7.net 而不是 OPC UA 或 Modbus TCP输送机这类设备PLC 侧通常是 S7-1200 或 S7-1500。Unity 要跟西门子 PLC 通信常见三条路线我的选择逻辑很直接先把 OPC UA 和 Modbus TCP 的成本摆出来再看 S7 协议够不够用。OPC UA 在 S7-1500 上自带服务器但 1200 的 OPC UA 能力受固件限制而且涉及证书、端点、命名空间、订阅周期一堆概念Unity 侧还要引入独立的 OPC UA 客户端库。链路每多一层联调时就多一层排查面。Modbus TCP 看着简单却要在 PLC 程序里专门调用 MB_SERVER 指令块把 DB 区映射成保持寄存器地址规划和工艺程序耦合在一起后面每次改点都要两头对表。S7.net 对应的原生 S7 协议跑在 TCP 102 端口PLC 侧不需要写任何通信程序C# 这边一个 Plc 对象就能连上直接按 DB 号和字节偏移读写。S7.net 的 C# 实现是 NuGet 上的 S7netplus命名空间为 S7.Net内置 S71200 和 S71500 两种 CPU 类型与类型化读取方法比手写二进制帧要省一个量级的代码。它的短板也明显单连接、请求响应式轮询不适合高频大包采集但输送机这种几十个点位、几十毫秒周期的场景完全在能力范围内。如果现场组网对端口和加密通道有硬性要求S7 协议裸跑 102 端口不一定被允许那种场景再回头评估 OPC UA。单台虚拟调试机联调无脑选 S7.net。2.2 博途项目里必须打开的开关PUT/GET、访问方式与连接资源在 TIA Portal V16 里组态好 CPU 之后通信联调前要做三个动作缺一个 Unity 都连不上。这三个开关我在每个输送机项目里都会先检查一遍因为它们各自对应一类典型故障现象。第一个是 PUT/GET 许可。在博途里选中 CPU进属性 → 防护与安全 → 连接机制勾选「允许来自远程通信伙伴的 PUT/GET 通信访问」。S7-1500 上还要顺手确认「允许 HMI 的 PUT/GET 访问」没有被单独关掉因为 1500 把这个权限拆成了独立选项。这个开关不开Unity 侧的表现是 Open 超时或者偶尔握手上但读出全零。第二个是 DB 的访问方式。通信用的 DB 块要在属性里取消勾选「优化的块访问」改完重新编译。优化访问下变量偏移由编译器决定S7.net 的字符串寻址拿不到确定字节偏移读 DB1.DBW2 会直接抛地址错误。第三个是连接资源。S7-1200 的 PUT/GET 占用通信连接资源如果 PLC 上已经挂了 HMI、触摸屏和编程器连接数可能不够现象是连上后周期性断线或者在 PLC 侧看不到这条连接。下面这张表是排查时的快速对照现象优先检查项在博途里的位置Open 超时 / SocketExceptionPUT/GET 许可、IP 和机架槽位CPU 属性 → 连接机制读地址抛错或数据全零DB 的优化访问未关闭DB 属性 → 一般连上后周期性掉线连接资源被占满CPU 属性 → 资源机架和槽位这里多说一句S7-1200 固定用 Rack 0、Slot 1S7-1500 常见也是 Rack 0、Slot 1。从别的项目拷来的代码如果写的是 Slot 2连 1200 时必然失败这在现场是最容易被忽略的「配置正确但连不上」的原因。2.3 先用 PLCSIM V16 验证链路再碰实体 PLC虚拟调试阶段不要一上来就抱实体 PLC。TIA Portal V16 自带 PLCSIM把项目下载进 PLCSIM 后它在本机监听 102 端口Unity 或任意 C# 程序把 IP 写成 127.0.0.1 就能通信。这一步的意义是先把「协议、地址、数据类型」这类软件问题清干净再上实物去排查网线、防火墙和 IP 冲突。PLCSIM 有两个限制要先记住它只接受本机连接且同一时刻只能运行一个模拟实例。如果之前开过别的 PLCSIM 没关干净新项目下载会失败去任务管理器把 PLCSIM 相关进程结束再试。实体 PLC 联调时把 IP 改成 PLC 实际地址机架槽位不动其余代码完全复用。提示PLCSIM 在线修改变量后如果 DB 勾选了「保持值」重启模拟后旧值还在容易让人误判通信是否真的通。3. Unity 2020.3.15f2 下的 C# 源码S7.net 连接封装与传送带运动3.1 把 S7.net 放进 Unity脚本运行时版本与引用方式Unity 2020.3.15f2 接入 S7.net 的第一步是环境准备。我一般从 NuGet 把 S7netplus 的依赖拿下来把 S7.Net.dll 复制到 Assets/Plugins 目录然后打开 Player Settings把 Api Compatibility Level 从 .NET Standard 2.0 改成 .NET 4.x。不改的话IL2CPP 打包时可能把 S7.Net 用到的网络与线程类型裁掉编辑器里跑得好好的打包出来反而连不上。接入之后要立一个规矩S7.Net 的 Plc 对象不是线程安全的整个项目共享一个实例所有读写收口到一个类里。不要在每个 MonoBehaviour 里各 new 一个 Plc那样连接数会迅速被占满而且断线重连逻辑各自为政现场会非常难排查。3.2 最小连接代码与断线重连下面这段是挂在场景空物体上的连接管理器职责是打开连接、暴露读取接口、断线自动重连并在退出时关闭连接using S7.Net; using UnityEngine; public class PlcConnector : MonoBehaviour { public string plcIp 127.0.0.1; // PLCSIM 用本机实体 PLC 改为实际 IP public int rack 0; public int slot 1; private Plc _plc; private float _nextReconnectTime; public bool IsConnected _plc ! null _plc.IsConnected; void Awake() { _plc new Plc(CpuType.S71200, plcIp, rack, slot); } void Update() { if (_plc null) return; if (!_plc.IsConnected Time.time _nextReconnectTime) { try { _plc.Open(); Debug.Log($S7 连接成功: {plcIp}); } catch (PlcException ex) { Debug.LogWarning($连接失败: {ex.Message}); _nextReconnectTime Time.time 3f; // 3 秒后重试 } } } public object Read(string address) { return IsConnected ? _plc.Read(address) : null; } void OnApplicationQuit() { _plc?.Close(); } }Plc(CpuType.S71200, plcIp, rack, slot)四个参数依次是 CPU 型号、IP、机架号、槽位号CPU 型号选错会在 Open 时直接抛错。重连放在 Update 里而不是 Start 里死等是为了不让连接失败卡住场景加载3 秒重连间隔是经验值现场如果 PLC 频繁重启可以改成指数退避。Read(string)是 S7.Net 的字符串寻址入口参数形如DB1.DBX0.0位或DB1.DBW2字返回值是 object实际类型由目标地址决定。这几个参数的对应关系如下参数典型值说明CpuTypeS71200 / S71500必须与 PLC 型号一致rack0S7-1200/1500 固定slot1S7-1200/1500 常用槽位3.3 把 DB 信号变成传送带运动状态映射与材质偏移输送机仿真里最常见的需求是 PLC 给一个运行位和一个速度字Unity 里的皮带视觉和辊筒跟着动。皮带这类循环纹理不适合直接移动模型我一般让材质的主纹理偏移和速度挂钩这样不用真建模一条带齿的皮带也能在视觉上看到连续的输送效果public class ConveyorBelt : MonoBehaviour { public PlcConnector connector; public Renderer beltRenderer; public Transform[] rollers; [Range(0f, 2f)] public float speedScale 1f; private bool _run; private ushort _speedSet; void Update() { object runObj connector.Read(DB1.DBX0.0); object speedObj connector.Read(DB1.DBW2); if (runObj null || speedObj null) return; _run (bool)runObj; _speedSet (ushort)speedObj; float ratio Mathf.Clamp01(_speedSet / 1000f); // 0-1000 归一化 float speed _run ? ratio * speedScale : 0f; Vector2 offset beltRenderer.material.mainTextureOffset; offset.x Mathf.Repeat(offset.x speed * Time.deltaTime * 0.1f, 1f); beltRenderer.material.mainTextureOffset offset; foreach (var roller in rollers) { roller.Rotate(Vector3.forward, speed * Time.deltaTime * 60f, Space.Self); } } }这段代码的关键是信号与表现解耦_run决定转不转_speedSet决定转速两者都来自 DB1。Mathf.Repeat让纹理偏移在 0 到 1 之间循环避免长时间运行造成浮点精度问题。辊筒用固定角速度倍数旋转想更真实就按辊筒半径换算线速度。一个常见误用是把connector.Read的结果直接驱动transform.position每帧两三次网络往返PLC 值在读取间隙变了表现就是物料抖动。4. 博途项目侧的数据规划输送机 DB 布局、访问方式与通信表4.1 输送机仿真 DB 块怎么排地址通信之前PLC 侧必须有一个结构清晰的 DB 块。输送机我一般建两个区命令输入区Unity 发给 PLC和状态输出区PLC 发给 Unity所有变量集中在一个 DB 里方便 PLCSIM 在线监视和 C# 侧统一读取。下面是压缩后的 DB1 布局字节偏移符号名数据类型方向说明DBX0.0RunCMDBoolUnity → PLC运行命令DBX0.1AlarmResetBoolUnity → PLC故障复位DBW2SpeedSetIntUnity → PLC目标转速 0-1000DBD4RunTimeSecRealPLC → Unity累计运行时间DBX8.0MatAtSensorBoolPLC → Unity物料到位信号DBW10CurrentFreqIntPLC → Unity变频器当前频率排布原则有三条。第一Bool 集中靠前放凑满 8 个位再放字避免奇数偏移把字地址拆开。第二速度值用 Int 而不是 RealUnity 侧归一化简单也避开了浮点字节序的歧义真需要连续调速把数值放大 10 倍传输比如 1234 代表 123.4。第三每个点位都要标明方向和单位这张表最终要和 C# 侧常量类一一对应项目交接时它就是唯一的通信依据。这个 DB 在博途里用标准访问创建符号名和注释都写全编译后绝对地址就是上面表格里的偏移。字段顺序一旦定下来后期不要往中间插变量只往后追加否则偏移全部变化Unity 侧地址表全要改。4.2 优化块访问和标准访问到底选哪个PLC 程序员在博途里默认建 DB 都是优化访问这对 HMI 和内部程序更友好但对 S7.net 是灾难。优化访问下DB 变量的实际偏移由编译器决定且可能随版本变化外部按DB1.DBW2这种绝对地址访问会直接报地址越界。S7.net 的字符串寻址完全不支持优化访问的符号名所以通信 DB 必须改成标准访问。改法在 2.2 节已经提过DB 属性里取消「优化的块访问」重新编译变量偏移就固定下来。代价是这种 DB 的扩展性差升级插入变量会顶掉后续偏移所以通信 DB 要单独建块别和工艺逻辑混在一起。如果项目规范强制用优化块备选做法是在 PLC 里另建一个标准访问的「通信镜像 DB」把优化块里的关键变量用 MOVE 指令周期性同步过去。这是西门子虚拟调试项目里常见的折中方案代价是 PLC 里多一段同步代码优点是安全认证不受影响。判断当前 DB 是不是标准访问最直接的办法是编译后看 DB 的监控表里是否显示字节偏移。显示偏移的就是标准访问显示一堆符号名和 ID 的就是优化访问。4.3 通信表与字节序C# 侧常量和字节序坑通信表同步比任何文档都可靠。我习惯在 Unity 里建一个静态类把所有地址写成常量避免字符串散落在各个脚本里改地址时只动一处public static class ConveyorPlcMap { public const string RunCMD DB1.DBX0.0; public const string AlarmReset DB1.DBX0.1; public const string SpeedSet DB1.DBW2; public const string RunTimeSec DB1.DBD4; public const string MatAtSensor DB1.DBX8.0; public const string CurrentFreq DB1.DBW10; }所有业务脚本引用这个静态类而不是直接写字符串哪怕通信表大改也能用编译错误快速定位遗漏点。字节序是这条链路上最容易翻车的一层S7 协议走大端高位在前PC 是小端。S7.Net 的类型化Read内部已经做了转换所以(ushort)Read(DB1.DBW2)拿到的数字和博途在线监视一致但一旦用ReadBytes做批量读取拿到的是原始字节数组必须自己处理字节序这个放在最后一章展开。5. 用 ReadBytes 做帧读取顺带完成全链路验证5.1 一次读取一整帧避开头尾不一致几十个点位逐个 Read会产生几十次 TCP 往返而且读到第 20 个点时前面几个点的数据已经是上一个周期了。做法是把连续地址段一次读回在本地解码byte[] frame (byte[])_plc.ReadBytes(DataType.DataBlock, 1, 0, 12); bool runCmd (frame[0] 0x01) ! 0; bool alarmReset (frame[0] 0x02) ! 0; ushort speedSet (ushort)((frame[2] 8) | frame[3]); float runTime BitConverter.ToSingle(frame, 4); bool matSensor (frame[8] 0x01) ! 0;ReadBytes的四个参数依次是数据区类型、块号、起始偏移、字节长度这里读 DB1 的前 12 字节。位按字节内掩码提取字手动拼大端浮点BitConverter.ToSingle在小端机上直接读大端字节会出错这是整条链路里唯一需要手写字节序处理的地方。5.2 轮询线程与 UI 更新分离把帧读取放进后台线程每 50ms 读一次解码结果写进共享结构体Unity 主线程在 Update 里做一次锁拷贝再驱动表现。这样 C# 上位机常见的「循环数据采集和 UI 刷新卡顿」问题不会出现阻塞式网络调用彻底离开主线程渲染线程永远拿最近一帧的一致数据。5.3 用点动功能收尾验证链路搭完做一次点动验证最直接在博途里把 DB1.DBX0.0 置 1、DBW2 置 500Unity 里皮带应立即半速转动再把点动命令写回 PLC日志能打出一组运行位和速度字的跳变记录说明双向链路都通。PLCSIM 下注意「保持值」会留下旧变量干扰判断验证前先清一遍否则上电瞬间的误动作会让你误以为通信时序有问题实际只是模拟器把上一次的运行状态带进了这一次会话。本文还有配套的精品资源点击获取
返回列表