news 2026/10/15 17:35:35

三菱FX5U以太网通信实战:SLMP/MC协议读写与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
三菱FX5U以太网通信实战:SLMP/MC协议读写与避坑指南

简介:这份资源面向工业自动化与上位机开发方向的C#程序员,聚焦三菱FX5U PLC的以太网通信实现,尤其适用于自动化立体仓库等需要与PLC高速交互的场景。内容围绕3E帧报文结构展开,涵盖连接建立、报文构造、数据传输、通信协议解析、错误重试机制以及WinForm界面设计等关键环节,属于网络编程与工业通信协议结合的中高阶实践素材。压缩包共35个文件,约79KB,以cs源码为主体,辅以config配置、resx与resources资源、sln与csproj工程文件及exe可执行程序,结构完整,可直接在Visual Studio中打开运行与调试。目前已有466人学习下载,适合希望理解PLC私有协议报文封装、掌握RawSocket与TcpClient通信差异、并借鉴上位机与业务逻辑分层设计的开发者参考,也可作为立体仓库控制系统通信模块的排错与二次开发起点。

1. 三菱FX5U上位机以太网通信:从一根网线到稳定读写的那点事

车间里一台FX5U跑得好好的,上位机却时不时报超时,重启软件又能撑半小时——这种玄学问题,十有八九不是PLC的锅,而是上位机以太网通信这一层没搭稳。所谓「三菱FX5U上位机以太网通信」,说白了就是让PC端的程序通过以太网口,按三菱自己的协议跟FX5U交换软元件数据:读D寄存器、写M继电器、拿X/Y状态,甚至做远程启停。它解决的是「不拆机、不接线、不占用编程口」的实时数据通道问题,适合做产线看板、MES对接、设备数据采集、上位机组态这类场景。这篇笔记不讲空泛概念,只讲我实际落地时怎么选协议、怎么配参数、怎么把读写跑通,以及那些让我翻过车的坑。

2. 先搞清楚走哪条路:MC协议、SLMP与开放通信的选型账

2.1 三种常见上位机通信方式到底差在哪

FX5U的以太网口不是只有一种玩法,常见做法有三类,选错了后面全是返工。

第一类是MC协议(MELSEC Communication Protocol),这是三菱自家的上位机通信协议,走TCP或UDP,端口默认5007(TCP)/5006(UDP,实际以工程设置为准)。它的特点是「拿来就能读写软元件」,指令格式固定,批量读D寄存器效率高,适合做数据采集和看板。缺点是报文要自己拼二进制或ASCII帧,第一次写容易在字节序上翻车。

第二类是SLMP(Seamless Message Protocol),可以理解为MC协议在以太网上的标准化封装,支持3E帧、4E帧。FX5U走3E帧二进制最常见。它比裸MC更规范,跨网段、跨设备兼容性更好,很多上位机库直接按SLMP实现。

第三类是Socket开放通信,也就是用PLC的「套接字通信」功能,自己定义收发数据。灵活度最高,但PLC侧要写梯形图配合,上位机侧要处理粘包,工作量翻倍,除非协议特殊,否则不建议新手一上来就选它。

选型结论很直接:做数据采集和常规读写,优先SLMP/MC协议的3E帧二进制;只有对方设备强制要求自定义报文时,才考虑Socket开放通信。

2.2 用Python把3E帧二进制读D寄存器跑通

下面这段是我常用的最小可跑示例,用socket直接发3E帧二进制,读FX5U的D100开始10个字。先确保PLC侧已设置好以太网参数、开放了MC协议。

