资讯动态

C#三菱FX系列PLC串口通讯实战:专用协议报文解析与源码

发布时间:2026/9/1 18:53:50 来源:尧图企业网站定制
简介本资源是一套面向工控自动化开发者的C#与三菱FX系列PLC串口通讯实战源码专为初学者及具备基础C#和PLC知识的工程师设计解决上位机与PLC编程口通信这一典型工业场景中的数据读取难题。压缩包共50个文件包含10个核心C#源文件如Form1.cs、PortControlHelper.cs、3个可执行程序exe、1个协议参考文档FX编程口协议中文.pdf以及项目配置.sln、.csproj、资源文件.resx、.ico和调试支持文件.pdb、.cache整体体积仅3.63MB结构清晰、开箱即用。已有1450人学习下载源码已在FX-3GA-40MT机型实测通过覆盖M、D、X、Y等常用软元件读取逻辑注释完整、模块解耦良好便于二次扩展至其他软元件或适配不同FX型号。读者可直接编译运行快速掌握串口帧构造、校验计算、响应解析等关键通信细节并基于现有框架开展定制化开发。 搞工控上位机的朋友应该都经历过这个阶段项目还没开始切需求甲方已经甩过来一句“用C#写个串口通讯把三菱PLC里的数据读出来”。听起来不复杂真动手才发现光是搞清楚三菱PLC那条串口线里跑的到底是什么协议、什么报文格式就能耗掉一整天。这篇文章我把自己落地的C#与三菱PLC串口通讯源码、报文拆解和实测过程中踩过的坑一次性整理出来给准备拿C#写三菱PLC通讯、尤其是接手FX系列串口项目的朋友做个参考。我自己是从组态软件转过来的以前读写PLC靠的是组态软件的现成驱动参数填完就能用。第一次裸写C#串口和三菱FX3U通讯的时候连“通讯协议”这个概念都是模糊的以为串口参数设对、发几个字节就有数据回来。结果不是PLC没反应就是返回Garbage乱码要么干脆串口被占用打不开。后来把协议、帧格式、校验和、返回帧结构一条条理清楚才发现这东西本质上一点不难。难的是没人告诉你哪些细节是决定成败的。1. 先别急着写代码三菱PLC串口通讯到底哪套协议1.1 三菱FX系列串口通讯的两条路线三菱FX系列PLCFX3U、FX3G、FX5U这些串口通讯常见的有两条路线。第一条是编程口协议也就是笔记本电脑用编程线连接PLC那个圆形mini-DIN 8针口或者FX5U的USB口GX Works2/3通过它读写PLC时用的就是这套协议。第二条是专用协议也叫Computer Link协议、MC协议串口版PLC上需要加装485扩展板或者通讯适配器比如FX3U-485-BD、FX3U-485ADP组态软件、触摸屏、上位机通过RS485总线访问PLC时用的更多是这套。做C#上位机通讯我个人优先建议走专用协议。原因是编程口的通讯线缆、握手时序、和上位机联调的限制相对多一些而且如果PLC程序在调试你上位机还连着编程口经常出现“通讯被占用”“下载程序时上位机掉线”这类麻烦事。专用协议走485总线调试时各干各的互不干扰。专用协议内部又分ASCII格式和二进制格式实际工程里用ASCII格式的最多帧里每个字节都是可见字符抓包调试时一眼能看明白即使没有专业抓包工具用串口调试助手也能把报文读出来。本文的源码就是基于这套协议写完并跑通的。1.2 为什么我推荐用三菱专用协议而不是Modbus我知道很多人会问现在Modbus-RTU这么通用为什么不用Modbus三菱FX3U从某个固件版本开始也确实支持Modbus-RTU从站功能写上位机时用现成的Modbus库甚至不用自己拼帧听起来好像更省事。但实际用下来专用协议有三个优势是Modbus替代不了的。第一个优势是地址直观。专用协议里读D100就是发“地址100”读M50就是“地址50”和PLC程序里的软元件编号一致不用像Modbus那样去查映射表。第二个优势是批量读写方便一次读连续的D寄存器、M继电器都很直接。第三个优势是把PLC作为主站连接变频器、仪表之类的第三方设备时三菱PLC程序里仍然是通过专用协议指令去读写的你在上位机侧用同样的协议思维去理解整个系统思路是连贯的。当然Modbus-RTU也有它的用武之地比如一个项目里既有三菱PLC又有第三方仪表、电力监控模块总线要统一走Modbus那该用还得用。这个后面第6章我详细说。1.3 硬件连接与PLC侧参数的一次性设置硬件层面FX3U-485-BD是一块插在PLC左侧或右侧的扩展板端子直接标了A和B对应RS485的A/B-。FX3U-485ADP则是独立的小模块供电和接线方式稍有不同但上位机视角看都是一条RS485总线。具体接线有几条铁律A接A、B接B这俩接反了PLC大概率完全没响应通讯线用屏蔽双绞线屏蔽层单端接地485总线的终端电阻短线几十米内不加问题不大超过一百米或者总线节点多要按手册要求并联终端电阻。PLC侧要确认通讯参数。FX系列有个特殊数据寄存器D8120专门用来设置串行通讯的格式包括波特率、数据位、校验位、停止位、协议类型、是否启用和校验等。这个寄存器可以在GX Works的“PLC参数-串口设置”窗口里配置也可以在PLC程序里用MOV指令赋值。这里有个工程上特别容易踩的坑如果你在程序里写了向D8120赋值的指令那编程软件里的串口设置可能会被程序运行时的值覆盖导致你在软件里看到的参数和实际跑的参数对不上。排查通讯问题时两个地方都要看。现场最常用的组合一般是19200bps、8数据位、偶校验、1停止位、和校验。上位机串口必须和PLC侧完全一致差一个校验位都不能通讯。我之前就干过串口参数里把偶校验选成无校验结果PLC面前就是不回应浪费了半小时。2. 报文拆解读一个D寄存器线上到底跑了什么2.1 读命令帧的组成与校验和计算三菱FX系列专用协议的读命令帧长这样ENQ 命令代码 起始地址 点数 ETX 和校验其中ENQ是0x05表示请求命令代码“01”代表批量读取ASCII字符0x30 0x31起始地址是4位十进制ASCII码D100就是“0100”点数是2位十进制ASCII码读1个字就是“01”ETX是0x03表示帧结束末尾再加2位十六进制ASCII码的和校验。举个例子读取D100开始的1个字报文是05 30 31 30 31 30 30 30 31 03 32 35拆开看0x05是ENQ“01”是读命令“0100”是D100的十进制地址“01”是点数0x03是ETX“25”是根据前面内容算出来的校验和。注意这里地址是十进制的100不是十六进制的0x64这个细节坑过不少刚上手的人。D1000就是“1000”D258就是“0258”前面补零到4位。校验和的计算方法是从命令字符的ASCII码开始加一直加到ETX就是0x03为止把累加和的低8位转换成2位大写十六进制ASCII字符。比如上面这个例子命令“01”的ASCII码0x300x310x61地址“0100”四个字符都是0x30加起来0xC0点数“01”又是0x300x310x61再加上ETX的0x03总和是0x610xC00x610x030x125取低8位0x25转成ASCII就是“25”。很多网上的例子发完帧不会算校验和或者把CRC16那套用在专用协议上那肯定是通讯不上的。专用协议用和校验不是CRC。2.2 写命令和批量读写写命令的帧格式和读命令几乎一样只是命令代码改成“02”正文里增加了要写入的数据内容每1个字用4位十六进制ASCII码表示高位在前。比如向D100写入0x1234这个值报文是05 30 32 30 31 30 30 30 31 31 32 33 34 03 校验和拆开看0x05是ENQ“02”是写命令“0100”是D100“01”是1个字“1234”就是要写入的数据0x03是ETX后面两位是校验和。如果是批量写3个字数据部分就是连续12个十六进制字符每个字4位按顺序排列。批量读取也是一样读D100到D102共3个字点数字段就是“03”返回的数据是连续的12个ASCII十六进制字符。不过实际使用中我建议单次读取点数不要超过64个字。一是三菱专用协议不同型号对单次读取的点数有上限要求二是上位机侧一次处理太多数据出现半包粘包时反而不好恢复。需要读大批量数据比如几百个D寄存器写成循环一次读64个或者32个分几次读完。2.3 PLC的返回帧长什么样读命令的返回帧很多人容易搞错。PLC正常响应时会先返回一个ACK0x06表示命令接收成功然后再返回一个数据帧数据帧格式是STX 数据内容 ETX 校验和STX是0x02数据内容是连续的4位ASCII十六进制字符每个字对应4个字符从低地址到高地址排列但每个字内部是高位字节在前。比如读D100和D101两个字的返回值可能是“1234ABCD”那D1000x1234D1010xABCD。这里有个常见坑串口数据是异步到达的ACK和数据帧可能分两次到达也可能粘在一起一次性到达。很多新手在读完一个字节发现是0x06就以为结束了或者只读到一个STX帧就把前面ACK当垃圾丢掉。正确做法是先把ACK读掉再等STX数据帧或者干脆做一个接收缓存把完整的一帧按帧头帧尾拼出来再解析。如果是写命令正常返回就是一个ACK0x06非常简单。如果返回NAK0x15说明PLC拒绝了这条命令后面通常还跟两位错误代码ASCII字符。比较常见的错误代码包括02控制帧格式错误、03校验错误、06地址越界、07数据超范围等。看到这些错误码基本就能定位到是地址写错还是帧格式不对了。2.4 浮点数的存储顺序做上位机控制变频器频率、读取温度、压力这类浮点数据时会碰到三菱PLC浮点数存储顺序的坑。三菱PLC里32位浮点数占用连续的2个D寄存器。按照FX系列常见的数据存储习惯16位的字组合成32位数据时通常是低地址存放低16位高地址存放高16位。比如D0和D1组合成一个浮点数D0存的是这个浮点数的低16位D1存的是高16位。举个例子PLC里想表示7.032位浮点数在IEEE 754编码下十六进制是0x40E00000那D00x0000、D10x40E0。C#这边要把两个16位无符号整数拼成float正确的做法是uint raw ((uint)high 16) | low; float value BitConverter.ToSingle(BitConverter.GetBytes(raw), 0);这里BitConverter.GetBytes(raw)在PC上得到的是小端字节序也就是低字节在前的4个字节刚好和PLC那边“低地址存低16位、高地址存高16位”的顺序对应上转换出来就是正确的浮点数。不过每个项目的PLC程序风格不一样有些老工程师习惯在PLC里把32位数据的高低字交换存储这种情况下上位机读出来的float数值就明显不对。排查方法很简单连续读两个D寄存器把高低16位换一下位置再转float对比哪个数值是“看起来合理的”就知道这个PLC的存储顺序了。我一般会在调试界面加一个“交换高低字”的勾选框实测非常省心。3. C#串口通讯源码可以直接复制进项目里的封装类3.1 环境准备与SerialPort配置我使用的是Visual Studio 2022项目类型是Windows窗体应用目标框架看现场电脑情况选如果是给客户部署的老Windows系统用.NET Framework 4.7.2最稳如果现场电脑是Win10/Win11且环境统一用.NET 6/8也没问题。注意.NET Core/.NET 5的System.IO.Ports不是在框架里默认带的需要通过NuGet安装System.IO.Ports包。串口关键参数就四个串口号、波特率、数据位、停止位、校验位。三菱FX系列专用协议最常见的组合是19200、8、偶校验、1停止位但最终以PLC侧D8120实际设置和编程软件参数里的配置为准。如果对不上表现就是PLC完全没反应或者返回乱码串口调试助手发命令都试不出东西来。还有两个小参数容易被忽略。一个是ReadTimeout和WriteTimeout必须设置否则PLC没响应时程序会卡死在读串口那一步整个界面假死。另一个是ReceivedBytesThreshold如果走DataReceived事件默认1个字节触发一次但对工控轮询场景我后面会说其实不推荐用这个事件。我先给出一段串口打开和参数配置的代码using System.IO.Ports; SerialPort port new SerialPort(); port.PortName COM3; port.BaudRate 19200; port.DataBits 8; port.Parity Parity.Even; port.StopBits StopBits.One; port.ReadTimeout 1000; port.WriteTimeout 1000; port.Open();3.2 FxSerialProtocol类的核心实现我封装了一个串口通讯类命名为FxSerialProtocol所有专用协议相关的操作都收敛在这个类里。这样窗体只关心业务逻辑不关心协议细节。类的基本成员和打开关闭方法如下public class FxSerialProtocol { private SerialPort _port; private readonly object _lockObj new object(); public bool IsOpen { get { return _port ! null _port.IsOpen; } } public bool Open(string portName, int baudRate 19200, Parity parity Parity.Even, int readTimeout 1000) { Close(); _port new SerialPort(portName, baudRate, parity, 8, StopBits.One); _port.ReadTimeout readTimeout; _port.WriteTimeout readTimeout; _port.Open(); return _port.IsOpen; } public void Close() { if (_port ! null) { if (_port.IsOpen) _port.Close(); _port.Dispose(); _port null; } } }注意所有读写操作都放在_lockObj锁里这是为了多线程环境下的安全。工控程序里Timer轮询、按钮写值、日志线程有可能同时访问串口对象不加锁会出现同一个串口被两个线程同时写入导致帧错乱的问题。接着是核心的发送和接收逻辑。BuildFrame方法负责把命令正文转换成一帧完整的报文包括ENQ、正文、ETX、校验和private byte[] BuildFrame(string commandBody) { byte[] body Encoding.ASCII.GetBytes(commandBody); int sum 0x03; // 先把ETX算进去 foreach (byte b in body) { sum b; } string sumText (sum 0xFF).ToString(X2); byte[] sumBytes Encoding.ASCII.GetBytes(sumText); byte[] frame new byte[body.Length 2 sumBytes.Length]; frame[0] 0x05; // ENQ Buffer.BlockCopy(body, 0, frame, 1, body.Length); frame[body.Length 1] 0x03; // ETX Buffer.BlockCopy(sumBytes, 0, frame, body.Length 2, sumBytes.Length); return frame; }接收响应时要区分两种情况写命令返回ACK读命令返回ACKSTX数据帧。所以我写了一个ReadResponse方法先探测第一个字节是不是ACK再继续读直到ETX结束private byte[] ReadResponse() { Listbyte bytes new Listbyte(); int first _port.ReadByte(); bytes.Add((byte)first); if (first 0x06) // ACK继续读数据帧读命令 { int second _port.ReadByte(); bytes.Add((byte)second); } // 逐字节读直到ETX while (true) { int b _port.ReadByte(); bytes.Add((byte)b); if (b 0x03) { break; } } // 把ETX后面的2个和校验字节也读掉保持接收缓冲干净 for (int i 0; i 2; i) { bytes.Add((byte)_port.ReadByte()); } return bytes.ToArray(); }这个是简化版实际项目里我会在循环里加一个最大长度限制防止PLC异常时无限等待。对工业场景来说宁可报超时错误重试也不要让程序卡死。批量读取字软元件的方法public ushort[] ReadWords(int startAddr, int count) { lock (_lockObj) { string body $01{startAddr:D4}{count:D2}; byte[] frame BuildFrame(body); _port.Write(frame, 0, frame.Length); byte[] response ReadResponse(); return ParseWordsResponse(response, count); } } private ushort[] ParseWordsResponse(byte[] response, int count) { int stxIndex Array.IndexOf(response, (byte)0x02); int etxIndex Array.IndexOf(response, (byte)0x03); if (stxIndex 0 || etxIndex 0 || etxIndex stxIndex) { throw new InvalidOperationException(返回帧格式错误); } string data Encoding.ASCII.GetString(response, stxIndex 1, etxIndex - stxIndex - 1); if (data.Length count * 4) { throw new InvalidOperationException(返回数据长度不足); } ushort[] result new ushort[count]; for (int i 0; i count; i) { string wordText data.Substring(i * 4, 4); result[i] Convert.ToUInt16(wordText, 16); } return result; }批量写多个字的方法public bool WriteWords(int startAddr, ushort[] values) { lock (_lockObj) { StringBuilder dataBuilder new StringBuilder(); foreach (ushort value in values) { dataBuilder.Append(value.ToString(X4)); } string body $02{startAddr:D4}{values.Length:D2}{dataBuilder}; byte[] frame BuildFrame(body); _port.Write(frame, 0, frame.Length); byte[] response ReadResponse(); return response.Length 0 response[0] 0x06; } }这几个方法看起来不多但已经覆盖了最常见的读D寄存器、写D寄存器场景。现场只要保持串口参数一致这套代码连上真实PLC就能用。3.3 把浮点数读出来的C#代码既然PLC里32位浮点数是占用两个D寄存器那上位机读浮点的方法就是连续读2个字然后组合。组合方式我在第2.4节已经说了下面给出完整方法public float ReadFloat(int startAddr) { ushort[] words ReadWords(startAddr, 2); ushort low words[0]; ushort high words[1]; uint raw ((uint)high 16) | low; return BitConverter.ToSingle(BitConverter.GetBytes(raw), 0); }如果你排查后确定现场PLC存储顺序是反的也就是高地址存放低16位那把组合语句换成uint raw ((uint)low 16) | high;就行。为了调试方便建议在窗体上做一个开关手动选择是高字在前还是低字在前第一次联调时两种都试一下总有一种读数是对的。4. 实测最容易翻车的几个点4.1 串口被GX Works占用的经典冲突这个是我接手现场项目时遇到最高频的问题。C#程序打开串口时报“串口被占用”或者打开成功但读写全部没反应排除了半天发现电脑上还开着GX Works2或GX Works3并且连接的就是同一个PLC。只要GX Works保持在线监控状态串口或者编程口就一直被它占用上位机程序根本没机会读写。解决办法很直接联调时只要跑上位机程序就把GX Works的在线监控断开反过来如果要去PLC里改程序先把上位机程序退出。还有一点如果你用的是485通信而电脑上同时插着USB转编程线连接到PLC的编程口有些型号PLC会在两个通讯口同时启用时出现访问阻塞表现为485总线上的响应特别慢。所以现场调试尽量只保留一条物理连接路径。4.2 485接线和USB转串口驱动的坑RS485接线接反是最经典的“没反应”故障。上位机发送读命令时用串口调试助手能看到发送的帧但永远等不到PLC的任何返回。这时候第一反应不要怀疑代码先去看接线A对A、B对B反了立刻换过来。好的USB转485模块上会有TX/RX指示灯如果发命令时模块的TX灯亮但RX灯不亮八成是PLC那边没响应如果RX灯亮但收到的数据是乱码检查波特率和校验位。USB转串口适配器这个环节也容易出问题。市面上的PL2303芯片在Win10/Win11上驱动兼容性差经常出现“设备管理器里显示正常但打开串口就是没数据”的情况本质上是因为新版驱动和旧版芯片不匹配导到通讯失败。这正好对应很多人在网上搜到的“PL2303HX停产装旧驱动无法通讯”的经典问题。我的经验是工控现场尽量用FTDI FT232或者CH340这类芯片的模块尤其是FT232兼容性在win10/win11下很稳定。如果只能用PL2303而且系统是新版本就在设备管理器里把驱动回滚到旧版或者手动指定驱动目录安装兼容驱动。4.3 半包、粘包和数据错位串口通讯的本质是字节流没有“一条完整报文”的概念。PLC返回的ACK和STX数据帧可能分两次到达也可能粘在一起。如果你在DataReceived事件里每次都简单ReadExisting然后直接解析很容易出现第一次只读到ACK、第二次才读到STX帧或者一次读多帧数据的情况。我的处理方式是读写全部走同步方式发完命令后阻塞等待完整响应用ReadByte逐字节读直到ETX为止。这样天然规避了半包粘包问题因为每次读响应都是针对当前这条命令的一读一答不会出现多帧数据堆积。程序里只要注意ReadTimeout别太短给PLC留够响应时间一般500毫秒到1秒是合理的。如果项目确实要用事件驱动模式比如PLC主动上报数据那接收逻辑就得改成一个缓存队列。把每次收到的字节追加到字节列表里然后循环检查列表里有没有完整的“STX...ETX...校验”帧解析完一帧就移除一帧。这个思路和TCP粘包处理一样工控程序里非常实用。4.4 DataReceived事件里的UI跨线程问题很多新手会直接在SerialPort.DataReceived事件里写代码更新窗体的Label和TextBox运行起来发现程序直接异常崩溃或者界面偶尔卡死。原因是DataReceived事件在后台线程触发而WinForms控件的UI属性只能在创建该控件的线程主线程上修改跨线程访问就会抛异常。解决办法有两个。第一个是使用Invoke强制切换到UI线程代码长且啰嗦第二个更推荐——不要在DataReceived事件里做业务处理改成Timer轮询。上位机读PLC的场景本来就是周期性刷新比如200毫秒读一次D寄存器刷新显示Timer里直接同步调用ReadWords方法就行简单直观完全绕开跨线程问题。串口事件留给那些“PLC主动上报”的场景用足够了平时轮询为主的项目根本不需要它。5. 从“能读能写”到可以交付轮询、点位表和日志5.1 用Timer做轮询刷新最简单功能Demo跑通之后接下来就是把它变成一个可以交付给操作工使用的上位机界面。多数工控上位机的核心逻辑就是定时器周期读取PLC数据、更新显示、用户点击按钮写值。我强烈建议用System.Windows.Forms.Timer做轮询而不是把读写逻辑塞在按钮事件里。窗体上放一个TimerInterval设200毫秒Tick事件里调用封装的FxSerialProtocol读取需要监控的D寄存器再更新到界面。200毫秒的刷新周期对大多数工业监控场景都够了肉眼看起来实时性很好而且给PLC留了充足的响应时间。如果刷新周期低于100毫秒就要注意了PLC的串口处理能力有限请求太频繁会造成响应延迟甚至触发PLC侧通讯超时。private void timerRefresh_Tick(object sender, EventArgs e) { if (!plc.IsOpen) return; try { ushort[] values plc.ReadWords(100, 4); textBoxStatus.Text string.Join(,, values); } catch (TimeoutException ex) { // 超时先重试连续几次失败再弹错误提示 retryCount; if (retryCount 3) { Log(通讯超时 ex.Message); retryCount 0; } } }5.2 点位表驱动几十台设备不至于写死如果项目里要监控的设备点位很少比如就三五个温度、一个启停状态直接在代码里硬编码寄存器地址也能凑合。但如果要监控几十上百个点位或者有多台PLC硬编码就是灾难。我习惯把点位做成配置表用JSON文件或者XML文件维护。每行记录包括点位名称、PLC站号、软元件类型、寄存器地址、数据类型、量程、单位、是否可写、报警上下限等信息。程序启动时加载这张表动态生成读写任务列表根据点位表循环刷新。这样新增加一台设备、一个监控点位只需要改配置文件重新启动程序不用重新编译代码。用点位表后有两点要注意。第一点位表的地址和数据类型必须和PLC程序约定一致尤其是浮点数、双字、位抽出来的时候别把两个相邻点位搞混。第二如果不同点位类型不同每种读取方式要独立封装好读浮点位和读整数点位不能串。5.3 写值前后的校验与日志上位机写PLC数据是对生产有实际影响的操作不能像Demo那样写个按钮就直接WriteWords。我的习惯是写值之前先做三查查写入范围、查当前值、查权限。比如要写变频器频率先检查目标频率是否在0到50Hz之间如果超出范围直接拒绝并提示。写值完成后还要读回验证。写一个值立刻读回同一个地址对比写入值和读回值是否一致不一致说明通讯过程中发生了数据错误需要记录日志并报警。这个流程对现场故障排查特别有价值可以快速判断是上位机没写进去还是PLC程序把值做了处理还是通讯线路丢包。日志记录建议把报文级别的信息也留下来包括发送的原始帧、接收的原始帧、解析后的业务值。这样一旦现场出问题直接看日志就能定位是协议层还是业务层的问题不用抱着笔记本去车间现场复现。6. 后续扩展从串口到以太网MC协议/MODBUS-TCP6.1 为什么Q系列设备普遍走以太网串口通讯靠谱归靠谱但有一个天然瓶颈单条485总线的速率和节点数有限。如果项目上到了Q系列PLC、L系列PLC或者一个车间几十台设备要组网通常就不会再考虑串口了而是走以太网。Q系列PLC的以太网模块上跑的MC协议和三菱FX系列串口专用协议是同一个思路帧结构相似只是传输载体从串口换成了TCP/IP并且增加了网络号、PC号、站号之类的路由信息。从C#上位机的角度看和网络MC协议通讯本质就是把之前往SerialPort里写字节改成往Socket里写字节报文的拼装逻辑和校验和逻辑很多可以复用。这也是为什么我建议哪怕只做串口项目也一定要把帧的拼装和解析单独封装不要和SerialPort绑死。6.2 三菱以太网MC协议和串口协议的关系以太网MC协议常见的帧格式有ASCII方式和二进制方式。如果是ASCII方式你会发现里面有很多熟悉的元素命令“01”还是读、“02”还是写数据排列还是4位ASCII十六进制字符校验方式也类似。区别在于报文开头多了以太网头包括子帧头、网络号、PC号、请求目标模块IO号、请求目标模块站号等固定字节结尾的校验和处理可能和串口不同。做迁移时不要把所有代码推倒重来把帧拼装的公共逻辑抽出来。比如构造一个抽象方法GetFrameHeader和GetFrameTail串口协议实现填ENQ/ETX/SUM以太网协议实现填网络头/尾帧。这样底层换传输介质上层业务代码基本不动。我去年把一个FX3U串口项目升级到FX5U以太网整个迁移过程只花了小半天得益于当时做了这个抽象。6.3 换个思路识别协议需求别一上来就写源码最后分享一个经验。但凡是接手新项目先不要急着打开Visual Studio写代码。先去车间看PLC型号、通讯模块型号、确认现场布线是串口还是以太网、搞清楚PLC程序里有没有读写D8120、确认通讯参数。最好用串口调试助手或者简单的测试程序先和PLC建立通讯能正常读回数据再开始写完整的上位机代码。我第一次做三菱PLC通讯项目时就是打开代码直接写写完发现PLC没反应来回改了几十次都没用最后发现是485线接反了。所以协议搞不清楚代码写得再漂亮也是白搭。把报文格式吃透用最小的工程验证链路通不通再往上叠业务逻辑这个顺序尽量不要反过来。在实际项目里我把这套C#串口通讯源码复制到新项目之后通常还会再做一个小调整把类里所有和窗体相关的东西全部去掉让它成为一个纯协议层组件这样即使是后续要做成Windows服务、或者嵌入到其他系统里这套通讯代码都可以直接复用。这也是我坚持在工控上位机开发里做分层的原因协议层、业务层、界面层各管各的不论项目大小长期维护下来都轻松得多。本文还有配套的精品资源点击获取

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

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

免费获取报价