资讯动态

欧姆龙FINS协议详解:报文结构、命令帧与TCP/UDP/串口调试实战

发布时间:2026/10/18 1:27:12 来源:尧图企业网站定制
1. 从一条产线调试的深夜求助说起凌晨两点群里弹出一条消息“PLC的地址表都对得上读D100就是没数据换了个端口号还是不行到底哪里出问题了”配图是一段抓包截图TCP流里能看到一串十六进制字节开头是46 49 4E 53后面跟着00 00 00 1A之类的字段。懂行的人一眼就能认出来这是欧姆龙FINS协议的标准报文头。FINS全称Factory Interface Network Service是欧姆龙为其PLC、运动控制器、视觉设备等工业自动化产品设计的一套应用层通信协议。它最特别的地方在于同一套命令帧格式可以跑在串口、以太网、甚至控制器背板总线上。也就是说你学会了FINS的命令结构换一种物理介质命令本身几乎不用改。这个设计在工业现场非常实用因为产线上经常出现“老设备只有串口、新设备只有网口”的混搭局面。这篇内容适合三类人看一是刚接触欧姆龙PLC、被地址格式和握手流程绕晕的自动化工程师二是需要做上位机数据采集、但被FINS报文结构卡住的软件开发三是做设备联网改造、需要把老旧欧姆龙设备接入新系统的集成人员。我会从报文结构讲起把命令帧、响应帧、地址编码、常见命令码、TCP/UDP/串口三种承载方式逐一拆开再补上实际调试中最容易踩的坑。全文基于公开协议规范和常见工程实践整理不涉及任何具体企业内部资料。2. FINS协议的报文骨架为什么它能在三种介质上通用2.1 FINS帧头的固定结构与字段含义FINS命令帧的头部是固定长度的这是它能够跨介质通用的根本原因。一个完整的FINS/TCP帧在TCP载荷里长这样46 49 4E 53 00 00 00 1A 00 00 00 02 00 00 00 00 |--FINS--| |--长度--| |--命令--| |--错误码--|前四个字节46 49 4E 53是ASCII的“FINS”这是FINS/TCP特有的封装头用来在TCP流中标识FINS报文的起始。紧接着的4个字节是后续数据的长度大端序再往后是4个字节的命令码最后4个字节是错误码。这16个字节是FINS/TCP的“信封”真正的FINS命令帧被装在这个信封里面。拆开信封之后里面的FINS命令帧结构是字段长度说明ICF1字节信息控制字段标识命令/响应、是否需要响应RSV1字节保留固定为0GCT1字节网关计数经过网关时递减DNA1字节目标网络号DA11字节目标节点号DA21字节目标单元号SNA1字节源网络号SA11字节源节点号SA21字节源单元号SID1字节服务ID用于匹配请求和响应命令码2字节具体操作如0101读、0102写数据区变长命令参数和返回数据这个结构里ICF、DNA、DA1、DA2、SNA、SA1、SA2这几个字段是FINS寻址的核心。在以太网环境下DNA和SNA通常填0本地网络DA1填目标PLC的节点号DA2填0CPU单元。串口环境下这些字段的填法又不一样。这就是FINS“一套命令、多种承载”的实现方式命令码和数据区不变只改寻址字段和底层传输方式。2.2 命令码的编码逻辑与常用命令速查FINS的命令码是2个字节高字节表示功能大类低字节表示具体操作。比如01 01读内存区Memory Area Read01 02写内存区Memory Area Write01 03读多个内存区01 04写多个内存区02 01读参数区02 02写参数区05 01读控制器状态06 01读时钟07 01读循环时间这个编码逻辑很直观高字节01代表“内存操作”02代表“参数操作”05代表“状态读取”。记住这个规律看到命令码就能大致判断它在干什么。实际工程中最常用的是01 01和01 02也就是读写DM区、CIO区、WR区等。以读DM区为例命令帧的数据区需要指定内存区代码1字节DM区是82CIO区是B0WR区是B1起始地址2字节大端序读取长度2字节大端序单位是“字”比如要读D100开始的10个字数据区就是82 00 64 00 0A。响应帧的数据区则直接返回20个字节的原始数据10个字×2字节。2.3 响应帧与错误码怎么判断命令到底成没成响应帧的结构和命令帧几乎一样区别在于ICF字段的最低位会从0变成1表示这是响应以及数据区的内容变成了返回数据或错误信息。判断命令是否成功不能只看TCP有没有收到数据必须看FINS层的错误码。FINS错误码在响应帧的固定位置常见的有错误码含义典型原因00 00正常命令执行成功01 01本地节点未加入网络网络号配置错误01 02目标节点未加入网络目标PLC节点号不对01 03目标节点未响应PLC断电或网线断开01 04目标节点忙PLC正在执行其他高优先级任务01 05目标节点无此单元单元号填错02 01命令码不支持PLC型号不支持该命令03 01地址范围超限读写的地址超出PLC内存范围03 02数据长度超限一次读写的数据量太大03 03地址格式错误内存区代码或地址编码不对03 04数据区格式错误数据区字节数不对04 01命令执行失败写保护或权限不足我见过太多人抓包看到TCP层有响应就以为成功了结果解析出来全是03 01。错误码必须逐字节解析不能跳过。尤其是03 01和03 03前者是地址越界后者是地址格式错误两者的排查方向完全不同。3. 三种承载方式TCP、UDP和串口的FINS有什么不一样3.1 FINS/TCP最常用但也最容易配错的方式FINS/TCP是目前上位机采集最常用的方式因为TCP可靠、编程方便。但它有一个容易被忽略的细节FINS/TCP在建立连接后需要先发一个“节点地址自动分配”的握手帧才能发真正的FINS命令。这个握手帧的格式是46 49 4E 53 00 00 00 0C 00 00 00 00 00 00 00 00 |--FINS--| |--长度--| |--命令--| |--错误码--|命令码00 00 00 00表示“节点地址自动分配请求”。PLC收到后会返回一个响应里面包含分配给客户端的节点号。如果你跳过这一步直接发01 01读命令PLC会直接拒绝或返回错误。这是新手最容易踩的坑之一。另外FINS/TCP的默认端口是9600但很多现场会改成其他端口。端口号本身不影响FINS命令结构但连接建立后的握手流程是一样的。3.2 FINS/UDP轻量但需要自己处理丢包FINS/UDP不需要握手直接发命令帧就行。它的封装格式比TCP简单没有那16字节的FINS/TCP信封UDP载荷直接就是FINS命令帧。目标端口默认也是9600。UDP的好处是开销小、延迟低适合高频采集。但坏处也很明显UDP不保证送达也不保证顺序。如果PLC忙或者网络抖动命令可能丢失。所以用UDP做采集时必须在应用层做超时重试和序号匹配。我个人的经验是如果采集周期在100ms以上用TCP更省心如果周期在10ms级别且网络质量好UDP更合适。但无论用哪种SID字段都要认真填因为它是匹配请求和响应的唯一标识。用UDP时如果SID重复响应就可能对不上号。3.3 串口FINS老设备的唯一选择串口FINS的帧结构和以太网FINS完全一样只是没有TCP/UDP的封装头。串口参数通常是波特率9600或19200数据位7或8停止位1或2校验偶校验。具体参数必须和PLC的串口设置完全一致否则连第一步都走不通。串口FINS的寻址字段填法和以太网不同DNA和SNA通常填0DA1填目标PLC的单元号不是节点号DA2填0。串口是点对点或总线式没有IP地址的概念所以节点号在串口环境下意义不大。串口调试最大的问题是没有抓包工具。以太网可以用Wireshark看原始字节串口只能靠串口监视器。我的做法是先用串口监视器确认PLC有没有返回数据再把返回的十六进制字节复制出来对照FINS响应帧结构逐字段解析。这个过程很笨但很有效。4. 地址编码为什么D100和CIO100的写法完全不同4.1 内存区代码与地址的换算关系FINS命令里的地址不是直接写“D100”而是用内存区代码偏移量的方式编码。不同内存区的代码和地址范围如下内存区代码地址范围单位CIOB00~6143字WRB10~511字DM820~32767字EM0900~32767字EM1910~32767字HRB20~511字ARB30~959字定时器090~4095位/字计数器090~4095位/字以DM区为例D100的编码是内存区代码82起始地址00 64100的十六进制读取长度00 0A10个字。所以数据区就是82 00 64 00 0A。这里有一个容易混淆的点地址是“字地址”还是“位地址”。读DM区时地址是字地址单位是字。但读CIO区的位时地址编码方式不同需要把位地址换算成“字地址位偏移”。比如CIO 0.05字地址是0位偏移是5编码时先写字地址00 00再写位偏移05。这个换算规则在FINS规范里有明确定义但不同PLC型号可能有细微差异最稳妥的办法是查对应型号的通信手册。4.2 位操作与字操作的命令差异FINS命令里位操作和字操作是分开的。读位用01 01但数据区里要指定“位”而不是“字”。写位用01 02数据区里每个位用1个字节表示00或01。实际工程中批量读位不如批量读字再在应用层拆位。因为FINS的位读取命令一次最多读256个位而字读取一次可以读512个字1024字节。如果产线上有几百个传感器信号要采集逐位读的效率远低于读一整块DM区再拆位。我做过对比读1000个位逐位读需要几十条命令读字再拆位只需要几条命令速度差了一个数量级。4.3 跨内存区批量读取的实现思路FINS支持01 03命令一次读取多个不连续的内存区。这个命令的数据区格式是先写要读的区数1字节然后每个区写内存区代码、起始地址、读取长度。响应帧则按顺序返回每个区的数据。这个命令在采集分散地址时非常有用。比如要读D100的10个字、CIO200的5个字、WR50的3个字用01 03一条命令就能搞定不需要发三条01 01。但要注意一次读取的总数据量不能超过PLC的响应缓冲区大小。不同型号的PLC缓冲区大小不同常见的是1024字节或2048字节。超过这个限制会返回03 02错误。5. 调试实战从抓包到跑通的完整排查链路5.1 用Wireshark确认FINS/TCP握手是否完成调试FINS/TCP的第一步不是写代码而是用Wireshark抓包确认握手流程。打开Wireshark过滤tcp.port 9600然后让上位机发起连接。正常的流程应该是TCP三次握手SYN、SYN-ACK、ACK上位机发送节点地址自动分配请求命令码00 00 00 00PLC返回分配响应包含分配的节点号上位机发送FINS命令如01 01读DM区PLC返回FINS响应如果第2步没有出现或者第3步的响应里错误码不是0后面的命令一定失败。我遇到过一种情况上位机代码里直接发了01 01跳过了握手PLC返回01 01错误码本地节点未加入网络。排查了半天才发现是握手缺失。5.2 解析响应帧时最容易忽略的字节序问题FINS协议的所有多字节字段都是大端序高字节在前。但很多编程语言的默认字节序是小端序解析时如果不做转换读出来的地址和长度全是错的。比如响应帧里返回的读取长度是00 0A大端序解析是10小端序解析是2560。这个错误很隐蔽因为TCP层看起来一切正常只是数据对不上。我的习惯是所有FINS字段解析都显式指定大端序不依赖语言默认行为。在Python里用struct.unpack(H, data)在C#里用BinaryPrimitives.ReadUInt16BigEndian在Java里用ByteBuffer.order(ByteOrder.BIG_ENDIAN)。5.3 超时与重试策略什么时候该重发什么时候该放弃FINS/TCP的超时设置很关键。如果超时太短PLC稍微忙一点就超时如果太长采集效率上不去。我的经验值是单条命令超时500ms~1000ms重试次数2~3次重试间隔100ms~200ms但不是所有错误都值得重试。错误码01 04目标节点忙可以重试因为PLC可能只是暂时忙。错误码03 01地址越界重试一万次也没用必须改地址。错误码01 03目标节点未响应要区分情况如果是网络抖动重试可能成功如果是PLC断电重试只是浪费时间。我的做法是在应用层维护一个错误码分类表可重试的错误才进入重试队列不可重试的错误直接上报。这样既不会因为网络抖动丢数据也不会在配置错误时无限重试。6. 工程化落地把FINS采集做成稳定可维护的模块6.1 连接池与心跳机制的设计要点如果上位机需要同时采集多台PLC不要每台PLC都开一个长连接然后不管。PLC的TCP连接数有限而且长时间空闲的连接可能被PLC主动断开。我的做法是维护一个连接池每个PLC对应一个连接每隔30秒发一条05 01读控制器状态作为心跳心跳失败时标记连接为“可疑”下次采集时优先重连连接空闲超过5分钟自动关闭释放资源心跳命令的选择也有讲究。05 01只读状态不涉及内存区对PLC的负担最小。不要用心跳去读DM区那样会增加PLC的扫描周期负担。6.2 数据缓存与变化上报的取舍很多采集场景不需要“每次读到的值都上报”而是“值变化时才上报”。这时候可以在应用层做缓存每次读到数据后和上一次比较只有变化超过阈值才推送到上层。但缓存会引入延迟。如果上层需要实时曲线缓存就不合适。我的经验是状态量开关、报警用变化上报模拟量温度、压力用定时上报。变化上报的阈值要根据物理量的噪声水平来定太小会频繁触发太大会漏掉真实变化。6.3 多PLC并发采集时的资源竞争问题当上位机同时采集几十台PLC时线程池的大小和命令队列的管理就成了瓶颈。如果每个PLC一个线程线程切换的开销会很大。如果用一个线程轮询所有PLC单台PLC的响应慢会拖累其他PLC。我的方案是用固定大小的线程池比如8~16个线程每个线程从队列里取任务任务粒度是“一台PLC的一批命令”。这样既不会创建过多线程也不会因为单台PLC慢而阻塞全局。队列里要设置优先级报警相关的命令优先于常规采集。另外FINS的SID字段在并发场景下要特别小心。如果多个线程同时向同一台PLC发命令SID必须唯一否则响应会串。我的做法是每个PLC连接维护一个SID计数器每次发命令前递增响应回来时按SID匹配。7. 几个让我印象深刻的现场问题第一个是地址偏移的“1”问题。某次调试读D100读不到读D101却能读到。查了半天发现是PLC的DM区起始地址被设置成了D1而不是D0。FINS命令里的地址是绝对地址但PLC内部可能有一个偏移量。遇到地址对不上时先确认PLC的地址设置再怀疑命令编码。第二个是响应数据长度和请求长度不一致。请求读10个字响应却返回了20个字。后来发现是PLC的“数据格式”设置成了“双字”模式每个地址占4个字节。FINS命令本身不感知这个设置它只按字节返回。所以解析响应时不能假设“请求10个字就返回20个字节”要以实际返回的长度为准。第三个是UDP采集时的乱序问题。用UDP高频采集时偶尔会出现响应顺序和请求顺序不一致。如果应用层按顺序解析数据就会错位。解决办法是在每条命令里填不同的SID响应回来时按SID匹配而不是按到达顺序匹配。这些问题的共同点是FINS协议本身不复杂复杂的是PLC的配置和现场的网络环境。协议规范只定义了“命令怎么发、响应怎么回”但PLC怎么配、网络怎么走规范里不会写。这些只能靠现场经验和抓包分析来补。8. 写给准备上手FINS的人如果你刚开始接触FINS我的建议是先不要写代码先用现成的调试工具把一条命令跑通。很多PLC厂商提供FINS调试工具或者可以用通用的TCP调试助手手动构造命令帧。手动发一条01 01读DM区看到正确的响应之后再把这个过程用代码实现。这样你能清楚地知道每一步在干什么而不是被代码里的封装层遮住细节。另外抓包工具是FINS调试的命根子。Wireshark能看TCP层的原始字节串口监视器能看串口层的原始字节。遇到问题时先把原始字节抓出来对照FINS帧结构逐字段解析。大部分问题都能通过这种方式定位。最后不要迷信“一次配置永久有效”。产线的网络环境、PLC的负载、上位机的并发量都会变化。今天跑通的配置明天可能因为网络抖动或PLC固件升级而失效。所以采集模块要有足够的日志和错误上报出问题时能快速定位是网络问题、配置问题还是PLC问题。FINS协议本身并不难难的是把它放在真实的工业现场里跑稳。希望这篇内容能帮你少走一些我当年走过的弯路。

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

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

免费获取报价 →
↑