西门子S7-1200 Modbus TCP通讯例程详解:从报文到服务器/客户端配置
2026/9/12 5:26:11 网站建设 项目流程

简介:西门子Modbus TCP通讯例程包基于工业以太网与TCP/IP网络环境,围绕Open Modbus TCP协议实现,提供PLC编程实例源码与配套工程文件。面向自动化工程师、电气专业学生及小团队开发者,既适合个人学习技术时做项目参考,也适合学生毕业设计项目参考,还可作为小团队开发项目的模型参考。压缩包共699个文件,文件类型以dbf/mdx数据库相关文件、s7h/s7p西门子项目源文件、pdf文档、ini配置、txt说明及src源码为主,整体仅2.15MB,体积小巧但结构完整,便于按需查阅。已有445人学习使用,覆盖从Modbus TCP协议原理认知到实际工程落地的常见场景。读者可参照其中的工程文件组织方式和程序框架,梳理通信建立、寄存器读写与异常处理等关键环节,并将其应用到自己的学习项目或开发模型中,缩短二次开发与联调测试的入门路径。

1. 西门子Modbus TCP通讯例子程序源码包,到底在解决什么问题

现场最常见的尴尬是:一台西门子S7-1200或S7-1500把设备逻辑跑得好好的,但边上那台第三方仪表、变频器或者上位机不认S7协议,只认Modbus TCP。两边语言不通,数据就过不来。而“Open Modbus TCP.zip”这类源码包,往小了说是一段能抄的PLC程序,往大了说,是把西门子PLC接入Modbus TCP这条链路的完整组态方法打包在一起:TCP连接描述、保持寄存器数据块、MB_SERVER/MB_CLIENT指令调用、上电后的调试顺序。对做设备集成、MES数采、触摸屏联调的工程师来说,照着例程改IP和数据库,比自己在TIA Portal里从零试错快得多。下文默认按S7-1200/1500加TIA Portal讲解,最后一章再补S7-200 SMART的差异和现场验证手法。

2. 先分清角色:Modbus TCP报文、功能码和西门子指令块的对应关系

2.1 一条Modbus TCP报文长什么样

Modbus TCP和西门子原生S7通信有本质区别。S7协议跑在TCP 102端口上,报文里封装的是ISO-on-TCP和S7 PDU,只有西门子设备之间能互相解析。Modbus TCP则是一个完全开放的协议,跑在502端口,任何设备只要能收发TCP报文就能参与。这个端口差异决定了:你不能拿S7连接去访问一台只开放502端口的第三方设备,反过来也一样。例子程序里之所以把连接描述、IP、端口单独做成一个数据块,就是为了让你能把“502”这个目标对准。

一条完整的Modbus TCP请求报文结构如下:

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

逐字节拆开看:

  • 00 01:事务ID,客户端自己维护,每次请求递增,用来匹配响应
  • 00 00:协议ID,Modbus TCP里恒为0
  • 00 06:后续字节长度,这里表示从单元ID开始到结尾还有6个字节
  • 01:单元ID,相当于串口Modbus里的从站地址,TCP模式下通常填1
  • 03:功能码,03代表读保持寄存器
  • 00 00:起始寄存器地址,注意这里是从0开始的偏移量
  • 00 0A:要读的寄存器数量,10个字

这条报文的意思就是:读单元ID为1的设备上,从偏移0开始的10个保持寄存器。对照到西门子PLC侧的数据块,偏移0就是数组下标0,对应Modbus行业约定地址40001。

2.2 四个功能码和一张地址映射表

做设备集成时,90%的通信需求都被四个功能码覆盖。下面这张表就是例程里MODE参数和Modbus功能码的对应关系:

功能码含义对应MODE参数典型用途
03 (0x03)读保持寄存器0读PLC数据块、读变频器设定值
04 (0x04)读输入寄存器1读模拟量输入、读仪表实时值
06 (0x06)写单个保持寄存器2写一个字的控制命令
16 (0x10)写多个保持寄存器3批量下配方、写多段参数

这里有个新手必踩的坑:40001这个地址是行业惯例里的“寄存器编号”,它在线路上发出去的并不是40001,而是偏移量0。读40001到40010,报文里起始地址就是0,数量是10。西门子的MB_SERVER和MB_CLIENT指令内部会帮你做这个换算,但你在数据块里定义数组时,要清楚下标0对应的是40001,下标9对应的是40010。

2.3 S7-1200/1500、S7-200 SMART和老300/400的指令差异

