ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

OMRON串口通讯实例:Host Link与FINS从接线到稳定读写

OMRON串口通讯实例:Host Link与FINS从接线到稳定读写

简介:这份资源是面向工业自动化工程师、PLC初学者及上位机开发人员的OMRON串口通讯实战示例,围绕RS-232、RS-485等串行标准,演示如何与OMRON PLC建立稳定数据交互,解决协议选择、参数配置与指令收发等常见问题。压缩包共38个文件,约1.64MB,以C#源码(14个cs)和窗体资源(4个resx)为主体,辅以可执行程序、调试符号、解决方案与说明文本,构成一套可直接运行的通讯测试工程。资源中提供了完整的通讯程序框架,涵盖串口参数设置、请求发送、响应解析与错误处理等关键环节,并配有通讯实例说明文档,便于读者对照代码理解ASCII、Modbus RTU等协议的实际落地方式。目前已有3657人学习下载,适合希望快速搭建OMRON PLC串口通讯原型、排查通讯异常或补充上位机开发经验的读者参考借鉴。

1. OMRON串口通讯实例:从一根线到稳定读写,中间隔着多少坑

车间里一台老设备还在跑,PLC 是 OMRON 的,上位机要拿它的产量和报警数据。你翻出通讯手册,看到 Host Link、FINS、无协议几种模式,心里大概有数,但真把线接上、命令发出去,回过来的却是一串乱码或者干脆没响应。OMRON 串口通讯实例这件事,难点从来不在协议本身,而在于接线、参数、时序、校验这四件事里任何一件对不上,整条链路就是黑匣子。这篇笔记面向两类人:一类是第一次用 OMRON PLC 做串口采集、想照着跑通最小闭环的工程师;另一类是已经能通、但偶尔丢包或超时、想搞清楚边界在哪的熟手。我会按「先立住原理、再动手复现、最后讲坑」的顺序,把 Host Link 和 FINS 串口这两条最常见路径拆开讲,参数怎么设、命令怎么拼、失败时看什么,都落到可抄作业的粒度。

2. 先把协议选对:Host Link、FINS 串口、无协议到底用哪个

选错协议是新手翻车的第一大来源。OMRON 的串口通讯不是只有一种玩法,上位机主动发命令、PLC 被动应答,这套主从结构下有三条主流路径,适用场景差别很大。先把它们摆清楚,再决定你的实例该怎么写。

2.1 三种模式的本质区别与选型依据

Host Link 是 OMRON 最早、最通用的一套上位机命令协议,走 ASCII 文本帧,命令形如@00RD00000001这种可读字符串,PLC 回@00RD00...加数据和 FCS 校验。它的好处是调试直观,串口助手直接敲命令就能看到回应,坏处是 ASCII 传输效率低,单次读写点数有限,适合小批量、低频采集。

FINS 串口是把 FINS 命令帧套在串口上跑,帧是二进制或 ASCII 可选,命令结构比 Host Link 复杂,但寻址能力强,能跨网络、能访问 DM/CIO/EM 等各区域,适合数据量大、需要结构化访问的场合。很多中高端机型同时支持 Host Link 和 FINS,低端机型可能只给 Host Link。

无协议模式则是 PLC 侧用指令自己收发原始字节,协议由你自定义,灵活但上位机要自己处理全部帧格式和校验,工作量最大,一般只在对接第三方设备、协议已经定死时才用。

模式帧格式调试难度单次数据量典型场景
Host LinkASCII 文本低小产量计数、状态读取
FINS 串口二进制/ASCII中大多区域批量采集
无协议自定义高任意对接既定第三方协议

选型建议很直接:能上 Host Link 就先上 Host Link,把链路跑通、把参数摸清,再考虑换 FINS 提效率。不要一上来就啃 FINS 帧结构,那样连串口通没通都验证不了。

2.2 物理层与串口参数:9600 8N1 不是随便写的

OMRON PLC 的串口默认参数在多数机型上是 9600bps、8 数据位、无校验、1 停止位,也就是常说的 8N1。这个默认值写在 PLC 的 DM 区设置里,不同机型对应的 DM 地址不一样,常见的是 DM6645 到 DM6648 这一段控制通讯口参数。如果你改了 PLC 侧参数,上位机必须同步改,否则收到的就是一堆错位字节。

接线是另一个高频翻车点。RS-232 和 RS-485 的接法完全不同:232 是点对点,TX/RX 要交叉,GND 必须共地;485 是差分总线,A 接 A、B 接 B,终端电阻在长距离时不能省。很多人用 USB 转 232 的线,结果 PLC 那头是 485 口,物理层就对不上,软件怎么调都没用。