import socket import struct PLC_IP = "192.168.1.10" # FX5U的IP,按实际工程改 PLC_PORT = 5007 # MC协议TCP端口,常见默认值 HEADER = b"\x50\x00" # 3E帧二进制子头 NETWORK = b"\x00" # 网络号 PC_NO = b"\xFF" # PC号,固定FF IO_NO = b"\x03\xFF" # 请求目标模块IO号,FX5U常用03FF STATION = b"\x00" # 站号 MONITOR_TIMER = b"\x10\x00" # 监视定时器,4秒 def build_read_cmd(device_code, head_addr, count): # 批量读指令 0401,子指令 0000 cmd = b"\x04\x01\x00\x00" # 软元件代码:D寄存器是A8,M是90,X是9C,Y是9D addr = struct.pack("<I", head_addr)[:3] # 3字节小端地址 dev = device_code + addr num = struct.pack("<H", count) return HEADER + NETWORK + PC_NO + IO_NO + STATION + MONITOR_TIMER + cmd + dev + num def parse_response(data): # 响应前11字节是头部,之后2字节结束代码,再往后才是数据 end_code = data[11:13] if end_code != b"\x00\x00": raise RuntimeError(f"PLC返回错误码: {end_code.hex()}") payload = data[13:] values = struct.unpack("<" + "H" * (len(payload)//2), payload) return values s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(3) s.connect((PLC_IP, PLC_PORT)) s.sendall(build_read_cmd(b"\xA8", 100, 10)) # 读D100~D109 resp = s.recv(1024) print(parse_response(resp)) s.close()

逻辑说明:3E帧二进制请求由「子头+网络号+PC号+IO号+站号+监视定时器+指令+软元件+点数」拼成,响应回来先看结束代码,非0就是PLC拒绝了。参数上,device_code决定读哪个区,D是A8、M是90、X是9C、Y是9D;head_addr是起始地址,注意是三字节小端;count是一次读的点数,别贪多。

2.3 写M继电器和批量写的参数差异

写单个M继电器用指令1401,子指令0001(按位写)。和读相比,写请求多了一个「写入数据」段,位写时数据是0x10表示ON、0x00表示OFF。批量写D寄存器用1401子指令0000,数据段按小端逐字拼。这里最容易错的是位地址和字地址的换算:M100在报文里不是直接写100,而是按位地址计算,M100对应字地址100//16=6、位偏移100%16=4,拼地址时要用这个字地址。我一般会封装一个bit_to_word_addr函数,避免每次手算。

提示:读写前先用编程软件确认PLC的以太网参数里「开放方式」选了MC协议,且「允许来自同一网络外的访问」按需勾选,否则连得上但一读就报错。

3. 把通信做稳:连接管理、超时与心跳的工程化处理

3.1 为什么「连上就完事」的想法一定会翻车

很多人第一次跑通读写就以为结束了,结果上线后每隔几十分钟断一次。原因是TCP长连接在工业现场并不天然可靠:交换机端口抖动、PLC侧连接数上限、上位机进程卡顿,都会让连接悄悄失效。我的做法是连接池只保留一条长连接,加心跳和重连,而不是每次读写都新建socket。新建连接的开销在几十毫秒级,高频采集时根本扛不住。

心跳不要用MC协议的空指令去探,简单粗暴地周期性读一个固定D寄存器(比如D0)即可,读到就认为链路活着。超时设3秒,连续两次失败就主动close再重连,别等操作系统TCP超时,那个默认可能是几分钟。

3.2 一个带重连和心跳的采集循环

import time class PlcClient: def __init__(self, ip, port=5007): self.ip = ip self.port = port self.sock = None self.fail_count = 0 def connect(self): self.close() self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(3) self.sock.connect((self.ip, self.port)) self.fail_count = 0 def close(self): if self.sock: try: self.sock.close() except Exception: pass self.sock = None def read_d(self, addr, count): try: self.sock.sendall(build_read_cmd(b"\xA8", addr, count)) resp = self.sock.recv(4096) self.fail_count = 0 return parse_response(resp) except Exception: self.fail_count += 1 if self.fail_count >= 2: self.connect() # 连续失败两次就重连 raise client = PlcClient("192.168.1.10") client.connect() while True: try: data = client.read_d(100, 10) print("D100~D109:", data) except Exception as e: print("采集异常,已触发重连:", e) time.sleep(1) # 采集周期1秒,按现场需求调

