十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

LabVIEW不靠OPC直连AB PLC,底层EtherNet/IP读写标签全解析

LabVIEW不靠OPC直连AB PLC,底层EtherNet/IP读写标签全解析 干过AB PLC和LabVIEW联调的人大概率都撞见过这么一件事现场上位机要用LabVIEW做界面底下PLC是罗克韦尔的ControlLogix或者CompactLogix两边得通讯。上网一查翻来覆去就那么几条路——装RSLinx/FactoryTalk走OPC或者花大价钱买第三方EtherNet/IP工具包再不然就有人建议“你自己拿TCP写一个EtherNet/IP客户端呗”。我前一阵就真选了最后那条路用LabVIEW把AB PLC的底层通讯协议栈从零搭了一遍读写标签全部搞定全程除了LabVIEW自带的TCP函数没有安装任何商业工具包。整个过程痛并快乐着踩了不少坑也把CIP协议的底细彻底摸清了。今天就把这套“硬刚”方案整理出来从协议拆解到LabVIEW实现再到实测排坑一条线讲清楚给正在犹豫“要不要自己写”的朋友做个参考。1. 为什么不用OPC服务器非要自己撸底层协议1.1 先聊聊常规方案在实际现场的毛病LabVIEW对接AB PLC多数工程师的第一反应是走OPC。这套方案的经典配方是PLC侧装FactoryTalk Linx或RSLinx Classic通过OPC接口把标签数据吐出来LabVIEW这边再用NI OPC Servers、DataSocket或者共享变量去对接。说起来开发速度快、配置文件一填、标签树直接映射过来听着挺美。但真把这套东西搬到现场问题一个接一个冒出来。首先是授权成本RSLinx Classic的OPC授权、FactoryTalk Gateway授权、NI OPC Servers授权单价都不低。对一个预算卡得紧的小项目这套软件授权费用甚至可能超过PLC本体的差价。其次是中间层故障OPC服务器是独立进程一旦它崩溃、内存泄漏或者被Windows更新搞出兼容性问题整个上位机的数据流就断了。远程维护的时候你还得想尽办法去重启那台OPC服务器相当被动。更麻烦的是部署复杂度现场新装一台工控机要装LabVIEW运行时、NI OPC、FactoryTalk客户端还得配置DCOM权限每个环节都能卡你半天。对可靠性和实时性要求高的测试台、产线工位来说OPC这个中间层就像一颗定时炸弹。我见过不少项目后期运维问题根本没有出在PLC或者上位机程序上而是那台装了OPC服务器的Windows工控机不知道哪天自动更新了一下DCOM权限就乱了数据全部卡死。1.2 LabVIEW直连的方案栈与可行性所以我给自己定了个硬性要求能不装商业工具包就不装能用LabVIEW自带的TCP节点解决的问题绝不引外部依赖。AB PLC的ControlLogix和CompactLogix都支持EtherNet/IP协议这个协议跑在标准TCP/IP之上端口号固定是44818。换句话说只要LabVIEW能开TCP Socket就能和PLC对话。实质上要做的就是在LabVIEW里实现一个精简的EtherNet/IP客户端。这个客户端只需要完成四件事建立TCP连接、注册会话、发送CIP请求、接收CIP响应。读写标签全部走显式报文根本不需要碰隐式I/O连接那种走UDP用于实时IO扫描的机制可以完全绕开。开发量没有想象中那么大核心代码二三十行就能把一次读写跑通。这个方案还有一个额外价值你会真正理解PLC标签访问的本质。搞懂了Read Tag和Write Tag两个服务之后再去看OPC服务器的日志、抓包软件里的协议流会发现全是同一个套路。从“能用”变成“懂原理”遇到问题就有了排查方向。1.3 三条路线怎么选成本与风险对照这里放一张对比表把常规方案和底层直连放在一起看方案开发速度成本可靠性可维护性适用场景FactoryTalk Linx/RSLinx OPC快高多套授权中依赖Windows服务一般DCOM等配置多长期项目、有IT支撑、预算充足第三方EtherNet/IP工具包中中中中不想自己写协议又想省OPC自研EtherNet/IP客户端慢前期低纯代码高单进程无依赖高代码完全可控预算有限、需要深度定制、技术自控我个人选第三条路还有一个现实原因现场工控机不允许随便安装未授权商业软件IT管理很死。自己写的代码不违反任何授权部署干净这是实际工程里的硬约束。不过也说句公道话如果项目周期特别紧、标签数量巨大、现场又有完善的IT运维团队OPC方案从来都不算错。自己写协议栈更适合下面几种情况标签数量不算爆炸几十到几百个读写频率要求不高几十毫秒到秒级不想在工控机上部署FactoryTalk类软件想规避授权和DCOM问题想彻底搞懂AB PLC通讯机制为后续项目沉淀一套自有库或者现场通讯已经出现疑难杂症需要用底层抓包和协议分析来定位问题。如果你符合其中任意一条下面的内容基本就是为你准备的。我会拿一个真实的ControlLogix控制器做示例从最底层的报文开始一步步把读写标签的流程完整跑通。2. AB PLC的EtherNet/IP协议到底长什么样2.1 CIP协议的核心思想别被“以太网”三个字带偏很多人一听EtherNet/IP就以为它是一种“工业以太网物理层协议”其实大错特错。EtherNet/IP本质上是CIP协议在以太网上的实现。CIPCommon Industrial Protocol通用工业协议是一个面向对象的协议每个设备都被抽象成一组“对象的集合”——比如Identity对象标识设备信息Message Router对象负责消息路由Assembly对象管理数据映射。AB PLC能通过EtherNet/IP通讯就是因为它的固件里内置了完整的CIP对象模型。CIP通讯分成两大类。一类是隐式消息Implicit Messaging走UDP 2222端口周期性地交换I/O数据适合运动控制、实时IO扫描这类高实时场景。另一类是显式消息Explicit Messaging走TCP 44818端口按需请求-响应适合参数读写、点对点数据采集。我们LabVIEW要读写PLC标签走的就是显式消息路线。它灵活可靠虽然实时性不如隐式消息但用在HMI数据采集、报表记录、参数下发上绰绰有余。CIP对象模型里有个关键角色叫Message Router它是所有显式消息的入口。我们发出的Read Tag请求本质上就是请求Message Router把数据转发到“符号对象”也就是标签系统去查找并返回标签值。所以每次组帧时CIP路径基本都指向Message Router具体到请求数据里再携带标签名。理解这一点就不会在组帧时纠结“路径该怎么写”——路径就是指向Message Router标签名是放在服务数据里的。2.2 封装协议数据格式24字节头部下的信息再往下看传输层。EtherNet/IP的每一次通信TCP载荷都以一个封装协议头Encapsulation Protocol Header开头总长24字节结构如下字段长度字节说明Command2命令字如0x0065注册会话、0x006B发送RR数据Length2后续数据区长度Session Handle4会话句柄注册成功后由PLC分配Status4状态码0表示正常Sender Context8发送者上下文用来匹配请求和响应Options4选项一般为0这个头部后面才是数据区。注册会话、注销会话、发送RR数据这些“命令”的区别其实就是头部里的Command字段不同后面挂的数据内容不同。这里要特别强调一个新手最容易栽的坑封装头里的多字节整数比如命令字、长度、会话句柄在以太网上传输时全部是小端序低位在前。所以0x0065这个命令字在字节流里要写成65 00而不是00 65。很多人在抓包软件里看到65 00 08 00对照文档写着“命令0x0065、长度8”一下就懵了。字节序这个坑在LabVIEW里尤其容易踩因为LabVIEW的字符串拼接节点默认按ASCII文本处理你拼一个“6500”十六进制字符串和拼65 00两个字节完全是两个概念。这一点后面实操中我会专门演示。对于响应报文TCP Read读回来的同样是先24字节头部紧接长度字段指定的数据区。所以只要先读24字节解析出Length字段再读第二段数据就能完整拿到响应包。这套“两步读”的逻辑在LabVIEW里非常实用后面会展开说。2.3 关键命令与服务会话与读写的完整链路我们实际用到的命令字其实就三个0x0065 Register Session注册会话向PLC申请一个唯一的会话句柄0x006B Send RR Data发送请求/响应数据之后所有读写标签的CIP服务都通过它传输0x0004 Unregister Session注销会话关闭前释放资源。CIP服务层面主要用两个0x4C Read Tag读取标签0x4D Write Tag写入标签。整条通讯生命周期是TCP连接 → Register Session拿到句柄→ 多次SendRRData读写标签→ Unregister Session → TCP断开。注册会话的报文长这样TCP连上以后发送一个封装包命令字0x0065数据区4字节内容为协议版本1和选项标志0。PLC收到后会返回一个封装包此时Session Handle字段就是有效值了后面所有SendRRData请求都要带上这个句柄。SendRRData的数据区结构再细看就几条接口句柄Interface Handle4字节显式消息填0超时Timeout4字节单位毫秒常见值3600数据项数量Item Count2字节通常为2地址项类型0、长度4、内容为0未连接场景数据项类型0x00B2未连接数据项长度N内容是一段完整的CIP消息。CIP消息内部遵循“服务路径数据”的结构服务码1字节如0x4C表示Read Tag路径大小1字节以16位字为单位路径指向Message RouterClass 0x02、Instance 1、Attribute 1CIP路径编码为20 02 24 01如果CPU不在以太网模块所在机架/槽位需要追加背板槽号路径段格式是30后跟槽号服务数据标签名0结尾 元素数量2字节小端。把这些字段照顺序组织出来就是一个完整的读取标签请求。看着链路长但真在LabVIEW里用字节数组拼起来工作量不大。难就难在每一步都要保持字节序正确、长度字段准确。3. LabVIEW硬核实现从TCP连接到读写标签3.1 建立TCP连接与会话注册的详细实现我在LabVIEW里把整个协议栈拆成了几个可复用的子VI。先看第一步建立会话。这里用到的LabVIEW关键节点TCP Open Connection.vi输入PLC的IP地址和端口44818TCP Write.vi发送字节数据TCP Read.vi读取响应字节TCP Close Connection.vi关闭连接。为了性能考虑我在程序启动时只做一次连接和注册把TCP连接引用和会话句柄保存下来后续每一次读写都复用这条通道。实测下来TCP连接加注册会话大概消耗20到30毫秒而建立好会话后单次读写请求响应只有3到8毫秒。如果每个标签读写都重新建连接性能完全没法看。注册会话的报文组帧我直接在LabVIEW里用一个U8字节数组Byte Array来组数据内容十六进制字节说明命令字65 000x0065小端长度04 00数据区4字节会话句柄00 00 00 00注册前为0状态00 00 00 000正常发送者上下文00 00 00 00 00 00 00 008字节选项00 00 00 000协议版本01 00版本1小端选项标志00 000总共28字节发送后TCP Read返回响应。返回报文前24字节是封装头其中第5到第8个字节就是PLC分配的会话句柄把它解析成U32即可。这里有个LabVIEW细节必须提醒组帧时千万不要用“Number To Hexadecimal String”这类把整数转成ASCII字符串的节点然后直接拼字符串。正确做法是把所有字段按字节放进U8数组最后用“Byte Array To String”或者“Flatten To String”转成字符串交给TCP Write。如果直接拼ASCII码协议直接废掉PLC那边完全不理你。3.2 读取标签数据的完整流程与响应解析会话建立后就可以发送Read Tag请求了。我用一个真实例子串一遍读取ControlLogix里名为“Line1.Status”的DINT标签。第一步组装CIP消息服务码4C路径大小02单位是16位字路径共4字节所以路径大小为2路径20 02 24 01Message Router对象实例1属性1请求数据标签名“Line1.Status”的ASCII码逐字节结尾加一个00终止符再接元素数量01 00小端表示读取1个元素第二步组装SendRRData数据区接口句柄00 00 00 00超时10 0E 00 003600毫秒小端数据项数量02 00地址项00 00 04 00 00 00 00 00类型0、长度4、内容0数据项头B2 00 长度2字节小端数据项内容第一步组装好的CIP消息第三步加封装头命令字6B 00长度数据区总字节数2字节小端会话句柄之前注册获取的U32值按小端排状态00 00 00 00发送者上下文8个字节填0选项00 00 00 00全部拼好总报文大概五六十字节通过TCP Write发出随后TCP Read读取响应。响应解析有个固定套路先读24字节封装头确认命令字0x006B、状态字段为0继续读数据区跳过4字节接口句柄、4字节超时、2字节项目数、8字节地址项来到数据项头类型0x00B2再后面2字节是CIP消息长度CIP消息第1个字节是响应服务码0xCC0x4C加0x80表示成功第2个字节是保留字节0从第3个字节开始才是真正的标签数据。对DINT类型来说后面就是4字节数据。在LabVIEW里把这4个字节用Unflatten From String按U32整形解出来数值就出来了。整个过程我封装成了一个子VIAB_Read_Tag.vi。输入是会话句柄、TCP连接引用、标签名和数据类型输出是解析后的数值。核心代码就是一个大的字节数组处理流程难点全在字节序和字段偏移上一旦把偏移搞对后面再读几十个标签就只是重复劳动。3.3 写入标签字节序问题集中体现写入标签比读取多一个操作要把待写入的数据直接拼进请求里。Write Tag服务码是0x4D请求数据结构是服务码4D路径大小02路径20 02 24 01标签名 00终止符元素数量01 00数据要写入的原始字节最容易踩坑的就是最后那段原始字节。拿DINT标签写入数值1来说正确数据是01 00 00 00小端序。如果你习惯按字符串“0000001”去组帧或者用Type Cast把数值转成了大端序PLC那边收到后要么出现一个巨大无比的数要么直接报错拒绝。这里提供一个通用验证方法先在PLC里手动写入一个已知值比如给DINT标签写0x01020304然后用LabVIEW把响应字节读出来。如果解析出来正好是0x01020304说明字节序处理正确如果变成0x04030201说明反了需要在LabVIEW里加Byte Swapping或者调整Unflatten的方向。这个方法简单有效能省下大量排查时间。REAL浮点数同样如此。REAL是IEEE 754单精度浮点4字节AB PLC内部也按小端存放。如果读出来的REAL标签是个天文数字或者诡异的科学计数法八成是字节序或者Type Cast尺寸不对。我的建议是读出4字节后先用Byte Swapping功能交换字节顺序再做Unflatten From String且格式选择SGL这样基本不会错。3.4 封装成可复用模块与批量读取优化把基础读写跑通之后我花时间把它做成了一个小型库后面用起来顺手得多。模块划分大概是AB_Session_Open.vi输入PLC IP输出TCP连接引用与会话句柄AB_Read_Tag.vi输入连接引用、会话句柄、标签名、数据类型输出数值AB_Write_Tag.vi输入连接引用、会话句柄、标签名、数值输出成功失败AB_Session_Close.vi注销会话并关闭TCPAB_Read_Tags_Batch.vi批量读取一次请求读多个标签。批量读取这件事值得专门说。如果上位机界面有几十个标签需要定期刷新逐个Read Tag虽然能跑但每个请求都有网络往返一多就慢。CIP提供了Multiple Service Packet服务码0x0A可以把多个服务请求打包进一个SendRRData里一次网络往返完成多处读取。我实测下来10个标签批量读只比单读多花一两毫秒而逐个读则要多花几十毫秒。对HMI刷新来说这个优化非常关键。Multiple Service Packet的组帧套路是前面一个字节表示子请求数量然后把每个子请求按“服务码路径大小路径请求数据”的格式依次排列。注意不要一次性往大了堆不同固件版本对批量请求的最大子请求数有限制建议先试5个以内确认没问题再往上加。4. 实测中的坑与解决方案4.1 会话句柄和超时问题的排查实录先聊我在调试初期遇到的最典型问题TCP连接是通的数据也发出去了但PLC就是不响应或者返回状态码很奇怪。排查思路按顺序来。第一确认封装头里的会话句柄。注册会话拿到的句柄是整条生命周期从头用到尾的如果某一次请求里会话句柄填成0PLC会直接忽略。这个问题很隐蔽尤其是把注册会话和读写会话拆在不同的子VI里却没有把句柄传递过去时最容易出现。我当时就吃过这个亏半天没找到原因最后抓包才发现请求里的会话句柄字段是0。第二确认超时字段。SendRRData数据区里的超时是4字节单位毫秒。我一开始图省事填了0结果PLC某些模块响应异常后面统一填3600就很稳定。超时字段的本质是告诉PLC“我这边可以等多久”填0在部分固件上会被理解为过期消息。第三确认TCP Read的读取字节数。PLC响应报文长度不是固定的不同标签类型数据长度不同。建议在组帧阶段就根据请求来估算最大响应长度。TCP Read节点有读取字节参数填少了会截断填多了会一直等。我的习惯是先读24字节头部从头部Length字段解析出后续数据长度再读第二段。虽然多了一次函数调用但逻辑稳抓包时也容易定位问题。4.2 标签类型与数据结构映射的实战对照AB PLC的标签类型和LabVIEW类型不是一一对应的下面是一张我实际用下来的映射表AB数据类型字节数LabVIEW对应类型备注BOOL1Boolean一个字节0或1SINT1I8有符号8位INT2I16注意小端序DINT4I32最常用的整数类型REAL4SGLIEEE 754浮点小端序STRING取决于声明字节数组/StringAB的STRING结构带前导长度字段数组类型要复杂一点。AB的DINT数组在内存里是连续存放的但LabVIEW端解包时要留意数组下标从0开始读出来的第0个元素对应标签名[0]。数组中每个元素依然是4字节小端排列批量解包时按I32逐个Unflatten即可。结构体UDT就更麻烦。CIP的Read Tag读UDT时返回的是结构体内部所有成员的连续字节且不含成员名信息。LabVIEW端只能按UDT声明时的成员顺序和类型手动去切分字节。比如PLC端UDT声明为Member1: DINT、Member2: REAL、Member3: BOOL那么在LabVIEW里先读4字节解析为I32再读4字节解析为SGL最后读1字节解析为布尔。顺序和类型不能出错错了就牛头不对马嘴。这种问题在抓包里看不出明显异常只能对着UDT定义逐段核对。4.3 与FactoryTalk/RSLinx共存的连接资源冲突这一点很多人未必注意。如果LabVIEW程序跑在同一台装有FactoryTalk Linx或RSLinx Classic的电脑上并且这些软件也在访问同一台PLC可能会遇到连接资源冲突。AB PLC的以太网模块对显式连接数量有限制大概在100个左右不同型号有差异。OPC服务器往往会占掉大量连接如果LabVIEW这边也反复建立连接不释放连接数很快就爆了。我建议的做法是LabVIEW端及时调用Unregister Session并关闭TCP连接尤其是程序退出、异常分支里要做清理不要每读一个标签就新建一条TCP连接一定要复用会话如果现场发现CPU的Connection Count经常接近上限适当降低刷新频率或者在PLC诊断页面上确认哪些连接被谁占用。这套操作在调试阶段就能提前规避真等现场爆连接数的时候再处理成本就高了。4.4 实测性能数据底层直连到底能跑多快最后放一组我在实验室环境实测的数据供参考。环境是ControlLogix L73CPU固件33.011上位机普通i5工控机网线直连无交换机。单次Read Tag读一个DINT标签平均3.5ms最大8ms单次Write Tag写一个DINT标签平均4ms批量Read Tag读取10个DINT标签平均5ms批量Read Tag读取100个DINT标签平均12ms每秒连续读同一标签大约180到200次约5ms一次。这个性能对HMI显示、报表记录、参数下发完全够用。但如果你要做高速同步采集或者运动控制级的数据交换还是别走显式消息这条路老老实实使用隐式I/O连接配合实时以太网或者直接走背板协议。写这么多其实就想说一句LabVIEW硬刚AB PLC的底层通讯没有很多人想得那么神乎其神它本质就是照着公开协议在自己的代码里实现一个客户端。真正难的不是协议本身而是调试时那些藏在字节序、长度字段、会话状态背后的坑。我做完这套通讯模块后再回头看OPC方案发现很多以前觉得莫名其妙的问题比如“OPC偶尔读不到值”“重启后连不上PLC”其实都能从底层协议层面找到原因。这对做自动化集成的人来说比省下那几万块授权费更有价值。最后再分享一个实用建议如果你也准备在LabVIEW里做类似的事先装一个Wireshark在抓包里对照协议文档把注册会话、Read Tag、Write Tag的报文从头到尾过一遍确认自己组帧是对的再写上位机界面。抓包这一步至少能帮你省掉两天跟PLC厂商扯皮的功夫。希望这篇实践经验能帮你少踩几个坑。
返回列表