做PLC上位机或设备联网这几年,欧姆龙PLC的通信协议是我花时间最多、踩坑也最密的一块。欧姆龙这个品牌很有意思:中小型项目里它的性价比和稳定性非常能打,可一旦你要做上位机、触摸屏、MES数据采集或者多台PLC组网,通信协议这一关就成了绕不过去的坎。我最早用CX-Programmer写好程序后,第一次尝试用上位机读取PLC里的D区数据,就被帧格式、字节序、节点号这些名词折磨得够呛。这篇文章就是想把欧姆龙PLC通信协议这件事一次讲透,把我踩过的坑、最后搞明白的机制和排查链路都写下来,希望能让后来人少走点弯路。
这篇文章适合谁看?如果你正在做欧姆龙CP1E、CP1L、CJ2M、NJ/NX系列PLC的数据采集,或者想用串口RS-485、以太网把欧姆龙PLC接入上位机、组态软件、MES系统,又或者在CX-Programmer和Sysmac Studio之间来回切换时被通讯参数搞晕,那这篇文章应该能帮到你。我会从协议选型、地址结构、帧格式、实操代码、踩坑排错一条线讲下来,不堆理论,尽量都是一个一个试出来的结论。
1. 为什么要搞明白欧姆龙通信协议:不只是写个上位机那么简单
1.1 欧姆龙PLC在数据采集场景里的特殊位置
欧姆龙的PLC在包装机械、半导体设备、制药设备、污水处理这些行业里占比很高。它的可靠性好,指令执行速度也快,尤其CP1H和CJ2M这两款,在小型设备里几乎随处可见。但设备一旦要做数据追溯,要把温度、压力、产量、报警代码上传到上位机数据库,PLC的通信能力就成了硬指标。
我接过一个项目,现场用的欧姆龙CP1H,要通过以太网把32路温度数据和8台变频器状态传到车间MES。一开始以为直接用CX-Programmer软件能看到的变量就能读出来,后来发现事情远没那么简单——PLC程序里的符号名(Symbol)对应到内存地址(CIO区、DM区、WR区),协议命令里根本不认识符号名,只认地址和区号。我得把程序里所有的符号表转成地址映射表,再通过FINS指令去读。这一步做了整整两天,才算把通信协议的第一个逻辑打通。
1.2 通信协议选错,后面全盘崩
欧姆龙PLC通信协议主要有这么几条路线:老一代HostLink串口协议、FINS协议(支持串口和以太网)、Modbus-RTU(串口和部分以太网都支持)、以及NJ/NX系列上的EtherNet/IP和OPC UA。选错协议方向的代价非常大,比如某次我用第三方组态软件走HostLink连接CP1L,结果波特率、数据位、停止位都对,却因为HostLink的帧格式里要算FCS校验并在前面加一个“@”字符,导致组态软件连帧头都识别不了。
选协议的逻辑其实很简单:
- 如果只是单台PLC和触摸屏通信,用触摸屏自带驱动选欧姆龙,协议不用管。
- 如果上位机是组态软件(组态王、WINCC、InTouch等),优先看驱动支持哪一种,大多数支持HostLink和FINS/TCP。
- 如果是自己用C#、Python等写程序,建议直接上FINS/TCP或FINS/UDP,帧结构清晰、支持网段寻址、读多个地址非常灵活。
- 如果PLC要和第三方设备做485通信,Modbus-RTU是最省事的。
踩过坑的人很容易陷入一个误区:觉得HostLink协议看起来简单,就先用它。但HostLink只适合串口,走以太网就有点别扭;而且HostLink一次能读的数据量远小于FINS,在大数据量采集场景下会被卡死。FINS/TCP的单条命令最多能读多少字,我在后面实测部分会详细说。
1.3 通信协议文档“难啃”的真相
欧姆龙的官方手册(尤其是《SYSMAC CS/CJ系列 通信命令参考手册》和《FINS命令参考手册》)内容非常全,但结构很绕。它把FINS命令定义成好多层级,先是FINS帧、再是命令码、再是参数区分配。很多工程师看手册第一眼就放弃了。
我的经验是:不要从头啃手册,先抓三条主线。第一,FINS帧的构成和地址映射规则;第二,命令码怎么用(读多个字的命令是0101,写多个字的命令是0102,读单个字的命令是0101配合变长方式也能用);第三,响应码的含义(常见的0401是正常结束,1101是区域规格错误,1103是地址越界,110B是命令过长,2002是数据错误)。一旦把这三条主线抓住,剩下的事情就是查表和Debug了。
2. FINS协议能把人绕晕的地址结构:网络号、节点号、单元号,还有DM区
2.1 FINS地址的三层定位逻辑
FINS这个名字是Factory Intelligent Network Service的缩写,它本质上是一套面向工厂网络的寻址体系。在以太网FINS/TCP里,为了找到目标PLC里的某个内存区域,需要三层定位:网络号(Network Number)、节点号(Node Number)和单元号(Unit Number)。网络号是子网编号,节点号是设备编号,单元号是PLC内部的通信模块编号(CPU内置以太网口通常是0)。
这里就要说到最容易踩的坑了:在CX-Programmer或者Sysmac Studio里,PLC本体设置的“节点号”要和FINS帧里的目的节点号保持一致,但很多人不知道的是——如果你用的是CPU内置以太网口,节点号往往和IP地址没关系;只有当你在FINS/TCP里通过IP地址连接时,上位机软件才会自动把目标IP地址映射到对应的FINS节点。如果上位机发送的FINS帧里目的节点号和PLC实际节点号不一致,PLC返回的响应码会是0101(节点号未定义)或者干脆超时。
我调试过一个项目,IP地址Ping得通,但是FINS命令发出去就超时。后来发现PLC内置以太网口的FINS节点号被改成了10,而上位机软件默认用IP地址最后一段(比如192.168.1.5的话就默认节点号是5)去发送,节点号不匹配,协议栈根本不到达应用层。这个问题最坑的是它不会立即报错,而是表现为“偶发超时”。
2.2 CIO区、DM区、WR区、HR区到底怎么换算
欧姆龙PLC的内存区在通信命令里用“内存区代码(Memory Area Code)”来标识。读多个字的命令里,地址由三部分组成:内存区代码(1字节)+ 首字地址(2字节)+ 位数(2字节)。常见的内存区代码对应关系如下:
| 内存区 | 说明 | FINS内存区代码(16进制) |
|---|---|---|
| CIO区 | 外部I/O和内部继电器 | 0x30(CIO 0-6143) |
| WR区 | 工作继电器 | 0x31 |
| HR区 | 保持继电器 | 0x32 |
| DM区 | 数据存储器 | 0x82 |
| EM区 | 扩展数据存储器 | 0xA0-0xAF |
比如要读DM区第100个字,内存区代码是0x82,首字地址是0x0064。这个还比较直白。真正让我栽跟头的是CIO区和符号名之间的换算。在大中型PLC里,CIO区既有物理输入(CIO 0.00-0.15是输入通道),也有内部工作位(W0.00这种),如果你直接在程序里用了W0.05这个符号,通信时对应的其实是WR区的0.05。如果你在程序里用CIO区内部位(比如CIO 1200.05),那内存区代码就是0x30,字地址是0x04B0,位号是05。
这里我建议的做法是:凡是做通信映射,一律在PLC程序里用AT指令把关键变量固定到DM区或者WR区的连续地址上,然后在FINS命令里只读这些固定地址。这样做的优点是地址固定、不随程序编译变化、不容易乱。我经手的项目,上位机点位表里几乎都是自定义的DM区地址,后面维护非常省心。
2.3 Word变量是什么:欧姆龙的数据类型命名习惯
热词里有人搜“欧姆龙 word变量是什么”,这其实是搞懂FINS通信的另一个小坎。在欧姆龙的语境里,Word指的是一个字(16位),比如D100就是一个Word变量。你要处理的温度、压力、速度这些数值,如果范围不超过16位有符号整数(-32768到32767),在DM区里就是一个Word。温度值120.5这种带小数的,在PLC里往往是放大10倍之后存成整数1205,上位机读出来再除以10。
欧姆龙没有像三菱那样直接把浮点数分成Float和Double来让你在通信命令里选择,它把浮点数也以两个Word的形式连续存放(32位浮点占用2个字)。如果你用FINS命令一次读两个Word(比如D100和D101),按小端序拼起来,再用IEEE754解析,就能得到浮点数。这一点不搞清楚,上位机读出来的数永远是乱码。我见过有人在D100读到一个整数,却始终不知道D101才是浮点数的另一半,最后死活解析不对。
3. 从HostLink到FINS/TCP:我实测过的协议细节和坑
3.1 HostLink:老协议也有它的逻辑
HostLink是欧姆龙串口通信的经典协议,走RS-232C或者RS-422/485,帧格式是ASCII码。在HostLink里,一次命令的格式大概是这样:@单元号 帧标识 命令码 正文 FCS 终止符。帧标识是“0”代表命令帧,“1”代表响应帧;FCS是帧校验和,把从帧标识到正文结尾的所有字符的ASCII码异或一遍,然后转成两位十六进制大写ASCII字符;终止符是*CR。
我最早在组态软件里配CP1E串口时,把所有通信参数都设对了,唯独忘了FCS校验。组态软件发的命令里FCS是空的,PLC收到后直接判定帧错误,整个过程没有任何响应,串口调试助手里只能看到一条命令发出去就没有下文。后来我在串口监控工具里抓了组态软件发的完整报文,又对照手册手动算了一遍FCS,才发现问题在“0”和“1”的帧标识后面必须紧跟两个FCS字符,而且FCS的算法是异或而不是求和。
HostLink能用在485总线多台PLC组网上,每条指令用单元号(00到31)来区分不同PLC。在485组网时,上位机发送@00RR0001CR,表示读单元号00的PLC的PC状态;如果要读DM区某地址,命令是@00RD+地址(4位十六进制)+位数(4位十六进制)+正文+FCS+CR。HostLink的地址只有4位十六进制,所以最多能寻址65536个字,因为它是按字地址来算的,所以比FINS的寻址范围小。我现在一般只在老设备改造、组态软件不支持FINS/TCP时用HostLink,新做的数据采集系统一律FINS/TCP。
3.2 FINS/TCP帧结构:连接握手和命令发送
FINS/TCP的连接过程是:上位机先发送一个FINS节点地址读取命令(帧类型码0x00,命令码是0x00),PLC会返回自己的FINS节点号;之后上位机才能发送真正的命令帧。连接握手这个细节经常被忽略,但非常重要。如果上位机不先做握手,直接往9600端口(FINS/TCP默认端口)发送命令帧,PLC大概率不会处理。
完整的FINS/TCP命令帧结构是:
| 字段 | 长度 | 说明 |
|---|---|---|
| FINS Header | 4字节 | 帧类型码(命令帧是0x00,响应帧是0x01) |
| 长度 | 4字节 | 从FINS帧开始的字节数,不包括这8字节头 |
| 命令码 | 2字节 | 例如读多个字是0x0101 |
| 错误码 | 2字节 | 响应帧中PLC返回的状态 |
| ICF | 1字节 | 信息控制域 |
| RSV | 1字节 | 保留 |
| GCT | 1字节 | 网关计数 |
| DNA | 1字节 | 目标网络号 |
| DA1 | 1字节 | 目标节点号 |
| DA2 | 1字节 | 目标单元号 |
| SNA | 1字节 | 源网络号 |
| SA1 | 1字节 | 源节点号 |
| SA2 | 1字节 | 源单元号 |
| SID | 1字节 | 服务ID |
| 命令参数 | 可变 | 根据具体命令码而定 |
手册上的这个结构第一次看确实容易劝退。但用熟了就会发现,中间的ICF、RSV、GCT这些字段绝大多数时候不需要动。ICF固定设0x80(表示需要响应),RSV设0x00,GCT设0x02,SID可以每次递增用于匹配请求和响应。DNA按网络号,DA1按PLC节点号,DA2设0(CPU内置端口)。自己写上位机时,这堆字段基本可以做成一个固定模板,只改地址和命令参数就行。
3.3 读多个字的实测案例:C#实现FINS/TCP读取
以读取CP1H的D100到D115共16个字为例,我常用的C#实现思路是这样的。先与PLC建立TCP连接(端口9600),发送握手帧,然后组装命令帧。
握手帧代码示例:
// FINS/TCP 握手帧 byte[] handshake = new byte[12]; handshake[0] = 0x46; // F handshake[1] = 0x49; // I handshake[2] = 0x4E; // N handshake[3] = 0x53; // S handshake[4] = 0x00; // 帧类型码:命令 handshake[5] = 0x00; handshake[6] = 0x00; // 长度 handshake[7] = 0x00; handshake[8] = 0x00; handshake[9] = 0x00; handshake[10] = 0x00; handshake[11] = 0x0C; // 长度:12字节 // 发送后接收12字节,其中最后4字节是PLC节点号真正读数据的命令帧:
byte[] readCmd = new byte[30]; readCmd[0] = 0x46; readCmd[1] = 0x49; readCmd[2] = 0x4E; readCmd[3] = 0x53; readCmd[4] = 0x00; // 命令帧 readCmd[5] = 0x00; readCmd[6] = 0x00; readCmd[7] = 0x00; readCmd[8] = 0x00; // 从FINS命令开始算长度 readCmd[9] = 0x00; readCmd[10] = 0x00; readCmd[11] = 0x16; // 22字节的FINS帧长度 // FINS帧开始 readCmd[12] = 0x01; // 读多个字命令高位 readCmd[13] = 0x01; // 读多个字命令低位 readCmd[14] = 0x80; // ICF readCmd[15] = 0x00; // RSV readCmd[16] = 0x02; // GCT readCmd[17] = 0x00; // DNA 网络号 readCmd[18] = 0x01; // DA1 目标节点号(必须和PLC设置一致) readCmd[19] = 0x00; // DA2 目标单元号 readCmd[20] = 0x00; // SNA readCmd[21] = 0x01; // SA1 readCmd[22] = 0x00; // SA2 readCmd[23] = 0x01; // SID // 参数区 readCmd[24] = 0x82; // DM区 readCmd[25] = 0x00; // 首字高字节 readCmd[26] = 0x64; // 首字低字节(D100) readCmd[27] = 0x00; // 位:从0开始 readCmd[28] = 0x00; // 读取字数高字节 readCmd[29] = 0x10; // 读取字数低字节(16个字)这里尤其要在意的是字节序。欧姆龙的命令参数里,地址是高位在前,然后才是低位;而读取的数据呢,响应帧里的数据在多个字时每个Word是高字节在前、低字节在后,和西门子的完全不一样。如果你把D100读出来的字节直接用BitConverter.ToInt16处理,很容易得到错误值,因为BitConverter在C#里默认按内存序(小端)解析,你要么手动调换高地位,要么用BinaryPrimitives.ReverseEndianness处理。
3.4 串口FINS和以太网FINS的差异
如果你用CP1L的串口做FINS(串口设置为FINS模式),帧结构里没有FINS/TCP那12字节的头部,而是类似HostLink那样以ASCII的FINS节点地址开头,但命令字段是二进制格式。串口FINS在传输数据量大的时候,效率没有HostLink高,而且错误排查更难,因为二进制数据在串口调试助手里看起来是一堆乱码。
我自己的选择是:能用以太网FINS绝不用串口FINS。除非设备位置很偏,只能拉RS-485,而且触摸屏没有网口,才会考虑串口FINS。一个很现实的坑是,CP1H的串口默认是HostLink模式,如果你改成FINS模式,CX-Programmer在线下载程序可能会连不上PLC,这时候必须用USB线下载。我在现场干过这个事:程序连不上,以为是驱动问题,折腾了半小时才发现是串口模式被改了,PLC的通信模式决定了CX-Programmer能不能通过串口连上。
4. 踩坑实录:一次典型的“偶发超时与数据错乱”排查链路
4.1 故障现象:重复请求后才拿到正常数据
有一次给一台贴标机做数据采集,PLC是欧姆龙CP1H,上位机每分钟读取一次产量和不良品计数值。调试阶段发现一个诡异问题:上位机第一次发送FINS/TCP读命令,收到响应的时间通常5毫秒就回来了,但偶尔要等200毫秒甚至直接超时。而且超时后重发一次,数据又是对的,数据本身从来没读错过。
这种“偶发超时+重发就正常”的现象,在工业现场最容易让人误判为网络问题。我先查了交换机,又查了网线,更换了TCP端口,问题还在。后来用Wireshark抓包,发现PLC根本没有回复第一帧命令,第二帧开始才正常回复。这说明请求没有到达PLC的应用层,或者到达了但被PLC丢弃了。
4.2 排查链路:从抓包到发现SID的问题
在Wireshark里对比成功的请求和失败的请求,唯一区别是SID(服务ID)字段不同。我的上位机代码里SID是随机生成的随机数,在超时的那次请求里,SID值和上一次请求的SID恰好相同。欧姆龙PLC的FINS/TCP处理机制里,相同SID且内容相似的命令在一定窗口内会被当作重复命令丢弃,简化处理了。这个机制本来是为了防止重复请求,但在上位机没有做严格响应匹配时,反而变成了一种“隐形的坑”。
解决办法非常简单:SID使用一个全局递增的字节,每次发命令都加1(到255后归零),不要复用。改了之后,偶发超时消失得干干净净。后来我还专门做了一轮暴力测试,每分钟连续发2000次请求,一次超时都没出现。
4.3 数据错乱的根源:位地址与字地址的混淆
另一个顽固问题是数据错乱。读取D100到D131共32个字,里面有一部分是浮点数。上位机显示出来的温度有时候正常,有时候出现一个巨大的负数。开始我以为是PLC程序里写数据有误,后来在PLC里用CX-Programmer监视D区,发现PLC里的值完全正常,问题出在上位机解析。
排查半天,发现是我的FINS命令里的首地址设定错了。我用的是“读多个字”命令0101,这项工作它一次最多能读多少个字?答案是受命令长度限制的,实测CP1H用FINS/TCP读多个字时,一次命令最多能读大约1000多个字节,建议每次控制在256个字以内,32个字完全没问题。问题在于我读取时把“首字地址”写成了D100的位偏移量,比如D100的位偏移是800(每个字16位,D100按位算就是第800位开始),而实际上0101命令里的“首字地址”就是字地址,直接填100就行,不需要乘16。这个误解导致命令读的地址其实是D50附近,D50区恰好存放了一些报警标志,数据自然就乱了。
4.4 使用485组网时遇到的一个大坑:终端电阻和共地
热词里有一条“1200plc与欧姆龙变频器的485通讯程序”,说明很多人用PLC和第三方设备做485通信。我在欧姆龙PLC和变频器走Modbus-RTU时,遇到过通信时好时坏的怪病。一开始以为是程序里的发送延时不够,后来排查到物理层才找到根因:485总线的屏蔽层没有接地,终端电阻也没接。在车间里电机一启动,干扰就会让总线上的数据帧产生偶发错误。
PLC和变频器走485通信时,主机轮询周期要设置得略大于变频器响应时间,而且从站地址不能设0(地址0是广播地址)。欧姆龙CP1H的串口做Modbus-RTU主站时,D寄存器里有一个专门的通信参数区,波特率、数据格式、从站超时时间都在固定地址里设置,不清楚的人直接在梯形图里写指令会一脸懵。这部分建议直接用欧姆龙官方的Modbus功能块,不要自己拼RTU帧,坑会少很多。
5. 搞明白通信协议之后,我总结的实用调试框架和工具链
5.1 调试工具怎么选:绝不盲目用串口助手看二进制
在协议调试阶段,工具的选用直接决定效率。我的固定组合是:
- 网络抓包用Wireshark,过滤器写
tcp.port == 9600,能直接看到FINS/TCP的完整帧。 - 串口调试用“友善串口调试助手”或者“SSCOM”配合十六进制显示,但真正要看协议解析效果,推荐用QModBus、Modbus Poll这种带协议解析的工具(前提是走Modbus)。
- 欧姆龙官方驱动里有一个FINS Gateway(常被安装到C:\Program Files (x86)\OMRON\FINSGateway),它自带一个工具叫FINSGateway Utility,可以做FINS路由表配置。这个东西配合CX-Programmer的“网络配置”使用,是排查网段连接问题的利器。
- 如果你用C#开发,NuGet上有别人封装好的FINS通信库,但里面质量参差不齐。我自己最后还是按照官方手册手写了核心帧,因为库封装得太厚,出问题根本定位不了。
5.2 协议调试三板斧:抓帧、算字节、查响应码
遇到任何通信问题,我从来不改参数乱试。我的排查顺序固定:
- 抓帧:把上位机实际发出去的报文原原本本抓出来。很多人用代码里的发送缓冲区来说话,但那不一定等于线上实际数据,中间可能有编码转换、字节序转换的坑。
- 算字节:对照手册,一字节一字节地核对命令帧的字段。重点核对内存区代码、地址、长度这三个参数。
- 查响应码:PLC返回的响应帧里最关键的就是错误码字段(FINS帧第5、6字节)。响应码查手册是最好的Debug线索,不要凭空猜。
我强烈建议在看响应码时,把常见响应码背下来。比如读命令发出的区域代码是0x82,但地址超过了该区最大范围,返回1101;如果命令超过最大长度,返回110B;如果PLC没有配置FINS节点,返回0101。很多工程师看到一串十六进制响应码就慌,其实这些码在手册附录里都有,只是没人耐心去查。
5.3 上位机通信框架设计心得:超时重试和地址映射表
通信协议搞明白了,还要有一个稳的上位机框架。我的经验是把通信封装成三层:
- 第一层:TCP连接管理和握手,保证TCP通道永远在线,断线自动重连。
- 第二层:FINS命令组装和响应解析,这层只负责帧格式,不关心业务。
- 第三层:点位映射表,把实际的PLC地址(比如D100、CIO 1200.05)映射成项目里的变量名(比如“一号线温度”)。
点位映射表要单独用一个配置文件或者数据库表维护,不要把地址硬编码在业务逻辑里。因为设备调试阶段,PLC地址经常变动,如果硬编码,改一次地址就要改代码重编译,很容易改错。我曾经因为地址映射表写错了一个数字,把一个设备的温度信号映射到另一台设备的产量信号上,生产数据在MES里错了一个星期才被发现。从那以后,点位表一定做二次校验:上位机读到的值要和触摸屏上同一个地址的值对比一致,才确认映射无误。
5.4 关于欧姆龙USB驱动和CX-Programmer的那些坑
热词里有“欧姆龙usb驱动”,这里顺便说一句。欧姆龙PLC的USB口驱动不是Windows自带的,必须安装CX-Programmer附带的驱动或者官方单独发布的USB驱动。很多人在Win10 64位上装完CX-Programmer发现USB连不上,是因为系统把设备识别成了通用串行设备,而不是欧姆龙专用设备。解决办法是先安装CX-Programmer,再插USB线,系统会按驱动自动识别。如果你安装CX-Programmer版本很老(比如9.x),在Win10上可能要手动禁用驱动签名强制,这时候一般装一个新版驱动就能解决。
另外,CX-Programmer的在线通信设置里,有一个“Direct Online”和“通过网络在线”的选择。很多人不知道这个区别,USB连接时选了通过网络连,结果一直连不上。USB直接连PLC时,要选“直接在线”,串口和以太网才选网络连接。
6. 从协议到工程:FINS通信在上位机里的完整落地经验
6.1 连上PLC之后,第一步先做读写自检
很多工程师写好了上位机通信代码,直接把点位表里的变量塞进去读一遍,发现一些变量不对就开始怀疑协议。我自己的习惯是,通信联调的第一天,只做三件事:
- 用FINS命令读PLC的型号和版本号(命令码0101读特殊单元状态,或者用0101读系统信息区域)。这能验证连接是最基本的。
- 在PLC程序里人为写一个固定值(比如D0里放12345),上位机读回来校验字节序。
- 从PLC的CIO区读输入状态,比如读取CIO 0.0到0.15,确认触点动作能实时反映到上位机。
这三步走通,协议层就基本稳了。接着再做点位表全量核对,效率会高很多。如果不先跑通这三步,直接就上百个变量采集,出了问题根本不知道是地址错了、字节序错了,还是数据本身是错的,排查起来非常痛苦。
比如字节序校验,我通常的做法是:在D0写入16#1234(十进制4660),上位机如果能读到4660,说明高字节低字节的顺序没搞错;如果读出来变成13330,说明字节序反了。浮点数的校验更严格,我会写入一个固定浮点比如3.14,看上位机读出来的字节顺序是不是和IEEE754一致。
6.2 多台欧姆龙PLC组网时,FINS路由表怎么配
如果你的系统里有多台欧姆龙PLC,比如一台CJ2M加两台CP1H,通过以太网交换机连在一起,上位机要访问每一台PLC,这时就涉及FINS路由表。
FINS路由表的作用是告诉PLC“哪些网络号/节点号要通过哪个端口转发”。每个PLC都要维护一张路由表。比如上位机(节点号80)要访问PLC1(网络号1,节点号1),直连即可;如果要访问PLC2(网络号2,节点号2),而PLC2挂在PLC1后面的另一个网段上,就要保证PLC1的路由表里有指向网络号2的转发规则。
配置路由表一般通过CX-Integrator(在CX-Programmer里打开IO表时能看到)设置。这个工具对于习惯用三菱PLCs的人来说比较陌生,因为它不是在PLC参数里直接填IP,而是通过构建FINS网络拓扑图来配置。新版Sysmac Studio里,对于NJ/NX系列,配置方法又不一样了。我的建议是,如果上位机要访问多台PLC,优先保证所有PLC在同一个物理网段内,网络号设为相同,节点号不重复,这样路由表几乎不用配,连上就能通。一旦跨了网段,路由表的排查难度会上一个台阶。
6.3 与组态软件和MES对接时的协议选择
对接组态软件时,我一般优先选择FINS/TCP驱动。组态王、WinCC里都有欧姆龙FINS以太网驱动,配置界面会让你填PLC的IP地址、端口号、网络号、节点号。有些组态软件(比如InTouch)默认配置的节点号可能跟PLC不一致,需要在PLC设置里把节点号改成和组态软件期望的值一致,或者反向改软件设置。这个一致性问题是最常被忽略的。
对接MES系统时,如果MES用OPC UA通信,欧姆龙NJ/NX系列可以直接做OPC UA服务器,不需要自己写协议。如果是老CJ系列,就得通过中间层程序(用FINS/TCP读取后转成OPC UA节点)来做。我做过一个项目,MES要求OPC UA,底层是三台CJ2M,我用C#写了一个网关服务,把三台PLC的FINS/TCP请求统一封装成OPC UA节点,这样MES只需要面对一个网关IP,可维护性提高了不少。
6.4 大数据量采集场景的优化:批量读和缓存策略
FINS协议的批量读效率其实不错,但一次读太少会非常浪费。假设你要读100个离散的DM区字,把它们拆成100条单字读命令和合并成3条批量读命令(根据地址连续性分组),性能差距是几十倍。我的做法是:先把点位表按地址排序,把连续的地址段合并成若干个“块”,每块用一条读多个字命令读取。这个优化很简单,但对现场性能提升巨大。
另外要注意PLC扫描周期和通信请求之间的配合,这是大家在实验室里很少遇到但现场非常重要的一个点。如果PLC程序在每个扫描周期都执行大量数值运算,那么数据在读取的瞬间有可能处于“非一致性”状态。比如两个Word组合成一个32位数据,但你第一条命令读取时高16位已经变化、低16位还没来得及变化,就会读到乒乓值。工程上的解决办法是:让PLC程序把要上传的数据先在固定的DM区连续地址里集中缓存,并且这个缓存区的更新在一个扫描周期内完成。上位机读取时,连续地址、一次性读完整数据块,这能最大限度避免乒乓值问题。
7. 写在最后:给后来者的一点实在建议
把欧姆龙PLC通信协议从一窍不通到基本通透,我最大的体会是:这套协议并不难,难的是被各种文档碎片和错误实践带偏节奏。如果你现在正在被欧姆龙通信折磨,我建议你按这个顺序推进:先确认PLC型号和固件版本,选对协议方向;再把FINS帧结构手写一遍,搞清楚地址映射;然后写一个最小可用的读取工具,用固定值校验字节序;最后再对接业务点位表。
别一上来就去抄现成的类库或者别人例程,那样出了问题你根本不知道从哪查。抄来的代码一旦通讯失败,你会同时怀疑地址、帧格式、字节序、网络环境、PLC设置,排查范围大得惊人。手写一遍帧的调试过程虽然慢,但它能让你把每一步都钉死,后面遇到任何问题,排查思路都是清晰的。
还有一个小细节:欧姆龙PLC通信协议手册里的各种地址、区号、命令码,其实都围绕着一个核心思想——设备和上位机要约定一套共同的“寻址语言”。西门子有它的S7通信区,三菱有它的MC协议软元件,欧姆龙就是FINS的内存区体系。一旦你把“内存区代码+字地址+位号”这个三元组彻底理解,整本手册读起来就顺了。
最后再多说一句,调试通信协议特别考验耐心,尤其在现场耳朵听着机器噪音、眼睛盯着调试工具的时候。但我可以负责任地说,欧姆龙PLC的通信协议只要按着帧格式严谨处理,稳定性是相当好的。我自己用FINS/TCP做的一套设备监控系统,连续运行了大半年,除了交换机断电重启,一次通信中断都没发生过。希望这篇文字能帮你少熬几个夜,早一点把数据稳稳地读上来。