资讯动态

三菱FX5U上位机以太网通信:SLMP协议与C#报文解析实战

发布时间:2026/10/21 5:08:45 来源:尧图企业网站定制
简介这份资源是一套三菱FX5U PLC以太网通信的上位机程序源码基于C#与WinForm框架开发面向工业自动化、自动化立体仓库等场景的开发者。程序围绕3E帧报文展开完整包含网络接口选择、自定义报文结构、字节序转换、指令收发、超时重试等核心环节可直接用于学习PLC以太网通信协议与上位机界面联动。资源包共35个文件以.cs源代码、.sln/.csproj工程配置、.exe可执行程序以及.resources/.resx/.pdb调试附属文件为主压缩包整体大小79KB结构清晰、便于阅读和二次开发。已有467人学习下载。研读代码可掌握3E帧的构造与解析方法理解RawSocket与TcpClient在工业通信中的不同取舍并参考完整的WinForm界面设计了解通信状态展示与数据读写结果的处理方式适合正在开发立体仓库控制系统或希望系统学习C#上位机与PLC通信的工程师。1. 三菱FX5U上位机以太网通信先搞懂这份资源到底解决什么问题做自动化立体仓库的上位机开发最难缠的不是WPF画界面而是和PLC之间那层“看得见摸不着”的以太网报文。三菱FX5U自带以太网口默认走SLMP协议很多C#开发者第一次接手时对着抓包工具里一串十六进制字符完全不知道哪段是地址、哪段是数据。这份以“三菱FX5U上位机以太网通信”命名的资源核心就是一套可以直接套用的通信模块包含报文组包/解包、Socket连接管理、Winform界面驱动轮询的完整示例适用场景就是自动化立体仓库里货位状态读取、堆垛机动作下发、穿梭车寄存器读写这类高频工位通讯。适合手里已经有PLC基础、但缺一个能跑的C#上位机通讯框架的人。2. 通信协议与选型SLMP、MC协议和FX5U的以太网模块2.1 为什么立体仓库上位机首选以太网而非串口早期自动化立体仓库很多用RS232/RS485PLC侧加个串口扩展模块上位机用SerialPort类收发Modbus RTU。但这类仓库一旦超过两万台货位点对点轮询就成了灾难。以太网通讯的好处在于一是速度快FX5U内置以太网口能跑100M一条报文轮询几十个货位状态只要几毫秒二是连接数多上位机可以同时开多个Socket分别处理堆垛机、输送线、提升机互不阻塞三是报文带网络层校验比串口在干扰环境下稳定得多。选型时务必确认PLC固件版本是否支持SLMP。FX5U从早期固件就支持SLMP但部分出厂固件版本较老的机器需要在GX Works3的CPU参数里把“SLMP连接”使能打开。常见做法的步骤是以太网设置里分配一个端口号默认是1024或自定义通信协议选择“MC协议”框架选“3E帧二进制”代码类型选“二进制码”还是“ASCII码”决定后续报文格式。2.2 SLMP与MC协议的区别帧格式、端口与报文结构很多人把SLMP和MC协议混为一谈。严格说三菱FX系列走的是MC协议SLMP是三菱电机为多个系列统一后的协议名字本质上是MC协议的以太网实现。FX5U的以太网帧有两种代表形式3E帧二进制数据紧凑效率高上位机开发最常用。4E帧多一层头信息一般用于Q系列或需要路由的场合FX5U用得少。3E帧二进制格式固定请求帧结构为子头2字节0x50 0x00、网络号1字节0x00、PC号1字节0xFF、IO编号3字节0x03FF、站号1字节0x00、CPU定时器4字节如0x0010、请求数据长度2字节、监视时间2字节、命令2字节、子命令2字节和变长数据段。响应帧则是子头、网络号、PC号、IO编号、站号、响应数据长度、结束码2字节、随后是数据本体。判断一次读取是否成功先看响应帧结束码0x0000代表正常0xC0A8这类非零值就是网线或站号设置问题。2.3 资源包里报文的组成帧头、命令、地址、数据与校验读设备命令固定是0x0504写设备命令固定是0x0514。地址由软元件代码加偏移组成比如D100的地址码是0xA844十进制地址加64000后的十六进制M100是0x0140M区偏移从0x00A0开始换算。初次接触会不习惯这种“软元件码 偏移”的规则但按公式计算即可。数据段里的批点数点数范围1-512在立体仓库轮询场景下特别有用。比如一次读取D100到D163共64个字把货位占用标志全部取回上位机比一条一条读快一个数量级。校验在TCP/IP帧层面由以太网保证3E帧内部不需要额外算CRC这一点和Modbus RTU不一样新手容易误加CRC导致帧长度错位。3. 把报文拆开看读写D寄存器、M软元件和批量操作的格式3.1 读设备命令0504命令的请求与响应先给一个最基础的二进制读请求示例。假设要读取D100连续10个字软元件代码为0xA8起始地址100字节序小端。50 00 00 FF 03 FF 00 00 10 00 14 01 00 04 05 00 00 A8 44 01 00 0A 00这段报文的含义50 00是子头00网络号FFPC号03 FFIO编号00站号00 10是请求数据长度16字节从01 00到最后的0A 0001 00监视时间1秒04 05是读设备命令00 00子命令A8 44 01 00表示D100的地址0xA844即64068小端存储0A 00表示读取10点。这里容易踩坑的是长度字段它指的是命令后面的数据段长度而不是整帧长度。响应帧中如果结束码为0000紧接着的数据就是10个字20字节按小端解析。上位机拿到后直接用BitConverter.ToInt16挨个转换存入仓库货位状态数组。3.2 写设备命令0514命令的请求与响应写单字相对简单帧逻辑相同只是命令换成04 15数据段多出要写入的值。如果写D200一个值为123450 00 00 FF 03 FF 00 00 10 00 1E 01 00 04 15 00 00 A8 C8 00 00 01 00 D2 04A8 C8 00 00是D200地址64068200-100? 这里省略实际按软元件码加偏移算01 00写1点D2 04是1234的小端十六进制。响应帧比请求短很多只带结束码。立体仓库中常用它来给堆垛机下发任务编号、给穿梭车下发目的地地址建议写完以后立即读回验证防止网线半路掉了导致状态丢失。3.3 批量读取与离散点位适合立体仓库货位状态轮询货位状态一般不是连续字而是每个货位一个BOOL点。FX5U支持位软元件批量读取命令仍是0504但软元件代码要换成M区的位地址。比如读M500开始的32个位M区的软元件代码是0x90地址计算规则是“十进制点号0x00A0”再转十六进制。更省事的方法是直接把货位占用状态映射到D区PLC里用MOV指令把M区打包成字上位机只读D区一个帧搞定几百个货位。批点数上限是960个字位的话是8192点但实际建议每帧不超过256字否则PLC响应时间会升高。使用500ms定时轮询单帧读64字对FX5U的负担可以忽略。4. C#上位机实现从Socket到Winform界面4.1 建立TCP连接Socket、连接池与超时重连上位机与PLC通信最忌讳每次读写都新建TcpClient。仓库现场电磁干扰、交换机重启都可能导致连接中断所以需要一个连接管理类维持一个TcpClient实例检测到断连后自动重连并设置合理超时。public class PlcTcpClient { private TcpClient _client; private readonly string _ip; private readonly int _port; private readonly int _timeoutMs 2000; public PlcTcpClient(string ip, int port) { _ip ip; _port port; } public void EnsureConnected() { if (_client ! null _client.Connected) return; _client?.Dispose(); _client new TcpClient(); var ar _client.BeginConnect(_ip, _port, null, null); if (!ar.AsyncWaitHandle.WaitOne(_timeoutMs)) throw new TimeoutException(PLC连接超时); _client.EndConnect(ar); _client.ReceiveTimeout _timeoutMs; } }这段代码的核心是EnsureConnected每次读写前调它如果连接已断就同步重连并且用BeginConnect加WaitOne实现超时控制避免界面卡死。参数上_timeoutMs建议设置为2000-3000毫秒低于1000毫秒在现场交换机flapping场景下容易误判断线。4.2 组包与解包构造请求、解析响应的核心代码组包逻辑用一个方法把命令、软元件、起始地址和点数拼成byte[]。注意所有16位数据都要用小端序低位在前这是无数新人翻车的地方。public byte[] BuildReadRequest(ushort startAddr, ushort point) { var req new byte[18]; req[0] 0x50; req[1] 0x00; req[2] 0x00; req[3] 0xFF; req[4] 0x03; req[5] 0xFF; req[6] 0x00; req[7] 0x00; req[8] 0x10; req[9] 0x00; req[10] 0x01; req[11] 0x00; req[12] 0x04; req[13] 0x05; req[14] 0x00; req[15] 0x00; req[16] (byte)(startAddr 0xFF); req[17] (byte)((startAddr 8) 0xFF); // 注意实际还需要附加软元件码、点数这里为简写省略长度计算 return req; }这里省略了软元件码和点数的拼接原因是要先把长度字段算出来。实际工程中我习惯先开一个List 依次Add命令和地址最后回过头把请求数据长度填进去。解包时先读前11个字节校验子头、结束码结束码非零就抛出带错误码的异常然后根据点数截取数据区用BitConverter.ToInt16逐字解析。响应帧长度字段是“从结束码开始到帧尾”的长度不要和请求帧长度混在一起。4.3 Winform中如何驱动轮询定时器、后台线程与UI刷新立体仓库上位机一般需要一个常驻轮询线程循环读货位状态、写堆垛机任务同时把数据推给UI。直接在定时器里做Socket读会卡界面正确做法是后台线程循环 信号通知。System.Timers.Timer pollTimer new System.Timers.Timer(500); pollTimer.Elapsed (s, e) { try { var data plc.ReadD(100, 64); if (data ! null data.Length 64) { this.BeginInvoke(new Action(() { for (int i 0; i 64; i) txtStatus[i].Text data[i].ToString(); })); } } catch (Exception ex) { this.BeginInvoke(new Action(() lblError.Text ex.Message)); } }; pollTimer.Start();参数说明pollTimer.Interval取500毫秒对应堆垛机一个动作周期ReadD返回short数组BeginInvoke把UI刷新切回主线程。如果PLC响应超过2秒后台线程会自动堆积此时需要加信号量或直接改用ManualResetEvent让上一次轮询结束再开启下一次避免多个线程同时读Socket造成数据错乱。我用过SemaphoreSlim(1,1)来控制效果比无脑定时器稳。5. 避坑与常见问题排查报文对了为什么还是收不到数据5.1 问题连接成功但响应超时现象上位机与FX5U已经TCP三次握手成功但发送读请求后一直收不到响应直到Socket超时。原因多数情况是端口号或协议类型没对上。PLC侧以太网配置里如果选的是“MC协议”上位机却把端口绑定到了其他服务或者请求帧里的“请求数据长度”比实际报文多/少字节。解决用Wireshark抓包先看握手是否成功再对比请求帧长度。重点检查帧前部的长度字段它只包含从命令字段开始的数据长度。常见做法是在资源包里加一个“报文打印”开关把发送和接收的十六进制打出来逐字节与样例比对。5.2 问题数据读出全为0或乱码现象请求正常响应也正常结束但数据区解析出来全是0或者值凭空翻倍。原因地址算错了。D100的地址不是0x0064而是64068十进制转换来的0xA844。另外字节序问题如果PLC侧用字存储上位机把高低字节颠倒数据就会变成乱码。解决先写一个固定值到D寄存器再用读命令读取比对。比如用GX Works3在线写入1234如果上位机读到4460那一定是字节序没换过来。习惯性在代码里写IPAddress.HostToNetworkOrder反而会错三菱是小端直接BitConverter即可。5.3 问题连续读写时偶发通讯失败现象轮询1小时没问题但突然某一次读写失败重试又好了。原因TCP层没有丢数据但应用层出现粘包半包。如果上位机只用一次Receive读响应而PLC把两条响应合并封包或者响应被拆分就会读错帧长度。解决不要裸用Read要循环Receive直到收满“响应长度字段”所标的字节数。先读2字节子头再读2字节长度按长度补齐剩余字节。资源包里的ReceiveExactly方法就是干这个的。private byte[] ReceiveExactly(int totalLen) { var buf new byte[totalLen]; int offset 0; while (offset totalLen) { int n stream.Read(buf, offset, totalLen - offset); if (n 0) throw new IOException(连接被关闭); offset n; } return buf; }5.4 问题PLC侧报“从站未准备好”现象上位机明明能读到数据但PLC侧诊断里报“SLMP从站未准备好”或类似逻辑错误。原因请求帧里的IO编号或站号与实际PLC参数不一致。一些项目里CPU编号不是默认值比如多CPU系统里IO编号要写实际槽号。解决在GX Works3的连接设置里查看“CPU通信端口”的具体编号把帧里的03 FF 00改成对应值。另外如果PLC侧“允许RUN中写入”没勾选写操作会被拒绝需要在PLC参数里勾选。5.5 问题立体仓库电机联动时上位机卡顿现象堆垛机启动瞬间上位机界面假死几秒随后所有读写恢复正常。原因不是PLC问题是上位机在主线程里做了字符串拼接、日志写入或数据库操作导致UI线程阻塞Socket轮询线程也被连带调度住了。解决把日志写文件、UI刷新和数据库插入全部移到独立队列通信线程只做收发。可以用BlockingCollectionstring把报文日志扔给后台线程批量写盘写盘操作完全不影响UI和通讯。6. 进阶技巧用模拟器离线调试把报文日志做成自查工具先谈离线调试。现场调试PLC非常痛苦堆垛机在走你不敢乱发写入指令旁边又有变频器干扰报文时序一乱就难分辨。我通常在代码里内置一个“模拟模式”不连真实PLC而是用一份录好的响应帧文件来模拟PLC应答。具体做法是把读请求的响应做成字典根据请求地址返回预设数据写请求则直接回固定成功帧。这样Winform界面可以在没有三菱实物的情况下完整跑通轮询、刷新和异常提示等到了现场只换一行IP即可。验证报文正确性强烈建议自己写一个日志窗每帧记录发送时间、方向、原始十六进制、解析后的结果。日志格式不要用简单文本框最好落到CSV包含时间戳,方向,帧类型,地址,点数,耗时ms,错误码。举一个我解决过的现场问题某项目立体仓库输送线频繁报“取货失败”但PLC程序看不出错。打开日志后发现输送线PLC的响应帧偶尔被拆成两个TCP包上位机第一次Receive只拿到12字节解析时长度字段只读到一半误判成错误帧。后来改成ReceiveExactly按长度收满失败率直接归零。从那以后我每次搭建上位机通讯都强制走一遍流程先用模拟器生成报文样本再写日志验证解包最后才连真机。真机连上后把GX Works3的诊断窗口和上位机日志同时开着一旦有异常先对比时间戳两分钟就能定位是PLC拒收还是TCP拆包。这套手法帮你省下的调试时间比下载任何代码包都值。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