不是所有西门子CPU都用同一套指令。这个例子程序如果标注的是1200/1500,那里面大概率是用MB_SERVER和MB_CLIENT这两个TIA Portal指令块实现的。这两个指令块配合TCON_IP_v4连接描述结构体,一个做服务器被动等连接,一个做客户端主动去连别人。

但现场还有两类常见情况:

  • S7-200 SMART:没有MB_SERVER/MB_CLIENT,指令库名字叫MBUS_SERVER和MBUS_CLIENT,在Micro/WIN SMART里调用,参数引脚和1200/1500长得完全不一样
  • 老S7-300/400:没有现成的Modbus TCP指令块,通常靠CP341通信模块或者自己基于AG_SEND/AG_RECV写报文,这种例程包一般不会覆盖到

所以拿到压缩包先看目录,路径里带“1200”还是“200smart”,直接决定你能不能照抄。汇川AM系列、台达这类国产PLC做Modbus TCP服务器时,指令风格接近西门子但寄存器偏移各有各的规矩,后面第四章会专门说轮询第三方设备时的改法。

3. 用TIA Portal把S7-1200配成Modbus TCP服务器,跑通回环测试

3.1 组态前需要确认的三件事

先别急着写代码,有三个前置条件不满足,后面全是白忙。

第一,CPU固件版本。S7-1200要从固件4.0开始才有MB_SERVER和MB_CLIENT这两个指令块,4.0之前的版本指令列表里根本找不到。看固件的方法是在TIA Portal里双击CPU打开属性,在线时直接看“常规-诊断”里的固件版本。S7-1500基本全系可用,新固件在通信稳定性上更好。

第二,硬件标识符。后面连接描述结构体里的InterfaceId参数,填的是CPU PROFINET接口的硬件标识符。常见默认值是64,但不要想当然,在TIA Portal里选中CPU的PROFINET接口,看“属性-系统常量”里那个“Hardware identifier”才是准的。

第三,如果你是在虚拟机里跑TIA Portal调试,VMware网络连接模式必须用桥接(bridged),不能用NAT。NAT模式下虚拟机能上网,但PLC往虚拟机IP发包会直接丢,Modbus Poll连不上还查不出原因。这是做西门子通信调试最常见的环境坑。

3.2 创建保持寄存器数据块,去掉优化访问

在TIA Portal左侧项目树里新建一个DB,名字叫HoldingRegs,在里面定义一个数组:

HoldingRegs : Array[0..99] of Word;

定义完数组后,关键一步是右键这个DB,在属性里取消勾选“优化的块访问”。为什么?因为MB_SERVER指令访问的是DB的绝对地址,优化访问会把符号名编译掉,指令块找不到数据区的偏移量。取消优化后,DB里能看到每个元素的偏移地址,调试时对着偏移量看数据会更直观。

这个数组就是PLC对外暴露的保持寄存器区。上位机或者触摸屏读40001到40010,实际上读的就是HoldingRegs[0]HoldingRegs[9]。程序里要往外发数据,就往这个数组里写值。

3.3 定义TCP连接描述结构体

连接描述在西门子指令里是一个专门的结构体,类型叫TCON_IP_v4。常见做法是在共享DB里定义一个变量,静态初始化好:

"ModbusConn_Server" : TCON_IP_v4 := ( InterfaceId := 64, ID := 1, ActiveEstablished := FALSE, RemoteAddress := '0.0.0.0', RemotePort := 502, LocalPort := 502 );

参数含义:

  • InterfaceId:3.1节里查到的PROFINET接口硬件标识符
  • ID:连接编号,同一个CPU里每个TCP连接要唯一
  • ActiveEstablished:FALSE表示被动监听,即本机作为服务器等待别人来连
  • RemoteAddress:服务器的远程地址填0.0.0.0表示不限制对端
  • RemotePort/LocalPort:Modbus TCP固定用502,两边都填502

顺便说一句,后面做客户端时ActiveEstablished要改成TRUE,RemoteAddress填对端IP,这两个是服务器和客户端配置上最大的区别。

3.4 调用MB_SERVER:完整SCL代码

在OB1里拖入MB_SERVER指令块,系统会提示自动生成背景DB,名字比如叫MB_SERVER_DB。然后补全引脚:

"MB_SERVER_DB"( CONNECT := "ModbusConn_Server", MB_HOLD_REG := "HoldingRegs", NDR => "MB_NDR", DR => "MB_DR", ERROR => "MB_ERROR", STATUS => "MB_STATUS" );

引脚说明:

引脚类型作用
CONNECTTCON_IP_v4连接描述结构体,指向刚才定义的ModbusConn_Server
MB_HOLD_REGVariant指向保持寄存器数据块HoldingRegs
NDRBool新数据到达标志,上位机写入后该位会置1
DRBool数据已读完成标志,上位机读走数据后置1
ERRORBool通信错误标志,ERROR为TRUE时看STATUS
STATUSWord错误状态码,0表示无错误

注意,MB_SERVER的MB_HOLD_REG也可以指向位存储区,但我建议单独建DB,因为DB里的数据断电保持的设置更灵活,而且程序里引用起来结构清晰。NDR和DR这两个标志位可以接到HMI上做通信状态显示,也可以用它们做读写计数。

3.5 用Modbus Poll做回环验证

程序编译下载后,打开Modbus Poll这个免费的调试工具,按下面步骤配置:

  1. Connection → Connect,选TCP/IP,Remote IP填PLC的IP,端口填502
  2. Setup → Read/Write Definition,功能码选03,地址填40001,长度填16
  3. 点OK后观察数据区有没有数值跳动

如果看到数据在变化,说明MB_SERVER已经正常监听,PLC侧的程序没有问题。如果超时,先从两层排查:

  • ping不通PLC的IP:检查VMware网络模式是不是桥接,或者Windows防火墙有没有挡502端口
  • ping通了但Modbus Poll超时:检查Modbus Poll里的Unit ID是不是1,MB_SERVER默认响应单元ID 1,调试工具里填别的值协议栈直接丢弃请求

提示:Modbus地址40001是行业惯例里的寄存器编号,线路上实际发送的起始地址是0。你在MB_SERVER的MB_HOLD_REG引脚上看到的是数据块的起始指针,偏移量的换算由指令内部完成,不要在PLC程序里自己再加偏移。

4. 把PLC配成Modbus TCP客户端,轮询第三方设备的参数

4.1 MB_CLIENT最小调用代码

PLC做客户端,最常见的场景是轮询第三方设备:一台变频器的频率、电流、故障字存在它的保持寄存器里,PLC周期性去读。这种例程在源码包里通常叫做“客户机”或“Client”模式,核心就是MB_CLIENT指令块。

最小调用代码如下:

"MB_CLIENT_DB"( REQ := "ReadTrig", CONNECT := "ModbusConn_Client", MODE := 0, DATA_ADDR := 40001, DATA_LEN := 10, DATA_PTR := "ReadData", DONE => "MB_DONE", BUSY => "MB_BUSY", ERROR => "MB_ERROR", STATUS => "MB_STATUS" );

代码逻辑说明:当REQ引脚出现上升沿时,MB_CLIENT按MODE指定的功能码,向CONNECT指向的远端设备发起一次请求。读取的数据写入DATA_PTR指向的存储区,完成时DONE置1,BUSY为1期间不要重复触发请求。

参数里最需要注意的是DATA_PTR,它通常指向一个独立的DB或者从站数据区,数据类型要和读取的寄存器数量匹配。读10个字,就定义一个Array[0..9] of Word的变量,确保没有越界。

4.2 MODE参数和DATA_ADDR的填法

MB_CLIENT的MODE参数直接映射第二章那四个功能码,但在TIA Portal里填法和串口Modbus有区别,下面这个是常驻手册的表格:

MODE功能DATA_ADDR填法DATA_LEN含义
0读保持寄存器(03)40001起读取的寄存器字数
1读输入寄存器(04)30001起读取的寄存器字数
2写单个保持寄存器(06)40001起固定为1
3写多个保持寄存器(16)40001起写入的寄存器字数

这里有个细节:MODE填0时DATA_ADDR写40001而不是0,这是西门子指令块为了和Modbus行业地址习惯保持一致做的设计。但如果你面对的不是西门子的上位机,而是汇川伺服、台达变频器这类设备,对方手册里如果明确说“寄存器地址从0开始”,那DATA_ADDR也可以直接写1或0,以对方手册的地址定义为准。

4.3 REQ上升沿和轮询状态机

MB_CLIENT的REQ引脚只认上升沿。如果程序里用常1驱动REQ,指令块会反复触发请求,把带宽全占满,CPU通信负载直线上升。所以必须用R_TRIG边沿检测:

"R_TRIG_Inst"( CLK := "Cycle_500ms", Q => "ReadTrig" );

上面这段代码用一个500毫秒的周期脉冲,每500毫秒产生一个上升沿,驱动一次MB_CLIENT请求。常见轮询周期是100毫秒到1秒之间,周期太短对方设备反应不过来,太长数据刷新不够快。

实际现场往往要轮询多个从站、多种功能码。用定时器加状态字的方式做轮询状态机是常见做法:

