
上个月刚做完一个两台汇川PLC的联动项目没有触摸屏、没有上位机纯粹就是设备与设备之间交换速度和状态数据。现场客户点名要求用Modbus TCP理由也很实在网口现成、不用铺串口线、以后还要把数据送给上位机。结果整个调试下来半天时间里有差不多两小时都耗在地址映射和报文排查上。回头想想这个事本身不难难的是把协议细节和汇川PLC的地址模型对齐。今天就把整个过程掰开揉碎给准备在汇川PLC之间做Modbus TCP通讯的朋友一份可以直接照抄的参考。1. 为什么选Modbus TCP从现场需求反推通讯方案1.1 两台PLC联动的典型场景先还原一下项目背景一台设备上两台PLC分站控制A站负责主工艺链B站负责辅助工位中间需要交换一段速度设定值、几个启停状态位、一个计数值。距离不过几米但中间隔着几个电柜走串口要额外布线走IO点对点要占用大量输入输出点而且以后客户还要把这两台PLC的数据统一采集到车间管理系统所以一开始就把目标锁定在以太网通讯上。这种场景在产线改造里特别常见。两台PLC之间并不需要毫秒级的硬实时同步Modbus TCP这种请求/响应模式的通讯完全够用。这里有个核心判断如果你的数据交换周期要求小于10ms同时又要求两边PLC程序同步扫描那应该考虑PROFINET或EtherCAT这种实时以太网甚至直接用IO映射。但如果是1秒刷新一次温度、速度、计数、状态Modbus TCP就是性价比最高的选择没有之一。1.2 Modbus TCP相比Modbus RTU和私有协议的优势很多老工程师第一反应是做Modbus RTU用485线把两个串口串起来。这当然可行但Modbus RTU在现场有几个绕不开的问题波特率、数据位、校验位必须完全一致485的A/B线一旦接反就完全不通长距离还要考虑终端电阻和地电位差。抗干扰上虽然485是差分信号但在变频器多的车间里通讯报CRC错是家常便饭。而且RTU是半双工一问一答速率上限就是115200bps实际应用基本9600或者19200。Modbus TCP把这些问题一下子全绕开了。物理层是标准以太网RJ45网线或者光缆都行不用管极性链路层有TCP保证丢包重传由协议栈处理数据帧里没有CRC校验这一说因为TCP的可靠性把这一层兜住了。更关键的是Modbus TCP的报文结构简单透明用Wireshark或者Modbus Poll这种工具一看就懂出了问题能快速定位到具体字节这一点在企业里做设备维护特别重要。再说私有以太网协议。像汇川自己的H3U和H5U之间用厂家私有协议确实支持很多高级功能比如直接读写软元件、远程启停、上下载程序但私有协议的绑定关系强不同系列之间的兼容性没那么透明。Modbus TCP是行业通用标准今天两台汇川PLC之间用明天换成西门子、三菱、施耐德后天接上位机、触摸屏、第三方仪表协议层面完全不需要改程序结构最多改改地址映射。这种通用性带来的维护价值往往比一时的性能差别重要得多。1.3 网络连接方式与IP地址规划设备距离近的话最推荐直接用网线把两台PLC的以太网口连起来不需要交换机。很多人担心直连网线是不是要用交叉线其实现在的PLC网口都是自适应网口普通超五类直通线就行。但要注意直连时两台PLC之间没有交换机做隔离如果其中一台关机或者网口故障另一台会持续发ARP请求在有些老型号固件上可能出现CPU占用升高的现象。所以我现在更倾向于在项目初期就配一台工业交换机哪怕只有两个口也能避免很多奇怪问题。IP规划是我每次都要反复强调的。Modbus TCP的目标地址就是IP地址一旦IP段不一致建连都建不上。我的习惯是主站PLC固定一个IP例如192.168.1.10从站PLC固定另一个IP例如192.168.1.20子网掩码统一255.255.255.0网关可以不填或者填实际管理网段网关。这里有个容易忽略的点有些汇川PLC出厂默认IP是192.168.0.10这类地址现场如果电脑也在这个网段插上去就能搜到一旦你改了PLC的IP电脑的IP也要跟着改到同一网段否则又搜不到了。我的经验是先在交换机上接电脑用汇川的AutoShop或者InoProShop的在线扫描功能把PLC当前IP记下来再规划最终地址不要凭感觉在程序里随便填。2. 寄存器模型和地址映射通讯前先把“内存地图”搞清楚2.1 Modbus的四种数据对象Modbus协议定义了四种数据对象很多人一开始总搞混我按使用频率给你理一遍对象类型对应地址范围读写属性单位线圈Coil00001 ~ 09999可读可写位离散输入Discrete Input10001 ~ 19999只读位输入寄存器Input Register30001 ~ 39999只读16位保持寄存器Holding Register40001 ~ 49999可读可写16位PLC与PLC之间通讯绝大多数场景只需要保持寄存器。原因很直接保持寄存器既能读又能写而且按16位寄存器访问既能装整数、浮点数也能用位操作拆出开关量。线圈虽然也能用但批量读写线圈的效率低一般只有和第三方设备配合时才用。重点说保持寄存器的地址偏移。Modbus协议中功能码03读保持寄存器请求报文里的起始地址是从0开始的协议地址。0号协议地址对应经典的40001号地址1号对应40002以此类推。这个“1和0的差别”就是无数现场故障的根源后面我会专门讲。2.2 从站PLC的数据区映射在汇川PLC里Modbus从站的数据区映射方式因系列不同略有差别但核心逻辑一致把PLC内部的存储器映射到Modbus地址空间外部主站通过读写Modbus地址实际上就在操作PLC内部寄存器。以汇川中小型PLC为例H3U、Easy系列AutoShop的Modbus TCP从站功能一般通过功能块初始化。你把从站功能块的数据区起始地址设为D0那么Modbus地址40001就对应PLC的D040002对应D140003对应D2以此类推。假设你要让主站读从站的D100~D109这10个寄存器那么主站读的Modbus地址就是40101~4011040001偏移100。汇川中型PLCAM系列、AC800系列用InoProShop组态Modbus TCP从站通常在设备组态里配置。IO映射表里会明确列出Modbus地址和PLC全局变量之间的对应关系你可以把%MW0映射到Modbus保持寄存器40001。这种组态方式的好处是映射关系非常直观不需要在程序里写地址偏移。说实话地址映射这件事我建议从站侧把数据集中放到连续的一段区域比如D100~D199专门作为Modbus通讯区。这样主站程序只需要连续读取既简化逻辑也方便后续给上位机留接口。不要今天映射D10明天映射D500自己后面都会被绕晕。2.3 主站侧的Offset到底填几这是我这几年被问得最多的问题。主站读取从站第一个保持寄存器40001时功能块里的起始地址Offset应该填0而不是1。因为Modbus TCP报文里的“起始地址”字段是协议地址从0开始。如果你填了1你实际读到的是从站的40002也就是第二个寄存器数据全错位了。有些品牌PLC或者组态软件为了让工程师直观允许你直接填40001软件内部自动减1。但汇川的Modbus通讯功能块我接触到的几个版本都要求填协议地址也就是40001对应Offset040002对应Offset1。这个细节不搞清楚程序里所有地址都会整体偏移一个寄存器而且这种错位比断连更难排查因为通讯是正常的数据也能传就是值对不上。3. 从站配置与主站编程参数和指令一次跑通3.1 从站端开启Modbus TCP服务不同系列操作位置不一样但核心三步是一样的。第一步设置从站PLC的IP地址。在AutoShop或InoProShop的PLC参数里找到以太网配置把IP设为规划好的地址例如192.168.1.20。设置完必须重新上电让网口按新参数初始化。第二步开启Modbus TCP从站功能。有的系列在系统参数里勾选“Modbus TCP Server”有的需要调用一个从站初始化功能块并保持使能。关键参数有两个端口号默认502一般不需要改和单元标识符Unit ID通常填1。Unit ID这个参数要特别记一下因为主站请求帧里的单元标识符必须和从站这个设置一致否则从站会返回异常或直接忽略请求。第三步把需要交换的数据整理到连续寄存器区。前文说了建议用一段连续的D区比如D100~D149作为通讯缓冲区。程序里用传送指令或者结构体赋值把真正要交换的数据搬运到这个缓冲区。搞明白“PLC内部数据”和“Modbus通讯区”之间的搬运关系是从站编程的重点。3.2 主站初始化通讯MBUS_CTRL主站端的编程分为初始化和数据请求两部分。初始化用MBUS_CTRL功能块相当于建立TCP连接的管理入口。MBUS_CTRL的关键参数Mode1TCP协议SlaveIP填从站IP例如192.168.1.20Port填502TimeOut填通讯超时时间我一般设100ms到300ms太短容易超时误报太长会拖慢故障响应。EN端用常通触点让通讯管理始终运行。功能块输出Done在初始化完成后变ONError和ErrorCode用于诊断。这里有个常见误区很多人以为MBUS_CTRL的EN只要接通TCP连接就一直保持。实际是只要初始化正确主站会在每次请求时自动维护连接。Modbus TCP是基于TCP长连接的不是每次请求都重新握手所以不需要你自己去管理Socket生命周期。另一个容易被忽略的点MBUS_CTRL只能在主站PLC里调用一次不能重复实例化。如果你同时要和多台从站通讯正确做法是MBUS_CTRL只做初始化后面对每一台从站的读写请求都通过各自的MBUS_MSG功能块进行轮询调度。3.3 周期读写数据MBUS_MSGMBUS_MSG是主站实际发送读写请求的功能块。它靠REQ引脚的上升沿触发一次请求。一次典型的读保持寄存器请求参数这样填REQ用定时器或程序逻辑产生一个脉冲信号建议一个扫描周期内只触发一次完成后再触发下一次。SlaveID从站的Unit ID和从站设置保持一致如1。FuncCode功能码读保持寄存器填3写单个保持寄存器填6写多个保持寄存器填16。Offset起始协议地址40001对应040002对应1以此类推。Len读写的寄存器个数。DataAddr读写的数据存放在主站PLC内部的起始地址。读请求读回来的数据会写入从这里开始的连续寄存器写请求要发送的数据从这里读取。用梯形图写的时候REQ我建议用一个自复位定时器产生周期脉冲比如每200ms触发一次读写。为什么不用常通因为MBUS_MSG执行需要时间如果REQ每个扫描周期都是上升沿会造成请求排队堆积反而容易超时。正确节奏是触发一次等待Done或Error信号再触发下一次。做轮询多台从站时尤其要注意必须等上一次请求完成后再切换下一次请求不能同时驱动多条MBUS_MSG。汇川中型PLC在InoProShop里用的是功能块比如FB_MBReadRegs、FB_MBWriteRegs参数逻辑大同小异。核心区别是中型的组态属性更强网络连接参数可以在设备配置里预先定义程序里只需要指定读写数据块。但地址映射和功能码的规则完全一致一通百通。3.4 一个完整的读写流程我习惯在程序里这样组织初始化段放MBUS_CTRL轮询段放一组MBUS_MSG分别完成读从站数据功能码3和写主站数据功能码16。举个具体例子主站要把D0~D9这10个值发给从站同时读取从站D100~D109的值。主站程序需要两条MBUS_MSG第一条第写多个寄存器FuncCode16Offset0对应从站的40001如果从站映射的起始地址是D100那这里Offset就填100-1000不对40001对应D0要写到D100则是偏移量100但这里需要根据从站映射计算。我重新想清楚例子假设从站映射起始是D10040001对应D100那么主站要写D100Offset应该填0对应协议地址0映射到从站第一个保持寄存器即D100。好那么第二条读从站D100也是Offset0。为避免混淆从站数据区统一规划从站D100~D109映射到Modbus保持寄存器40001~40010主站Offset0Len10。所以第一条写请求FuncCode16Offset0Len10DataAddr主站D0把主站D0~D9写入从站D100~D109。 第二条读请求FuncCode3Offset0Len10DataAddr主站D100把从站D100~D109读回主站D100~D109。这里故意把主站发送区和接收区用不同段避免数据在通讯过程中被覆盖。发送区D0~D9在程序其他部分赋值接收区D100~D109在程序其他部分使用。通讯缓冲区独立出来是PLC通讯编程里特别重要的设计习惯。4. 报文级验证数据对不上时把问题定位到字节4.1 Modbus TCP报文结构有时候程序看着都对通讯状态也正常但读回来的数就是不对。这时候报文分析工具是唯一能让你从“猜”变成“查”的手段。Modbus TCP报文结构很短学一次就会。一个完整的MBAP头加PDU字段长度说明事务标识符 Transaction ID2字节请求和响应一一对应用于匹配协议标识符 Protocol ID2字节Modbus固定为0x0000长度 Length2字节后面字节数单元标识符 Unit ID1字节从站地址功能码 Function Code1字节03/06/16等数据 DataN字节起始地址、数量、值等用Wireshark抓包或者Modbus Poll作为测试主站都能清晰看到这些字段。4.2 读请求逐字节解析假设主站要读从站Unit ID1协议地址0开始、连续10个保持寄存器。请求报文是00 01 00 00 00 06 01 03 00 00 00 0A逐段拆开00 01是事务标识符这条请求的编号00 00是协议标识符Modbus固定为000 06是长度表示后面还有6个字节01是单元标识符03是功能码读保持寄存器00 00是起始协议地址对应4000100 0A是数量10个寄存器。如果程序里读出来的数据整体偏移一个位置你回头看这条报文很大概率是00 00变成了00 01。这就是前面说的Offset填错问题。通过抓包看一眼起始地址字段问题立刻暴露。响应报文是这样的00 01 00 00 00 17 01 03 14 00 64 00 65 ...00 17是长度24字节14是字节数10个寄存器20字节后面就是20字节数据。00 64就是十进制的100说明第一个寄存器读出来是100。逐个比对你就知道从站映射和主站解析是否一致。4.3 异常响应码的排查思路如果请求本身有问题从站会回异常帧。异常帧的功能码会在原功能码最高位置1。比如读保持寄存器03异常时返回83后面跟一个异常码异常码含义常见原因01非法功能从站不支持该功能码02非法数据地址起始地址或数量超出映射范围03非法数据值写请求里的数据值非法04从站设备故障从站内部错误需查从站程序现场遇到02异常码的频次最高。从站允许访问的寄存器范围是有限的主站请求的地址数量超过了映射区末端从站就直接拒绝。比如从站只映射了100个字你用Len100去读起始Offset又是50要求读到150个字那必然超范围。解决办法是检查从站的映射区长度和主站的Len保证起始地址加数量不越界。另外Modbus Poll这个软件确实好用。它可以直接当Modbus TCP主站填上从站IP、端口、Unit ID、功能码、起始地址和长度一键读取。通讯不上它会在界面上报错通讯上但地址错位读回来的数值会明显不合理。我调试汇川PLC之间通讯时习惯先用Modbus Poll验证从站的地址映射确认从站没问题了再回到主站PLC程序里排查自己的请求参数。这个习惯帮我少走了很多弯路。5. 现场调试踩过的坑从“通讯正常”到“数据正确”的距离5.1 连接不上先查三件事通讯不上我从不急着改程序按顺序查三件事。第一是IP地址。两台PLC必须在同一网段尤其子网掩码要一致。这里有个小细节有些汇川PLC网口默认是自动获取IPDHCP如果现场没有DHCP服务器它会一直拿不到地址表现为怎么都连不上。进PLC参数里把IP改成静态地址重新上电。第二是Modbus TCP服务有没有真正使能。有些系列只开了以太网口并不等于开了Modbus TCP Server。必须在参数里勾选/使能很多工程师PLC参数里只设置了IP地址没注意服务开关导致电脑ping得通PLC但Modbus请求完全没响应。第三是端口和Unit ID。端口默认502但个别设备可能改成过其他端口主站里填的Slave ID/Unit ID和从站实际配置不一致连接也会失败。这里尤其强调Modbus TCP因为基于TCPIP决定了连接Unit ID看起来不那么重要但很多从站确实会校验Unit ID不一致时会返回异常码或者干脆不响应。5.2 地址偏移一位的经典错误我必须承认这个坑我自己也踩过。项目里主站程序明明写的是读取从站D0请求报文的起始Offset填了1结果读回来的是D1。当时在设备上还在想为什么速度值偶尔差一拍拆了报文一看起始地址字段是00 01问题当场暴露。这个错误之所以高发是因为不少上位机组态软件、触摸屏的Modbus驱动里地址定义是从1开始的工程师在屏幕上写40001就是第一个寄存器程序内部自动处理偏移。但PLC功能块里要求填协议地址0开头两边习惯一冲突就容易把1填进去。我的建议很简单在程序注释里写清楚“Offset0对应40001”尤其多人维护时这条注释能救命。如果项目里地址比较多还可以在程序里做一个地址映射常量表明确每个功能块读取的是40001还是40020一目了然。5.3 大批量读写与通讯超时Modbus协议规定一次请求最多读125个保持寄存器写多个寄存器最多123个。这是协议上限和PLC无关。如果你需要同步的数据超过这个数必须拆成多次请求。还有个常见性能问题一次读1个寄存器需要200ms连续读10个寄存器还是200ms因为一次TCP请求的时间成本主要在往返时延上不在数据字节数上。所以批量采集数据时尽量用一条请求读连续区域而不是一条请求读一个点。我在一个项目里看到别人用循环一次读一个寄存器30个点轮询一遍要6秒改成一条Len30的读请求后刷新速度直接提升到200ms以内效果立竿见影。超时时间也要合理设置。TCP建连和数据交换都有时延尤其经过交换机时几十毫秒的抖动很常见。TimeOut设得太短比如20ms会在现场环境稍有波动时就报超时太长比如1秒又会让故障响应变得迟钝。我现在一般设100~300ms工业交换机环境下这个区间比较稳健。5.4 汇川不同系列软件的差异汇川PLC产品线分得很细这恰恰是很多新手容易懵的地方。H1U/H2U/H3U、Easy系列这类中小型PLC编程软件是AutoShopModbus TCP功能块通常是MBUS_CTRL、MBUS_MSG编程风格接近三菱。AM400/AM600、AC800这类中型PLC编程软件是InoProShop基于CODESYS平台Modbus TCP的库函数和功能块名不一样组态方式也有区别但Modbus协议本身是同一套东西。AM系列在InoProShop里用ModbusTCP库使用FB_MBReadRegs这类功能块时本质也是组装一条Modbus请求报文。最怕的是工程师习惯了一套软件的参数命名到了另一套软件找不到对应项就开始怀疑通讯配置有问题。我建议不管用哪套软件心里始终装着Modbus最底层的五个要素IP、端口、Unit ID、功能码、寄存器地址。无论在哪个平台编程抓住这五个要素万变不离其宗。另外做大型项目时多台汇川PLC之间通讯建议把所有通讯配置和地址映射整理成一个表格每个PLC一张纸。哪台是主站、哪台是从站、哪些地址是主站写入、哪些是从站读取、IP是什么、Unit ID是什么清清楚楚。Modbus TCP这个协议本身很简单真正累的是维护这种跨设备的映射关系。最后还有一个容易忽略的细节程序里最好加一个通讯心跳监测。主站周期性地往从站写一个递增计数器从站程序判断这个计数器是否在刷新来判定通讯是否正常。一旦超时未刷新两台PLC都能在自己本地做报警或者联动停车。这套心跳逻辑不复杂但在现场调试和后续维护时真的能省掉大把时间。通讯建立只是第一步把通讯可靠性和可诊断性做好项目才算真正交付。