欧姆龙PLC通信协议详解:HostLink与FINS/TCP报文调试实战
做自动化上位机这几年我前后接过不少日系PLC的项目其中欧姆龙PLC打交道最多。说到欧姆龙PLC通信协议很多人第一反应是HostLink第二反应是FINS再往下就是各种读不出来、写不进去、偶发超时。这篇文章就围绕欧姆龙PLC通信协议把我在现场踩过的坑、最后搞明白的帧结构、地址映射和调试方法整理出来给正在调欧姆龙通信的同学做个参考。不管你是刚入行的电气工程师还是写上位机软件的开发只要项目里要跟欧姆龙PLC拉数据这篇内容应该能帮你省下不少试错时间。按欧姆龙PLC通信协议的使用深度来分串口时代大家多半从HostLink入门以太网时代基本都要转到FINS/TCP或FINS/UDP。协议本身不复杂难的是细节节点号错一个就报2101内存区码填错就报0201DM区字符串读回来高低字节反了全是乱码。这些坑我在现场都踩过所以这篇文章不打算写成官方手册的翻译版就按我实际调试的过程来讲尽量让你看完能直接上手。1. 别急着拼报文先搞清楚你用的是哪一层协议1.1 串口时代的HostLink简单但有边界欧姆龙的HostLink协议是串口通信时代的老将走RS-232或RS-422/485用ASCII码明文交换数据。主机发一条命令给PLCPLC回复一条响应一问一答非常直白。典型帧格式是这样的设备号 命令 正文 FCS * CR最前面是字符后面跟两位设备号01~31然后是命令码。比如读DM区用RD写DM区用WD读CIO区用RR。正文部分带上起始地址和长度末尾的FCS是两个ASCII字符表示从之后到FCS之前所有字符的异或校验值最后以*和回车结束。FCS的计算方式我一开始也搞错过后来才总结清楚把后面第一个字符开始一直到FCS前面的字符为止逐个字节做异或得到的结果拆成高4位和低4位再分别转成ASCII字符补上去。举个例子帧内容是01RD00640001那就从0开始异或1、R、D……一直到最后一个1最后算出来的值转成两位ASCII码接到后面。HostLink在单机、串口、数据量小的场景下完全够用很多老设备至今还在跑。但它的边界也很明显串口速率撑死了115200bps一条命令能读的数据长度有限而且一台串口只能挂一路通信。到了以太网时代现场动不动就是多台PLC联网、上位机集中采集HostLink就有点吃力了。做新项目时我基本不再优先选它除非现场设备确实只有串口。1.2 FINS才是欧姆龙的通用语言FINSFactory Interface Network Service是欧姆龙自己定义的一套报文协议最大特点是统一。无论是CJ系列、CP系列还是后来NJ/NX系列FINS命令码基本通用。它既可以跑在串口上作为HostLink的特殊命令也可以跑在以太网上走FINS/TCP或FINS/UDP。FINS/TCP默认端口是9600连接后上位机以请求-响应方式通信。和HostLink最大的区别是FINS不再用ASCII明文改用二进制帧数据密度高而且命令集更完善。读写内存区用0101和0102命令码读写时钟用0701和0702读系统状态也有专门命令。只要你会拼一条0101命令其他命令的拼法都是相通的学会一个就掌握了一大半。FINS/UDP则是无连接版本端口同样是9600适合广播或多点采集场景。不过无连接意味着没有重传和顺序保证现场调试时丢包很难排查我一般不太推荐对可靠性要求高的项目直接用UDP除非你对网络环境有绝对的把握。1.3 现场选型我的判断依据协议传输层典型端口适用场景学习成本HostLinkRS-232/422/485串口老设备、单PLC、低速采集低FINS/TCPTCP/IP9600多PLC联网、上位机集中监控中FINS/UDPUDP/IP9600广播采集、对实时性要求高的场景中我自己做的一条产线项目三台CP1H PLC一台工控机统一采集最终选了FINS/TCP。原因很直接以太网布线方便不用为每台PLC单独拉串口线TCP自带连接管理和重传机制通信稳定性比UDP好最重要的是FINS帧结构统一后面再加设备时代码基本不用大改。很多人纠结协议选型我建议先看现场网络条件。现场有交换机、PLC带以太网口直接FINS/TCP只有串口或老设备就HostLink如果要做广播控制或者PLC数量特别多再考虑FINS/UDP。选型不是越高级越好是越匹配现场越好。2. FINS报文逐字节拆解节点号、内存区码、地址偏移全是坑2.1 FINS/TCP包怎么组装很多人第一次拼FINS/TCP报文时看着手册里一堆英文缩写就懵了。其实拆开看一个完整的请求包只有三部分4字节的ASCII字符串FINS、4字节的报文长度、以及真正的FINS报文本体。先解释那4个ASCII字符。它相当于帧头标识让接收方一眼认出这是一条FINS/TCP报文。紧接着的4字节是长度字段按大端方式存放表示后面FINS报文本体有多少个字节。很多人在这个长度上栽过跟头比如多算了头部的8字节或少算了命令码的2字节导致PLC端解析出参数长度错误。FINS报文本体则从ICF字段开始共9字节头ICF控制标志请求帧通常填0x80表示需要响应。RSV保留字段固定填0x00。GCT网关数请求帧填0x02。DNA目标网络地址填0x00表示本地网络。DA1目标节点号也就是PLC的FINS节点号。DA2目标单元号CPU单元填0x00。SA1源节点号上位机填0x00即可但如果现场有多台上位机建议分别设不同值。SA2源单元号上位机填0x00。SID服务标识每次请求自增用来匹配响应和请求。9字节头之后再跟2字节命令码。比如0101表示内存区读0102表示内存区写。最后是命令参数区。整个FINS报文本体就是这样拼接起来的。2.2 内存区码和地址偏移别凭感觉填FINS命令里读写内存区时不是直接写个字符串名称而是用代码表示。最常用的两个DM区0x82CIO区0x80这俩我一开始总记混后来干脆在代码里定义常量注释写上“DM区0x82”再也没错过。地址部分分两部分2字节的字地址按大端排列1字节的位地址。如果你读写的是整个字位地址填0x00。这里有个特别容易犯的错位地址不是给点位编号用的它表示的是字里的位偏移比如要读CIO区的某个位需要把字地址定位到所在字再在位地址里填位号。举个例子要读DM100开始的2个字内存区码填0x82字地址十进制100转十六进制是0x0064大端排列就是00 64位地址填00长度填00 02。如果这块搞反了PLC会报地址超范围而且响应码种类很多排查起来特别费劲。2.3 完整报文示例读DM100两个字的十六进制长啥样我直接列出读DM100、长度为2个字的完整报文方便你对照调试46 49 4E 53 00 00 00 11 80 00 02 00 20 00 00 00 01 01 01 82 00 64 00 00 02逐段看前4字节46 49 4E 53就是ASCII的FINS紧接着00 00 00 11是长度0x11等于17正好是后面FINS报文本体的总字节数。从80到01是9字节头80是ICF00是RSV02是GCT00是DNA20是DA1目标节点号32再两个00表示DA2和SA1接着00是SA201是SID。01 01是内存区读命令。82是DM区00 64是字地址10000是位地址00 02是读取长度2个字。响应报文结构也类似前8字节还是FINS长度9字节头里的SID会回显请求里的值命令码保持0101后面跟主响应码和次响应码两个字节。正常时是00 00数据区从第18字节开始。判断一帧响应是否成功第一件事就是看00 00只要不是它后面数据都别信。3. 上位机写代码从拼帧到稳定轮询3.1 动手前先把环境跑通写代码之前我强烈建议先花十分钟手动验证通信链路。打开TCP调试助手填上PLC的IP和9600端口把上面那条读DM100的报文原样粘贴成十六进制发出去如果能收到带00 00响应码的回复说明网络、节点号、帧格式都没问题可以放心开始写代码。如果这一步都收不到响应那问题大概率在网络或PLC配置层面用代码调试只会更混乱。PLC这边的准备工作也要确认FINS节点号配好IP地址和上位机在同一网段PLC的以太网功能处于启用状态。新机型NX/NJ我还要额外检查一下PLC设置里是否开了FINS服务没开的话上位机怎么连都是超时。手动验证通过之后你的排错范围就缩小到了上位机代码内部效率会高很多。3.2 用C#封装一个最小可用的FINS/TCP客户端我在Windows工控机上习惯用C#写上位机机房环境里.NET运行时基本是标配。封装一个最小客户端不需要多少代码重点就是拼帧、发送、解析三个动作。下面给一个能直接改用的核心片段。public class FinsTcpClient { private TcpClient _client; private NetworkStream _stream; private byte _sid 0; public void Connect(string ip, int port 9600) { _client new TcpClient(); _client.Connect(ip, port); _stream _client.GetStream(); } public byte[] ReadDm(int startAddress, int wordCount) { var body new Listbyte(); body.Add(0x82); // DM区 body.Add((byte)(startAddress 8)); body.Add((byte)(startAddress 0xFF)); body.Add(0x00); // 位地址 body.Add((byte)(wordCount 8)); body.Add((byte)(wordCount 0xFF)); var data BuildFrame(0x0101, body); var response SendAndReceive(data); return ParseResponse(response, body.Count); } private byte[] BuildFrame(ushort command, Listbyte parameters) { var finsBody new Listbyte(); finsBody.Add(0x80); // ICF finsBody.Add(0x00); // RSV finsBody.Add(0x02); // GCT finsBody.Add(0x00); // DNA finsBody.Add(0x20); // DA1 PLC节点号 finsBody.Add(0x00); // DA2 finsBody.Add(0x00); // SA1 finsBody.Add(0x00); // SA2 finsBody.Add(_sid); // SID finsBody.Add((byte)(command 8)); finsBody.Add((byte)(command 0xFF)); finsBody.AddRange(parameters); var frame new Listbyte(); frame.AddRange(System.Text.Encoding.ASCII.GetBytes(FINS)); frame.Add((byte)(finsBody.Count 24)); frame.Add((byte)(finsBody.Count 16)); frame.Add((byte)(finsBody.Count 8)); frame.Add((byte)(finsBody.Count)); frame.AddRange(finsBody); return frame.ToArray(); } private byte[] ParseResponse(byte[] response, int dataStart) { int offset 4 4 9 2; if (response[offset] ! 0x00 || response[offset 1] ! 0x00) throw new Exception($PLC响应码异常: {response[offset]:X2} {response[offset 1]:X2}); int data offset 2; var result new byte[response.Length - data]; Array.Copy(response, data, result, 0, result.Length); return result; } }这段代码里有一个需要重点理解的地方ParseResponse的成功判断位置。响应报文经过8字节的FINS长度后是9字节的FINS头再往后是2字节命令码然后才是主响应码和次响应码。所以偏移量是4加4加9加2在第18、19字节判断00 00。一开始我图省事直接找固定位置换了PLC型号后解析全乱最后老老实实按这个偏移来再没出错过。SID的处理也比较关键。每次请求我把_sid自增发出去的帧里带上响应回来时检查回显值是否一致。如果连接是长连接且请求频繁SID能帮你识别是哪条请求的响应也方便排查偶发超时。3.3 轮询节奏别把PLC当成数据库高并发读代码能通之后另一个容易忽略的问题是轮询节奏。有些新同学一上来就开个while循环10毫秒读一次结果PLC扫描周期被通信任务拖慢产线设备跟着出怪问题。通信读写是需要占用PLC内部处理时间的虽然FINS本身很轻但高频请求依然会挤占扫描周期。我现在的做法是普通模拟量、状态字这类数据100毫秒到200毫秒轮询一次温度、压力这种变化慢的500毫秒都够用需要事件响应的写操作不用轮询按钮触发时发一条写命令即可。还有个细节如果一张页面同时读几十个点位不要把请求全部在同一个瞬间发出去错开几十毫秒PLC端的通信负载会平滑很多。TCP连接在长时间空闲时容易被防火墙或交换机老化掉我会在代码里加一个定时心跳每2秒读一次PLC的系统时钟既能维持连接又能实时确认PLC在线。心跳命令用0701参数比内存读写简单学会0101之后基本触类旁通。4. 现场调试踩过的坑这些报错和怪现象我全遇过4.1 FINS响应码速查表项目做多了我发现最省时间的做法不是翻手册而是把常见响应码记成一张表贴在自己的笔记里。下面这些是我遇到频率最高的几个响应码含义排查方法00 00正常无需处理01 01命令格式错误检查命令码、参数数量01 03地址超范围检查字地址是否超出该型号范围01 04数据长度超限减小单次读写长度分帧处理02 01内存区不存在检查内存区码比如DM区也不能填在CIO请求里02 02内存区写入保护检查PLC内存保护设置21 01目标节点不存在重点检查DA1节点号2101是最让我头疼的因为它经常不是帧格式问题而是PLC那边的FINS节点号没有配对。FINS/TCP不是靠IP直接寻址的报文里还得有节点号PLC侧要把节点号配成和实际网络对应关系一致。出现2101时先Ping通PLC的IP再在PLC软件里确认节点号最后检查报文里的DA1是否等于这个号三步走基本能定位。4.2 DM区字符串高低字PLC看着正常上位机读出来全是乱码这个坑我在现场排查了整整一个下午。PLC里监控DM区的数据字符串内容完全正确但上位机读出来全是乱码。当时第一反应是编码不对试了ASCII、GB2312、UTF-8都不行最后静下心把原始字节打出来才发现问题出在欧姆龙的内存存储方式上。欧姆龙的一个字是16位存ASCII字符串时两个字符放进一个字里而且低字节在前。比如上位机读到41 42PLC监控里显示这个字的值为0x4241。如果按顺序读串字符串是“AB”直接按字节流转ASCII没问题但如果按寄存器值拼字符串就会变成“BA”。这个反直觉的地方特别容易埋雷。双字整数也有类似的坑。32位数据占用两个连续字地址低字放在低位地址高字放在高位地址组合Int32时要注意低字在前。我在代码里写了一个通用的数据解析工具类按点位表里配置的类型把原始字节拼成对应的数值从此再没被这个问题坑过。4.3 偶发超时和连接断开两个最容易忽略的外部因素有一次客户现场反馈产线运行一段时间后上位机就开始读不到数据重启软件又好一阵子。排查到最后发现是工控机的防火墙把长时间空闲的TCP连接断掉了。欧姆龙PLC那边的以太网单元也会在一段时间无数据后主动释放连接所以长连接必须用心跳或者定期读写来维持。还有一次偶发超时是因为现场交换机端口协商成了半双工丢包和冲突特别严重。上位机Ping PLC的IP一直是通的但FINS请求经常超时。后来换了个交换机端口强制千兆全双工问题消失。这种网络物理层面问题用抓包工具看会发现大量重传抓包记录比代码日志更直观。4.4 其它经典问题与处理清单我再列几个短期内不会再忘的坑你照着排能省很多时间PLC处于停止或编程模式时部分型号会对FINS读写无响应或直接超时先确认PLC状态。写DM区时数据长度和实际字节数对不上PLC会报长度错误。FINS/TCP的报文长度字段是大端C#里用BitConverter.GetBytes前记得反转字节序。请求帧和响应帧的SID必须一致我见过有人复制请求模板没改SID导致多线程对不上响应。PLC扫描周期很长时通信请求响应会变慢先优化PLC程序逻辑比提高上位机请求频率更有效。5. 让通信代码在产线上稳定跑下去的工程习惯5.1 点表配置化把地址和类型从代码里挪出去写死地址是初做上位机最容易犯的错误。今天客户说把DM200改成DM300你得改代码、重新编译、部署万一漏了一个引用现场就等着发呆。我现在所有项目都做点表配置化用CSV或JSON维护一份点位文件每一行包含名称、内存区、字地址、位号、数据类型、读写属性、轮询组。名称内存区字地址位号数据类型读写属性一号工位温度DM1000UInt16读设备启停状态CIO2001Bit读配方号下发DM3000Int16写程序启动时加载点表按轮询组生成读取计划按数据类型映射解析函数。这样现场改点表只需要改配置文件不用动代码。后期维护成本直线下降项目交接给同事时对方也不用在代码里翻地址对应关系。5.2 留痕每一个请求响应都值得记录排查通信问题时原始报文比任何文本日志都更有说服力。我在通信模块里统一记录时间戳、请求帧hex、响应帧hex、响应码和通信耗时输出到日志文件。乱码问题靠日志里的原始hex一眼就能定位字节序偶发超时靠耗时统计能看出是不是特定时期才出现响应码变化也能从日志里找出规律。这个习惯看起来多写几行日志实际在产线救了我无数次。对日志文件我会设置按天滚动保留30天这就够覆盖大多数排查场景。注意不要在日志里记业务敏感数据只要原帧和结果避免数据合规风险。5.3 通信看护与安全通信代码跑在产线上不能只靠单次请求成功就乐观。我会加三重看护连续失败超过5次置通信故障状态界面上明确提示心跳丢失时自动重连并恢复上次读取点位写操作增加确认弹窗或者二次握手逻辑。写命令一旦发错比如把配方号写到一个只读区可能会导致设备误动作所以写操作的工程化必须谨慎。PLC的Stop/Run状态也要联动。如果PLC在Stop状态上位机读到的数据可能不是实时的写操作更不应该执行。我通常通过读取PLC的CPU状态字来判断运行状态在状态异常时自动暂停所有写操作。5.4 多PLC并发一个连接一套状态别共用Socket现场如果有多台PLC每台PLC要单独建一个FINS/TCP连接连接对象里各自维护SID、节点号、发送锁。千万不要把所有PLC的请求共用同一个Socket去发因为响应包回来时你没法判断是哪台PLC的而且一台PLC掉线会导致整个连接异常。每台PLC的连接独立之后还需要各自的看门狗和重连逻辑。产线上一台PLC断电重启上位机要在几秒内自动恢复连接其他PLC不受影响。这个架构做到位整个通信系统才真正具备产线级别的稳定性。最后分享一个我自己的习惯每次做欧姆龙项目拿到PLC的IP和节点号之后我第一件事不是写代码而是先打开TCP调试助手把一条读DM区的报文原样发过去看到00 00响应再开始动工。通信协议这东西真没什么玄学全是格式、字节序和边界条件的细节把每一步验证扎实了产线上那些“偶发超时”和“读出来是乱码”基本都能提前消灭。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →