ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TwinCAT与.NET通讯:用ADS实现PLC Runtime远程启停控制

TwinCAT与.NET通讯:用ADS实现PLC Runtime远程启停控制 如果你手上有台倍福TwinCAT的PLC或者你在做一个需要对接Beckhoff控制器的上位机项目那么“从外部把PLC Runtime启动或停止”这个需求迟早会找上门。产线需要无人化运行工控机重启后要自动拉起PLC程序中控室要远程复位一个卡死的任务调试人员不想每次改完逻辑都跑到设备柜前面去点按钮。这些场景都指向同一个技术点用.NET程序通过ADS和PLC通讯并控制它的启停。Beckhoff官方提供了ADS示例集合Sample14这个示例就是专门讲这件事的。我第一次拿到Sample14时第一反应是“这玩意儿不就在TwinCAT里点个按钮么”真自己动手写代码才发现坑不少。ADS通讯模型和普通Socket编程完全不一样.NET环境与TwinCAT版本之间的匹配问题就能卡住一批人。这篇文章我会从示例用途、通讯原理、代码实现、常见坑、工程化改造五个方面把Sample14完整拆开讲适合刚接触TwinCAT与.NET通讯的工程师参考也可以作为老手快速上手的速查笔记。1. 先弄明白Sample14在解决什么问题1.1 PLC启停控制究竟用在哪些场景很多人以为“PLC启停控制”就是指在TwinCAT界面上点一下运行、点一下停止实际上这是两码事。界面上的操作属于开发态操作而Sample14演示的是运行态外部控制——也就是说PLC程序已经在跑或者没有在跑你通过一个独立的.NET程序用ADS协议去改变TwinCAT Runtime的状态。这个能力在项目落地阶段非常关键。举几个我实际遇到过的场景。第一条中控室远程启停。设备分布在车间不同区域操作员不可能每次跑到电柜前面去操作TwinCAT中控系统通过.NET服务去控制每台PLC的启停就会方便很多。第二条工控机重启后自动拉起PLC任务。系统意外断电恢复后上位机软件往往需要自动把PLC Runtime启动起来否则生产线不会自己恢复。第三条产线联动急停。当上下游设备检测到故障时由统一的调度程序去停掉相关PLC这就不是PLC内部逻辑能解决的了需要用外部程序做跨设备联动。Sample14的价值就在于它把这一整套流程做成一个相对完整的示例建立ADS通讯、读取PLC当前状态、发出启停指令、监控状态变化。你把这个示例跑通一次底层原理就清楚了大半后面做任何二次开发都会顺手很多。1.2 ADS通讯模型AMSRouter、AmsNetId与端口要理解Sample14必须先弄懂ADS通讯的基本模型。ADSAutomation Device Specification是Beckhoff定义的一套通讯规范它不关注物理层用的是EtherCAT、以太网还是串口它关注的是不同设备之间怎么交换数据。在TwinCAT系统里每台设备都有一个唯一的AmsNetId类似IP地址格式是“x.x.x.x.x.x”比如最常见的是“127.0.0.1.1.1”。如果你在TwinCAT的路由器配置里看到本机NetId通常就是长这样的。ADS报文不是直接发到PLC端口上的它需要经过AMSRouter这一层。AMSRouter是TwinCAT系统中的一个服务你可以把它理解成快递中转站。所有ADS报文都先到这个中转站再由它分发到目标设备的对应端口。Sample14里的.NET程序发出一个请求包到达本机AMSRouterRouter根据AmsNetId和端口号把请求交给TwinCAT Runtime来处理处理结果再原路返回。这个过程看起来绕但好处是通讯的物理细节被完全屏蔽了你不需要关心对方走的是哪个网卡、用的是什么协议。端口号这个东西也容易搞混。ADS通讯里的端口号不是IP端口而是设备内部的服务地址。典型的几个端口851是PLC Runtime 1852是PLC Runtime 210000左右是IO设备等。Sample14控制PLC启停连的就是Runtime端口默认就是851。如果你连接的PLC系统里配置了多个Runtime就需要根据实际环境选择对应端口。1.3 官方示例还有哪些值得借鉴的设计Beckhoff的ADS官方示例是一个系列工程Sample14只是其中一个。它在整个系列里的定位不是“入门hello world”而是“控制类操作演示”。和纯读写变量的示例不同控制PLC启停涉及到状态机、指令应答、异步通知这些更复杂的机制所以示例里会有不少值得揣摩的细节。比如它没有用最简单的“发一条指令然后不管结果”的写法而是先查询当前状态再决定是否发送启停指令之后还会持续监听状态变化。这就是一个很工程化的设计思路控制类操作不能盲发必须要有状态确认机制。另外示例对PLC状态的处理也不是一次性读取而是通过ADS通知Notification订阅状态变量的变化这样既省CPU又能实时刷新界面状态。这个设计思路我会在后面专门展开讲。2. 开发环境准备.NET Framework与TwinCAT环境的匹配2.1 到底该用哪个.NET版本这个问题看起来基础但真能卡住人。Sample14是年代比较早的示例最初是基于.NET Framework 4.0写的而现在的开发机通常已经装上了.NET Framework 4.8甚至可能预装了更高版本。很多人在打开示例工程时VS会提示“目标框架不受支持”或者“这台计算机中已经安装了 .NET Framework 4.8 或版本更高的更新”然后工程就编译不过。解决思路其实就两条要么把目标框架改成你机器上实际有的版本比如4.7.2或者4.8要么保持4.0不动只要安装了4.8运行时.NET Framework 4.0的程序也能正常跑因为Framework 4.x系列是向后兼容的。我个人建议直接改到4.8因为新版本对64位、对TwinCAT.Ads库的兼容性都更好也省得以后出现各种莫名奇妙的问题。另外有个容易踩的坑如果你用的是.NET Framework 3.5那和ADS通讯可能牵扯到WCF、服务引用之类的老问题。如果在Windows 11上想装.NET Framework 3.5找不到安装包不要折腾离线包了直接在TwinCAT工程里把目标框架升到4.x远比你在系统组件里折腾半天省时间。TwinCAT 3本身也要求不低于.NET Framework 4.x这已经是大势所趋。2.2 添加TwinCAT.Ads引用ADS通讯的核心程序集是TwinCAT.Ads.dll。这个dll从哪里来看你装了什么版本的TwinCAT。如果安装了TwinCAT 3在安装目录的Components文件夹下能找到对应的dll可以直接添加引用。如果你是纯开发机没有装TwinCAT也可以在Visual Studio里通过NuGet包管理器安装TwinCAT.Ads包这个包由Beckhoff官方维护版本号和TwinCAT版本保持同步。这里有个版本细节需要注意。TwinCAT.Ads.dll有32位和64位之分很多时候连不上路由器、读取数据报错不是代码写错了而是你的程序集目标平台和AMSRouter运行环境不匹配。解决办法是在Visual Studio里打开“项目属性 - 生成 - 目标平台”选择一个明确的平台x86或x64不要用AnyCPU尤其在64位系统上建议直接选x64。还有一点如果你是通过NuGet安装的TwinCAT.AdsNuGet会自动处理依赖项比如TwinCAT.Ads.AdsRouter之类。但在老版本工程中经常找不到这些包最稳妥的办法还是从TwinCAT安装目录引用原生dll。等工程跑通之后你再决定要不要迁移到NuGet体系。2.3 获取AmsNetId和端口号的方法很多新手在“连接PLC”这一步就卡住了原因往往是不知道自己的AmsNetId是多少。获取方法不复杂在TwinCAT开发环境中点击系统的“Router”配置或者直接在系统托盘的TwinCAT图标上右键选择“Router - Edit Routes”就能看到本机的NetId。在工程环境里TwinCAT 3一般会显示为类似“127.0.0.1.1.1”或“192.168.0.10.1.1”这样的格式。Sample14这种示例工程通常测试时连接的都是本机也就是AmsNetId为“127.0.0.1.1.1”端口为851。如果你要连接远程PLC还需要在AMSRouter里添加一条Route把远程PLC的AmsNetId和IP地址映射起来。这一步很多人会漏掉直接在代码里填远程PLC的AmsNetId就试图去连结果连不上其实是因为本机Router根本没有到那台设备的路由信息。端口号的命名也容易混淆。在ADS中851一般记为“Port 851”对应“PLC Runtime 1”。如果你在代码里看到类似new AmsPort(851)的写法就是指这个。还有人在网上看到“连接端口852”先别急着抄852是第二个Runtime的端口只有你的工程确实有第二个Runtime时才用得到。3. Sample14核心代码逻辑拆解3.1 示例工程的界面布局与整体状态机Sample14的界面不复杂通常是一个WinForm窗口上面有连接配置区域AmsNetId、端口号、连接/断开按钮、当前PLC状态显示区域以及启动、停止、复位之类的操作按钮。这种布局非常典型你以后自己写上位机工具也可以参考。关键在于示例程序内部对PLC状态的建模。TwinCAT PLC Runtime有一组状态值常见的几个运行Run、停止Stop、复位Reset、异常Exception等。这些状态在ADS协议里有对应编号程序通过对状态值的比较来决定按钮是否可用、显示什么文案。比如当PLC处于运行状态时“启动”按钮应该禁用而“停止”按钮应该高亮反过来PLC停止时“启动”按钮才可点。这种状态机设计在简单示例里看起来有点“过度设计”但放到实际项目中非常有用。如果上位机不知道PLC当前处于什么状态它就可能在PLC已经运行的情况下再次发送启动指令轻则指令无效重则触发系统保护。所以Sample14里这个“先读状态再决定操作”的思路是我觉得整个例子最值得学习的地方。3.2 建立ADS连接的核心代码ADS连接建立本身很简单关键代码就这么几行。首先要创建TcAdsClient对象然后调用Connect方法传入AmsNetId和目标端口。以.NET Framework为例代码大致如下using TwinCAT.Ads; TcAdsClient adsClient new TcAdsClient(); string amsNetId 127.0.0.1.1.1; int port 851; adsClient.Connect(amsNetId, port);这段代码跑完如果没抛异常说明ADS连接已经建立。但是这里注意Connect方法是无条件的它只负责建立通讯链路不会去验证你连的端口上是否存在真的PLC Runtime。换句话说即使你连了一个不存在的端口只要AMSRouter通了它也可能不报错。所以示例里通常会紧接着读取一次PLC状态用返回值来确认目标确实存在。还有一点要提醒TcAdsClient使用完之后一定要释放建议用using块或者try-finally包裹。ADS连接是系统级资源频繁建立不释放会导致AMSRouter上的连接句柄耗尽最后连TwinCAT的调试都可能受影响。我见过有人在循环里反复new TcAdsClient跑了一会儿之后整个系统都连不上PLC了重启TwinCAT才恢复。3.3 读取当前PLC状态的常见做法连接建立之后第一件事就是读取PLC当前状态。这里涉及ADS协议里的系统服务。按最常见的实现方式程序会往PLC端口发送一个读取系统状态的ADS请求然后从返回数据里解析出状态码。示例代码里一般会用到类似这样的结构int state adsClient.ReadState().AdsState;实际上AdsClient提供了一个通用的ReadState()方法返回值中包含目标设备的状态。AdsState是枚举类型AdsState.Run表示运行AdsState.Stop表示停止AdsState.Reset表示复位等。这个方法比你去手动拼ADS请求包要方便得多官方示例以及我推荐的做法都是优先使用这些封装好的API。有一点要注意ReadState()返回的状态是ADS设备层的状态它不直接等于PLC程序是否在跑。在TwinCAT 3里PLC Runtime的运行状态和ADS状态大体对应但如果你用了多任务或多Runtime状态含义会复杂一些。对于Sample14这种单Runtime场景直接按AdsState.Run和AdsState.Stop来判断就够了。3.4 发送启动和停止指令的实现原理启动和停止的实现在ADS协议里其实也是“写状态”的过程。比较容易理解的方式是既然读取状态是ReadState()那写状态自然有一个WriteState()。在TwinCAT 2时代很多示例确实通过这种方式切换PLC的运行状态到了TwinCAT 3之后控制逻辑更规范化了一般建议通过向PLC的系统服务变量写入控制字来完成启停。代码层面常见的是用AdsClient.Write方法向一个约定的地址写入控制命令。比如向系统服务区写入一个表示“启动”的值或者写入“停止”的值。具体写到哪个地址、写什么值不同版本的TwinCAT会有差异这也是很多人在网上搜不到标准答案的原因。这里不建议大家死磕底层注册表式的地址映射因为不同环境差异很大。更稳妥的做法是你的PLC程序里定义一个结构体或者一个数组用来接收外部控制命令上位机往这个变量写入启动/停止请求PLC内部逻辑收到后再去调用系统功能块完成切换。这样做的优势很明显把通讯层和业务逻辑层解耦PLC工程师可以在程序里加各种保护条件和状态联动上位机只负责发请求不直接碰系统底层。example中之所以能直接控制启停是因为它作为官方示例走的是系统级接口。你在实际项目中如果只是为了满足产线调度需求我更推荐用“PLC变量握手”的方式安全性和可维护性都高得多。3.5 用Notification订阅状态变化而不是死循环轮询Sample14里一个很容易被忽视的细节就是它采用了通知机制来监控PLC状态变化。传统做法是做定时器每隔几百毫秒去读一次状态。这种轮询方式在简单场景下没问题但存在几个隐患一是频繁的ADS请求会占用AMSRouter的处理能力PLC负载高时可能会影响实时性二是轮询周期不可太短否则UI线程容易卡顿三是轮询无法做到真正实时状态切换的瞬间你总是会延迟一个周期才感知到。ADS的通知机制Notification则优雅得多。你对某个目标变量或设备状态创建一条通知订阅指定传输周期和缓冲区大小之后当数据发生变化时ADS服务端会主动推送消息给客户端。这样程序只需要在处理回调时更新UI即可既不浪费CPU也不会丢失状态变化。示例里在连接成功后通常会对PLC状态创建一个Notification然后在回调函数里更新界面上的状态文本和按钮状态。这个思路在你后面做更复杂的上位机时非常有用。比如你想实时监控PLC里几十个变量的变化用Notification远比OpenRead区域再手动刷新高效。4. 实操中常见的坑与排查技巧4.1 连不上AMSRouter先检查这四件事连不上ADS是出现频率最高的问题。哪怕代码写得完全正确环境不对劲也是白搭。我自己排查这类问题基本都是按下面这个顺序来。先看TwinCAT的Router服务有没有启动。很多开发机上装了TwinCAT但Router服务是手动启动的或者被杀毒软件禁了。打开系统服务列表找TwinCAT Router相关服务确认它处于运行状态。再看AmsNetId填对没有。本机测试是“127.0.0.1.1.1”远程设备则要确认对方Router里的NetId不要在代码里猜。接着确认端口号是不是851有时你连的是PLC环境但目标端口写成了852或者10000自然连不上。最后如果你用的是远程通讯还要确认Windows防火墙是否放行了TwinCAT相关端口。第一次跑Sample14时被防火墙拦掉的情况我至少遇到三次。如果上面四项都查了还是不行用TwinCAT自带的TwinCAT System Manager里的“Router”测试工具尝试和目标设备建立路由。Router层通不了代码层再怎么折腾都没用。4.2 .NET目标平台和TwinCAT.Ads版本冲突这个坑隐蔽但常见。当你程序集目标平台是AnyCPU或者64位系统上误选了x86而TwinCAT的Router服务跑在64位模式下ADS底层的通讯组件可能会因为位数不一致而加载失败。报错信息往往很泛什么“未能加载文件或程序集”之类的看着像引用问题其实是平台位问题。解决办法很直接打开项目属性把“目标平台”明确改为x64如果系统是64位且TwinCAT为64位重新编译问题基本就消失。如果你用的TwinCAT.Ads版本过老还会出现不支持64位的情况那就需要升级到包含x64版本的Ads dll。TwinCAT 3之后配套的ADS库对64位支持已经很完善。4.3 指令发出去了PLC状态却没变启动指令写进去了PLC却纹丝不动这是最让人头疼的问题。首先要确认你的ADS连接目标到底是不是PLC Runtime。如果连到了一个不存在的端口WriteState可能不报错但指令也不会生效。你可以用ReadState()读一下返回状态如果返回异常值说明目标不对。如果目标正确但状态不变化第二件要查的是安全权限。TwinCAT的PLC Runtime默认有一套安全机制R0权限以下的连接可能无法执行启停操作。这通常涉及到Windows用户权限、TwinCAT用户账号配置。把当前Windows账号加入TwinCAT用户组或者以管理员身份运行你的程序能解决大部分权限问题。还有一种情况你连接的Runtime是“配置模式”Config Mode而非“运行模式”Run Mode在这种模式下即使发出了启动指令PLC Runtime也不会真正进入运行状态。检查TwinCAT主界面右上角的模式状态确保系统处于运行模式。4.4 UI卡死和线程问题WinForm程序操作ADS时最常见的一个错误是在UI线程里直接调用同步的ADS方法。比如在按钮点击事件里调用ReadState()或者WriteState()等待返回一旦ADS通讯慢或者目标设备无响应UI线程就会被卡住整个窗口变成“未响应”状态。这在现场调试时特别尴尬因为界面死了你连“断开连接”的按钮都点不到。解决办法是把ADS操作放到后台线程或者使用async/await结合TwinCAT.Ads提供的异步方法。示例里没有做太重的异步处理因为它的ADS逻辑执行速度足够快但如果你的目标PLC在网络对端或者网络条件不太好异步方案必不可少。另外要提醒一下Notification的回调函数是运行在ADS通讯线程上的不要在回调函数里直接操作UI控件。必须要通过Invoke或者BeginInvoke把更新操作切回UI线程。这个坑几乎所有人都踩过第一次写Notification回调时报“跨线程操作无效”的异常基本就是没做线程切换。4.5 Sample14相关常见问题速查表问题现象可能原因处理建议打开示例工程提示.NET目标框架不支持本机.NET Framework版本高于工程目标版本修改目标框架为4.8或保持4.0运行编译时找不到TwinCAT.Ads.dll未添加引用或NuGet包未安装从TwinCAT安装目录引用或安装TwinCAT.Ads包连接时抛出“ADS Error 0x...”Router未启动或AmsNetId错误检查Router服务、确认NetId和端口代码运行但PLC状态不变权限不足或连接目标不是PLC端口检查用户权限、确认连接的是851端口UI界面卡死在UI线程同步调用ADS方法改用异步方法或后台线程Notification回调里更新控件报错跨线程操作UI使用Control.Invoke切回UI线程64位系统下程序偶发崩溃平台目标设置不当将目标平台设为x645. 从示例到工程Sample14的改造思路5.1 把ADS操作封装成独立的通讯类跑通Sample14只是第一步实际项目里你不可能把连PLC的代码和窗体逻辑混在一起写。最直接的做法是封装一个ADS通讯管理类把连接、断开、读状态、写状态、订阅通知这些操作收拢到一起。这样做的好处很多窗体代码干净了数据层可以单独测试了以后从WinForm换成WPF甚至控制台服务也只需要复用这个类。我习惯的类结构大概是这样一个AdsController类内部持有TcAdsClient实例向外暴露Connect()、Disconnect()、GetPlcState()、StartPlc()、StopPlc()等公共方法。内部通过事件或者回调把状态变化推送给界面层。所有ADS调用都封装在类的内部处理异常也统一在这一层捕获记录不让异常直接抛到界面层。封装类时还要注意一个细节断线重连逻辑。ADS通讯虽然稳定但在现场环境下网线松动、Router服务重启、TwinCAT系统切换模式等情况都会导致连接断开。如果你的上位机没有重连机制就只能人工重启软件这根本谈不上可靠。我的做法是封装类里内置一个连接状态监测以及自动重连的退避策略首次断开后1秒重试然后2秒、4秒逐步加大间隔直到重连成功。5.2 配置化设计AmsNetId和端口不要写死在代码里我能理解临时测试时图方便把AmsNetId和端口直接写在代码里。但项目一旦实施到现场设备数量多起来每台PLC的AmsNetId都不一样你不可能为了换个设备就重新编译一次程序。把所有连接参数放进配置文件是所有ADS项目上线的必修课。在.NET Framework的WinForm项目中最常见的做法是放一个App.config文件把AmsNetId、端口号、连接超时时间、重连次数都写成配置项。程序启动时读取配置界面上也允许手动修改并保存这样现场调试时不需要改代码只需在配置界面里输入新设备的路由信息和端口号。我甚至见过一个项目里把几十台PLC的通讯参数都放进了配置文件程序启动时根据选择的任务自动切换目标非常灵活。配置文件的价值不只是省事还在于它把“设备定制部分”和“程序逻辑部分”分开了。上位机程序是通用的每台设备只需要一份不同的配置文件部署运维的复杂度会大大降低。5.3 加入日志和异常告警在调试Sample14时代码量少逻辑简单不写日志问题也不大。但改造到真实项目里ADS通讯是现场故障排查的关键线索没有日志出了问题就只能干瞪眼。建议在封装的ADS通讯类里统一接入一个日志接口记录连接建立、连接断开、状态切换、指令发送、异常信息等关键事件。日志里至少要有时间戳和操作上下文。比如“14:23:01.123 发送启动指令目标127.0.0.1.1.1:851返回成功”这样的日志对分析问题是决定性的。如果PLC没有按预期启停翻日志就能判断是通讯层问题、权限问题还是PLC端逻辑问题可以省下大量现场排查时间。另外建议把异常类型和具体错误码记进日志。ADS错误码非常多不同错误码对应不同问题能记录原始值最好。我在排查问题时经常靠日志里的ADS错误码直接定位到问题比如0x743表示设备没有响应0x752表示参数无效这些信息在线上工时价值巨大。5.4 和PLC程序握手从“底层启停”升级为“业务联动”官方Sample14直接控制系统启停适合演示但在实际产线中我更倾向于让PLC程序配合上位机一起完成启停控制。方案是PLC程序里预留一组控制变量和状态变量上位机写入“启动请求”或“停止请求”PLC逻辑读取到请求后先检查当前设备状态、安全条件再决定是否执行启动或停止最后把实际执行结果反馈给上位机。这样做的好处非常明显。第一安全可靠性高。PLC内部可以加入互锁、急停、报警等判断确保只有在满足条件时才允许启动而系统级启停指令一旦发出去是不管你设备状态如何的容易出事故。第二可读性好。PLC程序和上位机程序的工程师看到“启动请求”这样的变量一眼就明白逻辑意图比处理底层状态码直观得多。第三扩展性强。你可以在PLC侧加入预启动检查、启动次数限制、操作员权限校验等业务逻辑上位机不需要任何改动。当然如果你只是做一个调试工具Sample14式的系统级启停更方便因为不需要修改PLC程序就能控制Runtime。但如果你想把这套功能做成正式的上位机功能我一定建议走“PLC变量握手”的路线。这也是我在实际项目里最常用到的方案。5.5 界面层如何配合从按钮到状态可视化最后说一下界面层。Sample14的界面很简单几个按钮加一个状态文本。做成正式工具时界面可以考虑做得更直观一些。比如用颜色区分状态绿色表示运行中灰色表示已停止红色表示异常再用一个大按钮或指示灯图形来展现当前状态而不是靠一行文字判断。按钮的可用状态也要跟着PLC状态实时变化。PLC在运行时只亮“停止”按钮PLC停止时只亮“启动”按钮通讯断开时所有操作按钮禁用。这些逻辑看似简单但处理不严谨的话操作员很容易误触。有一个小细节任何启停操作按钮点击后都应该弹确认框特别是“停止”操作避免误操作导致产线停线。还有一点如果上位机和PLC之间是长连接界面上最好实时显示通讯状态。比如在窗口底部放一个连接状态栏显示当前AmsNetId、端口、连接状态、最后通讯时间。这样一旦出问题操作员能第一时间发现并及时通知维护人员。写在最后Sample14这个官方示例表面上只是演示了PLC启停控制实际上是把ADS通讯的核心流程完整地串了一遍连接Router、获取状态、发送指令、订阅变化。你把这个示例吃透就等于掌握了用.NET操作TwinCAT的最核心方法后面不管是读写变量、订阅PLC变量、还是控制不同Runtime都是在这个框架上做扩展。我个人的体会是刚开始学ADS时不要急着去研究底层协议细节先把官方示例跑起来让它能连上你本机的PLC能控制启停先建立“原来就是这么回事”的整体印象。等真正动手做项目了再回过头去研究ADS报文格式、AMSRouter路由、Notification机制会顺畅很多。最后再分享一个小技巧。调试Sample14这类程序时把TwinCAT的Router配置窗口开着时刻观察本机Router和目标设备路由是否正常。很多ADS通讯问题你一眼就能从Router窗口里看出来而不是靠代码日志反复猜。这个习惯能帮你省下大量排查时间。
返回列表