逻辑说明:fail_count是重连的触发条件,连续两次失败才重连,避免偶发丢包就疯狂重连。time.sleep(1)是采集周期,做看板1秒够用,做闭环控制要压到100ms以内,但那时得换更高效的批量读策略。参数上,recv(4096)的缓冲区要大于单次响应长度,读点数多时按13 + count*2估算。

3.3 批量读的点数上限与拆分策略

FX5U单次批量读的点数有上限,常见做法是一次不超过960个字,超了就拆成多次请求。我一般按512字一批,既留余量又好算偏移。拆分时注意每批的起始地址要按字对齐,位软元件(M/X/Y)批量读时返回的是按位打包的数据,解析时要按位展开,别直接当字用。

软元件代码单次建议上限数据单位
DA8512字16位/字
M90512字(按位打包)1位
X9C512字(按位打包)1位
Y9D512字(按位打包)1位
RAF512字16位/字

注意:位软元件批量读返回的是「按位压缩」的数据,比如读M0~M15返回2字节,bit0对应M0。解析时用(data[i//8] >> (i%8)) & 1取第i位,别用字解析。

4. 避坑与排查:那些让我加班到深夜的通信故障

4.1 连得上但一读就报错,结束代码C059

现象:socket能connect成功,发读指令后返回结束代码C059。原因通常是PLC侧MC协议没开放,或者请求里的IO号、站号跟实际工程不匹配。解决:回编程软件看以太网参数,确认「开放方式」是MC协议,IO号按实际模块填,FX5U常见是03FF,站号默认00。

4.2 读回来的数据全是0或者明显错位

现象:D100明明有值,读回来是0,或者数值对不上。原因八成是字节序或地址偏移搞错了。3E帧二进制地址是小端三字节,数据也是小端。解决:先用编程软件确认D100的实际值,再用单点读验证,确认地址拼法和解析顺序。如果读的是32位数据(比如D100+D101组成一个浮点),要按小端拼成4字节再解float。

4.3 跑几小时后连接假死,send不报错但recv超时

现象:程序跑一段时间后卡在recv,send也不报错。原因是TCP连接被中间设备静默断开,上位机不知道。解决:加心跳读和超时重连,别依赖TCP自身的保活。我一般把心跳周期设成采集周期的5倍,比如1秒采集就5秒心跳一次。

4.4 多台上位机同时连,PLC只认一条

现象:第二台上位机连不上,或者连上后第一台断了。原因是FX5U的MC协议连接数有上限,常见是同时只允许少数几条。解决:做数据采集时尽量用一台中间服务统一读,再分发给其他客户端,别让多台机器直连PLC。

4.5 写M继电器没反应,读回来还是旧值

现象:写指令返回成功,但M没动作。原因可能是写的是位地址算错了,或者PLC程序里该M被其他逻辑覆盖。解决:先确认位地址换算正确,再用编程软件监控该M的实际状态;如果被程序覆盖,说明这个M不该由上位机写,换一个空闲的M区。

5. 进阶:把通信层封装成可复用组件与验证方法

走到这里,读写和稳定性都跑通了,下一步是把它做成能复用的东西,而不是每个项目重写一遍。我的习惯是分三层:传输层只管socket和重连,协议层负责拼帧和解析,业务层暴露read_d(addr, count)、write_m(addr, value)这种语义接口。这样换PLC型号时只改协议层,业务代码不动。

验证方法上,我一般做三步。第一步用编程软件强制改D100的值,看上位机读回来是否一致,验证读通道。第二步从上位机写M100,用编程软件监控M100是否翻转,验证写通道。第三步跑一个持续采集脚本,挂两小时,看有没有断连和重连日志,验证稳定性。三步都过,才敢往产线上放。

再补一个具体技巧:把常用软元件的地址和类型做成配置表,而不是硬编码在代码里。现场改地址时只改配置,不用重新打包程序。配置表用JSON或CSV都行,我一般用JSON,字段包括name、device、addr、type、count。采集循环按配置表遍历,读回来的数据按name映射到业务字段,这样MES对接时字段名直接可用。

import json TAGS = json.loads(""" [ {"name": "产量", "device": "D", "addr": 100, "type": "uint16", "count": 1}, {"name": "运行状态", "device": "M", "addr": 100, "type": "bit", "count": 1}, {"name": "温度", "device": "D", "addr": 200, "type": "float32", "count": 2} ] """) def read_by_tags(client): result = {} for tag in TAGS: code = {"D": b"\xA8", "M": b"\x90", "X": b"\x9C", "Y": b"\x9D"}[tag["device"]] raw = client.read_d(tag["addr"], tag["count"]) if tag["device"] == "D" else None if tag["type"] == "float32" and raw: import struct b = struct.pack("<HH", raw[0], raw[1]) result[tag["name"]] = struct.unpack("<f", b)[0] elif raw: result[tag["name"]] = raw[0] return result

逻辑说明:配置表驱动的好处是现场改点不用动代码,type字段决定解析方式,float32要拼两个字再解。参数上,count对float32是2,对uint16是1,别填错。这套封装我在几个采集项目里复用,改的只是配置和IP。

最后说个血泪教训:别在采集线程里做耗时操作,比如写数据库、发HTTP请求。通信线程只负责读,读到就丢进队列,另起线程消费。我早期图省事在采集循环里直接写库,结果数据库一卡,通信超时,PLC连接被拖死,排查了半天才定位到是消费端拖累了生产端。把生产和消费拆开,通信层就稳了。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 17:35:09

欧冠赛制算法题:从足球规则到代码契约的建模实践

1. 这不是体育新闻&#xff0c;而是一道被低估的算法实战题“欧洲冠军联赛”这六个字出现在程序设计训练课的作业列表里&#xff0c;第一反应往往是困惑——难道要写个足球比分爬虫&#xff1f;还是模拟球员转会系统&#xff1f;我第一次看到这个标题时也愣了三秒。但翻开题目描…

作者头像 李华
网站建设 2026/10/15 17:35:06

ASP.NET Core网上商城源码运行与生产部署指南

简介&#xff1a;这是一份基于.NET Framework开发的完整网上商城系统源代码&#xff0c;面向C#与VB.NET初学者及Web开发入门者&#xff0c;帮助理解ASP.NET Web Forms架构下的电商应用实现逻辑。资源包含151个文件&#xff0c;涵盖33个VB后台代码文件&#xff08;如Global.asax…

作者头像 李华
网站建设 2026/10/15 17:34:19

qextserialport:Qt 4/5老项目串口通信轻量救急方案

简介&#xff1a;本资源是面向Qt初学者与嵌入式/工业通信开发者的跨平台串口编程扩展库qextserialport-1.2win-alpha&#xff0c;专为Windows与Linux环境下Qt应用的串口通信需求设计&#xff0c;有效弥补Qt原生框架对底层串口支持的不足。压缩包共94个文件&#xff0c;涵盖12个…

作者头像 李华
网站建设 2026/10/15 17:33:40

Delphi加密组件包实战:AES、RSA与哈希在XE10.2中的集成与避坑

简介&#xff1a;TMS Cryptography Pack 3.4.0.1 是面向 Delphi XE10.2 开发者的加密组件库&#xff0c;适合需要在桌面与移动应用中集成数据保护、安全通信的中高级开发者。它覆盖 AES、DES、3DES、Blowfish、Twofish、RC4、RSA 等对称与非对称算法&#xff0c;并提供 MD5、SH…

作者头像 李华
网站建设 2026/10/15 17:33:39

C++手写线性回归神经网络:从原理到嵌入式部署实战

1. 为什么用C手写线性回归神经网络1.1 从一次实际需求说起去年帮一个做工业设备监测的朋友处理振动传感器数据&#xff0c;需要在嵌入式采集板上直接跑回归模型做实时趋势预测。板子资源很紧张&#xff0c;Python解释器根本塞不进去&#xff0c;PyTorch那套更是想都别想。当时第…

作者头像 李华