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

资讯详情

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

MCGS触摸屏ModbusTCP数据写入实战:报文解析与地址偏移避坑指南

MCGS触摸屏ModbusTCP数据写入实战:报文解析与地址偏移避坑指南 前两天还有个朋友在群里问MCGS触摸屏和电脑走ModbusTCP数据怎么都写不进去报文也抓了就是不知道问题出在哪。我把他的配置截图看了一眼立刻发现问题了通道属性里地址写的是40001可实际报文里发出去的是0000差的那个1正好是协议里最经典的偏移陷阱。这种问题在工业现场太常见了。MCGS触摸屏作为国内组态屏里出货量很大的品牌几乎每个做自动化项目的人都会碰到而ModbusTCP又是现在上位机和触摸屏通讯的主流方式。数据写入、寄存器映射、报文解析这三件事看着简单真要落地的时候坑是一个接一个。这篇文章我就拿真实项目经验来写把MCGS触摸屏和电脑之间通过ModbusTCP做数据写入的思路、报文细节、实操步骤、抓包验证以及我踩过的那些坑一次性说透。适合正在调试触摸屏通讯、或者想从串口转到以太网通讯的工程师也适合刚入行但想搞懂ModbusTCP协议本质的朋友。1. 先定方案MCGS为什么要走ModbusTCP1.1 以太网通讯的优势和场景判断很多人用习惯了MCGS的串口通讯232也好、485也好总觉得够用就行。但一旦现场有数据量要求、通讯距离要求、或者要接多个上位机系统的时候串口就非常难受了。ModbusTCP直接把通讯迁移到以太网速度从原来的9600、19200波特率提升到百兆甚至千兆而且网络拓扑灵活一台触摸屏可以被好几台上位机同时访问。我见过不少项目触摸屏本地显示是一套中控室的数据采集系统又是一套两套系统都要从触摸屏里读数据。用串口只能点对点中控室要想拿数据就得额外加硬件。换成ModbusTCP一根网线进交换机谁都能访问成本还更低。另外一个场景是数据写入。项目里经常需要中控室远程修改触摸屏里的配方参数、报警阈值、或者临时修正某个设定值。串口要写数据得先解决主从冲突以太网就没这么多麻烦ModbusTCP天然支持上位机主动写入触摸屏只要把对应变量配置成可读写就行。1.2 主站和从站先搞清楚谁写谁读ModbusTCP里面只有两种角色主站Master/Client和从站Slave/Server。很多人一上来就找“主站从站怎么设置”结果把自己绕晕了。说白了就是一句话谁主动发起请求谁就是主站谁被动响应请求谁就是从站。MCGS触摸屏这款产品比较灵活它既可以当从站让电脑来读写它内部的变量也可以当主站主动去读写电脑上或者其他设备的数据。这两种角色对应完全不同的配置方式。如果电脑上位机要向触摸屏写入数据那电脑是主站、触摸屏是从站。这种情况下你在触摸屏里要做的是把内部的变量映射到Modbus从站寄存器上让外部可以通过Modbus地址访问到它。这时候的关键点是触摸屏的“本地设备”属性你得把触摸屏侧当成一个被访问的设备来配置。反过来如果是触摸屏主动把数据写到电脑比如电脑上跑了一个从站模拟器或者数据服务程序那触摸屏是主站、电脑是从站。这时候你在MCGS的设备窗口里添加的是“ModbusTCP主站设备”远程IP指向电脑再为要写入的数据对象建立通道映射。我去过不少现场帮人排查发现大多数“写不进去”的问题其实是在第一步就搞反了角色。所以建议你先拿笔写下来你的项目里谁主动发起通讯是电脑读触摸屏还是触摸屏读电脑这个判断准了后面所有配置才不会歪。1.3 选ModbusTCP而不是ModbusRTU的原因可能有人会问MCGS触摸屏自己也支持ModbusRTU为什么非要选TCP我的看法是能走以太网就尽量别走串口。ModbusRTU的报文里带CRC校验传输距离在485模式下可以到一千米但它有两个硬伤第一是速率限制第二是主从一对一的架构一个主站轮询多个从站通讯周期和数据量上来之后会非常吃力。ModbusTCP把校验交给了TCP/IP协议栈底层由网卡和操作系统处理报文里直接去掉CRC长度字段帮你在应用层做了完整性检查。再加上全双工通讯主站发了请求不用等上一个响应就能继续往下走对大数据量的读写操作友好得多。当然不是所有场景都适合以太网。有些老设备只有485口那就老老实实用RTU。但如果你的设备有网口或者加了串口服务器我建议优先走ModbusTCP调试体验和后续扩展性都好很多。2. 协议拆解ModbusTCP报文里到底装的什么2.1 MBAP报文头7个字节的门牌号ModbusTCP报文由两部分组成MBAP头Modbus Application Protocol header加PDUProtocol Data Unit。MBAP头一共7个字节功能就是给每个请求和响应编号定位让TCP链接上跑的多个Modbus事务互不混淆。前两个字节是事务处理标识符Transaction Identifier客户端每次发请求时自己递增服务端响应时会把相同的值复制回来。这个字段主要是为了在同一个TCP连接上同时发很多请求时能把响应和请求对应上。第二个字段是协议标识符Protocol Identifier固定是0x0000表示这是Modbus协议。如果以后扩展别的协议这个字段就会变。接下来是两个字节的长度字段Length这个长度指的是从“单元标识符”开始往后的所有字节数也就是单元标识符1个字节加PDU的字节数。最后一个字节是单元标识符Unit Identifier用来区分链路上挂在同一网关后面的不同从站设备。在纯TCP直连、没有网关的情况下一般填0x01或者0xFF都行但要跟从站配置一致。很多人在报文解析的时候容易把长度字段算错结果在自定义程序里解析失败。记着长度字段只统计它自己后面的、剩下还没算进去的字节不包括MBAP头本身的7个字节。2.2 PDU数据单元功能码和寄存器地址去掉MBAP头之后剩下的就是PDU。PDU的核心是功能码和具体数据。功能码告诉从站“你要干什么”寄存器地址告诉从站“你在哪个位置干”。Modbus协议里把数据分成几类常见的有线圈Coil、离散输入Discrete Input、输入寄存器Input Register、保持寄存器Holding Register。其中和MCGS触摸屏变量最相关的是保持寄存器因为它既支持读也支持写。线圈支持读写通常对应开关量。输入寄存器只读只能读不能写。在ModbusTCP的报文中寄存器地址是16位的范围从0x0000到0xFFFF也就是从0到65535。但很多设备厂家和组态软件在界面上显示的时候习惯用PLC风格的“4xxxx”来表示保持寄存器比如40001、40002这个是从1开始编号的。所以你在写程序或者配置通道的时候必须把界面显示的地址减1才是真正报文里要发送的地址。这个“偏移1”的问题是Modbus调试里最常见的错误来源。比如触摸屏里配置一个通道地址是40001你在上位机里发请求时寄存器地址要写0不是1。2.3 数据写入的两种姿势06写单寄存器和16写多寄存器ModbusTCP写数据主要用两个功能码一个是0x06写单个保持寄存器另一个是0x10十进制16写多个保持寄存器。对应到数据比如你要修改触摸屏里的一个温度设定值用一个寄存器就能存下来的用0x06就够了如果一次要写一串数据比如一组配方参数连续存放那就用0x10一条请求全发过去。0x10的报文结构比0x06多几个字段起始寄存器地址2个字节、寄存器数量2个字节、后面跟的字节数1个字节、然后是要写入的数据。这个“字节数”字段表示后面数据的实际字节长度等于寄存器数量乘以2因为在Modbus协议里一个寄存器固定是16位两个字节。我建议你写多寄存器的时候寄存器数量不要一次搞太大。有些从站设备内部缓冲区有限一次写几百个寄存器容易超时或者直接报错。稳妥的做法是分批次写每次控制在120个寄存器以内这个数值是我在多个设备上试出来的算是安全范围。2.4 三个关键细节字节序、地址偏移、通信方向字节序是Modbus协议里水最深的地方。Modbus标准规定寄存器内部的数据是大端字节序也就是高字节在前、低字节在后。比如0x1234这个16位数值在报文里传输顺序就是0x12、0x34。但是在多数PC上用Wireshark抓包看时你会看到原始字节流确实是大端排列的。真正坑人的是32位数据比如浮点数、32位整数。MCGS触摸屏里一个32位数据对象会占用两个连续的保持寄存器。不同设备到底哪个寄存器存高16位、哪个存低16位是不一致的有的设备高字在前有的低字在前。ModbusTCP标准里没有明确规定多寄存器数据的字序这就导致你在不同品牌的触摸屏和PLC之间互通的时候经常看到数据被拆成两个错位的整数。地址偏移的问题上面说了就是界面显示地址和通讯报文地址之间差1。还有一个值得注意的东西是通讯方向。ModbusTCP是同步请求响应的模式客户端发请求给服务端服务端处理完以后返回响应。如果你在上位机里发了一个写请求没有收到响应那你就要怀疑是网络链路问题还是从站本身没有正确支持这个功能码。从报文层面看正常响应里会把你发的功能码原样返回如果从站返回的功能码最高位置1比如0x86、0x90那说明它报错了错误码在数据字节里常见的非法功能码是0x01、非法数据地址是0x02、非法数据值是0x03。3. 实操从PC向MCGS触摸屏写入数据3.1 MCGS设备组态与变量映射先说MCGS作为从站的场景。在MCGS的组态软件里默认是工程环境。你要做的是在“设备窗口”里添加一个“通用TCPIP父设备”然后在父设备下面挂一个“ModbusTCP从站设备”。父设备负责网络参数配置这里要填触摸屏的本机IP地址和端口ModbusTCP默认端口是502。子设备负责功能配置其中有一个关键的属性叫“从站地址”这个地址要和上位机请求里的单元标识符对应上。如果上位机请求里单元标识符填的是0x01那这里的从站地址就填1。接下来是变量的映射。在MCGS实时数据库中先建立你要暴露给外部访问的变量比如“温度设定值”数据类型可以为数值型。然后在ModbusTCP从站设备的通道配置里把变量和寄存器地址绑定。通道地址的填写格式要特别注意比如保持寄存器区填写40001那外部要访问时用的报文地址就是0。如果你填了40001作为起始地址而且这个变量是32位的那它实际会占用40001和40002两个寄存器上位机访问的时候要注意连续读两个寄存器才能拼出完整数值。这里顺便提一句MCGS画面里的数据对象一旦组成了通道它的读写属性也要检查。点开通道属性里面会问你这个通道是“只读”还是“读写”。如果你想从外部写入那必须选“读写”只选“只读”的话上位机写入请求发过来触摸屏可能直接拒绝或者不更新。3.2 用Modbus Poll验证写入装一个Modbus Poll软件这是Modbus调试的标配。用Modbus Poll模拟电脑作为主站去连MCGS触摸屏配置连接参数从站IP填触摸屏的IP端口502从站ID和MCGS里配置的从站地址一致。连上之后先读一遍确认通讯正常。然后切换到写入功能选“06 Write Single Register”把地址填成你通道地址减1之后的值。比如MCGS里配置的是40001这里填0数据填一个测试值比如12345点发送。正常情况下MCGS实时数据库里你绑定的那个变量应该立刻变成12345触摸屏画面如果绑定了这个变量显示也会同步刷新。如果发送之后Modbus Poll返回超时或者提示“Exception Response”那说明你的配置还有问题。第一步先检查IP和端口能不能通用ping测一下IP用Telnet测一下端口。502端口如果被系统防火墙挡了Modbus Poll这边就是一直超时触摸屏侧没有任何反应。3.3 用Python写一个写入Demo用现成工具验证只是一部分有时候项目要落地还是得自己写代码。我比较推荐用Python的pymodbus库几十行代码就能搞定一个读写客户端。下面是实际可用的写入Demo已用pymodbus 3.x版本测试过。from pymodbus.client import ModbusTcpClient client ModbusTcpClient(192.168.1.10, port502, timeout3) if not client.connect(): print(连接失败检查IP和端口) exit(1) # 写单个保持寄存器地址0对应触摸屏通道40001 result client.write_register(0, 12345, slave1) if result.isError(): print(写入失败:, result) else: print(写入成功) client.close()要注意pymodbus旧版本和新版本API差异很大如果你是1.x版本写法可能是client.write_register(0, 12345, unit1)新版本改成slave了。还有一个容易踩的坑是pymodbus默认的从站ID参数不填的时候是0而很多MCGS从站配置的单元标识符是1所以这里要显式传slave1。如果要写浮点数到MCGS的32位变量不能直接把float丢进write_register得先把浮点数拆成两个16位寄存器值按MCGS的字序排列好再发送。拿常见的IEEE 754浮点数来说先转成32位无符号整数然后取高16位和低16位再分别写入两个连续的寄存器。import struct def float_to_registers(val): packed struct.pack(f, val) # 大端字节序的浮点数 high, low struct.unpack(HH, packed) return high, low high, low float_to_registers(25.5) client.write_registers(10, [high, low], slave1)MCGS实际用的字序未必是标准的大端字序很可能需要调换high和low的顺序。这个没有统一的答案必须实测确认最好的办法就是先写一个已知值比如25.5然后在触摸屏上查看变量显示是否等于25.5如果不是把顺序换一下再试。3.4 Wireshark抓包逐字节解析调试ModbusTCPWireshark是必须学会的。电脑上跑Wireshark过滤条件填tcp.port 502然后发一次写请求你能直接看到完整的请求和响应报文。我以一个写入示例报文逐字节讲讲请求部分MBAP头加PDU一共12个字节00 01 00 00 00 06 01 06 00 00 30 39第一个00 01是事务处理标识符第二次请求它会变成00 02依次递增。第二个00 00是协议标识符固定零。第三个00 06是长度表示从这里往后还有6个字节分别是01单元标识符、06写单个寄存器功能码、00 00起始地址0、30 39数据12345的十六进制。最后一个字节是PDU的最后一个字节。最妙的是Wireshark本身对ModbusTCP有解码器它会直接把这些字段解析成可读的信息你不需要自己数十六进制字节。重点看两个地方的对应关系请求里的寄存器地址和你组态里配置的通道地址是否满足减1的规则请求里写入的数据值、响应里回显的数据值、触摸屏上实际显示的值是否三者一致。如果响应报文里返回的是06 00 00 30 39说明从站按正常流程做了写确认。如果功能码位置变成86后面跟着02说明地址非法。看到03说明数据值非法比如写了一个超范围的数。这个排查思路适用于绝大多数Modbus通讯调试。4. 反向操作MCGS做主站把数据写到电脑4.1 电脑端搭建ModbusTCP从站服务MCGS做主站的场景也很经典触摸屏主动把数据推送到电脑通常是为了让上位机软件、数据库或者组态监控系统能拿到触摸屏采集到并暂存的现场数据。这时电脑端需要跑一个ModbusTCP从站服务。实现方式有很多种用Modbus Slave软件快速模拟或者用Python的pymodbus库写一个服务器。下面是一个简单的pymodbus从站服务示例from pymodbus.server import StartTcpServer from pymodbus.datastore import ModbusSequentialDataBlock from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext store ModbusSlaveContext( diModbusSequentialDataBlock(0, [0]*100), coModbusSequentialDataBlock(0, [0]*100), hrModbusSequentialDataBlock(0, [0]*100), irModbusSequentialDataBlock(0, [0]*100), ) context ModbusServerContext(slavesstore, singleTrue) StartTcpServer(context, address(0.0.0.0, 502))运行这个服务后电脑的502端口就开放了一个Modbus从站。为了验证可以先用Modbus Poll客户端连上来读写寄存器确认服务正常再开始配MCGS。4.2 MCGS设备窗口配置主站通讯切换到MCGS组态软件在设备窗口里添加“通用TCPIP父设备”这次注意选择的是作为“主站”再挂一个“ModbusTCP主站设备”。父设备里填电脑的IP地址端口502。子设备里需要配置“从站地址”也就是电脑端Modbus服务响应的单元标识符和Python服务里的slave ID对齐。通道配置时你要为每个要写入的MCGS变量分配一个远程寄存器地址。比如变量“产量”配置到远程保持寄存器40001那么MCGS内部变量和电脑端地址0就形成了映射关系。有人会把MCGS通道地址填成0在有些版本里MCGS界面上的地址也是从0开始的这个要看具体组态软件版本和所选驱动类型。稳妥的做法是先填40001实测读写不匹配再改0不要想当然。4.3 写入联调的注意事项MCGS做主站时写入是触摸屏MCGS主动发起的这就会引入一个写周期的问题。在MCGS的设备通道属性里有一个采集周期设置默认可能是100ms或者500ms。如果你的变量需要在变化时立即写出去把这个周期调短就行但不要调到10ms以下否则设备和网络压力都会很大。联调时建议先在电脑的从站服务端加日志能看到每一次请求的功能码、寄存器地址、数据值。我第一次调通的时候发现MCGS写入的寄存器地址和我配置的始终差一个数后来日志一看才知道MCGS在界面显示用40001在报文中发出去的其实是0。如果你在从站日志里看到地址和预期不一致先想偏移问题。还有一个要注意的地方MCGS做主站写数据它的采集是周期性的只有变量值发生变化后触发的写入才会生效。一些MCGS版本对通道有“写入最优”的优化策略如果你从站上没看到写入请求可能是因为MCGS认为这个值没有变化不认为需要重新写入。5. 常见问题与排查技巧实录5.1 连不通IP、端口、防火墙三板斧通讯不了第一步永远是查网络别急着怀疑协议。先确认触摸屏的IP和电脑的IP在同一个网段子网掩码一致。然后用电脑ping触摸屏的IP能通就说明二层三层没问题。端口是第二个检查点。ModbusTCP固定用502端口MCGS侧有些驱动允许自定义端口如果改了端口两边必须一致。电脑防火墙经常把502端口默默地挡掉我遇到过好几次最后在防火墙入站规则里把502放行、或者关掉防火墙测试就通了。查端口可以用telnet命令telnet 192.168.1.10 502如果端口开放会进入空白界面立刻关闭即连接成功。5.2 能读不能写属性和功能码的坑“读取正常一写就失败”是我收到过最多的求助。这种情况分两类一类是触摸屏侧变量属性被设成了只读外部请求写了但被忽略另一类是写请求功能码不被支持。MCGS从站设备对功能码的支持要查具体型号和驱动版本老型号可能只支持03读、06写一个寄存器不支持10写多个寄存器。你可以在Wireshark里看如果上位机发的是10功能码而MCGS返回错误功能码0x90开头说明它不支持多寄存器写入这时要把上位机侧改成06单寄存器写入来规避。5.3 数据错乱大小端和32位变量问题写入成功后数据不对这90%是字节序和字序的问题。16位整数还好办大端字节序是标准问题基本出在32位和浮点数上。MCGS里的32位数据对象在不同版本、不同驱动下寄存器的排列顺序可能不一样。调试这个问题的标准做法是向触摸屏写入一个特征明显的值比如十六进制0x12345678然后在触摸屏画面或者数据查看里看它解析成了什么。看到0x1234和0x5678是反的把字序交换再试一次这就是最直接的验证方式。另外要注意的是如果MCGS变量本身是16位整数而上位机按32位方式去写也会产生奇怪的结果根本原因不是协议问题而是双方对同一块寄存器区域的解释不一样。最靠谱的办法是在项目前期明确一份数据映射表把变量名、数据类型、寄存器地址、读写权限、数据格式全部列出来两边按表开发后期能省大量扯皮。5.4 通讯时断时续超时与重试机制调优通讯偶尔断一下多数不是协议问题而是设备和网络处理不过来。MCGS作为从站时如果上位机连续高频读写触摸屏内部处理速度跟不上就会出现超时。这种情况可以在上位机侧适当调大超时时间比如从1000ms调到3000ms同时降低读写的频率不要在一个循环里死命发请求。MCGS作为主站时如果采集周期设置得太短网络波动时它自己就会把通讯状态挂掉。设备窗口里有重连次数和重连间隔的参数建议把重连次数调高重连间隔设置在1秒以上避免设备在短暂故障后因为重试过于频繁再次卡死。5.5 排查工具箱与独家技巧做ModbusTCP调试我的常用工具组合基本固定了Modbus Poll或者Modbus Slave用来模拟通讯Wireshark用来抓包看报文Python脚本用来写自定义场景。三者结合几乎可以排查所有问题。有几个独家技巧分享给你。第一是抓包不要只抓一次请求要抓请求到响应的完整交互重点看响应是否携带异常码异常码才是真正的线索。第二是验证写请求是否成功最好的方式不是看发送端的提示而是去触摸屏画面看变量实际值有没有变化以对端结果为准。第三是测试时数据值不要用1、2、3这种低数值最好用纯十六进制特征值比如0x5A5A、0x12345678这样一旦数据发生大小端错误你能立刻看出来。还有一点502端口在某些电脑上会被其他程序占用比如一些组态软件自带的服务。如果python的从站服务启动时报端口被占用先看看是不是有别的程序已经在监听502了。做这套通讯调试我最大的感受是ModbusTCP协议本身真的不难难的是各种组态软件和设备对寄存器地址的呈现方式不统一导致你在界面上写40001而在抓包软件里看到的是0这一来一回就把人绕进去了。只要你建立了“界面地址减1等于报文地址”这个意识大小端规则搞明白再有Wireshark帮你盯着报文MCGS触摸屏和电脑之间的数据写入基本就是水到渠成的事。遇到数据写不进去的时候别急着怀疑设备坏了先按功能码、地址、属性这个顺序排查一遍百分之八十的问题都能自己解决。
返回列表