CASE "PollState" OF 0: // 读从站1保持寄存器 "MB_CLIENT_DB".MODE := 0; "MB_CLIENT_DB".DATA_ADDR := 40001; "MB_CLIENT_DB".DATA_PTR := "Slave1_Data"; IF "MB_DONE" THEN "PollState" := 1; END_IF; 1: // 读从站2输入寄存器 "MB_CLIENT_DB".MODE := 1; "MB_CLIENT_DB".DATA_ADDR := 30001; "MB_CLIENT_DB".DATA_PTR := "Slave2_Data"; IF "MB_DONE" THEN "PollState" := 2; END_IF; 2: // 写控制字到从站1 "MB_CLIENT_DB".MODE := 2; "MB_CLIENT_DB".DATA_ADDR := 40001; "MB_CLIENT_DB".DATA_PTR := "ControlWord"; IF "MB_DONE" THEN "PollState" := 0; END_IF; END_CASE;

每次只允许一个请求在总线上跑,DONE或者ERROR之后才跳转到下一个状态。这比用定时器硬堆多个MB_CLIENT实例要稳得多。

4.4 连接建立和断线重连的几个坑

MB_CLIENT的CONNECT结构体里,ActiveEstablished要设为TRUE,RemoteAddress填对方IP。这里有几个从实际调试里积累出来的坑,源码包里的注释通常不会写这么细:

  • 对方设备没上电或者IP不对,STATUS里会出现80C8,这是连接建立失败的标志。这时候不要用100毫秒周期去重试,程序会不停地在TCP层尝试握手机制,加重CPU负载,把重试周期放大到2到3秒。
  • 连接一旦建立成功就是长连接,不要每轮询一次就断开重建。README里如果写了“自动重连”,它指的应该是检测到断开后再重连,而不是一次请求一次连接。
  • 威纶通触摸屏、KingSCADA这类组态软件做Modbus TCP服务器时,它们的寄存器地址偏移和西门子不一样。有的从0开始,有的从1开始,连不上时先抓包看往返报文,确认对方返回的是不是异常码。

5. 拿到例程压缩包后,先改这三处再下载到PLC

5.1 第一处:IP、端口和接口硬件标识符

解压Open Modbus TCP.zip之后,别急着把整个项目下载到PLC。先定位到连接描述块,也就是那个TCON_IP_v4结构体。改三个值:RemoteAddress改成对端设备IP,LocalPort保持502,InterfaceId改成你当前CPU的PROFINET接口硬件标识符。很多照着例程做的人只改了IP,忘了确认InterfaceId,结果程序下载后连接永远建立不起来,在线诊断里查TCP连接状态,一直是“未连接”。这三个值是一组,改完要一起编译下载。

5.2 第二处:寄存器号和数组下标的换算

例程里的HoldingRegs数组下标0对应Modbus地址40001,下标9对应40010。这个换算关系在第三、四章已经反复强调,但下载前还是值得对着图纸核对一遍:上位机组态里填的40001到40010,在PLC里就是HoldingRegs[0]HoldingRegs[9],不要画蛇添足在程序里再加偏移1。曾经遇到一个案例,有人在OB1里写了HoldingRegs[1] := "ActualValue",以为这样对应40001,结果整个数据块错位了一个字,上位机读到的所有数据整体偏了两个字节,排查了一下午。

5.3 第三处:用一条原始socket报文验证服务器在监听

程序下载后,可以先不用Modbus Poll,用一条Python脚本直接验证PLC的502端口是否在正常工作。这个验证方法胜在干净,排除掉调试工具本身的配置干扰。

import socket import struct s = socket.create_connection(("192.168.0.10", 502), timeout=3) req = struct.pack(">HHHBBHH", 0x0001, 0x0000, 0x0006, 0x01, 0x03, 0x0000, 0x000A) s.send(req) resp = s.recv(256) print(resp.hex())

这段代码做的事情就是第二章那条报文的实际发送:事务ID=1,协议ID=0,长度=6,单元ID=1,功能码03,起始地址0,寄存器数量10。如果PLC的MB_SERVER工作正常,响应包应该长这样:

0001 0000 0017 01 03 14 [20个字节的寄存器数据]

响应里第8个字节是0x14,也就是十进制的20,表示后面跟着20个字节的数据,正好是10个寄存器。能收到这条响应,说明MB_SERVER已经在正常监听,上位机配置的问题就去查IP和Unit ID。收不到响应,在PC上用Wireshark抓一下502端口,看SYN报文有没有发出,发出后对方有没有回SYN-ACK,这个抓包习惯能节省大量联调时间。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询