做LabVIEW上位机这些年三菱PLC通讯是我绕不开的环节。上个月给一台设备写监控程序下位机是一台三菱FX5UFX5U又拖了三条JE-A伺服的轴每根轴都带5比1减速器同步轮是5M20规格设备要求线速度稳定在0.8米每秒。上位机既要实时刷新伺服位置、速度、报警状态又要在切换工件时批量下发运动参数。一开始我想偷懒找现成的LabVIEW与三菱PLC通讯工具包结果要么商用授权贵要么封装太黑通讯出了问题根本不知道是协议帧错了还是地址算错了。最后干脆自己写了一套专门面向三菱PLC通讯、以数据批量读写为核心的LabVIEW库从协议帧构造到地址换算全部自己掌握。这篇就把整个开发过程的思路、细节、踩坑点完整记录下来给同样需要在LabVIEW里做三菱PLC通讯的同行一个能直接参考的作业。这库除了解决FX5U以太网通讯也兼容了三菱FX系列常用的串口协议核心模块包括批量读D寄存器、批量写D寄存器、M点批量读写、字符串和浮点数的字节序处理。做设备联调这一行通讯库稳定、高效、可排查是关键尤其是批量读写场景处理不好就是帧头错位、数据乱码、偶发超时轮流来。希望这篇能帮你少走几周弯路。1. 项目背景与需求拆解1.1 这套系统到底要通讯些什么先说清楚我面对的硬件和控制架构。上位机是LabVIEW写的工控界面下位机是三菱FX5UFX5U下面挂了三台JE-A伺服驱动器。机械结构上每台伺服电机输出端接一个5比1行星减速器减速器输出轴装的是5M20同步轮同步带拖着负载直线运动要求带速0.8米每秒。这种结构在场里很常见一般就是定位、搬运、速度控制这几种运动模式。LabVIEW在这个系统里的角色是监控和调度中枢不是插补核心。PLC负责实际的伺服控制和圆弧插补逻辑上位机只需要做好几件事把操作员设定的目标位置、运行速度、加减速时间批量写到PLC的D寄存器区周期读取伺服当前的实际位置、实际速度、扭矩、报警码等运行数据对M位元件进行读写比如启动、暂停、急停、复位这些控制信号如果配方切换还要一次写好几十个参数。这个需求不需要LabVIEW和PLC之间传输高带宽数据但是对通讯的可靠性和批量操作的效率要求很高因为刷新周期要在50毫秒级别而且产线不允许丢包。1.2 通讯需求量化之后长什么样我习惯一上来先整理通讯数据表这比选协议还重要。表里每一行是一个数据项标清楚类型、方向、刷新周期、是否允许偶发失败。以这台设备为例读的数据主要是三类每个轴的D寄存器区连续50个字存当前位置、目标位置、实际速度、电流、报警码、状态字还有一组M元件存各轴的报警标志、回零完成标志、自动运行标志再就是一些特殊继电器SM的值用于判断PLC运行状态。写的数据主要也是三类每轴运动参数区连续20个字存目标速度、加速时间、减速时间、微动速度、软限位等控制字M点启动、急停、使能等以及配方区一次可能连续写30到40个字。把这些数据格式定出来之后通讯库的核心需求就清楚了必须支持按连续地址批量读、批量写而且最好能用一个函数同时处理字元件和位元件。刷新周期上监控类数据用50ms定时循环读参数下发属于事件驱动不上高速。这个量化过程一定要做在前面不然后面库的接口设计得改来改去。1.3 为什么选MC协议而不是Modbus TCP很多人第一反应是直接用Modbus TCP因为LabVIEW的Modbus库很成熟网上例子一大堆。但我衡量之后还是选了三菱的MC协议准确说是MC协议3E帧二进制模式原因有几个。Modbus TCP在三菱PLC里做批量读保持寄存器确实不难FX5U也有现成功能块但它的数据模型是03功能码、04功能码这种需要通过PLC侧配置软元件映射表把D区映射到保持寄存器地址。一旦项目里软元件规划变化映射表也要跟着改多一层维护成本。而且Modbus的字节序在跨厂商设备之间经常出幺蛾子读32位浮点要么高低字互换要么字节反序排查起来很费劲。MC协议是三菱自己的协议FX5U原生支持指令里直接指定软元件类型和软元件编号比如要读D100就是起始地址100、软元件代码A8一句话说清楚。批量读、批量写、位操作都是标准指令不需要额外映射。更重要的是MC协议在FX5U上走以太网时是SLMP帧本质上就是一个轻量级应用层协议帧结构清晰非常方便自己在LabVIEW里封装这对我们这种想把协议细节攥在手里的场景最合适。2. 通讯协议选型与MC协议帧格式分析2.1 MC协议3E帧的请求报文长什么样MC协议有串口版和以太网版串口版常用QnA兼容3C帧以太网版用3E帧二进制。我这套库以3E帧为主因为FX5U、Q系列、L系列都支持未来项目复用性也强。3E帧请求报文的结构是这样的先是固定的子帧头两字节十六进制是50 00然后是请求数据长度字段两字节小端表示指的是后面请求数据区的字节数再往后的请求数据区包含PLC编号固定FF FF通常都填FFFF代表本机然后是监视定时器两字节一般是10 27也就是十六进制的0x2710换算过来是10000毫秒意思是如果PLC在10秒内没处理完请求就让上位机报超时然后是命令字两字节和子命令字两字节。命令字0x0401是批量读0x1401是批量写注意小端下在帧里写成了01 04和01 14。子命令字0x0000表示字单位0x0001表示位单位。然后是软元件起始地址字段这是很多初学三菱通讯的人最容易懵的地方。起始地址是三字节加一字节软元件代码三字节里放软元件的编号偏移低16位有效小端排列。软元件代码单独占一字节比如D寄存器是A8M继电器是90。最后是软元件点数两字节小端表示这次要读或写多少个点。2.2 一个读请求的完整字节拆解我举个实际例子批量读D0到D9一共10个字的请求帧十六进制写出来是这样的50 00 0E 00 FF FF 10 27 01 04 00 00 00 00 00 A8 0A 00从前往后拆50 00是子帧头0E 00是请求数据长度0x000E等于14字节也就是从FF FF开始到最后的0A 00一共14字节FF FF是PLC编号10 27是监视定时器01 04是批量读命令00 00是字单位子命令00 00 00 A8中前三字节是起始地址0A8是D寄存器代码0A 00是点数0x000A等于10个点。对应响应帧也有固定格式先是D0 00响应帧头然后响应数据长度字段两字节小端接着是响应数据区。响应数据区里前四字节固定是FF FF和00 00其中00 00表示正常应答如果有错误这里就是错误代码从第五字节开始才是真正读回来的数据。比如读回来第一个字是0x04D2第二个字是0x162E那响应帧就是D0 00 18 00 FF FF 00 00 D2 04 2E 16 ...后面还有18个字节请求和响应这一来一回只要理解透彻了在LabVIEW里做进制转换和数组拼接就非常清晰。2.3 响应错误代码要能看懂PLC返回的错误代码是排查通讯问题的最直接线索。正常应答是0000。常见的错误代码有C051、C052、C056、C057、C05B、C064等其中C051和C052是帧格式、长度出错C056是请求的起始地址超出范围C057是点数设置不对C05B是命令字不支持C064是访问的软元件被某些功能锁定了。还有一类错误代码的格式是C0_ _含义对应到具体的软元件状态和访问权限。我在库里专门做了一个错误代码解析模块把收到的错误代码映射成中文描述字符串联调时直接在界面上显示软元件地址超范围而不是一串十六进制排查效率天差地别。这个模块非常值得做后期现场调试没有它很难受。2.4 软元件代码表和地址换算规则三菱不同系列PLC的软元件代码基本一致FX5U常用的我列一张表软元件类型代码十六进制说明DA8数据寄存器最常用WB4内部字继电器RAF文件寄存器ZRB0文件寄存器ZR区M90内部继电器SM91特殊继电器X9C输入继电器Y9D输出继电器L92锁存继电器F93报警器BA0链接继电器TNC2定时器触点当前值等需细分CNC0计数器触点当前值等需细分地址偏移的换算是另一个坑。D寄存器和M继电器的偏移就是软元件编号本身比如D100编号是100十六进制0x0064那起始地址字段就填64 00 00。M100就填64 00 00代码写90。但X和Y不是这样X和Y的编号本身是八进制的比如X7后面是X10不是X8。所以做地址换算时X10对应的偏移是8X17对应15X20对应16也就是把X后面的编号当八进制转成十进制才是MC协议里的起始地址。第一次做三菱通讯时我就因为想当然用了十进制解析X10这个点读到的数据永远是错的。3. 库的架构设计与核心VI拆分3.1 分层设计连接层、协议层、应用层写通讯库最忌讳把所有逻辑塞进一个大VI。我采用的架构分成三层连接层负责TCP连接或串口连接的建立、关闭、状态监测协议层负责MC协议帧的构建、发送、接收、响应解析、错误码转换应用层对外提供统一的高层接口比如PLCReadWords、PLCWriteWords这种输入是软元件类型和地址编号输出是数据数组和错误簇调用者完全不需要关心协议帧长什么样。这样的分层有个明显好处之后如果要换串口协议或者加一个Modbus TCP支持只需要在协议层加一个适配器应用层接口稳定不动。LabVIEW里实现分层不复杂每一层放到独立文件夹VI命名遵循统一前缀。为了交付时防止核心算法泄露我还会在Build Specifications里把这些核心VI设置为加密打包成LLB或者独立rtexe客户拿到手能运行但看不了源码这个对设备厂商交付是基本操作。3.2 连接管理VITCP连接不能每次现开连接管理看起来简单但细节不少。TCP连接如果每次读写都现开现关光TCP握手就要消耗掉好几毫秒而且Windows下TIME_WAIT会积累很多socket一段时间以后连接就会不稳定。所以库里的连接管理VI采用全局会话模式PLCConnect.vi负责建立TCP连接并保存连接引用到一个功能全局变量里PLCRead和PLCWrite只用这个引用最后统一关连接。连接管理还要处理断线重连。我在PLCRead外围包了一层带重试的包装VI当检测到底层socket错误时自动关闭旧连接重新走一遍PLCConnect流程重试次数和重试间隔做成输入参数。现场电源抖动或者交换机重启PLC掉线几秒再恢复上位机没有断线重连机制就只能手动重启程序有了自动重连以后这种故障在界面上只会闪一下警告。3.3 帧构建与解析模块是库的心脏协议层里最核心的两个VI是MCBuildReadFrame.vi和MCParseResponse.vi。构建模块的输入参数包括协议类型、软元件类型、起始编号、读取点数。内部逻辑是用一个查找表根据软元件类型拿到代码再根据X/Y的八进制规则算出地址偏移然后拼帧头、长度、监视定时器、命令、子命令、地址、点数。这里有一个关键点请求数据长度字段必须在数据区拼好之后回填长度字段的值是整个数据区的字节数如果先填了固定长度再改数据区长度前面数据就会错位。解析模块更考验细节。第一步检查帧头响应帧头必须是D0 00不是就说明收到了错误数据第二步检查数据区长度是否等于预期值第三步读应答码应答码非0就跳到错误码解析第四步才是把数据区里的原始字节按用户要求的类型转换成LabVIEW的数值。这四个步骤每一步都有对应的错误输出不能混在一起。调试阶段我会把收发双方的完整十六进制帧通过字符串控件显示在界面上配合这个模块定位问题会非常快。3.4 类型转换与日志记录不能偷懒类型转换和日志这两个模块看着不起眼实际上对通讯库的稳定性影响极大。类型转换模块负责把响应帧里的原始字节数组转换成U16数组、I16数组、U32数组、浮点数组或者布尔数组核心是字节序处理。LabVIEW里用Unflatten From String这个函数时注意有一个byte order输入参数必须手动指定小端因为三菱3E帧的数据区默认就是小端。如果偷懒不指定直接默认值在x86机器上可能碰巧是对的但一旦代码迁移或者换协议路径数据就全乱。日志模块我用队列加文件写入的方式实现记录每一次请求时间、请求帧、响应帧、响应耗时、错误信息保留最近7天日志文件。这招在客户现场调试时太有用了设备偶尔出一次通讯故障如果没有日志根本复现不出来有日志把那一秒的收发帧拉出来一对比问题基本就定位了。4. 批量读写的核心实现细节4.1 批量读D寄存器一次读回N个字批量读D区的完整流程是这样先把软元件类型、起始编号和点数传入帧构建VI得到一字节数组然后通过队列发送线程发出这一帧在同一个TCP连接上等待响应收到响应帧后先校验帧头和长度再解析数据区。举个例子我需要读D120开始的40个字也就是D120到D159。起始地址是三字节64 00 00吗不对D120的编号是120十六进制是0x0078所以起始地址字段是78 00 00软元件代码A8点数28 00即40个。构建出来的请求帧核心部分是50 00 0E 00 FF FF 10 27 01 04 00 00 78 00 00 A8 28 00这一步里有一个需要特别注意的事项起始地址的高字节在一些资料里可能会被忽略但FX5U地址确实有可能超过16位范围尤其是ZR文件寄存器这种大区段所以我在帧构建模块里对三字节起始地址做了完整填充即使代码只有A8这种也把高字节填成0没有遗漏。4.2 批量写D寄存器连续地址一次下发批量写相比批量读最大区别是请求帧的数据区里要多出一块待写入数据区而且长度字段要把这块数据区的字节数算进去。写D100到D102如果写入的三个字分别是0x5678、0x1234、0x0000那么请求帧是50 00 14 00 FF FF 10 27 01 14 00 00 64 00 00 A8 03 00 78 56 34 12 00 00拆开讲14 00是请求数据长度0x0014等于20字节。01 14是批量写命令子命令00 00是字单位。64 00 00是D100地址A8是D代码03 00是点数3。数据区从78 56开始每两个字节一个字小端排列。这个函数的应用场景很广。我在这台设备里设置配方参数就是这么用的LabVIEW里把配方结构体的各个字段打包成U16数组一次写入PLC预先规划好的配方D区总共40个字一次搞定。相比老式做法一个参数一次写通讯请求数量降了一个数量级总耗时从几十毫秒降到了两三毫秒。4.3 位元件批量读写M点打包处理位元件和三菱PLC通讯里经常被忽略因为它涉及位打包处理起来比字元件麻烦。批量读M点我要用到子命令0x0001也就是位单位。读M0到M15请求帧是50 00 0E 00 FF FF 10 27 01 04 00 01 00 00 00 90 10 00注意这里子命令变成了00 01起始地址还是0软元件代码是90点数10 00即16个点。响应帧里数据区的每一个字节代表8个位低字节第一位对应M0依次排列。如果M0为ONM1为OFFM2为ON那第一个字节的Bit0是1Bit2是1其他是0即0x05。批量写位也是类似原理数据区按位打包写入。在LabVIEW里处理这种位打包关键是用整数位运算来做把布尔数组转换成U8数组。这里有一个LabVIEW数组学习的经典场景布尔数组转U8时数组大小要跟位宽对齐不满8位的补零处理这类细节一旦写错M点写进去就会出现一个字节里8个点互相串位的问题。4.4 稀疏地址和混合类型数据别用逐点读有时我们要读的数据不是一整段连续地址而是分布在D区不同位置中间夹杂着别的不需要的数据。低效做法是每个地址单独调用一次批量读结果100个分散点就是100次往返通讯延迟会非常夸张。高效做法有两种。第一种是区域拼读把这些分散点按照所在区域归并成若干个连续段每段用批量读只读一次然后在上位机本地做数组切片和重组。第二种是整块读取如果分布区域不太大干脆把整个地址块读回来比如从D100到D300一共201个字全部读回再用数组索引取出需要的那几个字段。实测结果表明MC协议一次读200个字和读1个字的网络耗时几乎没有明显差异都在2毫秒左右所以第二种方法在地址规模不是特别巨大时更简单可靠。混合类型数据比如一个区域里既有U16又有浮点又有字符串就需要在应用层定义好数据结构读回原始字节数组后按偏移量逐段解析。我建议在工程文档里用一张区域布局表把地址、类型、长度、含义列清楚代码里按这张表定义常量这样后期维护非常省心。5. 实战场景伺服运动系统与批量参数下发5.1 线速度0.8m/s是怎么换算出来的回到这台设备JE-A伺服的电机输出端是5比1减速器减速器输出轴装5M20同步轮。5M20的同步轮节距是5毫米20个齿节圆周长就是20乘以5等于100毫米也就是0.1米。如果负载线速度要求0.8米每秒换算下来同步轮输出轴转速就是8转每秒也就是480转每分。减速比5比1所以电机实际转速就是480乘以5等于2400转每分。这个转速对于JE-A伺服来说在额定范围内设计没有问题。搞清楚转速换算电机的脉冲频率也就有依据了。假设伺服编码器分辨率是17位即131072脉冲每转且电子齿轮比设成1比1电机2400转每分换算成40转每秒那需要的脉冲频率就是40乘以131072约等于524万脉冲每秒即5.24MHz。这个脉冲频率对FX5U的高速脉冲输出口来说需要重点确认如果超出范围就要通过电子齿轮比把脉冲频率降下来。这些计算不是通讯库本身的事但它决定了LabVIEW下发目标速度时上位机到底该下发什么单位、什么量纲的数据。通讯库只是管道数据语义必须由应用层定清楚。我在配方区里统一使用毫米每秒作为线速度单位PLC侧程序负责把线速度换算成电机转速这样上位机侧语义清晰不容易出单位换算错误。5.2 批量下发运动参数的节奏控制实际运行时LabVIEW先下发一组运动参数比如目标位置、运行速度、加速时间、减速时间、停止方式再下发一个启动M点。这里有个关键经验参数写入和启动信号必须分开两步而且启动信号要最后发。原因在于PLC的扫描周期和通讯时机不同步如果启动命令和参数在同一个时刻到达PLC可能先扫到启动位参数还没来得及更新到对应寄存器就会用旧参数动作。我在库里专门封装了PLCWriteParamBatch.vi这个VI把目标位置、速度、加减速时间打包成一个结构体数组连续写入预设的D区地址等这个函数返回成功后再单独调用PLCWriteOneBit.vi把启动位置位。中间由LabVIEW程序控制一个10毫秒间隔确保PLC至少有1到2个扫描周期去更新参数区。这个细节别看小产线上因为这个出过好几次位置跑错的事故。5.3 圆弧插补参数的批量下发场景FX5U的圆弧插补指令需要一组完整的轨迹参数包括起点、终点、圆心、方向、速度、加减速时间等。用LabVIEW做上位机时界面上画好轨迹计算出来的这些参数临时放在一个环形的D区队列里PLC侧每次通过一个FIFO指针取走一组参数。上位机批量写入这段队列每写一组就递增写入指针PLC处理后递增读取指针。这种环形结构的读写用逐点写会产生大量通讯请求而且可能出现读写指针错位导致PLC取到半截数据。我实现的方案是每次写一整组轨迹参数到一起通讯库返回成功后上位机再单独写一个参数有效字值从0变成1。PLC侧检测到这个字变化后才去读参数。整个过程中MC协议的批量写指令只用了三个请求但保证了数据的一致性。如果我当时用的是Modbus逐寄存器写这个项目大概率会在联调阶段被频繁的数据错位折磨。6. 编码转换与字节序两个最容易翻车的地方6.1 PLC字符串读回来乱码GBK和Unicode的坑LabVIEW默认字符串是UTF-8或本地编码但三菱PLC里存储的字符串大多是GBK编码。直接读回来在界面上显示常见情况是中文全部变成乱码英文和数字正常。这个问题我最早做FX3U串口通讯时遇到过排查了一整天最后才发现是编码问题。解决办法在LabVIEW里不算复杂核心是利用.NET的System.Text.Encoding类。在程序框图中放置.NET构造函数节点选择System.Text.Encoding调用GetEncoding方法输入参数是GBK字符串拿到一个Encoding对象。然后再设置一个调用节点调用这个对象的GetString方法输入参数是原始字节数组输出就是正确解码的Unicode字符串。写入方向反过来先用Encoding.GetBytes把Unicode字符串转成GBK字节数组再通过批量写函数写到PLC的字符区。性能上这个转换每次调用大概零点几毫秒对PLC通讯完全够用。需要注意的是.NET节点所在程序运行环境需要能访问.NET Framework大多数Windows工控机上没问题但如果部署到RT Target上就走不通了那时只能调用系统API或在PC机上转换后再传。6.2 大端小端为什么数值读回来天差地别字节序问题几乎每个做LabVIEW和三菱PLC通讯的人都踩过。三菱3E帧的数据区默认是小端也就是低字节在前这和x86平台是一致的但问题是LabVIEW里字符串和数值之间的转换函数非常多你选了哪个函数、字节序参数填了什么差别极大。举一个实际的例子PLC里一个U32的数值0x12345678在小端规则下寄存器排布是D00x5678D10x1234响应帧里的字节是78 56 34 12。如果你用String To Byte Array拿到字节数组再直接用Type Cast转成U32由于LabVIEW在x86上碰巧也是小端结果数值是对的0x12345678。但如果你把字节数组进行了一次字符串显示转换或者用Flatten/Unflatten函数时没指定字节序很容易得到0x78563412也就是字节完全反序。稳妥做法是统一使用Unflatten From String函数输入参数手动指定Little Endian同时指定数据宽度比如U16、U32、SGL、DBL。这样所有数据的字节序有据可循不需要依赖内存布局的巧合。浮点数据更要小心尤其是FX5U的浮点数常见做法是低地址放低字、高地址放高字但也有些老型号PLC存储顺序相反最可靠的办法是先写一个已知的浮点数比如1.5到PLC再读回来验证字节序以实测结果为准。6.3 位打包与字节对齐M点顺序不能拍脑袋位元件按位单位读取时响应数据区中第1个字节的Bit0对应起始地址的那个M点Bit1对应下一个依此类推。如果起始地址是M0那M0到M7对应第一个字节M8到M15对应第二个字节。但如果起始地址不是8的整数倍比如从M3开始读16个点那么第一个字节内Bit0对应M3Bit1对应M4后面依次排列这种错位特别容易让没有经验的人读出来数据完全不对。我在库的接口设计里做了一件事外部调用者传入的起始M点编号可以不是8的倍数但帧构建模块会自动把起始地址向下取整到8的倍数增加读取点数然后在解析结果时按实际偏移量丢弃多余的数据。这样做让外部接口更友好同时保证了帧内数据始终对齐到字节边界。写M点方向也是类似处理保证对PLC侧的位操作是按字节打包的。这里再分享一个经验LabVIEW里做位打包和解析建议用数字题目的Shift和Mask操作而不是字符串拼接因为字符串拼接在循环里会产生大量内存拷贝在50ms刷新周期里虽然不至于卡死但会让CPU占用率明显偏高尤其当读点数达到上千时。7. 调试实录常见问题与排查技巧7.1 帧能发出但PLC没反应查长度字段我最早调试自己写的MC协议帧时遇到过一个问题用网络调试助手能把帧发到PLCPLC也有响应但在LabVIEW里写的帧发到PLC后毫无回应。对比半天发现问题出在请求数据长度字段。我用字符串拼接时自动把长度算得和帧不对应PLC解析帧头之后长度字段和实际数据区长度不一致导致PLC认为这是一个不完整帧直接丢弃。排查这类问题最快的方法是用一个十六进制字符串显示控件把库构建出来的请求帧显示出来手工对照协议手册逐字检查。开发期在帧构建模块后面临时接一个Hex显示调试完再摘掉。这个习惯帮我排查掉很多因为拼接顺序、小端写反导致的坑。7.2 通讯成功但数据全为0或FFFFFFFF数据返回成功但读回的值是0或者FFFFFFFF这种问题常见原因有三个。第一个是地址偏移算错比如软元件代码用错该用A8的用了B4PLC不会报错因为地址对应到另一个不存在的区域读回的可能就是0或者异常值。第二个是PLC侧相应地址被程序占用或者根本没有使用读回默认值。第三个是数据类型解析错误实际是浮点数据当U16解析得到一堆没有意义的数字。排查方法是在PLC里强制写入一个已知数值比如D200写1234然后上位机读D200看是不是1234。如果读到1234正常说明地址链路没问题如果读数是0优先检查软元件代码和地址换算如果读数莫名变成一个大数就要检查字节序和数据类型宽度。这个流程屡试不爽。7.3 偶发超时和断线重连策略现场通讯最怕偶发超时就是大多数时间正常偶尔某一次请求要几百毫秒甚至直接超时。最常见的原因是交换机或PLC的CPU扫描周期里被某个高优先级中断拖住了单个请求响应时间偶尔超标另一个常见原因是上位机某个时刻同时多个线程发请求TCP连接上帧交错导致响应解析出错。我做了三个层面的防护。第一层超时时间不要设死做成可配置参数对不同类型的请求可以分别设置。第二层对偶发超时做重试重试次数2次重试间隔50毫秒。第三层如果连续重试都失败触发自动重连流程。实测这套策略下来产线上偶发通讯故障的恢复时间从分钟级降到了秒级而且有日志可查。7.4 多线程串包问题用单例和队列锁住发送LabVIEW里多个循环同时调用PLCRead和PLCWrite如果不做互斥两个VI同时在同一个TCP连接上发送数据非常容易出现一帧的末尾被另一帧的头截断结果是两个请求都解析失败。这个问题在LabVIEW里尤其隐蔽因为LabVIEW的VI默认是可以并行重入调用的两个循环里放同一个VI它们会各自复制一份运行空间。解决方案是用Action Engine也就是功能全局变量加队列。我把库里所有发送请求的操作收敛到一个独立的通讯管理循环里这个循环里维护一个队列PLCRead和PLCWrite只是往队列里投递任务通讯循环串行处理。这样既保证了帧不被截断也天然实现了单例模式。这个设计在LabVIEW多线程项目里非常关键强烈建议专门实现。7.5 性能实测批量读比单点读快多少我在这台FX5U上实际测过一组数据单次读取1个字大约耗时2到3毫秒其中包含TCP往返和PLC处理时间读取100个字的情况下耗时大约3到5毫秒比单点读多个字多了不到2毫秒。也就是说批量读100个字和逐点读100个字相比耗时从两三百毫秒降到了三五毫秒性能差距有几十倍。方式读100字总耗时通讯请求数逐点读取约200~300ms100批量读取约3~5ms1批量读取并网络优化约2~3ms1所以只要数据分布允许一律走批量。这也是这套库存在的最大意义不是写一个读函数那么简单而是让应用层养成按块读写数据和设计地址规划的习惯。8. 一点经验之谈这套库开发完又做了两个项目逻辑基本没动只改参数表。回头看我最大的收获不是协议帧怎么写而是通讯库这类底层组件设计时一定要把调试手段和错误信息做在前头。最初一版库能跑通时功能很简单但没有十六进制日志、没有错误码翻译每次出问题都像在黑盒里猜。后来把日志、错误码解析、帧显示这些调试工具补齐之后整个系统才算真正稳定因为问题出现时你能在三分钟之内定位是地址算错、字节序错还是PLC侧逻辑错。还有一个经验是地址规划要趁早。项目开始就把所有D区、M区按功能划分好做成一张Excel表发给PLC程序员和上位机程序员一起对着做各写各的也不乱。通讯库只是管道管道里流的什么数据、流到哪儿去必须靠好的规划约定清楚。以后再有人问我LabVIEW和三菱PLC通讯怎么做我都会先问他你的地址规划表做好了吗做好了再来谈通讯库。
