ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

LabVIEW封装三菱FX系列PLC通信库:批量读写与协议解析实战

LabVIEW封装三菱FX系列PLC通信库:批量读写与协议解析实战 去年下半年帮朋友调试一条小型装配线两台三菱FX3U、一台FX5U本来以为三天能搞完结果光是通信这块就折腾了快一周。最开始项目点位少我直接逐个读一个D寄存器发一次请求PLC回一次程序也能跑。但等到单台PLC要监控的数据点超过200个的时候整条线的节拍完全压不住上位机界面卡到像PPT。没办法只能停下来认真研究“如何把数据批量读写做进一个库里”这件事。这篇就纯分享一下我自己用LabVIEW封装三菱PLC通信库的实战过程包含报文结构、库接口设计、批量读写实现以及我实测中踩过的几个大坑。内容主要针对FX3U/FX5U这类常用机型的用户尤其是正在做设备上位机、又想摆脱“读一个点发一次指令”模式的朋友应该会有用。1. 通信库要解决的真实场景点位多、节拍紧、帧太碎1.1 逐点轮询为什么会卡死整条产线先说一个最直观的对比。假设一台FX3U上有120个D寄存器要读用最老实的办法上位机发送一条读请求等PLC返回解析完再发下一条。在9600波特率下一条读写请求加响应即使数据量很小单个周期也要10到15毫秒算上程序处理的余量一百多个点跑一圈差不多要1.5秒。这在单机设备上可能感觉不明显但如果产线上有三台PLC每台都要300毫秒到500毫秒刷新一次整线的联动信号延迟就会累加。我当时遇到的情况是一个扫码工位要等PLC把30个配方参数传过来逐点读用了将近800毫秒扫码枪那边等不及直接报超时。问题本质不是PLC慢而是通信帧太碎——每一次请求里真正有用的数据可能就两个字节剩下全是协议开销。批量读写的思路就是把“多帧小数据”压缩成“少量大数据”。比如D100到D199这100个字一条读命令就能拿回来算上响应时间大概只有30到50毫秒比逐点读快了一个数量级。这也是我为什么要把它封装成库因为一旦涉及地址偏移、帧长度计算、数据转换这些东西直接写在主程序里很容易乱封装好以后谁都敢用。1.2 “库”的边界只做协议不做业务设计库之前先把边界想清楚。我这里说的“库”是一个只负责“和三菱PLC通信”的工具集它做三件事初始化串口、生成请求帧、解析响应帧。至于读回来的数据是温度还是转速、要不要报警、怎么显示那是上层程序的事我坚决不塞进库里。这个边界很重要。我见过有人把业务判断直接写在通信VI里比如“根据D100的值判定OK/NG然后亮灯”短期看着方便但换一台设备、换一组点位整个通信模块也要跟着改等于把库做死了。我的习惯是库只暴露几个干净的接口比如“读指定起始地址的连续N个字”“写一组连续数据到指定起始地址”返回结果就是普通数组业务层想怎么处理就怎么处理。另外一个经验是库内部必须做超时和重试但重试策略要可配置。因为不同项目的通信环境不一样有的用原装编程线有的用第三方USB转RS422有的走以太网超时时间、重试次数肯定不能一个参数吃遍天。我自己的库默认超时200毫秒重试1次实际用下来大多数场景都够。2. 三菱FX系列串口通信的核心帧理解后才能封装2.1 编程口协议报文格式拆解LabVIEW里写通信库最底层还是得回到“报文”。三菱FX系列编程口协议计算机链接协议的帧格式比较固定以读取D寄存器为例上位机发送的请求帧长这样STX0x02表示帧开始命令两个ASCII字符读字是“WR”写字是“WW”读位是“BR”写位是“BW”起始地址4个ASCII十六进制字符表示软元件编号点数4个ASCII十六进制字符表示读多少个连续地址ETX0x03表示帧结束校验和两个ASCII十六进制字符举个例子我想读D100开始的连续2个字命令字符串就是STX WR 0064 0002 ETX 校验和这里有三个地方容易搞混。第一命令、地址、点数在帧里都是ASCII字符不是二进制数。第二地址的十进制100要转成四位十六进制ASCII字符“0064”而不是直接发字符“100”。第三不同PLC型号的软元件编号和地址映射可能不同FX3U、FX3G、FX5U编程口的D区映射表不完全一致我强烈建议封装库之前先翻对应型号的编程手册确认起始地址编码别照着网上的旧帖抄。响应的格式和读命令对应如果读成功返回“STX 数据 ETX 校验和”数据部分是一大串连续ASCII十六进制字符。每个D寄存器占4个字符也就是2个字节。读10个字数据区就是40个字符。如果命令出错PLC会返回NAK0x15有的情况还会附带错误码这点后面细说。2.2 校验和计算与帧边界判断校验和是整个帧里最容易写错的地方。三菱编程口协议的校验和是从STX之后、到ETX为止包含ETX的所有字节ASCII码值累加取累加和的低8位再把这个字节拆成两个十六进制ASCII字符。用伪代码表示def calc_checksum(frame_without_stx_checksum): total 0 for ch in frame_without_stx_checksum: total (total ord(ch)) 0xFF return %02X % total比如帧内容是“WR00640002”加上ETX0x03把所有字符的ASCII码累加结果取低8位转成两个大写十六进制字符就是校验和。这里有个细节有些厂家的转换器或者第三方软件会把校验和算成小写三菱PLC本身能识别大写但为了兼容性我统一用大写。帧边界判断也不能偷懒。串口是字节流没有明确的“一帧一帧”边界所以接收方必须自己从流里截帧。我通常的做法是先按期望的响应长度去读串口读到后再检查STX和ETX的位置。如果是批量读取响应长度是固定的数据区字符数是“点数×4”再加上STX/ETX/校验和长度可以提前算出来。如果是ACK/NAK长度更短。收到数据后先找STX再找ETX两个中间夹着的才是有效数据校验和拿来验证验证不过就丢弃。2.3 读命令和写命令的差异写命令比读命令多一个数据区。比如要把10个字写到D100开始的位置请求帧是STX WW 0064 000A 数据区(40个ASCII字符) ETX 校验和写命令的响应比较简单PLC一般不用回传数据只回一个ACK0x06表示成功回NAK0x15表示失败。这里要特别提醒写完指令后不能马上认为数据已经写进去了最好把ACK收完整。因为PLC在收到帧后需要时间处理如果上位机发完就关串口或者去读很可能读到的是空的。读命令和写命令的处理逻辑差异决定了库接口设计时要把它们分开。读命令是“请求-数据响应”写命令是“请求-确认响应”它们的超时判断、响应解析都不应该共用一个解析VI不然代码里全是分支条件看着累改着也累。3. 库接口设计让上层调用不再关心报文细节3.1 初始化VI串口参数与连接方式用LabVIEW封装库我的模块划分是三层底层驱动、协议解析、上层接口。底层驱动封装VISA串口操作协议层负责生成和解析帧上层接口只暴露几个方便调用的VI。初始化VI是上层第一个要碰的接口。FX3U编程口默认串口参数是9600波特率、8位数据位、偶校验、1位停止位。但注意如果你的PLC里通过D8120寄存器改过通信参数那上位机必须和PLC保持一致否则双方根本对不上话。我见过一次特别折腾的案例PLC的D8120被人改成38400上位机一直按9600发请求结果串口监控软件里能看到PLC回NAK但上位机永远解析不了最后是拿着GX Works把D8120读出来才定位到问题。初始化VI里还需要考虑连接的物理链路。FX3U的编程口是圆头8针RS422电平跟PC串口的RS232不直接兼容。如果用手头的USB转RS232线通常还需要一个RS232转RS422的转换器而很多转换器还需要外部供电不然带不动PLC侧的光耦。我的建议是直接买三菱原装的USB-SC09-FX或者兼容线省得在接线定义上耗费精力。网上流传的圆头针脚图有一些是错的如果必须自己接线先拿万用表量一下哪几个针之间有电压差再对应手册确认别凭感觉。3.2 批量读接口从地址到U16数组批量读接口的输入参数我一般这样定义串口句柄、起始地址、读取点数、超时时间输出是一个U16数字数组。调用者不需要知道帧长怎么算、校验和怎么生成只需要告诉库“从D100开始读30个字”。接口内部的核心逻辑分四步生成读请求帧STX “WR” 起始地址编码 点数编码 ETX 校验和。通过VISA向PLC发送然后按响应长度循环读取串口。校验帧头帧尾和校验和提取数据区。把数据区的ASCII十六进制字符两两转换成一个U16数值放入数组。第四步最关键。假设收到的数据区是“006400650066”这是三个字的ASCII表示前4个字符“0064”对应第一个寄存器十六进制0x0064就是100。LabVIEW里“十六进制字符串转数字”可以直接做这件事但要注意选择“字符串转字节数组”节点的数据格式别把字符数组和十六进制数混淆。3.3 批量写接口连续地址与数据对齐批量写接口的输入是串口句柄、起始地址、数据数组。接口内部先检查数据数组的元素个数再生成对应的数据区拼到写命令帧里。写接口有一个隐蔽问题点数上限。FX3U编程口虽然理论上允许一次写很多字但实际试验下来单帧写超过64个字PLC偶尔会忙不过来返回NAK或者干脆不响应。为了稳定我把库内部的最大写入点数限制在32个字如果上层一次要写超过32个接口自动拆成多次写请求。这不是协议限制更多是工程上的可靠性取舍实测下来32字一帧在9600波特率下非常稳。另一个容易踩的坑是数据对齐。批量写时数据区的顺序要和PLC字地址递增方向一致第一个数组元素写到起始地址第二个写起始地址1。如果上层程序把数组顺序搞反了写进去的数据全部错位。库的文档里我会特意写明“数组索引从0开始对应起始地址、起始地址1、起始地址2…”并且在接口内部加一个简单的循环校验确保数组长度不为0。4. 数据回来之后类型转换、大小端和各种“不对”4.1 ASCII HEX转数值的两个隐蔽错误批量读接口返回U16数组后很多人会直接在后面接“数值显示控件”结果发现数字完全不对这是数据类型转换的锅。第一个错误是用错了转换节点。LabVIEW有“十六进制字符串转数字”和“字符串转字节数组”两种路径前者能把“0064”这种字符串变成数值100后者返回的是每个字符的ASCII码比如‘0’的ASCII码是48。如果你把返回的数据区字符串直接强转成U16数组得到的一定是乱码。第二个错误是大小端顺序。三菱FX系列在串口协议里一个16位寄存器的两个字节从响应数据区看是“高字节在前、低字节在后”比如寄存器值0x1234前面先收到“12”再收到“34”。LabVIEW的“强制类型转换”会把内存里连续两个字节按本机小端解释如果你直接把字节数组用强制类型转换转成U16刚好反了读出来的数变成0x3412。这个坑几乎每个人都会踩一次。安全的做法是自己按“高字节×256 低字节”组合或者把接收到的两个字节交换顺序后再强制转换。4.2 32位数据和浮点数的高低字顺序如果只读16位整数第三节的批量读接口就够用了。但实际项目里更多情况是读32位整数和单精度浮点数——伺服位置、速度、压力值这些三菱PLC里通常占两个连续D寄存器。这里有一个让很多人头疼的排序问题。三菱FX3U的32位数据存储规则是低16位放在地址小的寄存器高16位放在地址大的寄存器。比如一个32位数据存在D10和D11里D10存低16位D11存高16位。对应到批量读接口返回的数组里数组第0个元素是D10的低16位第1个元素是D11的高16位。要把它们组合成32位值正确做法是32位整数 (U32)(D11的值) 16 | (U32)(D10的值)如果是浮点数先用上面的公式拼成一个U32再用LabVIEW的“数值转换”把U32按单精度浮点数解释。千万别看到“低地址存低16位”就以为要交换字节序这里是“字序”问题不是“字节序”问题两个概念分开记就不会乱。4.3 字符串与GBK转Unicode的常见处理热搜词里有人问“LabVIEW中怎么把GBK转换成Unicode”这个在PLC通信里经常遇到。PLC里存字符串时通常一个D寄存器存两个字符低字节在前、高字节在后。比如D100的低字节存字符‘A’高字节存字符‘B’。从PLC读回字符串的正确步骤是先把批量读接口得到的U16数组还原成原始字节数组。这一步要特别小心因为U16数组里每个元素的高字节和低字节需要先交换回来再逐个压到字节数组里顺序错一个字符串就乱了。字节数组得到以后如果PLC里存的是中文字符编码可能是Shift-JIS日文系统或GBK国内设备常见。LabVIEW的字符串控件默认按Unicode解释直接把GBK字节数组转成显示字符串就全是乱码。Windows下我一般用.NET节点调System.Text.Encoding先GetEncoding(936)拿到GBK编码再用GetString还原成Unicode字符串实测稳定。5. 实测三连坑粘包、超时与半帧5.1 发送后立刻读十有八九超时我第一次写完批量读VI后连着串口调试工具测试发现一个规律发送请求后马上读串口经常第一遍读到空重试第二次才能成功。后来想明白了PLC从收到请求到返回数据中间有处理时间这个时间通常几毫秒到几十毫秒不等但串口读操作是“立即返回当前缓冲区里已有的字节”你发完就立刻去读缓冲区往往是空的。解决思路有两个。简单粗暴的方法是发送请求后加延时比如10到20毫秒再去读。但更好的办法是把VISA读取的超时时间利用起来请求发送后用循环反复读串口直到读够期望帧长或超过超时时间才退出。这个做法的好处是不需要关心PLC具体处理多久只要在超时范围内来多少读多少。5.2 ACK粘在第一帧前面批量读模式下正常响应是“STX 数据 ETX 校验和”不会出现ACK。但有个特殊情况如果库内部做了写后读连续操作或者同一时刻有两个上位机任务在操作同一个串口PLC可能会把上一次写命令的确认ACK和这次读命令的响应一起发出来导致响应帧前面带了一个多余的0x06字节。第一次遇到这种“粘包”时我的解析逻辑直接报错因为帧头找不到或者长度对不上。后来在帧解析VI里做了容忍处理先扫描缓冲区找到第一个STX从STX开始按期望长度截取如果STX前面有ACK/NAK之类控制字节就主动丢弃。项目里如果必须多线程访问同一串口我建议干脆在外层加互斥锁同一时刻只允许一个线程发送请求从根上避免这种半粘连问题。5.3 重试机制怎么设计才不会雪上加霜通信偶尔失败是正常的所以库里面要有重试机制。但重试不是简单地“失败就再发一次”而是要区分失败原因。如果收到的是NAK说明PLC收到了请求但认为帧有错可能是校验和的问题也可能是命令格式不合法。这种情况盲目重试只会重复生成错误的帧解决不了问题应该把错误帧内容记录下来给上层排查。如果是超时未收到响应情况更复杂。有可能是串口线松动、PLC忙、或请求帧根本没发出去。这时重试还可以接受但重试次数不宜太多我一般默认1次最多不超过2次。因为如果PLC真的忙不过来你发得越多它越乱重试还会把总线占满其他设备更没机会通信。做好错误分类和日志记录比把重试次数调到无限大有用得多。6. 再往前走FX5U以太网与队列调度的方向6.1 FX5U与SLMP协议的变化FX5U和FX3U最明显的区别是网口成为标配走的协议是SLMP也就是三菱的MC协议以太网版。它跟编程口协议不一样不再用STX/ETX这种字符帧而是二进制帧结构通过TCP端口5000通信。如果用LabVIEW做FX5U通信不需要再手动拼接STX只要用TCP函数库连上后按SLMP帧格式组包即可。SLMP连接有一个“打开请求”的过程上位机先发送一个打开请求报文PLC返回打开完成响应之后才能进行数据读写。这一步很多人第一次做会漏。打开后的批量读请求二进制帧里包含副头部、命令、子命令、起始地址和点数命令0x0401表示批量读0x1401表示批量写。和串口一样也分ASCII模式和二进制模式二进制模式帧更短、效率更高。我用FX5U做过一次测试100个D寄存器批量读TCP方式一轮不到10毫秒跟串口完全不是一个量级。如果你的设备已经上了FX5U我非常建议直接走以太网SLMP没必要再纠结串口线的握手和电平转换问题。6.2 生产者消费者队列才是通信稳定的最终解库本身再完善如果上位机程序结构不合理通信依然会不稳定。最典型的问题是把通信VI直接拖进“While循环事件结构”的UI线程里用户拖一下窗口或者弹个对话框通信循环就被打断Timing全乱。我现在的做法是标准的“生产者-消费者”模式。一个生产者循环负责把要读的地址清单或写命令封装成指令对象放进队列一个消费者循环专门跑通信从队列里取指令、发串口/TCP、等响应、解析然后把结果放进另一个队列供UI消费。这样通信节奏完全由消费者循环自己控制界面再卡也不影响通信周期。在这个基础上我还会在消费者循环里维护一个简单的指令表记录每条指令的发送时间、重试次数、最近一次状态。这样即使通信出错也能很快定位是哪一步、哪一条请求出的问题。这套结构配合前面的批量读写库基本上可以应对绝大多数产线设备上位机的项目需求。我个人用下来的体会是上位机和三菱PLC通信最怕的不是协议复杂而是没有一个清晰的分层。把帧格式、校验、批量读写这些细节全部收进库里上层只面对“读哪里、读几个、给我数组”这种简单逻辑整个项目的调试周期会明显缩短。如果你也在做类似的设备上位机建议先把手头PLC的编程口协议手册找出来把帧结构画一遍再动手写VI比我当初一边查手册一边改代码要顺利得多。
返回列表