提示:先用串口助手以 8N1、9600 手动发一条 Host Link 读命令,能收到正确回应再写代码。这一步能排掉八成「代码没问题但就是不通」的情况。

2.3 最小可复现的 Host Link 读命令

Host Link 命令帧结构是:起始符@+ 单元号(2 位)+ 命令码(2 位)+ 数据区 + FCS 校验(2 位)+ 结束符*+ CR。以读 DM 区为例,读 DM0000 开始的 1 个字:

# Host Link 读 DM 区最小示例 # 帧: @ + 单元号 + RD + 起始地址(4位) + 点数(4位) + FCS + * + CR unit = "00" # PLC 单元号,默认 00 cmd = "RD" # 读 DM 区 addr = "0000" # 起始地址 DM0000 count = "0001" # 读 1 个字 body = unit + cmd + addr + count # FCS 计算:对 body 所有字节做异或 fcs = 0 for ch in body: fcs ^= ord(ch) frame = "@" + body + format(fcs, "02X") + "*\r" print(repr(frame)) # 输出: '@00RD00000001' + FCS + '*\r'

逻辑说明:body是不含起始符、FCS、结束符的纯命令体,FCS 是对它逐字节异或得到的两位十六进制。参数上,unit要和 PLC 的单元号设置一致,多台 PLC 挂在同一 485 总线上时靠它区分;addr是 4 位十六进制地址,count是 4 位十六进制点数,读多个字时改count即可。发出去后 PLC 回帧格式类似,数据区就是你要的值,回帧的 FCS 也要校验一遍,不通过说明传输有误。

3. 用 Python 把 Host Link 读写跑通:串口配置与帧收发

原理清楚之后,落到代码。这一章给一个能直接跑的 Python 实例,覆盖串口打开、命令拼装、发送、接收、FCS 校验、超时处理。用 pyserial 这个库,跨平台,Windows 和 Linux 都能用。

3.1 串口打开与参数配置

import serial import time # 打开串口,参数必须与 PLC 侧一致 ser = serial.Serial( port="COM3", # Windows 用 COMx,Linux 用 /dev/ttyUSB0 baudrate=9600, # 与 PLC DM 区设置一致 bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=1.0 # 读超时 1 秒,避免死等 ) if not ser.is_open: ser.open()

参数说明:timeout是关键,设成 None 会一直阻塞,设太小又会把正常慢响应误判为超时。OMRON PLC 的响应时间通常在几十毫秒量级,1 秒足够宽松。baudrate如果 PLC 侧改过,这里必须同步,改完记得重新打开串口,热改波特率在部分 USB 转串口芯片上不生效。

3.2 命令拼装与 FCS 校验函数

def build_hostlink(unit, cmd, addr, count): """拼装 Host Link 命令帧""" body = f"{unit}{cmd}{addr}{count}" fcs = 0 for ch in body: fcs ^= ord(ch) return f"@{body}{fcs:02X}*\r" def check_fcs(resp): """校验 PLC 回帧的 FCS""" if not resp.startswith("@") or "*" not in resp: return False body = resp[1:resp.index("*")] recv_fcs = body[-2:] calc = 0 for ch in body[:-2]: calc ^= ord(ch) return format(calc, "02X") == recv_fcs.upper()

逻辑说明:build_hostlink把单元号、命令码、地址、点数拼成命令体,算 FCS 后加上起始符和结束符。check_fcs从回帧里剥出命令体,取最后两位作为收到的 FCS,再对前面部分重算比对。这两个函数是整个通讯的核心,写错一个字节,PLC 要么不回,要么回错误码。

3.3 发送、接收与超时处理

def read_dm(ser, addr, count): frame = build_hostlink("00", "RD", addr, count) ser.reset_input_buffer() # 清掉残留数据,避免串帧 ser.write(frame.encode("ascii")) time.sleep(0.05) # 给 PLC 一点响应时间 resp = ser.read(64).decode("ascii", errors="ignore") if not resp: raise TimeoutError("PLC 无响应,检查接线与参数") if not check_fcs(resp): raise ValueError(f"FCS 校验失败: {resp!r}") # 回帧格式: @00RD00 + 数据 + FCS + * data = resp[7:-4] return data # 使用 try: val = read_dm(ser, "0000", "0001") print("DM0000 =", val) except Exception as e: print("读取失败:", e)

