拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

汇川PLC之间Modbus TCP通讯实战:地址映射与调试全解析

汇川PLC之间Modbus TCP通讯实战:地址映射与调试全解析

上个月刚做完一个两台汇川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协议定义了四种数据对象,很多人一开始总搞混,我按使用频率给你理一遍:

对象类型对应地址范围读写属性单位
线圈(Coil)00001 ~ 09999可读可写位
离散输入(Discrete Input)10001 ~ 19999只读位
输入寄存器(Input Register)30001 ~ 39999只读16位
保持寄存器(Holding Register)40001 ~ 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的D0,40002对应D1,40003对应D2,以此类推。假设你要让主站读从站的D100~D109这10个寄存器,那么主站读的Modbus地址就是40101~40110(40001偏移100)。

汇川中型PLC(AM系列、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对应Offset=0,40002对应Offset=1。这个细节不搞清楚,程序里所有地址都会整体偏移一个寄存器,而且这种错位比断连更难排查,因为通讯是正常的,数据也能传,就是值对不上。

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的关键参数:Mode=1(TCP协议);SlaveIP填从站IP,例如192.168.1.20;Port填502;TimeOut填通讯超时时间,我一般设100ms到300ms,太短容易超时误报,太长会拖慢故障响应。EN端用常通触点,让通讯管理始终运行。功能块输出Done在初始化完成后变ON,Error和ErrorCode用于诊断。

这里有个常见误区:很多人以为MBUS_CTRL的EN只要接通,TCP连接就一直保持。实际是,只要初始化正确,主站会在每次请求时自动维护连接。Modbus TCP是基于TCP长连接的,不是每次请求都重新握手,所以不需要你自己去管理Socket生命周期。

另一个容易被忽略的点:MBUS_CTRL只能在主站PLC里调用一次,不能重复实例化。如果你同时要和多台从站通讯,正确做法是MBUS_CTRL只做初始化,后面对每一台从站的读写请求都通过各自的MBUS_MSG功能块进行轮询调度。

3.3 周期读写数据:MBUS_MSG

MBUS_MSG是主站实际发送读写请求的功能块。它靠REQ引脚的上升沿触发一次请求。一次典型的读保持寄存器请求,参数这样填:

  • REQ:用定时器或程序逻辑产生一个脉冲信号,建议一个扫描周期内只触发一次,完成后再触发下一次。
  • SlaveID:从站的Unit ID,和从站设置保持一致(如1)。
  • FuncCode:功能码,读保持寄存器填3,写单个保持寄存器填6,写多个保持寄存器填16。
  • Offset:起始协议地址,40001对应0,40002对应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:

第一条第,写多个寄存器,FuncCode=16,Offset=0(对应从站的40001,如果从站映射的起始地址是D100,那这里Offset就填100-100=0?不对,40001对应D0,要写到D100则是偏移量100,但这里需要根据从站映射计算)。

我重新想清楚例子:假设从站映射起始是D100,40001对应D100,那么主站要写D100,Offset应该填0,对应协议地址0映射到从站第一个保持寄存器即D100。好,那么第二条读从站D100也是Offset=0。为避免混淆,从站数据区统一规划:从站D100~D109映射到Modbus保持寄存器40001~40010,主站Offset=0,Len=10。

所以第一条写请求:FuncCode=16,Offset=0,Len=10,DataAddr=主站D0(把主站D0~D9写入从站D100~D109)。 第二条读请求:FuncCode=3,Offset=0,Len=10,DataAddr=主站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 ID=1,协议地址0开始、连续10个保持寄存器。请求报文是:

00 01 00 00 00 06 01 03 00 00 00 0A

逐段拆开:00 01是事务标识符,这条请求的编号;00 00是协议标识符,Modbus固定为0;00 06是长度,表示后面还有6个字节;01是单元标识符;03是功能码,读保持寄存器;00 00是起始协议地址,对应40001;00 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个字,你用Len=100去读,起始Offset又是50,要求读到150个字,那必然超范围。解决办法是检查从站的映射区长度和主站的Len,保证起始地址加数量不越界。

另外,Modbus Poll这个软件确实好用。它可以直接当Modbus TCP主站,填上从站IP、端口、Unit ID、功能码、起始地址和长度,一键读取。通讯不上它会在界面上报错;通讯上但地址错位,读回来的数值会明显不合理。我调试汇川PLC之间通讯时,习惯先用Modbus Poll验证从站的地址映射,确认从站没问题了,再回到主站PLC程序里排查自己的请求参数。这个习惯帮我少走了很多弯路。

5. 现场调试踩过的坑:从“通讯正常”到“数据正确”的距离

5.1 连接不上:先查三件事

通讯不上,我从不急着改程序,按顺序查三件事。

第一是IP地址。两台PLC必须在同一网段,尤其子网掩码要一致。这里有个小细节:有些汇川PLC网口默认是自动获取IP(DHCP),如果现场没有DHCP服务器,它会一直拿不到地址,表现为怎么都连不上。进PLC参数里把IP改成静态地址,重新上电。

第二是Modbus TCP服务有没有真正使能。有些系列只开了以太网口,并不等于开了Modbus TCP Server。必须在参数里勾选/使能,很多工程师PLC参数里只设置了IP地址,没注意服务开关,导致电脑ping得通PLC,但Modbus请求完全没响应。

第三是端口和Unit ID。端口默认502,但个别设备可能改成过其他端口;主站里填的Slave ID/Unit ID和从站实际配置不一致,连接也会失败。这里尤其强调:Modbus TCP因为基于TCP,IP决定了连接,Unit ID看起来不那么重要,但很多从站确实会校验Unit ID,不一致时会返回异常码或者干脆不响应。

5.2 地址偏移一位的经典错误

我必须承认,这个坑我自己也踩过。项目里主站程序明明写的是读取从站D0,请求报文的起始Offset填了1,结果读回来的是D1。当时在设备上还在想,为什么速度值偶尔差一拍?拆了报文一看,起始地址字段是00 01,问题当场暴露。

这个错误之所以高发,是因为不少上位机组态软件、触摸屏的Modbus驱动里,地址定义是从1开始的,工程师在屏幕上写40001就是第一个寄存器,程序内部自动处理偏移。但PLC功能块里要求填协议地址,0开头,两边习惯一冲突,就容易把1填进去。

我的建议很简单:在程序注释里写清楚“Offset=0对应40001”,尤其多人维护时,这条注释能救命。如果项目里地址比较多,还可以在程序里做一个地址映射常量表,明确每个功能块读取的是40001还是40020,一目了然。

5.3 大批量读写与通讯超时

Modbus协议规定,一次请求最多读125个保持寄存器,写多个寄存器最多123个。这是协议上限,和PLC无关。如果你需要同步的数据超过这个数,必须拆成多次请求。

还有个常见性能问题:一次读1个寄存器需要200ms,连续读10个寄存器还是200ms,因为一次TCP请求的时间成本主要在往返时延上,不在数据字节数上。所以批量采集数据时,尽量用一条请求读连续区域,而不是一条请求读一个点。我在一个项目里看到别人用循环一次读一个寄存器,30个点轮询一遍要6秒,改成一条Len=30的读请求后,刷新速度直接提升到200ms以内,效果立竿见影。

超时时间也要合理设置。TCP建连和数据交换都有时延,尤其经过交换机时,几十毫秒的抖动很常见。TimeOut设得太短(比如20ms)会在现场环境稍有波动时就报超时;太长(比如1秒)又会让故障响应变得迟钝。我现在一般设100~300ms,工业交换机环境下这个区间比较稳健。

5.4 汇川不同系列软件的差异

汇川PLC产品线分得很细,这恰恰是很多新手容易懵的地方。

H1U/H2U/H3U、Easy系列这类中小型PLC,编程软件是AutoShop,Modbus 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都能在自己本地做报警或者联动停车。这套心跳逻辑不复杂,但在现场调试和后续维护时真的能省掉大把时间。通讯建立只是第一步,把通讯可靠性和可诊断性做好,项目才算真正交付。

返回列表