ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

欧姆龙PLC通信协议全解析:HostLink/FINS/Modbus-RTU

欧姆龙PLC通信协议全解析:HostLink/FINS/Modbus-RTU 干工控这些年欧姆龙PLC的通信协议算是我交学费最多的地方之一。从CP1H的串口折腾到NJ/NX的EtherNet/IP从HostLink到FINS再到Modbus-RTU每一套协议都有不少让人抓狂的细节节点号对不上、帧格式错一位、波特率设错、地址偏移算错……表面上看到的都是“通信不上”背后原因五花八门。这篇文章把我踩过的坑和最后搞明白的事按协议类型、按排查路径整理出来希望能让刚接触欧姆龙的朋友少走弯路也让已经在跳坑里的人尽快爬出来。1. 欧姆龙通信协议全景先把“家族成员”认明白欧姆龙的PLC通信协议其实是一套“家族体系”。很多人一上来就搜“欧姆龙PLC通信协议”以为只要知道一个协议就行实际上欧姆龙至少有四五种常用通信方式。如果不先搞清楚你手上这台PLC支持哪些协议、上位机或触摸屏打算走哪种后面调起来会很乱。我在现场见过不少同行把CP1H当西门子那样用上来就写Modbus却不知道怎么映射数据区也见过有人把NJ的网口当成普通Modbus TCP去读标签结果死活读不到。所以第一步很重要认清楚欧姆龙这套通信家族里都有谁。1.1 串口时代的三大协议HostLink、FINS、Modbus-RTU先讲串口。虽然现在网口普及了但大量老设备、仪表、变频器、触摸屏还在用RS232/RS485串口协议仍然是绕不开的。欧姆龙PLC在串口上最常见的协议有三个HostLink、FINS、Modbus-RTU。HostLink是欧姆龙自有的串口命令协议也是很多触摸屏选“欧姆龙”驱动时默认走的协议。它的报文是ASCII字符帧格式大概是“单元号 命令 数据 FCS * 回车”。因为是人可读的ASCII码调试时拿串口助手直接看就能发现格式问题非常直观。代价是效率偏低一个命令只能干一件事批量读写DM区或CIO区时帧比较长适合数据量不大的上位机、文本屏场景。CP1H自带的RS232C口和外设口都能跑HostLink默认参数是9600、7位数据、偶校验、2停止位单元号0。这个默认参数我要特别提醒很多人拿8N1去连门都摸不到。FINS是欧姆龙更底层的协议分串口版和以太网版。串口版FINS主要用于SYSMAC WAY方式连接报文是二进制帧比HostLink紧凑得多功能也强很多能读指定内存区、执行远程程序控制、查PLC状态。FINS报文的头部字段比较多ICF、RSV、GCT、DNA、DA1、DA2、SNA、SA1、SA2、SID后面才是命令码和数据。新手看着这些缩写头大其实拆开看就是“从哪个网络、哪个节点、哪个单元来到哪个网络、哪个节点、哪个单元去”再加上一个“服务ID”用于匹配应答。用熟了以后批量读写内存比HostLink顺畅很多。Modbus-RTU则是工业通用协议欧姆龙很多型号内置了Modbus主站/从站功能。比如CP1H的串口可以配置成Modbus-RTU简易主站用一条指令轮询变频器、温控表、电表也可以配置成从站让组态软件或别的PLC来读。Modbus的好处是通用性极强任何品牌的设备几乎都支持。坏处是欧姆龙的内存区和Modbus寄存器之间不是天然一一对应需要做地址映射这里非常容易踩坑后面单独讲。这三个协议怎么选我一般按通信对象来定。触摸屏和文本屏优先HostLink简单稳定面板上填好单元号就行。上位机软件或者机器人的PLC通信库优先FINS尤其是网口FINS功能强、速度快。带一堆变频器、仪表优先Modbus-RTU因为这类设备几乎都只认Modbus。1.2 网口时代的两大主力EtherNet/IP 与 FINS/TCP现在的欧姆龙PLC基本都带以太网口像NJ/NX、CJ2、CP1H加扩展模块也有网口。网口上的通信比串口丰富但也更容易让人混淆。最常见的是FINS/TCP、FINS/UDP和EtherNet/IP。FINS/TCP和FINS/UDP是把FINS命令封装进TCP或UDP包里。端口固定TCP/UDP都是9600。这里有个很多人忽略的机制FINS/TCP通信建立时上位机需要和PLC协商节点地址一个连接对应一个FINS节点。如果你的上位机程序只做了socket connect就直接发FINS命令没有先协商节点有些PLC会直接拒绝。FINS/UDP则不一样它默认就是无连接的把FINS报文发过去就行但需要考虑路由器、防火墙以及应答端口的问题。我在现场遇到过几次FINS/UDP命令发出去了PLC也处理了但应答发到了一个固定端口上位机却只监听发起端口导致“看起来没反应”。抓包才发现应答包在另一个端口上躺着。EtherNet/IP则是基于CIP协议的工业以太网欧姆龙NJ/NX系列支持得特别好。它分显式报文和隐式IO报文两种显式报文适合来回读写下数据像访问标签隐式IO是周期性交换数据适合PLC和PLC、PLC和视觉系统之间实时交换大量点位。EtherNet/IP的坑主要在配置上第三方上位机需要安装EDS文件才能识别设备型号连接类型要选对是“定时IO”还是“未连接”IO映射表和PLC标签数据类型要匹配。相比之下FINS/TCP更像一个“通用网口协议”不用装EDS自己做报文适合上位机开发。1.3 选型思路先问“谁和谁通信”再选协议我见过很多朋友一上来就问“欧姆龙PLC为什么连不上”其实是还没想清楚到底要完成什么通信任务。选协议不是越高级越好也不是哪个流行用哪个而是先回答三个问题谁发起通信通信对象是什么设备数据量多大实时性要求多高如果PLC和触摸屏通信HostLink永远是稳妥选择触摸屏组态里直接选欧姆龙HostLink驱动填单元号不用写PLC程序。如果PLC和PC上位机、机器人控制柜通信我会优先用FINS/TCP因为它不需要在PLC里组态数据标签直接读CIO、DM地址和三菱的MC协议一个思路。如果PLC要读一堆变频器、仪表、传感器那基本就是Modbus-RTU轮询走485总线注意终端电阻和从站地址。PLC和PLC之间则另说。同品牌欧姆龙之间可以用FINS/UDP或者FINS/TCP也可以用EtherNet/IP标签数据链接后者更适合周期性同步生产数据。跨品牌的话西门子和欧姆龙之间经常用PROFINET或者EtherNet/IP网关这时EtherNet/IP反而是通用语言。想清楚“谁和谁通信”再回头查协议手册效率会高很多。2. 通信失败我踩过的坑大多集中在这几个地方通信协议本身并不难难的是工程现场一堆变量叠加。我这些年遇到的“死活连不上”九成问题出在物理层参数、FINS地址字段、PLC侧设置这几类。把这几类坑记牢排查速度能快一倍。2.1 电缆与串口参数通信第一关是物理层不是协议先说说最容易被忽视的RS232和RS485接线。CP1H自带的RS232C口是标准DB9母头但很多调试线是“公母转接头”组装出来的针脚定义对不上就完蛋。外设口更是特殊它不是标准DB9需要欧姆龙专用编程电缆或者按外设口针脚定义自己做线。现场没有示波器的话接线错了根本看不出来只能拿万用表量。RS485接线则要注意A/B标号混乱。不同厂家对A/B、D/D-的定义可能刚好相反同样的线接A家设备能通接B家设备就乱码。我在调485时习惯先看对方设备的说明书标号再统一用“同名相接”原则而不是凭经验。另外485总线两端要接终端电阻尤其距离超过几十米、波特率又高的时候没有终端电阻会出现偶发通信错误和CRC校验失败。串口参数更是重灾区。欧姆龙HostLink默认是9600、7位数据、偶校验、2停止位也就是7E2。很多人上位机默认8N1就去读结果连响应都没有。还有一点PLC的串口参数不是改完立即生效改完通讯设置后需要断电重启或者让PLC重新上电否则上位机看到的还是旧参数。CP1H还容易忽略一个设置串口通信模式在PLC系统设置里必须选“HostLink”如果选成了“无协议”不管上位机发什么命令PLC都不会回。2.2 FINS三号缺一不可网络号、节点号、单元号FINS报文里有一串地址信息DNA目标网络号、DA1目标节点号、DA2目标单元号还有对应的源地址SNA、SA1、SA2。这里最容易犯的错就是只改IP地址没有改报文里的目标节点号。我见过一个实际案例上位机程序里IP设对了PLC的IP也Ping通了但FINS指令一直超时。查了半天发现FINS报文里的DA1还是默认值1而PLC的FINS节点号被设成了10。PLC收到帧后一看目标节点号不是自己直接丢弃根本不回。FINS节点号不等同于IP地址它是FINS层独立的编号。以太网模块或内置网口的FINS节点号需要单独去设置在CX-Programmer的IO表、或者Sysmac Studio的网络配置里能看到。同样容易错的是单元号DA2CPU单元号一般为0。如果有多CPU系统或扩展通信单元DA2要填对应单元号不填0就找不到目标。建议调任何FINS通信前先把三号写在一张纸上网络号填多少、节点号填多少、单元号填多少然后再对照报文逐字节核对能省下大量时间。2.3 FINS/TCP与FINS/UDP各自藏着不一样的坑FINS/TCP和FINS/UDP虽然命令格式一样但坑各有各的坑。FINS/UDP的坑在对端口的理解和防火墙。UDP不需要建立连接但要保证上位机发送请求的端口和接收应答的端口一致。部分PLC型号会把应答发到请求IP加固定端口而不是原样发回源端口。我建议先用网络调试助手抓包看PLC是否回了包、回包发到哪个端口再决定上位机绑定哪个端口监听。防火墙也是老问题Windows防火墙默认可能拦截UDP 9600调完记得加白名单。FINS/TCP的坑则在连接建立和连接数限制。FINS/TCP不是连上TCP就能直接发FINS命令要先做节点地址协商。很多开源库会自动处理这个协商但如果你自己写socket就要先搞清楚协议PLC会分配一个FINS节点号给上位机协商成功后才能正常收发。另外FINS/TCP连接数有上限比如NJ系列某些型号默认只允许几个并发连接调试时开了一堆上位机软件不关新连接可能直接被拒绝。遇到连接不上的情况把其他上位机断开再试常常就好了。2.4 PLC程序侧被忽略的三个晴雨表设置很多通信问题不在上位机而在PLC侧。我归纳了三个最常被忽略的设置项顺手记下来能少踩很多坑。第一是串口协议模式。CP1H和CJ系列串口默认可能是“无协议”模式只能通过串口指令功能收发自定义数据不能响应HostLink或Modbus命令。必须进PLC系统设置把端口模式改成HostLink或Modbus-RTU下载后重启生效。第二是Modbus从站的数据映射。如果欧姆龙PLC做Modbus-RTU从站不是把CIO/DM随便暴露出去就行需要在设置里选择映射区。默认映射关系往往是从40001开始对应一段连续保持寄存器区而这和PLC内部地址不一定对得上。做从站前先查清楚映射表。第三是EtherNet/IP标签刷新。NJ/NX上用标签通信时如果标签的数据类型和上位机里定义的不一致会出现数据错位或者读取失败。此外如果PLC的IO周期设置得很短而上位机RPI周期设置得很长两边数据刷新不同步会出现“读到的数据跳变”的假象。3. 走通一条完整链路从接线到上位机读到DM区数值光讲概念不够我拿一套最小可复现的配置把从物理连接到成功读数据的完整步骤走一遍。这套路我只要换现场就会重复一遍几乎可以复制到任何欧姆龙PLC通信场景。3.1 硬件准备与连接先把电缆和参数固定下来这里以最经典的CP1H-XA40DR-A为例PLC自带一个外设口和一个RS232C口。我要用串口走HostLink所以把USB转RS232调试线连到PLC的RS232C口。注意不要插到外设口上外设口需要专用编程电缆普通DB9线直接插上去针脚定义完全不同。串口参数按HostLink默认来96007位数据偶校验2停止位单元号0。用串口调试助手打开COM口设置好参数发送区勾选“发送新行”或手动在帧尾加回车。有的调试工具默认以文本模式发送如果FCS计算出来的字符是十六进制要确保发送的是ASCII字符而不是十六进制字节。我在现场经常遇到一个问题“PLC串口助手发了命令没反应”。先确认三件事PLC供电是否正常PLC有没有处于运行或监视模式编程模式下HostLink也能通但个别功能受限串口设置有没有下载并重启。如果都正常那问题大概率在命令格式上。3.2 手工发送一条HostLink命令算FCS校验再发我们来读DM区D1这一个字的当前值。HostLink读DM区命令格式00RD00010002xx*拆开看固定起始符00是单元号默认0号固定两位RD是Read命令读内存区00是内存区代码00代表DM区0001是起始地址D1就是地址0001注意要写成4位十六进制0002是读取字数这里读1个字就够了但HostLink按字读取写0001表示读D1这一个字xx是FCS校验码两个字符*是结束符后面跟回车FCS的计算方法是对后面的所有字符逐字做异或直到*之前。我们算一下字符串“00RD00010001”起始地址改成0001读取字数0001字符依次为0、0、R、D、0、0、0、1、0、0、0、1。 对应ASCII码逐字节异或 0x30 XOR 0x30 0x00 0x00 XOR 0x52 0x52 0x52 XOR 0x44 0x16 0x16 XOR 0x30 0x26 0x26 XOR 0x30 0x16 0x16 XOR 0x30 0x26 0x26 XOR 0x31 0x17 0x17 XOR 0x30 0x27 0x27 XOR 0x30 0x17 0x17 XOR 0x31 0x26最后结果是0x26即ASCII字符“26”。所以完整命令是00RD0001000126*发送后PLC正常情况下会回复类似00RD000100010026*这个帧里“0001”是读的字数“0026”是数据表示D1当前值是0x0026。如果你看到响应代码不是0000而是其他值说明命令格式有问题或地址超出范围。顺带提一句地址换算特别容易错。读D100时起始地址是0064而不是0100因为D后编号是十进制而地址字段是十六进制。读W100也一样W后的编号要转十六进制再填入地址。这个坑我在现场见过不止一次上位机程序读出来全是对的就是地址不对数据全是0。3.3 用Python走通FINS/TCP一个可用的最小例子串口HostLink适合验证和简单上位机真正做项目时我更多用FINS/TCP。这里给出一个极简Python示例用来读取CP1H或CJ/NJ系列的DM区数据。import socket import struct PLC_IP 192.168.1.10 PLC_PORT 9600 MY_NODE 0x01 # 上位机FINS节点号需和PLC侧不冲突 PLC_NODE 0x0A # PLC的FINS节点号需查PLC配置 def send_fins_tcp(memory_code: int, start_addr: int, count: int): # 1. 建立TCP连接 s socket.create_connection((PLC_IP, PLC_PORT), timeout3) # 2. FINS/TCP协议头 FINS报文 # FINS/TCP头: FINS(4字节) 长度(4字节) 命令(2字节) 错误码(2字节) 节点号(1字节) fins_tcp_header bFINS struct.pack(I, 26) b\x00\x00\x00\x00 bytes([MY_NODE]) # 4. FINS命令读内存区 # 发送端信息忽略ICF0x80, RSV0x00, GCT0x02, DNA0x00, DA1PLC_NODE, DA20x00 # 源端信息SNA0x00, SA1MY_NODE, SA20x00, SID0x00 # 命令码0101为读内存区 # 内存区代码DM0x82起始地址2字节读取字数2字节 fins bytes([ 0x80, 0x00, 0x02, 0x00, PLC_NODE, 0x00, 0x00, MY_NODE, 0x00, 0x00, 0x01, 0x01, memory_code, (start_addr 8) 0xFF, start_addr 0xFF, (count 8) 0xFF, count 0xFF, ]) s.sendall(fins_tcp_header fins) # 5. 读取响应 resp s.recv(1024) s.close() # 6. 解析响应里第19字节起是命令码第21-22字节是响应码后面是数据 res_code struct.unpack(H, resp[21:23])[0] if res_code ! 0x0000: raise RuntimeError(fPLC返回错误码: 0x{res_code:04X}) # 每个字2字节高字节在前 words [] for i in range(count): val struct.unpack(H, resp[23 i * 2 : 25 i * 2])[0] words.append(val) return words if __name__ __main__: data send_fins_tcp(0x82, 100, 10) # 读DM区D100开始的10个字 print(data)这段代码有个容易迷的地方FINS报文里的起始地址在“response解析时”要按帧实际结构来数。我写的时候故意留了个注释“第19字节起”的约定实际不同固件版本可能因为FINS/TCP头长度不同而有偏移所以最好先用抓包工具确认一次。读DM区用内存代码0x82读CIO区用0x80读W区用0x31读HR区用0x32。这些代码值建议抄在小本子上。这个例子没有处理FINS/TCP的握手协商实际用有些型号的PLC会直接拒绝不协商的请求。如果要更严谨需要先发送节点地址设定命令命令码0x00子命令0x00从响应里拿到PLC分配给你这个连接的FINS节点号再用这个节点号做后续通信。入门阶段可以先在PLC网口参数里关闭“FINS节点地址协商要求”但正式项目不建议关。3.4 地址换算和内存区代码一张备忘对照不同系列的欧姆龙PLC内存区名称和FINS代码基本上通用但还是有细微差别。我做了一张自己常用的对照表内存区常用名称FINS内存代码说明CIO区CIO0x80最常见的IO和内部继电器区WR区W0x31工作继电器区掉电不保持HR区H0x32保持继电器区掉电保持DM区D0x82数据存储器按字访问EM区E0x98扩展数据存储器部分系列才有定时器TIM0x89读取定时器当前值或状态读CIO区和DM区最常用但地址换算要注意CIO区通常按“通道”访问比如CIO100就是0x0064。DM区按D后的编号直接换算十六进制。WR区也是W0就是0x0000W100就是0x0064。很多PLC程序里看到的“CIO 10.00”指的是通道10的第00位如果你只读一个字那就是通道10的整个值。关于地址换算我的实际经验是先在PLC编程软件里看到对应变量的绝对地址再换算十六进制。别靠心算现场一旦算错排查时间成倍增加。写个脚本或者用计算器转一下稳得多。3.5 响应超时与并发轮询的经验值上位机通信超时时间设置多少合适我通常这样设串口HostLink轮询周期至少100ms以上超时500ms到1秒FINS/TCP超时200到500msEtherNet/IP隐式IO的RPI周期从10ms到100ms看需求。不是说越快越好太快会把老型号PLC的通信模块拖死导致偶发超时。轮询多个站时我习惯把读取数据量合并成大帧。比如同一台PLC要读20个温度值不要发20条单字读取命令而是发一条连续读取20个字的FINS命令一次往返全部拿回效率能提高一个数量级。Modbus-RTU轮询变频器也是一样尽量连续读寄存器块而不是逐寄存器读。4. 常见问题速查一张表记住八成故障到现场排查通信问题很多时候没空从头看协议文档。我把自己多年遇到的典型问题整理成一张速查表基本覆盖了八成故障现象按表排查比较快。4.1 高频故障排查速查表故障现象可能原因处理方式串口发命令PLC完全无响应串口参数不对PLC未处于HostLink/Modbus模式确认7E2、9600、单元号0重新下载设置并重启收到响应但FCS报错FCS计算错误或发送时多加了不可见字符用串口调试助手查看发送的十六进制字节逐字符核算FCS485通信时通时断A/B反了缺终端电阻波特率过高检查接线、加120欧终端电阻、降波特率到9600FINS/TCP连不上未做节点协商连接数满防火墙拦9600先协商节点关掉其他上位机加防火墙白名单FINS/UDP收不到应答应答端口不一致防火墙拦截UDP 9600抓包看应答端口绑定对应端口监听读数据全0或全F地址换算错误起始地址填了10进制D100填0064W100填0064CIO100填0064读个别地址数据乱跳IO周期和上位机读取周期不同步调小RPI或增大读取间隔保持数据一致快照Modbus读到的寄存器值对应关系错地址偏移没有处理好40001对应协议地址0PLC组态里填0不能填40001EtherNet/IP设备无法识别没装EDS文件或型号版本不匹配安装对应版本EDS文件检查连接类型通信超时频繁轮询周期太短或PLC扫描周期太长合并批量读命令轮询周期放到100ms以上4.2 Modbus的40001偏移陷阱Modbus的保持寄存器地址经常被写成40001、40002这样的“PLC地址”但协议报文里的寄存器寻址是从0开始的。也就是说设备手册上写“保持寄存器40001对应参数X”你发Modbus报文时地址其实要填0。在欧姆龙CP1H的Modbus-RTU简易主站指令里地址参数很多资料直接要求填“寄存器编号减1”也就是40001填0、40002填1。这个偏移问题在接变频器时特别明显。有次我帮朋友调一台国产变频器Modbus Poll里填40001读出来转速对不上填0才对。因为变频器从站内部把40001对应到第一个保持寄存器也就是协议地址0而Modbus Poll这种上位机软件有时会自动帮你做地址转换有时不会必须自己搞清楚软件是否帮你减了1。欧姆龙PLC做Modbus主站也一样。如果用功能码03读保持寄存器目的起始地址要填协议地址如果用触摸屏自带的Modbus驱动去读触摸屏软件通常让你填40001它会自动处理。结论就是先确认通信对象是PLC还是第三方设备再确认填的是协议地址还是PLC地址。这个确认动作花30秒能省至少半天排查时间。4.3 扫描周期、IO刷新与通信负载还有一个容易被忽略的场景PLC扫描周期对通信的影响。有个项目里NJ的EtherNet/IP标签数据链接总是延迟很大点动按钮要隔几百毫秒才反应。查到最后发现PLC程序里有一个很长的循环扫描周期被拖到80ms而EtherNet/IP的RPI设的是10ms两边数据刷新频率严重不匹配。把循环优化掉之后扫描周期降到2ms通信延迟立刻正常。上位机读数据的频率也要考虑PLC的通信负载。老型号串口PLC在9600波特率下一条HostLink命令带校验和回车大概是几十个字节传输时间就得几十毫秒。如果上位机每10ms发一条命令数据根本来不及回通信就会堆积、超时。稳妥的做法是每50ms到100ms轮询一次或者用批量读取代替零散读取。4.4 调试工具的实用心得调欧姆龙通信我包里永远带着三样工具串口调试助手、网络调试助手、Modbus Poll/Modbus Slave。串口调试助手调HostLink和Modbus-RTU特别好用注意看十六进制发送和ASCII发送的区别。HostLink是ASCII协议直接用文本模式发送但要确保FCS字符是ASCII字符不是十六进制字节。Modbus-RTU是二进制协议必须用十六进制模式发送一个字节一个字节拼完全帧。网络调试助手用来抓FINS/TCP和FINS/UDP的原始报文能看到PLC到底回没回、回的内容是什么、发到哪个端口了。很多“PLC没反应”的真相是“上位机没接对端口”抓包一眼就看出来。Modbus Poll是调从站设备的神器支持Modbus-RTU和Modbus-TCP可以直接读寄存器验证地址和功能码。想确认欧姆龙PLC做Modbus从站是否正常就用Modbus Poll去连PLC比自己写测试程序快得多。最后再分享一个小技巧调FINS通信时无论协议多复杂先从最简单的“读PLC型号”命令开始验证。这个命令不带数据地址只要地址信息对PLC一定会回一个设备型号字符串。如果连这条都超时说明物理层或FINS三号还有问题如果这条通了再继续读内存区。这种“先小步验证再扩大范围”的调试思路比拿着完整命令一头扎进去要省时间得多。我个人在实际项目里还有一个习惯每种通信方式第一次调通后把调好的接线定义、串口参数、地址映射、上层代码片段存成一个项目模板。下次接到同类任务直接套模板改地址就行。欧姆龙的通信协议种类多但万变不离其宗物理层、链路层、应用层一层一层排查基本没有解不开的死结。
返回列表