
干了这么多年工控要说两台汇川PLC之间传数据这事我太有发言权了。前几天刚帮一个现场处理完两台AM401互相传产量数据的问题A机是输送线控制B机是包装机控制老板就一句话A机的产量和运行状态B机那边要实时能看到必要时候还得反向控制一下。就这么个需求最后选来选去还是用了Modbus TCP。原因很简单两台PLC都带以太网口一根网线或者一台交换机就搞定不加任何硬件成本程序写起来也不费劲。这篇文章我把整套实现流程从头到尾捋一遍包括服务器端怎么配、客户端怎么读、调试时踩了哪些坑全给你交代清楚。正在做汇川PLC多机通讯项目的工程师或者刚接触Modbus TCP想快速上手的小白这篇都很适合你。1. 为什么PLC之间通讯会选Modbus TCP1.1 一个再常见不过的场景多台PLC在一条产线上协同工作这几乎是每个产线都会遇到的需求。输送线机台、液位控制机台、包装机台不可能用一台PLC全扛下来分开控制是行业惯例。但设备一旦分开数据就成了孤岛。A机知道今天干了多少个活B机不知道C机报警停机了D机还在傻傻往那边送料。所以必须让PLC之间能说话。汇川的PLC系列里AM系列AM401/AM402/AM403和中型的AM600、AM800是支持以太网通讯的H系列比如H2U则往往要加装以太网扩展模块Easy系列也有部分型号带网口。通讯方式上光汇川自家就有好几套EtherCAT、MODBUS TCP、OPC UA、甚至EtherNet/IP。但如果只是PLC和PLC之间简单传数据Modbus TCP是我用过性价比最高、上手最快的方式。它不需要额外买通讯模块只要PLC有网口就行不需要配置复杂的从站设备描述文件程序侧调几个功能块就完事。1.2 不是必须要用Modbus但Modbus TCP确实省事有人可能会问汇川AM系列都支持EtherCAT为什么不用EtherCAT做PLC与PLC之间的耦合EtherCAT确实快但它的设计初衷是运动控制主站挂伺服、挂IO从站PLC和PLC之间走EtherCAT反而很别扭需要把其中一台配成从站还要处理耦合映射关系配置工作量明显更大。Modbus TCP就不一样了它是TCP/IP网络上的标准协议所有PLC厂家都支持跨品牌也好用。另外一个关键点Modbus TCP非常“轻”。报文结构简单功能码就那么几个算上行开销也就几十个字节。对PLC之间传工艺参数、产量计数、状态字这种低频小数据量场景几百毫秒的周期完全够用。它不像PROFINET、EtherCAT那样对实时性要求苛刻不需要专用芯片任何标准以太网交换机都能承载这也是它在工业现场这么多年经久不衰的原因。2. 动手前先分清两个概念谁是主站谁是服务器2.1 Modbus TCP的主从关系没那么复杂很多刚接触Modbus TCP的朋友一上来就被“主站”“从站”“客户端”“服务器”这几个词绕晕了。其实按TCP视角理解最顺发起连接请求的一方是客户端Client被动等着被连接的一方是服务器Server。放到Modbus TCP的语境里客户端就是主动读数据的那个服务器就是提供数据被读的那个。在我们这个需求里A机有数据要送给B机那A机就做成服务器ServerB机做成客户端ClientB机主动去A机那边把数据“抓”过来。注意虽然我们通常说“主站”“从站”但Modbus TCP里并没有严格意义上的主从关系服务器也可以主动发请求给客户端只要双方都支持就行。不过大多数场景下都是客户端主动轮询服务器被动响应。我在实际项目里也是按“一台服务器、一台客户端”来规划逻辑最清晰。还有个概念叫Unit ID单元ID在Modbus TCP报文里占一个字节通常填1就够。当服务器是网关设备、下面挂一串Modbus RTU从站时Unit ID才需要填具体从站号。汇川PLC作为服务器直接网口接入时Unit ID保持默认1即可。2.2 地址体系别把40001和0搞混了Modbus定义了四种常见数据对象线圈Coil可读可写0xxxx、离散输入Discrete Input只读1xxxx、输入寄存器Input Register只读3xxxx、保持寄存器Holding Register可读可写4xxxx。PLC之间传数据90%以上用的都是保持寄存器因为它既能写也能读非常灵活。这里的坑在于Modbus协议报文中保持寄存器的地址是从0开始的但大家在设备表、触摸屏、组态软件里看到的地址是40001开始。也就是说报文里的地址0对应软件层面的40001地址1对应40002以此类推。很多通讯不上或者数据错位的问题就出在这个偏移量上。举例说你在服务器端映射表里看到“40001 对应 %MW0”那客户端去读的时候请求报文里的起始地址就要填0而不是40001。如果填了40001实际上请求的是软件的40002地址数值全错位了。这个细节我在调试现场吃了好几次亏谁踩谁知道。3. 服务器端配置把“门”打开并贴好门牌号3.1 确认PLC有没有Modbus TCP服务器功能汇川的AM系列走的是CODESYS平台编程软件是InoProShop。这个平台本身对Modbus TCP支持得比较完整内置了Modbus TCP服务器和客户端功能不需要额外购买授权。H系列用的是AutoShop通常是靠以太网扩展模块或者专用指令库来实现原理一样只是配置入口不同。Easy系列则要看具体型号有些内置了网口和Modbus TCP功能有些不支持动手前先查一遍该型号的手册最稳妥。我这里以AM系列为例详细讲H系列/Easy系列的方法论通用就是配置界面和指令名称不同。3.2 在InoProShop里配置AM系列服务器端第一步把AM PLC的IP地址固定好。AM系列默认支持DHCP但车间环境里乱分配IP最耽误事。我一般建议在设备树里双击PLC节点进入“以太网”配置页设置为静态IP比如A机固定192.168.1.10B机固定192.168.1.20子网掩码255.255.255.0网关按现场网络情况留空或填真实网关。两台PLC接到同一台交换机物理链路就通了。第二步添加Modbus TCP服务器设备。在InoProShop的设备树里右键点击Application或者本机节点选择“添加设备”在设备列表里找到“Modbus TCP Slave”或者“Modbus TCP Server”不同版本显示名略有差异本质相同。添加之后界面上会出现这个从站设备的配置页。第三步配置端口和启停。默认端口是502这是Modbus TCP的标准端口一般不用改。但要注意如果一个PLC同时做服务器又做客户端端口502被服务器占用客户端的连接请求是主动发出去的不占用本地502所以不冲突。大部分情况下一台PLC既能当Server又能当Client互不影响。第四步也是最核心的配置数据映射表。在“Modbus TCP Slave”设备的配置界面里会有一个数据映射区域。这里可以逐条添加映射项把Modbus地址映射到PLC的内存区域。比如添加一条Modbus地址40001类型保持寄存器映射到%MW0长度10。意思就是外部设备读写40001~40010相当于读写PLC内部的%MW0~%MW9也就是M区从第0个字开始的10个字。最终验证通讯是否正常最简单的方法是在A机程序里给%MW0~%MW5赋一组测试值比如1、2、3、4、5、6下载运行。B机用客户端读取后能读到这几个数说明链路通、地址对、数据类型对齐。我在现场从来都是先用这种“打固定值”的办法排除问题再接入真实工艺数据效率特别高。3.3 服务器端的数据映射表设计数据映射表不是随便映射的一定要提前规划好。我建议把所有需要对外交换的数据集中存放在PLC内存的一块连续区域里比如定义从%MW100开始的一段数组。这样映射表只需维护一条连续映射项客户端读取时也可以连续读减少请求次数。举个例子A机需要提供这样一组数据数据名称类型占用字数规划Modbus地址产量计数DINT32位240001运行状态字WORD16位140003报警代码WORD140004实际速度REAL32位240005这样总共只用了5个寄存器客户端一次请求读5个字就全拿到了。运行状态字可以把运行/停止/故障/连锁中这些开关量按位打包省空间又清晰。我习惯的做法是第0位“运行”第1位“故障”第2位“手动状态”第3位“自动状态”剩下位留作扩展。4. 客户端实现主动去“取数”才是关键4.1 客户端功能块选择与连接配置服务器端配好了只是被动等待真正干活的是客户端。在InoProShop的CODESYS环境里需要先确认已经把Modbus TCP相关的库添加到工程里。库管理器里一般有“CAA Modbus TCP”或者类似命名的Modbus库包含Master客户端和Slave服务器两类功能块。不同版本的库功能块名称略有差异常见的是CAA_ModbusTCPMaster、CAA_ModbusTCPSlave。如果工程里找不到就在库管理器里点“添加库”搜索“Modbus”关键词把对应库勾上。声明一个Master功能块实例后第一步是配置连接参数。连接参数包含目标IP地址、端口号默认502、Unit ID默认1。如果是读A机数据目标IP就是192.168.1.10A机的IP。这里有个习惯问题我见过很多人在代码里硬编码IP这样程序移植性差。更稳妥的做法是把IP参数放到PLC内部变量里或者做成可配置参数以后改通讯对象只需要改参数不用动逻辑。4.2 读取与写入的完整逻辑核心功能块的调用逻辑我用ST语言写个示例VAR fbRead : CAA_ModbusTCPMaster; // 客户端功能块实例 stConn : CAA_ModbusClientConfig; // 连接配置 xTrig : BOOL; // 读取触发信号 arrData : ARRAY[0..9] OF WORD; // 读取到的数据缓冲区 xDone : BOOL; // 读取完成标志 xError : BOOL; // 错误标志 udiErrID : UDINT; // 错误代码 END_VAR // 初始化连接配置 stConn.IPAddress : 192.168.1.10; stConn.IPPort : 502; stConn.UnitID : 1; // 每2秒触发一次读取请求 IF NOT xTrig THEN xTrig : TRUE; END_IF IF xDone OR xError THEN xTrig : FALSE; END_IF // 调用功能块读取保持寄存器起始地址0对应40001读10个字 fbRead( xExecute : xTrig, Connect : stConn, StartAddr : 0, Quantity : 10, DataPtr : ADR(arrData), Done xDone, Error xError, ErrorID udiErrID );这段代码的意思很直观B机每隔2秒连一次A机服务器从保持寄存器的0号地址即40001开始连续读10个16位寄存器读回来的数据放在arrData数组里。某一次通讯失败也不用慌等等再重试就行。写入数据的逻辑跟读取类似只是功能码不同。CAA_ModbusTCPMaster功能块内部有读写模式参数读保持寄存器用功能码0x03写多个寄存器用0x10。你可以在调用时通过参数选择是执行读操作还是写操作。例如B机要控制A机停机就往A机映射区的一个寄存器里写一个特定的十六进制值A机程序里检查到这个值后执行停机逻辑。注意写操作要注意目的寄存器必须是可写的也就是映射到保持寄存器区域线圈和离散输入是没法用这种方式写。4.3 多寄存器连续读写与轮询策略数据少的场景一个Master功能块实例、一个触发条件就够了。但是当通讯数据量变多比如既要读A机数据又要读C机数据还得往A机写控制指令这时候单实例单触发轮询就会显得忙乱。我的做法是定义多个Master功能块实例分别对应不同的通讯任务。或者用同一个实例配合一个简单的状态机轮流执行“读A机”“写A机控制字”“读C机”这些任务。轮询周期也需要结合现场实际来定。如果只是产量这类变化不频繁的数据500ms到1s读一次完全足够。如果涉及联锁控制信号建议压缩到100ms左右。Modbus TCP单次请求开销不大局域网内部延迟也低100ms周期完全不会给PLC扫描周期造成明显压力。不过要注意客户端功能块在执行期间会占用一部分程序扫描时间如果PLC本身扫描周期很短比如0.5ms那就要评估一下通讯请求占用的时间。必要情况下可以使用CODESYS的多任务机制把通讯代码放到独立的低优先级任务里比如10ms或20ms任务周期避免拖累主任务的实时性。5. 调试纪实用监控和抓包让数据“透明”5.1 PLC在线监控排查调试永远是从确认基础网络开始。把B机客户端的笔记本电脑网口接进同一台交换机先做一件事命令行里ping一下A机的IP看通不通。ping 192.168.1.10如果不通优先检查两台PLC和笔记本是不是同一网段、IP有没有配错、网线水晶头是否压好、交换机端口亮没亮灯。这一步过了之后再到InoProShop里把A机程序下载运行强制给映射区的寄存器写入测试值比如%MW0赋值十六进制0x1234。这个值很典型0x1234的字节顺序观察起来特别方便。接着在B机程序里跑客户端功能块在线监控arrData[0]看是否等于十进制46600x1234。如果相等整个链路完全打通后面接真实工艺数据基本不会再出问题。如果不相等或者通讯一直Error就看下一节的排查表。5.2 网络侧验证的实用方法PLC在线监控能看到功能块层面的错误码但有些问题需要看报文才能定位。我调试Modbus TCP时最顺手的是用笔记本上的第三方便携调试工具来当临时客户端直接模拟B机去读A机服务器。这类工具网上很多选一个支持Modbus TCP的就行。它可以直观地看到连接是否建立发送了什么请求报文收到了什么响应报文寄存器里的原始值是多少举个例子A机服务器端映射的40001寄存器里放的是0x1234用第三方工具去读如果读到的是0x3412说明字节序不匹配大小端问题你就知道下一步要处理什么。这比自己人肉看报文快得多。另外有条件的话可以用Wireshark抓包过滤条件写tcp.port 502能完整看到Modbus TCP的请求响应交互。虽然日常工作不一定要抓包但遇到疑难问题时这是终极排查手段强烈建议学会基本用法。5.3 常见故障对照速查我整理了一张故障排查表基本覆盖了大部分现场问题现象很可能的原因处理办法客户端一直报超时IP不通、网线问题、端口被占用先ping再逐段检查物理链路确认服务器端502端口没有被其他程序占用连接能建立但读出来全是0服务器端映射表没生效或者PLC程序没运行检查服务器端设备是否下载成功映射到的内存区是否被写入了数据读出来的数据全错位起始地址偏移不对明确服务器端映射的Modbus地址计算客户端StartAddr时要减140001对应地址0读回来的值和写入值顺序颠倒字节序不匹配用0x1234这类特征值测试依据结果调整字内字节交换偶发通讯中断时好时坏交换机端口不稳定、网线质量差、IP冲突更换品牌工业交换机端口重新压水晶头检查现场是否有设备抢IP连续读取大量数据时报错一次读取寄存器数量超上限将Quantity限制在125以内拆成多次小批量轮询注意Modbus TCP单次读取保持寄存器的最大数量是125个这是协议规范的上限。一次要读的数据量超过这个数必须拆成多次请求。6. 实际项目里那些细思极恐的坑6.1 地址偏移与数据类型匹配地址偏移在前面提过了这里重点强调数据类型。Modbus寄存器是16位的但PLC里的DINT浮点数等是32位甚至64位的跨寄存器时地址排列顺序很关键。比如服务器端把一个REAL型变量写在40001-40002两个寄存器里客户端读回来的是两个16位WORD要组成一个合法的32位FLOAT才能正确解析。在CODESYS平台里可以在客户端把读取数组定义成WORD数组然后用指针转换或者MEMCPY函数把两个WORD拼成一个REAL。操作起来稍显繁琐这也是我建议尽可能用INT/UINT/WORD这种16位数据类型做跨PLC交换的原因。碰到必须传浮点数的场景先把数值放大为整数比如速度值乘以100发送到了对端再缩小能省掉一堆麻烦。6.2 大小端字节序真的是老生常谈同品牌PLC之间通讯大小端一般是一致的基本不会出问题。但一旦涉及第三方设备或者上位机软件就很可能出现字节序不匹配。我用一个典型例子说明A机寄存器里存了0x1234B机读回后显示0x3412这就是字的两个字节被交换了。CODESYS平台默认的Modbus TCP通讯字节序是按大端高字节在前处理的有些系列PLC或者老型号可能不一样。验证和调整方法是用一个已知特征值0x1234、0xABCD这种高低字节差异明显的数写入服务器端的某个寄存器客户端读回来对照如果发现字节颠倒就在功能块里加一个“字节交换”的配置项或者程序里写一个交换函数处理。调试时记住一个口诀“先打特征值再看字节序别拿真实值猜。”6.3 断线重连与通讯异常置位Modbus TCP建立在TCP连接之上TCP连接是会断的。对端PLC断电、网线松动、交换机重启都会导致连接断开。由于TCP是流式协议断开的连接如果重新建立客户端功能块通常会自动处理但如果通讯代码写得不好就会出现“连一次就卡死”的现象。我的经验是给每次通讯触发加一个自动重试机制用TON定时器比如2秒周期性地给xExecute上升沿当没有Done和Error时说明通讯还在进行中不重复触发当Done或Error出现后拉低触发信号准备下一轮。这样就算TCP连接断了2秒后也会再次发起连接请求实现自动恢复。再一个建议利用客户端功能块的错误状态在上位机触摸屏上做一个“通讯正常”的指示灯。这样就算通讯突然断掉现场操作员也能第一时间发现不至于设备停了半天还没人知道。我一般会在通讯正常时置位一个BOOL量输出给触摸屏通讯异常时定时闪烁效果非常直观。还有一个容易被忽视的点服务器端PLC停机或程序修改后重新下载TCP连接会被中断。客户端不会知道自己收到的是旧数据还是新数据可能继续用缓存里的旧值。我建议在交换数据区里放一个“心跳计数寄存器”服务器端每个扫描周期加1客户端读取后做差分检测。连续三个周期心跳值都没变判定通讯数据已失效程序里应当把相关数据强制为安全值防止连锁误动作。这是工控安全的一道重要底线。最后再分享点实在的我做的这么多项目里Modbus TCP一直是那个低调但可靠的家伙。它不像EtherCAT那样动不动就谈微秒级同步也不像OPC UA那样配置复杂但它胜在简单、通用、不挑设备。汇川PLC之间的Modbus TCP通讯只要把服务器映射表、客户端参数、数据类型、字节序这四件事想清楚基本就八九不离十了。真遇到疑难杂症记住一条黄金法则先ping再用特征值验证最后才看协议字段。按这个顺序排查九成的通讯问题都能定位。