逻辑说明:reset_input_buffer在每次发送前清空接收缓冲,这是防止上一帧残留导致串帧的关键一步,很多人丢包就是因为省了这行。time.sleep(0.05)是给 PLC 的处理窗口,太短会读到空,太长影响吞吐。回帧的数据区位置取决于命令码,RD 命令下数据从第 7 个字符开始,到 FCS 前结束,实际项目里建议按命令码分别解析,不要写死偏移。

注意:如果连续读多个地址,不要每条命令都 sleep 固定值,改成读回帧长度判断更稳。固定 sleep 在波特率变化或 PLC 负载高时会失效。

3.4 写命令与读命令的差异

写 DM 区用WD命令,帧结构多一段写入数据:

def write_dm(ser, addr, value_hex): """写单个 DM 字,value_hex 为 4 位十六进制""" body = f"00WD{addr}{value_hex}" fcs = 0 for ch in body: fcs ^= ord(ch) frame = f"@{body}{fcs:02X}*\r" ser.reset_input_buffer() ser.write(frame.encode("ascii")) time.sleep(0.05) resp = ser.read(32).decode("ascii", errors="ignore") # 写成功回帧以 @00WD00 开头 return resp.startswith("@00WD00")

写命令的回帧只表示「收到并执行」,不返回写入值,所以判断成功看回帧的命令码和结束码是否为00。写操作比读操作更容易受时序影响,连续写多个地址时建议每条之间留 20 到 50 毫秒,太快 PLC 可能丢命令。

4. FINS 串口实例:什么时候该从 Host Link 换过去

Host Link 跑通之后,如果发现单次读点数不够、或者要访问多个存储区、或者要跨网段,就该考虑 FINS。FINS 串口不是另起炉灶,而是把 FINS 帧套在串口链路上,理解它的结构能让你在数据量上来之后不慌。

4.1 FINS 帧结构与 Host Link 的本质差异

FINS 帧由 ICF、RSV、GCT、DNA、DA1、DA2、SNA、SA1、SA2、SID 这十个字节的头部,加上命令码和参数区组成。头部里 DA1/DA2 是目标节点地址,SA1/SA2 是源节点地址,串口直连时通常都填 00。命令码是两字节,比如读 CIO 区是0101,读 DM 区是0101加不同区域码。

和 Host Link 最大的差异在于:Host Link 是 ASCII 文本、命令即语义;FINS 是二进制为主、头部字段多、需要理解节点寻址。好处是 FINS 一次能读的字节数远大于 Host Link,且能访问所有存储区,坏处是调试时不能靠串口助手直接敲,得先算好二进制帧。

4.2 用 Python 构造 FINS 串口读帧

import struct def build_fins_read_dm(start_addr, count): """构造 FINS 读 DM 区命令帧(串口直连,节点地址全 0)""" # FINS 头部 10 字节 icf, rsv, gct = 0x80, 0x00, 0x02 dna, da1, da2 = 0x00, 0x00, 0x00 sna, sa1, sa2 = 0x00, 0x00, 0x00 sid = 0x00 header = struct.pack("BBBBBBBBBB", icf, rsv, gct, dna, da1, da2, sna, sa1, sa2, sid) # 命令码 0101 = 读,区域码 82 = DM 区 command = struct.pack(">HH", 0x0101, 0x0082) # 起始地址 2 字节 + 点数 2 字节 params = struct.pack(">HH", start_addr, count) return header + command + params

逻辑说明:icf=0x80表示需要响应,gct=0x02是网关计数,串口直连时固定值。command里0x0101是内存区读命令,0x0082是 DM 区区域码,读 CIO 区要换成0x00B0之类。params是起始地址和字数,都是大端两字节。整个帧发出去后,PLC 回的帧头部相同,命令码后跟结束码和读到的数据。

4.3 FINS 回帧解析与结束码判断

def parse_fins_response(resp): """解析 FINS 回帧,返回 (结束码, 数据)""" if len(resp) < 14: raise ValueError("回帧过短") # 头部 10 字节 + 命令码 2 字节 + 结束码 2 字节 end_code = struct.unpack(">H", resp[12:14])[0] data = resp[14:] return end_code, data # 结束码 0x0000 表示正常,非零要查手册

结束码是 FINS 排错的核心,0x0000正常,0x0101之类是命令不支持,0x0201是区域码错误,0x0301是地址越界。拿到非零结束码不要瞎猜,对照 FINS 命令手册的结束码表逐条查,比反复改代码快得多。

4.4 串口 FINS 与 Host Link 的混用策略

