拓冰建站拓冰建站
首页 / 资讯中心 / 正文

S7-1200 Modbus TCP通信工程复盘:从报文解析到调试实战

简介面向LabVIEW与西门子S7-1200 PLC通信开发人员这份压缩包提供了完整的Modbus TCP通讯练习资料。资源以S7-12001214C与LabVIEW 2014 SP1为平台涵盖PLC侧程序、LabVIEW上位机程序及配套的V4.3通信说明文档适合正在学习工业以太网通信、需要参考实际工程代码的初学者或工程师。压缩包共41个文件大小约4.45MB主要包含PDF通信指导、LabVIEW VI源程序、PLC工程配置相关文件如ap14、db、xml等以及少量辅助工具压缩包类型覆盖工程、源码与文档便于对照学习。目前已有597人浏览学习内容虽为个人练习项目但程序结构相对完整可直接打开参考结合PDF说明快速理解Modbus TCP指令块与VI程序的配置思路对搭建类似通信测试环境有实际参考价值。 前阵子整理项目档案从移动硬盘里翻出一个 s7 1200 modbusTCP.zip解压一看是两年多前做的一台 S7-1200 与第三方能耗平台对接的通信工程包。当时为了把这套 Modbus TCP 通信跑稳前前后后折腾了快一周如今回看真正的难点根本不在 TIA Portal 里拖功能块而在报文理解、地址映射和异常排查这些容易被忽视的细节。这篇文章就把这个工程包背后的通信方案、调试链路和踩坑记录完整复盘一遍给正在拿 S7-1200 做 Modbus TCP 对接的同行做个参考。1. 打开压缩包之前先定主从再谈程序1.1 两种组网位置决定你该用哪个功能块拿到任何一个 S7-1200 做 Modbus TCP 的工程第一步不是看程序里调用了什么而是先判断这台 PLC 在通信链路里的位置。位置不同选型完全不同。如果 S7-1200 作为 Modbus TCP 主站主动去读取现场仪表、变频器或者电表的数据那么核心功能块是MB_CLIENT。这个块解决的是PLC 主动发起读/写请求的场景你需要配置远程设备的 IP、端口 502、功能码、起始地址和数据长度。如果 S7-1200 作为 Modbus TCP 从站让上位机、触摸屏或者第三方采集网关来读取 PLC 里的数据那么核心功能块是MB_SERVER。PLC 在 502 端口被动等待连接把内部的保持寄存器区域暴露给外部系统外部系统用标准的 Modbus 报文就能读写这块区域。我那个 s7 1200 modbusTCP.zip 工程包里其实两个块都用了一组 MB_CLIENT 去读一台第三方电力仪表的电压电流另一组 MB_SERVER 把 PLC 计算后的电量和状态字暴露给能耗平台。一个工程解决了两个方向的通信需求。后面我在 TIA Portal 实操章节里会分别展开。1.2 什么时候才需要 Modbus TCP而不是 S7 协议很多刚接触 S7-1200 的人会问西门子自己有 S7 协议为什么还要折腾 Modbus TCP答案很简单S7 协议是西门子私有协议第三方系统要读 S7-1200通常得装 Simatic NET 或者用 Snap7 这类第三方库要么收费要么有兼容性限制。而 Modbus TCP 是公开协议几乎所有的上位机组态软件、能源管理平台、边缘采集网关都原生支持不需要额外授权报文结构也简单。另外还有一种场景S7-1200 要去采集第三方设备的数据而这些设备根本不支持 S7 协议只提供 Modbus TCP 接口。这时候 MB_CLIENT 就是唯一选择。说白了Modbus TCP 在工业现场是最大公约数各种品牌设备只要带网口基本都支持它。1.3 压缩包里先看哪几个文件这类工程包打开之后不要急着看 OB1先找几个关键对象看一遍基本就能摸清整个通信全貌全局 DB 里有没有存放 CONNECT 参数的结构体通常是 TCON_IP_v4 类型有没有专门给 MB_CLIENT 和 MB_SERVER 用的背景 DB以及 OB1 里调用时的轮询逻辑是怎么写的。这三个点看明白了整个工程的通信方式也就清楚了。2. 一组报文的两种方言Modbus RTU 与 Modbus TCP 的区别2.1 同一句读保持寄存器两种报文长什么样Modbus 协议家族里RTU 和 TCP 是两种最常见的载体。它们的应用层数据基本一致但封装的壳不同。我以读取从站地址 1、起始地址 0、连续 10 个保持寄存器为例把两个报文放在一起对比差异一眼就能看出来。RTU 报文十六进制01 03 00 00 00 0A C5 CD其中01是从站地址03是功能码00 00是起始地址00 0A是寄存器数量C5 CD是 CRC16 校验码共 8 个字节。TCP 报文十六进制00 01 00 00 00 06 01 03 00 00 00 0A前面 7 个字节是 MBAP 协议头后面从01开始的数据部分和 RTU 几乎一样但去掉了 CRC 校验。为什么能去掉 CRC因为 TCP/IP 协议栈自己已经做了可靠传输和错误检测链路层有帧校验传输层有序列号和重传机制再在应用层做一次 CRC 属于重复劳动。当初 Modbus TCP 协议设计时正是考虑到这一点所以明确把 CRC 省掉了。2.2 从站地址与单元标识符的映射关系RTU 报文里的第一个字节01是从站地址它决定这条报文在串行总线上由哪个从站来响应。到了 Modbus TCP 里这个角色由 MBAP 头的单元标识符Unit ID来承担。直连场景下一个 Modbus TCP 服务器的 IP 已经唯一确定了通信对象Unit ID 填 1 还是 255 其实影响不大很多设备也不校验这个值。但如果你通过 Modbus TCP 转 RTU 的网关去带一串串行从站那 Unit ID 就至关重要了网关收到 TCP 请求后会剥掉 MBAP 头把 Unit ID 直接映射成 RTU 报文里的从站地址。比如你请求 Unit ID 3网关转成 RTU 后就是03 03 ...。这种情况下Unit ID 必须和挂在串行总线上的从站地址严格对应否则从站不会应答。2.3 网关转发时还有一个细节容易忽略用 Modbus TCP 转 RTU 网关时时间等待也需要注意。RTU 要求帧与帧之间至少间隔 3.5 个字符时间网关把 TCP 请求转成串口帧时如果处理不当下一帧提前发出来从站可能把它当成同一帧的一部分而解析失败。所以选网关时尽量选大厂产品它们对帧间隔处理比较成熟。如果现场串口从站经常无响应但 TCP 侧报文看起来完全正常大概率就是网关的 RTU 帧间隔时间设置不合理。3. MBAP 协议头逐字节拆解Length 和 Unit ID 最容易翻车3.1 这 7 个字节各管什么Modbus TCP 请求相比 RTU最明显的区别就是前面多出了 MBAP 协议头一共 7 个字节四个字段字段字节数示例值作用事务标识符 Transaction ID200 01用于匹配请求与响应客户端每次请求递增即可协议标识符 Protocol ID200 00固定为 0表示 Modbus 协议长度 Length200 06后续字节数从单元标识符开始算到报文结束单元标识符 Unit ID101相当于 RTU 报文里的从站地址事务标识符太重要了。Modbus TCP 允许在同一个 TCP 连接上并发地发送多个请求接收端就是靠事务标识符来区分当前响应到底对应哪一次请求的。如果你自己写主站程序请求一个事务 ID 1下一次请求还没收到响应就又发了事务 ID 2那么响应回来时你就得靠这个字段去配对。S7-1200 的 MB_CLIENT 内部已经处理了这个逻辑但如果你用 TCL 脚本、Python 或者 C 自己实现主站这个字段必须维护好。3.2 Length 字段的计算一个错误示范Length 字段表示的是从单元标识符开始一直到报文结尾的字节数。注意它不包含事务标识符、协议标识符和 Length 字段本身。以读保持寄存器请求00 01 00 00 00 06 01 03 00 00 00 0A为例Length 的计算方式是单元标识符 1 字节 功能码 1 字节 起始地址 2 字节 寄存器数量 2 字节 6所以 Length 00 06。我见过很多新手写抓包工具把 Length 算成了整个报文的长度 12结果被解析方直接判为格式错误。更隐蔽的问题是响应报文里的 Length 是1 功能码 数据字节数比如读 10 个寄存器正常响应数据是 20 字节那么响应报文的 Length 1 1 20 22 00 16。如果从站返回了异常码Length 就会变成 3单元 ID 功能码 异常码这是判断响应是正常还是异常的一个快速手段。3.3 Unit ID 填多少直连与网关要区别对待S7-1200 自带的 MB_SERVER 功能块NODE 引脚默认填 255这也是 Modbus TCP 规范里推荐的未指定值。但实际现场你会发现有些主站设备默认发 Unit ID 1尤其是一些国产组态软件和 DTU 网关根本不给你配这个参数的机会。所以我的建议是直连调试时把 MB_SERVER 的 NODE 填成 255 或者和主站一致的数值两边对上就行如果要用网关下挂多个从站Unit ID 就绝对不能乱填它必须和串口总线上每个从站的地址一一对应。4. TIA Portal 实操记录MB_CLIENT 与 MB_SERVER 的调用细节4.1 指令位置与一个容易被忽略的版本问题在 TIA Portal 里S7-1200/1500 的 Modbus TCP 指令位于通信 - 开放式通信 - 其他 - Modbus TCP目录下。V15 以上的版本基本都直接内置了V13 或者更老的版本可能需要额外安装库文件。我当时用的 CPU 是 1214C DC/DC/DC固件版本 V4.4TIA Portal V15.1。如果你用的固件版本比较新比如 V4.5 以上MB_CLIENT 和 MB_SERVER 引脚没有本质变化但 CONNECT 结构体的数据类型在某些版本里可能显示为TCON_IP_v4或者TCON_IP_v4_SEC后者是带安全集成的类型。组态时先确认你选的是哪个别搞混了。4.2 MB_CLIENT 做主站MODE、DATA_ADDR、DATA_PTR 是三个关键引脚MB_CLIENT 的引脚比较多但真正决定通信行为的是下面这几个REQ上升沿触发一次通信请求。CONNECT指向一个 TCON_IP_v4 结构体里面有远程 IP、远程端口 502、本地接口 ID。MODE选择功能码。这一点是新手最容易懵的地方MODE 的值并不是功能码而是一个选择编号。DATA_ADDRModbus 数据起始地址。访问 40001 时填 1访问 30001 时也填 1具体是哪种地址区由 MODE 决定。DATA_LEN数据长度。读线圈/离散输入时是位数量读寄存器时是字数量。DATA_PTR指向存放数据的 DB 地址通常是一个打包好的 Word 数组或者字节数组。MODE 与功能码的对应关系我整理了一张表建议你直接截图存下来MODE功能码操作4FC1读线圈3FC2读离散输入2FC3读保持寄存器1FC4读输入寄存器0FC5写单个线圈8FC6写单个保持寄存器5FC15写多个线圈7FC16写多个保持寄存器当时我被一个数据地址的坑困了好几个小时。第三方仪表说明书上写着电压寄存器地址 40001我按照以前用国产 DTU 的习惯在 DATA_ADDR 里直接填了 40001结果通信一直报地址非法。后来才意识到S7-1200 的 MB_CLIENT 里 DATA_ADDR 填的是 Modbus 逻辑地址也就是去掉区号后的偏移量。40001 对应的 DATA_ADDR 是 1MODE 选 2 表示读保持寄存器。这个映射关系在工程里反复出现一定要记牢。4.3 MB_SERVER 做从站保持寄存器区要单独规划MB_SERVER 的调用比 MB_CLIENT 简单一些核心是 CONNECT 结构体和 MB_HOLD_REG 指针。CONNECT 结构体里ActiveEstablished 必须填 FALSE因为 PLC 是被动等待连接方LocalPort 填 502RemoteAddress 通常填 0.0.0.0表示允许任意主站连接但具体写法要看 TIA 的版本有些地方是用四个字节的 0 数组表示。MB_HOLD_REG 指向一块 Word 数组区域这就是对外暴露的保持寄存器区。外部主站读 40001读到的就是这块区域的第 1 个字。所以组态之前先规划好 DB 块哪些字放电压、哪些字放电流、哪些字放状态标志最好画一张寄存器映射表方便后续和上位机同事对接。4.4 轮询逻辑必须做成状态机不能裸奔MB_CLIENT 的 REQ 引脚如果直接用定时器周期性给脉冲很容易出问题。Modbus TCP 请求在网络上存在一个响应时间如果上一次请求还没完成下一次上升沿又到了MB_CLIENT 内部可能直接报错或者 STATUS 出现连接冲突。我的做法是写一个简单的三步状态机在 OB1 里空闲状态延时 500ms时间到后置位 REQ进入等待状态检测到 DONE 或者 ERROR 后复位 REQ记录状态码并重新进入空闲状态。整个逻辑用 SCL 实现也就是二十几行。这样每个请求之间都有明确的完成标志不会因为网络抖动导致请求重叠。5. 现场排错实战用 TCL 脚本模拟主站把问题摁在报文层5.1 为什么我坚持用 TCL 脚本而不是直接开 Modbus Poll到现场调试时电脑上未必装了 Modbus Poll或者组态软件的授权锁没带。但几乎每台工控机上都能找到 Tcl/Tk 环境哪怕没有一个不到 10MB 的 ActiveTcl 装起来也很快。用 TCL 脚本直接构造 Modbus TCP 报文发给 PLC相当于把通信问题和程序逻辑问题彻底隔离开脚本通了说明 PLC 侧的服务在正常工作问题大概率出在你的程序地址映射上脚本不通那就是网络、端口、PLC 功能块配置的问题跟上位机代码无关。5.2 一段可用的 TCL 脚本读保持寄存器 FC3这段脚本是我现场常用的发一个标准的 FC3 读保持寄存器请求然后把响应原样打印出来支持自定义 IP、起始地址和读取数量。#!/usr/bin/env tclsh # Modbus TCP 主站测试脚本默认读取保持寄存器(FC3) package require socket set remote_ip 192.168.0.1 set remote_port 502 set unit_id 1 set start_addr 0 set reg_count 10 set timeout_ms 3000 # 请求 事务ID(2) 协议ID(2) 长度(2) 单元ID(1) 功能码(1) 起始地址(2) 数量(2) # 长度字段 单元ID(1) 功能码(1) 起始地址(2) 数量(2) 6 set req_hex [format 00010000%04x%02x03%04x%04x \ [expr {1 1 4}] $unit_id $start_addr $reg_count] set sock [socket $remote_ip $remote_port] fconfigure $sock -translation binary -blocking 0 -buffering none puts -nonewline $sock [binary format H* $req_hex] flush $sock proc read_n {sock n deadline} { set buf while {[string length $buf] $n} { set part [read $sock [expr {$n - [string length $buf]}]] append buf $part if {$part eq [clock milliseconds] $deadline} { break } after 20 } return $buf } set deadline [expr {[clock milliseconds] $timeout_ms}] set head [read_n $sock 7 $deadline] if {[string length $head] 7} { puts 超时或连接关闭未收到完整响应头 close $sock exit 1 } binary scan $head H4H4H2H2 r_txn r_proto r_len r_unit set pay_len [expr {0x$r_len - 1}] set body [read_n $sock $pay_len $deadline] binary scan $body H* body_hex puts 事务ID$r_txn 协议ID$r_proto 长度$r_len 单元ID$r_unit puts 响应数据: $body_hex close $sock脚本的请求报文构造和我在第 2 节讲解的格式完全一致。你可以把remote_ip改成 PLC 的 IP然后直接跑。如果 PLC 侧 MB_SERVER 已经运行脚本会打印出类似响应数据: 01030014008000c80064...的内容03表示功能码正常后面的十六进制就是寄存器数据。5.3 从脚本输出反推现场故障脚本返回的结果大致能分成三类每一类对应的排查方向完全不同。第一类是超时未收到完整响应头。这说明 TCP 连接可能建立失败或者 PLC 侧根本没有响应。优先检查 IP 能不能 ping 通502 端口是否打开。Windows 下可以用telnet 192.168.0.1 502验证通了说明端口在监听不通就要回去看 MB_SERVER 的 LocalPort 和 ActiveEstablished 配置。第二类是收到了响应头但长度字段很小比如 3。这种一般是异常响应。响应报文中功能码的最高位会被置 1比如请求是03异常响应就是83。紧跟后面的一个字节是异常码01表示功能码不支持02表示数据地址非法03表示数据值非法。遇到02优先检查 DATA_ADDR 是否超出了从站寄存器范围比如从站只有 100 个保持寄存器你偏偏从地址 200 开始读必然报错。第三类是响应数据长度对但数值看起来完全不对。这时候 90% 是字节序问题我在下一节详细展开。6. 项目收尾时我保留的几张检查表和几条保命经验6.1 字节序一个字的高低字节排序差点让液位数据跳变S7-1200 内部采用大端字节序Modbus 协议本身也是大端理论上按字读取不会有问题。但很多第三方设备在实现 Modbus 时并没有遵守这个约定典型的坑是 16 位寄存器高低字节颠倒。我遇到过一台国产液位计说明书明确写着数据低字节在前用 MB_CLIENT 读回来后液位数值在 0 和 65535 之间来回跳。排查了半天最后用脚本抓到原始报文发现寄存器里返回的是34 12而仪表实际值应该是12 34。在 PLC 程序里加了一条 SWAP 指令把高低字节交换后数据立刻恢复正常。如果是 32 位浮点数情况更复杂。常见的字节序组合有 AB CD 和 CD AB 两种也就是每个寄存器内部的字节顺序以及两个寄存器的前后顺序都可能导致数值异常。处理这类问题我的经验是不要猜直接用脚本读原始值人工换算成浮点数看看对不对再决定程序里做 SWAP 还是 MOVE。6.2 连接资源与 REQ 触发节奏通信不稳定先查这两个S7-1200 的开放式通信连接数量是有限的具体取决于 CPU 规格。MB_CLIENT 每实例化一次就占用一个连接资源MB_SERVER 也一样。如果一个项目里同时跑了多个 MB_CLIENT要留出足够的资源余量否则 CPU 会报通信资源不足之类的错误。另外 REQ 的触发节奏直接影响通信稳定性。我见过有人用 100ms 定时器周期给 REQ 发脉冲结果现场每隔几分钟就丢一次数据。原因就是从站响应稍微慢一点下一次请求就重叠了。正确的做法我前面提到过用状态机等待 DONE 再复位 REQ必要时在完成和下一次触发之间加 100~200ms 的间隔。Modbus 本身没有实时性要求宁慢勿乱。6.3 最后一道防线把排查项做成清单每次收尾调试我都会过一遍这张表能省掉大量回头路排查项典型表现检查重点网络连通性脚本超时ping IPtelnet 502 端口CONNECT 参数连接建立失败InterfaceId、ActiveEstablished、LocalPort功能码与 MODE异常码 01MODE 与功能码是否匹配数据地址异常码 02DATA_ADDR 是否对应 40001 去区号字节序数值乱跳对比脚本原始报文必要时 SWAP单元 ID网关场景无响应Unit ID 与 RTU 从站地址一致轮询节奏偶发超时REQ 状态机是否加入 DONE 判断最后分享一条个人习惯接手任何 Modbus TCP 通信项目我一定是先在电脑上把 TCL 脚本跑通、把报文抓明白再去碰 TIA Portal 里的块。大多数通信问题在报文层解决的成本是最低的。那个 s7 1200 modbusTCP.zip 里的程序和 DB本质上只是把验证过的报文逻辑固化下来而已。这个习惯帮我省了无数次现场返工也推荐你试试。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门