ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

欧姆龙PLC串口通讯实战:FINS协议帧结构与DM区读写

欧姆龙PLC串口通讯实战:FINS协议帧结构与DM区读写 简介面向工业自动化工程师、PLC调试人员及设备维护人员欧姆龙PLC串口FINS命令协议通讯演示文档聚焦PC与CJ2M系列PLC的串口通信解决命令帧构造与数据读写难题。文档共1个doc文件压缩包大小321KB内容精炼易读。已有155人学习下载。内容从实验配置入手详细讲解PC直连PLC时的命令帧格式涵盖单元号、头代码、响应等待时间、ICF、DA2、SA2、SID等关键字段并给出DM区与W工作区的读写实例附具体Hex命令和返回码解析。同时总结了存储区代码对照、尽量一次读写连续通道等实用技巧帮助读者快速掌握FINS协议串口通讯的帧结构、调试方法与排错要点。1. 欧姆龙 PLC 串口调试的第一关把 FINS 当成地址而不是协议接手一台欧姆龙 CP1H编程线不在手边只有一根 USB 转串口线和一份写着“串口 FINS 演示”的文档这种情况在产线调试里很常见。很多人第一反应是按 Modbus 的思路找寄存器偏移量结果发现 FINS 根本没有偏移量这回事它自带一套网络号、节点号、单元号组成的四层寻址。FINS 是欧姆龙贯穿以太网和串口的应用层协议串口上的帧就是把以太网帧剥掉 IP 层之后剩下的那部分。这套方案不需要额外装驱动库用任何能收发字节的编程语言都能直接读写 PLC 的 DM 区、CIO 区。下面这套做法面向的是设备数据采集、上位机对接和售后诊断目标是让 PLC 的 D 区像一块可以远程读写的内存卡一样被访问。2. FINS 串口帧结构与地址模型先看懂一帧命令2.1 为什么不直接用 Host Link 或 Modbus欧姆龙早期串口通讯走的是 Host Link命令是 ASCII 字符串比如读写 DM 区用 RD/WR 开头的指令。Host Link 的问题在于它只能跑在串口上无法平滑迁移到以太网。Modbus 在部分欧姆龙 PLC 上以选件或功能码形式存在比如 CP1W-MODBUS 单元但 Modbus 的寄存器模型和欧姆龙内部的内存区格式之间有一层映射开销。FINS 的优势是命令码统一。读内存区是 0101写内存区是 0102无论底下是串口、以太网 FINS/TCP 还是 Controller Link这条命令的格式都一样。CX-Programmer 和 Sysmac Studio 里把串口通信方式设为 FINS 后PLC 串口收到的就是这种帧。欧姆龙协议宏功能本质也是把 FINS 命令模板塞给 PLC 串口让它作为主站去轮询第三方设备而本文要做的正好相反上位机作为主站主动发 FINS 帧给 PLC。所以先把裸帧格式吃透后面无论用协议宏、C# 库还是 Python 脚本都只是在拼这一串字节。2.2 一帧 FINS 命令按字节拆开看FINS 串口帧由三部分组成ASCII 开头的FINS四个字节、FINS 报文本体包括头和命令、结尾的 FCS 校验字符和结束符。网上很多简化图把FINS头直接省略然后让人对着文档里的“ICF 0x80”找位置结果实际发出去了 PLC 完全没反应。完整的一帧长这样字段长度字节典型值说明FINS446 49 4E 53ASCII 字符 FINS是串口帧的识别头ICF10x80命令帧为 0x80响应帧为 0x00RSV10x00保留字段固定 0GCT10x02允许网关转发次数未使用网关时固定 2DNA10x00目标网络号本地网络填 0DA110x00目标节点号即 PLC 的节点号DA210x00目标单元号CPU 单元填 0SNA10x00源网络号上位机在本地网络填 0SA110x01源节点号即上位机自己SA210x00源单元号上位机填 0SID10x00服务号用于匹配请求与响应命令码20x01010101 读内存区0102 写内存区数据N按命令变化地址、长度、写入值等FCS2例如2A从 ICF 到数据末尾的异或转大写 ASCII结束符22A 0D星号*加回车 CR注意这里从头到尾没有长度字段接收端是靠超时和结束符*\r判断一帧是否收完的。这和 Modbus RTU 靠字节间隙区分帧不同写接收循环时要小心。2.3 SA1/DA1 到底该怎么填有人在欧姆龙手册里看到“SA1”会以为是什么特殊地址其实它只是源节点号。FINS 寻址由四部分组成网络号、节点号、单元号外加内存区地址。DNA/DA1/DA2 描述“我要找的设备在哪”SNA/SA1/SA2 描述“我是谁”。上位机没有网络接口时SA1 通常填 1DA1 填 PLC 的节点号。部分 PLC 支持 DA1 填 0 表示“本机”但在多节点环境下这种写法很容易把帧发到错误设备。稳妥做法是在 CX-Programmer 里连上 PLC查看“节点号”设置把这个值填进 DA1。SID 随意但建议每次发送递增因为响应帧会把 SID 原样返回上位机可以拿它匹配请求防止串口上多条命令对不上号。用 Python 定义一个 FINS 命令头下面这个函数生成 FINS 报文头不含FINS四个 ASCII 字符因为 FCS 校验不覆盖它们def fins_header(sid: int 0x00, da1: int 0x00, sa1: int 0x01) - bytes: # 目标: 本地网络0, PLC节点号da1, CPU单元0 # 源: 本地网络0, 上位机节点sa1, 单元0 return bytes([ 0x80, 0x00, 0x02, # ICF, RSV, GCT 0x00, da1, 0x00, # DNA, DA1, DA2 0x00, sa1, 0x00, # SNA, SA1, SA2 sid # SID ])ICF 是 0x80 表示这是一条命令帧PLC 回给上位机的响应帧里 ICF 会是 0x00。如果上位机发出去的帧 ICF 填错方向PLC 会直接丢弃而且不会给任何错误码排查时会表现为“发出去没反应”。3. FCS 校验与完整帧拼装串口上多出来的两个字符3.1 FCS 是一次纵异或不是 CRC16FINS 串口帧的校验字段叫 FCS全称 Frame Check Sequence算法非常简单把从 ICF 开始到数据区最后一个字节为止的所有字节做异或得到一个 8 位结果再把这个结果转成两个大写 ASCII 十六进制字符放在帧尾。例如异或结果是0x2A就写入2A这两个 ASCII 字符。很多照着 Modbus 习惯来的人会在这里踩坑FINS 不用 CRC16用 CRC 算法算出来的校验位 PLC 完全不认。XOR 结果只有 8 位转成 ASCII 后占用两个字节这也就是为什么最后结束符前面永远多出两个看起来像“数据”的字符。另外FINS四个字母不参与校验异或的起点是 ICF。3.2 用 Python 写一个完整拼帧函数先写 FCS 计算再写帧拼装def _fcs(data: bytes) - str: xor_sum 0 for b in data: xor_sum ^ b return f{xor_sum:02X} def build_fins_frame(body: bytes) - bytes: # body 是从 ICF 开始的 FINS 报文本体不含 FINS 头 frame_prefix bFINS fcs _fcs(body) # 结束符是 * CR注意没有 LF return frame_prefix body fcs.encode(ascii) b*\r_fcs把异或结果格式化成两位大写十六进制build_fins_frame负责拼装。需要注意fcs.encode(ascii)会把2A这样的两个字符变成两个字节而不是一个字节。结束符用的是*\r也就是 ASCII 码0x2A 0x0D。如果误用了\r\nPLC 会把\n当成下一帧的第一个字节导致后续帧全部错位。3.3 完整拼一帧读 D100 的命令把第 2.3 节的fins_header和build_fins_frame组合起来就得到一条可发送的完整读命令def make_read_dm(addr: int, count: int 1, sid: int 0x01) - bytes: header fins_header(sidsid) # 0101 读内存区命令 # 0x89 DM区, 字单位; addr 转大端双字节; count 转大端双字节 body header bytes([0x01, 0x01]) bytes([0x89]) addr.to_bytes(2, big) count.to_bytes(2, big) return build_fins_frame(body)命令码 0101 后面跟三项内存区码0x89表示 DM 区字单位起始地址 D100 转成大端00 64读取字数0001表示读 1 个字。这里最容易错的是地址字节序D100 要写成00 64如果按小端写成64 00PLC 实际访问的是 D25600数值能读回来但是错的。发送端还需要处理一个细节FINS 串口是严格的一问一答模型。上位机发完一帧必须等响应回来才能发下一帧一帧没完整收完就发下一帧PLC 会把两帧数据混在一起解析表现为响应帧超时。4. 读写 DM 区与响应码排查4.1 用 pyserial 发送读命令并解析响应下面用一个最小脚本把第 3 节的拼帧函数跑通import serial from time import sleep ser serial.Serial( portCOM3, baudrate9600, bytesize8, parityserial.PARITY_EVEN, stopbits1, timeout0.5 ) frame make_read_dm(100, count2, sid0x01) ser.write(frame) sleep(0.1) # 等 PLC 处理不能为零 resp ser.read(256) ser.close() print(resp.hex( ))串口参数 9600 8 E 1 是欧姆龙 PLC 串口的默认配置具体要跟 PLC 侧“外围端口”设置一致。timeout 设 0.5 秒覆盖了 PLC 一个扫描周期内的响应时间。sleep(0.1)不是万能的在 Windows 上如果串口驱动的缓存延迟较大应改为循环读取直到收到*\r。响应帧的解析逻辑如下跳过最前面的FINS四个 ASCII 字节接着是 10 字节 FINS 头再往后是命令码01 01然后是 2 字节响应码def parse_read_dm_response(resp: bytes): assert resp[:4] bFINS, 帧头错误 cmd resp[14:16] end_code resp[16:18] if end_code ! b\x00\x00: print(f错误响应码: {end_code.hex()}) return None values [] data resp[18:] for i in range(0, len(data), 2): values.append(int.from_bytes(data[i:i2], big)) return valuesresp[14:16]是响应帧里的命令码位置计算方式是 4 字节FINS 10 字节 FINS 头 14 字节偏移。如果响应码不是00 00后面跟的就不是读取数据而是错误码。注意即使是错误响应帧头和结束符也是完整的。4.2 写 D100 的 0102 命令写内存区命令是 0102数据区比读命令多了一项要写入的值。下面写 2 个字到 D100def make_write_dm(addr: int, values: list, sid: int 0x02) - bytes: header fins_header(sidsid) body header bytes([0x01, 0x02]) body bytes([0x89]) body addr.to_bytes(2, big) body len(values).to_bytes(2, big) for v in values: body v.to_bytes(2, big) return build_fins_frame(body)values 里的每个元素会被转成 2 字节大端。DM 区是 16 位存储单位写入值超过 0xFFFF 会触发错误写入负数要用补码形式例如 -1 写成 0xFFFF。4.3 响应码对照表响应帧里紧跟命令码后面的两个字节是 End Code它直接告诉上位机 PLC 为什么没执行成功End Code含义常见原因00 00正常完成无01 01本地节点错误FINS 头字段格式错误ICF 或 GCT 填错01 03目标节点不存在DA1 节点号与 PLC 设置不一致01 04目标单元不存在DA2 单元号填错11 01内存区不存在内存区码填错例如 0x89 写成 0x8811 03地址或数据非法地址超范围、写入值超限、向只读区写入20 03接收数据错误FCS 校验失败、帧格式错误、结束符缺失实际调试中 20 03 出现频率最高原因多半是异或范围算错把FINS四个字节也算进去了。11 03 则多在读写超过 D 区最大地址范围时出现CP1H 的 D 区范围是 D0 到 D32767超出就会报这个码。串口调试助手在这种场景下非常好用。把make_read_dm(100)生成的那一帧十六进制字符串直接粘贴到 XCOM 或其它串口助手的发送框里点发送看返回的十六进制是否以FINS开头。先用工具手动发一帧再用程序发一帧能快速定位问题到底出在拼帧还是串口参数上。如果对帧内容没把握串口监听工具可以把上位机和 PLC 之间的原始字节流完整抓下来对比发送和接收两边的 FCS 是否一致。5. 没有 PLC 时怎么验证搭一个回环从站5.1 用 Python 模拟 FINS 串口从站手边没有 PLC 又想验证拼帧逻辑可以用另一块 USB 转串口共地连接跑一个最简单的回环从站把收到的每一帧原样发回去。这样第 4 节的读命令脚本发出去后收回来的是同一帧能验证的是帧头、FCS、结束符有没有拼错。代码如下import serial s serial.Serial(COM4, 9600, parityserial.PARITY_EVEN, timeout1) while True: chunk s.read(1) if not chunk: continue buf bytearray(chunk) while not buf.endswith(b*\r): b s.read(1) if not b: break buf.extend(b) s.write(buf) # 原样回显 print(buf.hex( ))这个脚本不解析任何字段只是一个“帧完整性的回声”。如果上位机程序收到的回显帧和发出去的字节完全一致说明 FCS 计算和帧尾拼装没毛病如果收到的和发出去的不一样先查结束符和 FCS 的大小写。回环通过之后再把DA1改成实际 PLC 节点号直接连真机。5.2 三个最容易忽略的坑第一串口参数不匹配比协议错更隐蔽。欧姆龙默认 8 位数据位、偶校验、1 位停止位但有的老型号出厂设置是 7 位数据位。连不上时先看 CX-Programmer 的 PLC 设置页核对“外围端口”里的波特率和校验位。USB 转串口用的是 CH340 驱动时还要确认设备管理器里枚举出的 COM 口号有的板子会随机占用高位 COM 端口代码里写死 COM3 就会报端口不存在或串口烧写失败。第二仿真软件不跑串口协议栈。欧姆龙 CX-Simulator 或 Sysmac Studio 的仿真模式只能验证梯形图逻辑FINS 串口帧收发依赖 PLC 固件里的串口驱动仿真环境里完全没有这条通路。想调试通讯要么等真机要么用回环从站方案。第三协议宏和上位机直发 FINS 是两套思路。协议宏是把 FINS 帧模板烧录到 PLC 串口上让 PLC 作为主站主动轮询外部仪表上位机直发则是把 PLC 当从站由上位机发起每次读写。两者不能混着用否则 PLC 串口自己发了帧又回你的帧总线上会同时出现主站和从站。把一帧验证过的 FINS 命令保存在串口调试助手的快捷发送列表里换 PLC 型号时只改 DA1 和地址比每次重新拼帧可靠得多。本文还有配套的精品资源点击获取
返回列表