简介这份资源是面向工业自动化开发者的C#与OMRON PLC通信实战源码基于TCP/IP协议实现上位机对欧姆龙PLC的数据读取适合具备一定C#基础、希望切入工控通信领域的程序员参考学习。压缩包共49个文件约218KB包含6个cs源代码文件、6个resources资源文件、3个resx与2个settings配置项以及csproj、sln工程文件和exe、dll、pdb等编译产物另有xml、txt说明与png图片结构完整可直接用Visual Studio 2010打开运行。源码覆盖Socket客户端连接、OMRON通信协议交互、寄存器数据读取与解析、异常处理、异步编程及二进制到整型浮点的类型转换等关键环节并保留连接状态管理与调试测试痕迹。目前已有682人学习下载读者可借此理解C#网络编程与PLC集成的完整链路掌握远程监控与控制设备的实现思路是工控上位机开发入门与排错的实用参考。1. TcpEthernet 读取 OMRON PLC为什么我最后放弃了 Fins 指令封装库车间里一台 CJ2M 已经跑了六年上位机还是 XP 时代的 VB6 程序屏幕花得看不清。产线班长要求三天内换一套能看实时数据的 C# 上位机我第一反应是找现成的 Fins 封装库结果翻了三四个开源项目要么只支持串口 HostLink要么把 Fins/TCP 的握手头写死成固定值换一台 NJ 系列直接连不上。最后我回到最笨的路子用 C# 的TcpClient直接怼 OMRON 的 Fins/TCP 协议自己拼帧、自己解析。这套方案就是标题里说的 TcpEthernet 读取 OMRON PLC核心不是某个库而是把以太网帧结构吃透。它适合两类人一是手头有 CJ/CS/CP 系列带以太网模块、需要快速搭 C# 上位机的工控开发者二是被各种PLC 通讯库坑过、想搞清楚底层到底发了什么字节的较真派。下面我按自己踩过的顺序把选型、拼帧、读写、避坑一次讲透。2. Fins/TCP 协议拆解从 TCP 三次握手到一条 DM 区读取指令2.1 为什么是 Fins/TCP 而不是 HostLink 或 ModbusOMRON 的以太网通讯有好几条路Fins/TCP、Fins/UDP、HostLink over Ethernet还有第三方网关转 Modbus TCP。选型时我列过一张对比表实际落地只看三个维度——是否需要长连接、单次读写延迟、以及能不能直接访问 DM/CIO 区。方式传输层典型延迟地址空间适用场景Fins/TCPTCP 96005~15msDM/CIO/WR/HR 全支持上位机实时监控Fins/UDPUDP 96003~8ms同上高频采集、可容忍丢包HostLink串口/以太网20~50ms受限于命令格式老设备、无以太网模块Modbus TCP 网关TCP 50210~30ms映射后地址多品牌混用Fins/TCP 的优势在于它是 OMRON 原生协议不需要额外网关且支持一次请求读连续多个字。代价是帧结构比 Modbus 复杂握手阶段有个节点地址协商容易被忽略。我一般会先确认 PLC 的以太网模块型号比如 CJ2M-EIP21 或 CP1W-CIF41因为不同模块对 Fins/TCP 的端口和最大帧长支持略有差异。2.2 Fins/TCP 帧的三段结构一条完整的 Fins/TCP 报文分三段Fins/TCP 头8 字节、Fins 命令帧10 字节固定头 数据区、以及可选的响应尾。很多人翻车就翻在把 Fins/TCP 头当成 Fins 帧的一部分算错偏移。Fins/TCP 头结构如下字节 0-3固定FINS的 ASCII 码即0x46 0x49 0x4E 0x53字节 4-7命令码0x00000000表示客户端发起的节点地址协商0x00000002表示 Fins 帧发送字节 8-11数据长度大端序指后面 Fins 帧的字节数字节 12-15命令码对应的附加数据节点协商时是客户端节点地址Fins 发送时固定0x00000000Fins 命令帧本身以0x80 0x00 0x02开头ICF、RSV、GCT接着是目标节点、源节点、服务 ID然后是 MRC/SRC 主次命令码。读 DM 区的 MRC 是0x01SRC 是0x01。2.3 节点地址协商最容易被跳过的一步Fins/TCP 在发第一条读写指令前必须先做一次节点地址协商。客户端发一个命令码为 0 的 Fins/TCP 头PLC 返回它分配的节点地址。如果跳过这步直接发读写帧PLC 会直接断开连接而且不返回任何错误码——这就是典型的黑匣子式翻车。// 节点地址协商发送 Fins/TCP 头命令码 0 byte[] NegotiateFrame() { var frame new byte[20]; // FINS ASCII frame[0] 0x46; frame[1] 0x49; frame[2] 0x4E; frame[3] 0x53; // 命令码 0x00000000大端 frame[4] 0x00; frame[5] 0x00; frame[6] 0x00; frame[7] 0x00; // 数据长度 12 frame[8] 0x00; frame[9] 0x00; frame[10] 0x00; frame[11] 0x0C; // 错误码占位 frame[12] 0x00; frame[13] 0x00; frame[14] 0x00; frame[15] 0x00; // 客户端节点地址0 表示自动分配 frame[16] 0x00; frame[17] 0x00; frame[18] 0x00; frame[19] 0x00; return frame; }这段代码里字节 8-11 的数据长度写0x0C是因为协商请求的附加数据固定 12 字节。字节 16-19 填 0 让 PLC 自动分配节点地址实际项目中我建议固定一个不冲突的值比如 0x01方便抓包时辨认。协商成功后 PLC 返回的帧里字节 16-19 就是它分配的节点地址后续所有 Fins 帧的源节点字段都要用这个值。3. C# 实现 TcpClient 长连接连接、心跳与断线重连3.1 建立连接与超时控制用TcpClient连 PLC 有个坑Connect方法在跨网段时可能阻塞十几秒。我一般用带超时的异步连接避免 UI 线程卡死。async TaskTcpClient ConnectPlcAsync(string ip, int port, int timeoutMs 3000) { var client new TcpClient(); var connectTask client.ConnectAsync(ip, port); var timeoutTask Task.Delay(timeoutMs); var completed await Task.WhenAny(connectTask, timeoutTask); if (completed timeoutTask) { client.Close(); throw new TimeoutException($连接 {ip}:{port} 超时); } await connectTask; // 传播可能的异常 client.NoDelay true; // 关闭 Nagle降低小帧延迟 return client; }NoDelay true是关键参数。Fins 帧通常只有几十字节Nagle 算法会攒包导致延迟从 5ms 涨到 40ms 以上。timeoutMs设 3000 是经验值跨交换机时给到 5000 更稳。连接成功后不要立刻发读写帧先做节点协商否则 PLC 侧可能还没准备好。3.2 心跳与断线检测PLC 侧有 TCP 空闲超时长时间不发数据会被动断开。我一般每 5 秒发一次节点协商帧当心跳成本低且能验证链路。检测断线不能只靠Socket.Connected那个属性在对方半关闭时仍返回 true。可靠做法是读操作超时或返回 0 字节时判定断线。async Taskbool HeartbeatAsync(TcpClient client) { try { var stream client.GetStream(); var frame NegotiateFrame(); await stream.WriteAsync(frame, 0, frame.Length); var buf new byte[24]; using var cts new CancellationTokenSource(2000); int n await stream.ReadAsync(buf, 0, buf.Length, cts.Token); return n 24; // 协商响应固定 24 字节 } catch { return false; } }心跳间隔设 5 秒是折中太短增加 PLC 负担太长断线发现慢。如果产线要求秒级恢复可以缩到 2 秒但要确认 PLC 的以太网模块能承受这个频率。断线后重连要加退避我一般用 1s、2s、4s、8s 递增避免网络抖动时疯狂重连把 PLC 连接数占满。3.3 读写指令的帧拼装读 DM 区的完整帧拼装如下。假设读 D100 开始的 10 个字PLC 节点地址为 1客户端节点地址为 1。byte[] BuildReadDmFrame(byte plcNode, byte clientNode, ushort startAddr, ushort wordCount) { var fins new byte[18 0]; // 读指令无数据区 // Fins/TCP 头 fins[0] 0x46; fins[1] 0x49; fins[2] 0x4E; fins[3] 0x53; fins[4] 0x00; fins[5] 0x00; fins[6] 0x00; fins[7] 0x02; // 命令码 2 fins[8] 0x00; fins[9] 0x00; fins[10] 0x00; fins[11] 0x0C; // Fins 帧长 12 fins[12] 0x00; fins[13] 0x00; fins[14] 0x00; fins[15] 0x00; // Fins 帧 fins[16] 0x80; // ICF fins[17] 0x00; // RSV fins[18] 0x02; // GCT fins[19] plcNode; // 目标节点 fins[20] clientNode; // 源节点 fins[21] 0x00; // 服务 ID可自增 fins[22] 0x01; fins[23] 0x01; // MRC/SRC 读 fins[24] 0x82; // DM 区 fins[25] (byte)(startAddr 8); fins[26] (byte)(startAddr 0xFF); fins[27] 0x00; // 位偏移 fins[28] (byte)(wordCount 8); fins[29] (byte)(wordCount 0xFF); return fins; }注意这里我把 Fins/TCP 头和 Fins 帧拼在同一个数组里总长 30 字节。字节 8-11 的数据长度写0x0C是因为 Fins 帧固定头 10 字节加地址数据 2 字节共 12 字节。0x82是 DM 区的区域代码CIO 区是0xB0WR 区是0xB1。服务 ID 每次请求递增响应里会原样返回用来匹配请求和响应多线程环境下必须用这个字段做关联不能靠响应顺序。4. 避坑与排查连不上、读回乱码、多线程抢连接的 5 个血泪记录4.1 现象Connect 成功但发帧后立刻被断开原因跳过了节点地址协商或者协商帧的命令码写成了0x00000002。PLC 收到非协商帧时如果还没分配节点会直接 RST。解决抓包确认第一条发出的帧命令码是 0且数据长度字段是0x0C。协商响应必须完整读取 24 字节后再发下一条。4.2 现象读回的数据字节序颠倒D100 的值 0x1234 变成 0x3412原因Fins 协议是大端序而 C# 的BitConverter.ToUInt16默认按小端解析。直接拿响应字节数组转换就会翻。解决手动按大端拼装或者用BinaryPrimitives.ReadUInt16BigEndian。我一般写个扩展方法统一处理避免每处都记着翻转。ushort ReadUInt16BigEndian(byte[] buf, int offset) (ushort)((buf[offset] 8) | buf[offset 1]);4.3 现象多线程同时读写时响应错位A 线程拿到 B 线程的数据原因Fins/TCP 是请求-响应模型一个连接上不能并发发多条指令。多个线程共用一个NetworkStream时响应会交叉。解决给连接加锁或者用请求队列串行化。我一般用一个SemaphoreSlim(1,1)包住整个发送-接收过程服务 ID 只作为二次校验。如果吞吐要求高就开多条 TCP 连接每条连接一个线程。4.4 现象读 CIO 区返回错误码 0x00004001原因区域代码写错。CIO 区是0xB0不是0x82。另外 CIO 区的地址是位地址读字时要确认起始地址对齐。解决对照 OMRON 的 Fins 命令手册确认区域代码。DM 区0x82、CIO 区0xB0、WR 区0xB1、HR 区0xB2。错误码0x00004001表示区域指定错误看到这个先查区域代码。4.5 现象运行几小时后连接还在但读不到数据原因PLC 侧以太网模块有最大连接数限制通常 16 或 32旧连接没释放导致新连接被拒。或者客户端没处理半关闭socket 处于 CLOSE_WAIT 堆积。解决每次重连前先Close旧TcpClient并设置LingerState让 RST 立即发出。监控CLOSE_WAIT数量超过 5 个就强制重建连接池。我一般在上位机里加个定时器每 10 分钟主动重建一次连接比等它出问题再修省心。5. 进阶把读写封装成可复用的 PLC 客户端与批量优化技巧走到这一步裸帧拼装已经能跑通但每个项目都重写一遍不现实。我的习惯是抽一个OmronFinsClient类把连接管理、节点协商、读写、重连全包进去对外只暴露ReadDM(ushort start, ushort count)和WriteDM(ushort start, ushort[] values)两个方法。这样换项目时只改 IP 和地址映射。批量读取是性能优化的重点。Fins 单次最多读 990 个字不同模块略有差异但实际项目中我一般控制在 100 字以内因为单帧越大PLC 处理时间越长且一旦丢包重传成本高。如果上位机要采集 500 个 DM 字我会拆成 5 次读每次 100 字而不是一次读 500。实测下来5 次 100 字的往返总耗时比 1 次 500 字少 20% 左右因为 PLC 的响应缓冲区有限。另一个技巧是写操作合并。如果连续写 D100 到 D110不要发 11 条写指令拼成一条写 11 个字的帧。写指令的 MRC/SRC 是0x01 0x02数据区格式是起始地址 位偏移 字数 数据。注意写指令的数据长度字段要算上数据区别沿用读指令的0x0C。验证方案是否可靠我一般用两个手段一是用 Wireshark 抓包过滤tcp.port 9600逐帧核对 Fins/TCP 头和 Fins 帧的偏移二是在 PLC 侧用 CX-Programmer 监控 DM 区手动改值看上位机是否实时刷新。两个手段交叉验证基本能覆盖 95% 的协议问题。最后说个我自己的教训早期我图省事把节点协商的响应直接丢掉不解析结果换了一台 NJ 系列 PLC 后一直连不上查了两天才发现 NJ 分配的节点地址不是 1而我在后续帧里写死了 1。从那以后协商响应里的节点地址我一定存到字段里所有后续帧都从这个字段取。这个习惯帮我省了至少三次类似的排查。希望帮到你。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
芯片应用与质量:从9435电路看硬件设计的关键 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/28 1:17:35
电磁超声测厚为何必须用ZYNQ-7020?硬件确定性是精度根基 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/28 1:17:35
Qt 6 QHttpServer实战:5分钟实现GET/POST Web接口 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/28 1:17:35
Python搭建QQ聊天机器人极简教程 随着QQ粉丝群管理需求的不断增长,简单的群管工具难以满足复杂的信息响应和自动化需求。现有的自动回复机器人虽然功能强大,但其高昂的年费成为不少用户的顾虑。因此,通过搭建一个自定义机器人来实现自动回复,成为解决这一问题的有效途径。
基于此需求,本文介绍了使用go-c… · 2026/9/28 2:14:08
Python整理百度云盘文件大量重复无用文件 百度云盘容量有限,当文件数量逐渐增多,空间很容易被填满。删除重复文件可以帮助释放大量空间。通过获取云盘缓存目录并使用Python脚本来整理数据,可以高效识别重复文件并避免手动操作的繁琐。
此方法基于 sqlite3 和 pandas 进行数据处理,简单快捷。 文章目录 云盘数据整理… · 2026/9/28 2:14:07
Python实现将图片转化为具有视觉震撼效果的字符图 字符画是一种将图片转化为字符的艺术表现形式,它通过字符的密度和排列来模拟图片的色彩和形状效果。这种技术不仅在视觉上充满了创造力,还在文字处理领域展示了字符的丰富表现力。通过Python,可以将图片转换为字符画,生成具有视觉冲击力的字符艺术。
本文将通过具体步骤和… · 2026/9/28 2:13:48
Python实现将目录下的图片合并成PDF文件 在图像处理和文档管理中,经常需要将一系列图片文件合并为PDF格式,以便于传输、存档和阅读。Python凭借其丰富的第三方库,为图像处理和PDF操作提供了便捷的解决方案。
本文将详细介绍如何通过Python脚本,将目录中的所有图片合并为一个PDF文件,内容包括从基础环境配置到代码… · 2026/9/28 2:13:48
Python实现文件移动到指定文件夹 在编程过程中,经常需要对文件进行整理和管理,将不同类型的文件分类存放在指定文件夹中。Python提供了强大的文件操作模块,使得文件的移动操作变得简单高效。这篇教程将详细讲解如何使用Python实现将文件移动到指定文件夹的功能,帮助理解并掌握文件操作的基本方法和常见应用… · 2026/9/28 2:13:47
【PyQt】PyQT6制作一个Django项目启动器 在现代的桌面和Web应用开发中,Python以其简单高效的特点获得了广泛的应用。通过集成PyQt和Django框架,将桌面应用的便捷操作与Django项目的后端处理相结合,不仅能够提升用户体验,更能显著提高开发的便利性和效率。
本文将聚焦于如何构建一个基于PyQt的Django项目启动器,实… · 2026/9/28 2:13:40
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01
制作网页比较方便的软件怎么选?一文搞懂避坑指南 制作网页比较方便的软件怎么选?一文搞懂避坑指南 很多老板一上来就问:做个网站多少钱?但我反问他:你的域名买了吗?服务器租了吗?他一脸懵。这就是典型的“域名服务器搞不懂”。别急,今天咱们不聊虚的,直接 一文搞懂 那些让你头秃的技术名词。… · 2026/9/28 0:00:06
婚恋网站实战案例:避开3个高价坑,省钱50%还能跑赢流量 婚恋网站实战案例:避开3个高价坑,省钱50%还能跑赢流量 找婚恋网站建站公司,最怕的就是被坑高价。很多同行跟我吐槽,报价单上写得模棱两可,功能栏里全是“高级定制”、“专属UI”,结果落地全是套壳。今天不聊虚的,直接甩几个我经手的 实战案例… · 2026/9/28 0:00:19
济南做网站多少钱:3个案例拆解,防黑源码下载全攻略 济南做网站多少钱:3个案例拆解,防黑源码下载全攻略 上周济南一个做建材的老板找我,脸都绿了。他的官网首页弹出了赌博广告,后台被植入了挖矿脚本。他慌得问我:“网站被黑挂马不知道怎么办?能不能直接找之前的外包公司要源码下载,看看哪里被动了手脚?… · 2026/9/28 0:00:25