实际项目里不必二选一。低频状态量用 Host Link,直观好维护;高频批量数据用 FINS,效率高。两条路径可以共用同一个物理串口,只要命令帧不交叉、每次收发完整,PLC 不会混淆。但要注意,混用时单元号和节点地址要理清,Host Link 的单元号和 FINS 的 DA1 不是一回事,别把两套寻址搞混。

5. 避坑与排查:OMRON 串口通讯最常见的五类翻车

这一章是血泪经验集中区。下面五条都是实际调试中反复出现的,每条按现象、原因、解决写,照着排查能省大量时间。

5.1 现象:发命令完全无响应

原因:物理层没通,或者串口参数与 PLC 不一致。最常见的是 USB 转串口线的电平不对,或者 TX/RX 没交叉,或者 PLC 侧通讯口被设成了无协议模式而不是 Host Link。

解决:先用万用表确认线序,再用串口助手以 8N1、9600 发一条最简单的 Host Link 读命令。如果串口助手能通、代码不通,问题在代码;如果串口助手也不通,问题在物理层或 PLC 参数。PLC 侧要确认 DM 区里通讯模式设的是 Host Link 而不是无协议。

5.2 现象:能收到回应但 FCS 校验失败

原因:回帧里混入了上一帧的残留数据,或者读取长度不对导致截断。串口是流式的,没有帧边界,读的时候如果没清缓冲,很容易把两帧粘在一起。

解决:每次发送前reset_input_buffer,接收时按回帧的结束符*和 CR 判断边界,不要用固定长度读。如果波特率较高,考虑用read_until(b"*\r")代替read(n)。

5.3 现象:偶尔丢包,重发就好

原因:发送间隔太短,PLC 还没处理完上一条命令就来了下一条。或者上位机 sleep 时间固定,但 PLC 负载波动时响应变慢。

解决:把固定 sleep 改成基于回帧的判断,收到完整回帧再发下一条。连续写操作时每条之间留足间隔,必要时加重试机制,重试前先清缓冲。

5.4 现象:读到的数据错位或值不对

原因:地址算错,或者区域码用错,或者数据解析偏移写死。Host Link 的地址是十六进制,但有些手册里给的是十进制,换算错一位整个数据就偏了。

解决:先用已知值验证,比如往 DM0000 写一个固定值再读回来,确认地址和解析都对。FINS 的区域码要对照手册,DM 区是0x82,CIO 区是0xB0,别混。

5.5 现象:多台 PLC 挂同一总线时互相干扰

原因:单元号或节点地址重复,或者 485 总线终端电阻没接、线太长导致信号反射。

解决:每台 PLC 的单元号必须唯一,Host Link 靠单元号区分,FINS 靠 DA1 区分。485 总线超过一定长度要在两端接终端电阻,线材用双绞屏蔽线,屏蔽层单端接地。

6. 进阶技巧:把串口通讯做成可维护的采集模块

跑通单条命令只是起点,真正上线要的是稳定、可观测、可恢复。这一章讲几个我实际项目里会用的技巧,帮你把实例升级成模块。

第一个技巧是命令队列加状态机。不要在主循环里直接发命令,而是把读任务放进队列,一个 worker 串行处理,每条命令带超时和重试次数。这样即使某条命令失败,也不会阻塞整个采集。状态机记录当前处于「发送中」「等待响应」「解析中」哪个阶段,出问题时能直接定位。

第二个技巧是心跳与断线重连。串口线松动、USB 转接器掉驱动都会导致链路断开,代码里要定期发一条轻量读命令当心跳,连续失败就关闭串口重新打开。重连逻辑要带退避,不要疯狂重试把 CPU 打满。

第三个技巧是原始帧日志。把每次发送和接收的原始字节按十六进制记下来,出问题时翻日志比现场复现快得多。日志要带时间戳和方向,方便对齐 PLC 侧的事件。

技巧解决的问题实现要点
命令队列+状态机单条失败阻塞全局串行 worker,超时重试
心跳+重连链路意外断开定期轻量读,退避重连
原始帧日志现场难复现十六进制+时间戳+方向

最后一个习惯:任何参数改动都先记下来再改。波特率、单元号、DM 设置地址这些,改之前截图或抄一遍,改完不通能快速回退。我吃过一次亏,把 PLC 通讯口参数改了没记,结果调了一下午才发现是参数问题,有后悔药的话就是那份记录。串口通讯这东西,玄学感往往来自没记录的变化,把变量控制住,它就是个确定性的工程问题。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进