
简介欧姆龙FINS协议动态库是一套面向自动化系统开发者的通信组件支持Delphi、VC、VB等多种编程环境用于实现与欧姆龙PLC、HMI等设备的FINS协议交互解决多串口并发通信与数据读写问题。压缩包共42个文件约1.32MB涵盖动态库DLL、C/C头文件、Delphi/Pascal源码、VB工程文件、CHM手册及可执行示例等类型完整且目录按语言分模块组织。其中Omron Fins协议dll手册.chm提供函数调用与错误处理说明Delphi、VC、VB三套Demo配套源码和可运行程序另有可直接引用的DLL实例便于开发者参照集成并快速验证通信逻辑。已有868人浏览学习适合需要实现欧姆龙设备上位机通信的工程师参考。 做上位机开发的兄弟十有八九会遇到需要和欧姆龙PLC对接的场景。我最早是直接用组态王或者官方FinsGateway简单是简单但一遇到需要高并发读写、批量采集、跟MES深度集成的项目就会觉得这套方案太笨重。后来我花了两三个星期把所有通信逻辑封装成一个基于FINS协议的动态库前端不管是C#、C还是Python都通过这个库跟PLC对话一直用到现在。这个库解决的核心问题就是让上位机用统一的接口读写欧姆龙PLC而不用关心协议细节同时还能在无人值守的产线上稳定跑。下面讲的东西不是纯理论是我在几个真实项目里反复踩坑总结出来的。如果你是刚接触欧姆龙PLC通信可以把它当成一份“可直接抄作业”的工程笔记如果你已经写过一版FINS通信代码相信也能从里面的细节里找到可优化的点。1. 为什么做了这个库给欧姆龙PLC上位机通信的替代方案1.1 现成组件不够用才想到封装动态库做系统集成时最省事的做法是装一个FinsGateway或者直接用组态王自带的欧姆龙驱动。但这类方案有几个绕不开的问题第一授权费用按点数或按驱动收项目一多成本马上上来第二网关进程一旦崩溃上位机和PLC之间就断了现场运维压力很大第三当你需要做自定义业务逻辑比如按条件读取特定地址、把PLC数据直接转换成MES报文时会发现配置界面反而成了瓶颈。我自己遇到的典型场景是一条产线有十几台欧姆龙CP1H和CJ2M上位机每100毫秒要采集一次各工位的传感器数据、扭矩值、不良品计数还要在出现异常时精确写入停线指令。用现成驱动做这种高频读写要么延迟不稳定要么老是把整个线程卡死。于是我把FINS协议通信封装成动态库把“建立连接、组帧、发请求、解析响应、重发异常”这些脏活全部收进库里对外暴露出几个干净的函数上位机程序只管调就行。1.2 动态库比静态库更适合多项目复用有人可能会问为什么非要用动态库直接把源码编进上位机项目不是更省事答案是场景不同。如果只有一个单机版工具静态库编译进去问题不大但如果你像我一样要在MES系统、视觉检测上位机、数据追溯服务里同时复用这套通信逻辑动态库的优势就非常明显同一份协议逻辑只维护一个文件升级协议版本、修BUG时替换DLL即可不用重新编译所有上位机程序不同的语言都能通过标准接口调用C#用P/InvokeC直接链接Python用ctypes一套核心代码覆盖多种业务前端减少重复开发和交叉编译问题只要注意好调用约定和数据类型对齐就能长期稳定运行。当然动态库也有它的“坑”比如32位和64位版本必须分清楚依赖的C运行库要打包齐全。这些后面我会专门讲。2. 动手前必会的FINS协议基础2.1 以太网FINS的链路与端口FINS是欧姆龙自定义的工业以太网协议全称是Factory Interface Network Service。在Ethernet上运行时默认端口是9600支持UDP和TCP两种传输方式。我平时首选UDP因为它的报文开销小、响应速度快适合100ms级别的循环采集TCP则适合对可靠性要求更高的点位写入但需要额外处理断线重连。这里有个关键点PLC侧不是只有IP地址就行还要确认FINS节点号。节点号在PLC网络设置里配和IP地址最后一段不一定相同但通信帧里会同时用到对方节点号和本机节点号。很多第一次写FINS的人明明网段通、地址也对却一直收不到响应八成就是节点号填错了。FINS报文本身的结构比较规整一条命令帧里包括10字节的FINS头和后面的命令码、参数、数据。FINS头里有源节点号、目的节点号、SID等字段。SID相当于是每次请求的序号上位机用它来匹配响应和请求这在多线程场景下非常重要。2.2 三个必须搞清的命令码、内存区与错误码FINS的命令码有几十条但日常和PLC交换数据绝大多数时候用到的是读内存区和写内存区。把它们记住动态库的主干就出来了功能命令码说明读内存区0101读取连续或单个地址的数据写内存区0102写入连续或单个地址的数据内存区传送103在PLC内部不同区之间复制一般不常用内存区代码也比较固定比如CIO区是B0WR区是B1HR区是B2DM区是82。不同区能读写的地址范围不一样现场写代码前最好先对着对应PLC型号的编程手册确认一遍我上面给的是最常用的几类。响应帧的前两个字节是结束码00 00表示正常。如果返回1101、1102、1103这类代码分别对应未定义命令、内存区不存在、地址越界等问题。动态库在解析响应时不应该只关心“有没有数据”而是要把结束码先判断掉否则很容易把错误数据当成正常数据用。2.3 地址换算与数据类型FINS协议里“地址”不是字符串而是按区代码地址编码的二进制结构。以DM区为例PLC编程软件里的D100在FINS帧里不会直接出现“D100”这3个字符而是被拆成区代码82和两个字节的字地址。很多初学时搞不清“字地址”和“真实PLC地址”之间的映射其实把编程软件里的十进制地址转换成十六进制就能解决比如D100对应的FINS字地址就是0x0064而D200对应的就是0x00C8。组装帧时还要注意字节序。FINS规定多字节数据一律大端传输比如一个16位整数值0x1234在帧里是先传0x12再传0x34。如果你在C#里直接把byte[]转成short不处理大小端读出来的数往往会乘个256或者变成负数。这个坑我至少看到三个同事踩过。数据类型方面FINS读出来的是一个一个Word16位具体怎么解释成有符号整数、无符号整数、浮点数取决于PLC程序里的变量定义。浮点数在FINS里通常占两个Word四个字节的排列顺序也要和PLC侧保持一致这块最好在动态库里提供专门的转换函数避免上层业务代码到处写位操作。3. 动态库的模块划分与API设计3.1 分层架构设计我在设计库的时候没有把所有代码堆成一个文件而是分成三层第一层是传输层负责封装Socket连接、UDP收发、断线重连、超时控制。这一层不关心任何FINS帧业务只保证“把一段字节发出去再把一段字节收回来”。第二层是协议层负责FINS帧的构建和解析。它从传输层拿到原始字节根据命令码拼出读请求、写请求收到响应后校验结束码、提取数据。这一层是库的核心也是最容易被写乱的地方。我建议把帧解析单独写成纯函数方便单元测试。第三层是接口层对上层提供稳定的C语言接口比如Fins_Open、Fins_Close、Fins_ReadWord、Fins_WriteWord。之所以用C接口而不是C#类库或C类是为了让所有语言都能通过导出函数调用。这就是“动态库”能跨语言复用的核心原因。3.2 对外API清单我公开的接口不多但够用大致分成管理类、读写类、状态类三种函数名作用关键参数Fins_Open建立与PLC的通信会话IP、端口、节点号、超时时间Fins_Close关闭会话释放资源句柄Fins_ReadWord从指定区读连续Word区代码、起始地址、数量、缓冲区Fins_WriteWord向指定区写连续Word区代码、起始地址、数量、数据Fins_SetTimeout动态调整请求超时毫秒Fins_GetLastError获取最近一次错误码输出缓冲区句柄的设计很关键。我内部定义了一个上下文结构体里面保存Socket、对方节点号、请求序号、超时参数、互斥锁。每次Fins_Open返回一个int类型的句柄上层所有操作都要传入这个句柄。这样做的好处是一个进程里可以同时连接多台PLC互不干扰也为以后扩展冗余通信留了口子。3.3 线程模型与超时机制PLC通信最忌讳的是“一个请求卡死整个程序”。由于FINS over UDP没有天然的会话状态我把每次请求都设计成同步请求-等待-响应模式同时用互斥锁保证同一个句柄同一时刻只有一个请求在等。这样上位机开多个线程分别读不同地址也没问题因为锁的粒度是句柄级的。超时机制分两层第一层是Socket收包超时我一般设300到500毫秒第二层是应用层重试一次超时后再重试1到2次仍失败才返回错误。设置太短容易把正常慢响应误判成失败设置太长会让异常恢复变得迟钝。经过现场调优我最终把默认超时定在500ms重试2次大多数场合都能在1.5秒内给出明确结果。4. 实操接入从编译到上位机调用4.1 编译与部署注意事项库本身我用C/C开发编译时直接把Socket相关的Windows库或Linux的socket库链接进去。要注意的是动态库的输出必须明确区分Release和Debug而且最好同时输出x86和x64两个版本因为现场Windows上位机32位和64位并存的情况非常常见。部署时最容易出的问题有两个一是缺少VC运行库明明代码没写错加载DLL却失败报“找不到入口点”或“初始化失败”二是把32位DLL放到了64位程序里。我这里整理了一份常规的发布清单照着做能省很多现场排查时间发布目录里同时放x86和x64两个子目录按程序架构选用使用动态库时把对应的MSVC运行库以可再发行组件方式装到目标系统DLL文件名不要随便改因为头文件里的导入库是按原文件名生成的如果目标系统是Windows 7最好用VS2015以上工具集编译并确认系统补丁完整。4.2 C#调用示例C#调用这套动态库非常直接用P/Invoke声明函数原型就可以不需要额外引入NuGet包。下面是一个最简单的读取DM区数据的示例[DllImport(OmronFinsClient.dll, CallingConvention CallingConvention.Cdecl)] public static extern int Fins_Open(string ip, int port, int nodeNo, int timeoutMs); [DllImport(OmronFinsClient.dll, CallingConvention CallingConvention.Cdecl)] public static extern int Fins_ReadWord(int handle, int areaCode, int address, int count, ushort[] data); [DllImport(OmronFinsClient.dll, CallingConvention CallingConvention.Cdecl)] public static extern int Fins_Close(int handle); static void Main() { int handle Fins_Open(192.168.250.10, 9600, 10, 500); if (handle 0) { Console.WriteLine(连接失败); return; } ushort[] buffer new ushort[10]; int ret Fins_ReadWord(handle, 0x82, 100, buffer.Length, buffer); if (ret 0) { for (int i 0; i buffer.Length; i) { Console.WriteLine($D{100 i} {buffer[i]}); } } Fins_Close(handle); }注意这里把区代码直接用0x82传进去调用方必须看明白DM区的含义。实际工程中我一般会用枚举或常量表把区代码定义好比如AreaCode_DM 0x82避免到处写魔法数字。4.3 批量采集与点位映射真实项目里很少单点读写更多的是批量采集。比如每100ms采几十个温度值如果一个个读光帧头加地址解析的时间就足够拖慢循环。更好的做法是把需要采集的地址分段能连续的尽量连续用一次0101命令读取多Word不能连续的再分成几次读。点位映射这块我强烈建议引入欧姆龙Sysmac Studio导出的变量XML。PLC工程师在软件里定义好变量名、数据类型导出XML后上位机程序直接解析XML生成地址表C#里甚至可以自动生成对应的数据类。这样避免了两边各维护一份Excel点位表最怕的就是PLC侧改了地址上位机还在用旧地址读写。我这里常用的做法是让PLC工程师在变量表里把DB区的偏移量当成“地址注释”写清楚XML里再带上内存区类型上位机启动时加载XML建立变量名到FINS区代码、字地址、数据类型的映射关系。过程虽然要写点代码但后面维护项目会轻松很多。5. 现场问题排查与避坑记录5.1 常见问题速查表把我在项目里遇到的高频问题整理成一张表供大家现场对照排查现象可能原因解决对策连不上PLCUDP发出去没响应IP不通、节点号配置错误、PLC未开以太网端口先ping通IP再核对FINS节点号能连上但读写数据全部为0区代码填错、地址越界、PLC程序锁定访问权限用CX-Programmer或Sysmac Studio在线确认地址数据值忽大忽小或负数大小端解释错误、数据类型不匹配在动态库统一处理大端转本机字节序程序运行一段时间后卡死未处理超时异常、Socket缓冲区全被占满增加超时重试和Socket资源清理DLL加载不了位架构不一致、VC运行库缺失换成对应位数的DLL并安装运行库5.2 几个当事人才会知道的细节第一PLC上的程序停止状态和运行状态FINS读响应是有差异的。有些型号在程序停止时内存区还是可以读但定时器、计数器这类和任务相关的数据不会刷新导致上位机读到的值像卡住了一样。处理办法是和PLC工程师约定一个“心跳字”或“运行模式字”上位机先看这个信号再登数据。第二UDP端口9600不能随便占用。我曾经见过一台工控机上装了杀毒软件把UDP 9600当成可疑端口拦截了现场抓包怎么都不通最后关掉实时防护才好。排查这类问题时Wireshark抓包是最快的判断手段先看PLC IP有没有发出响应帧再顺着响应判断是帧解析问题还是网络问题。第三FINS报文里的SID是请求与响应匹配的关键。如果上位机用多线程频繁读PLC又复用了同一个SID响应就可能串掉。我在动态库内部用自增序号每发一个请求就换一个SID同时用一个最近N条请求的结构做匹配基本杜绝了错包现象。6. 后续可以怎么扩展6.1 从单台到多台再到冗余通信当前动态库的核心功能是单台PLC的读写。实际用到后期我发现几个方向可以继续扩展。第一个是PLC切换当一台PLC故障时需要用同样地址的备用PLC接管动态库可以在上层增加“连接组”的概念一个虚拟句柄背后绑多台PLC读请求自动切到当前主PLC。第二个是MQTT桥接把FINS读到的数据定期发布到消息中间件让云端服务不再直接和PLC耦合。6.2 几个我的个人建议如果你只是临时联调没有长期维护压力直接用FinsGateway也能跑。但只要是面向产线长期运行的项目我还是建议把FINS通信封装成独立的动态库哪怕一开始只是为了读几十个点位。因为产线系统最怕的不是功能复杂而是出了问题大家互相推诿有了独立的协议库协议层面的问题边界就会非常清楚。最后再分享一个我实际踩过的坑很多欧姆龙PLC在上电后需要等待以太网模块就绪动态库初始化后立刻发FINS请求会偶发超时。我现在的做法是连接成功后再做一次空读测试失败则按200ms间隔重试5次。这个小动作解决了我至少三个项目的现场调试问题你可以直接拿过去用。本文还有配套的精品资源点击获取