西门子S7-1200 Modbus TCP通讯例程详解:从报文到服务器/客户端配置
简介西门子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协议IDModbus TCP里恒为000 06后续字节长度这里表示从单元ID开始到结尾还有6个字节01单元ID相当于串口Modbus里的从站地址TCP模式下通常填103功能码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 );参数含义InterfaceId3.1节里查到的PROFINET接口硬件标识符ID连接编号同一个CPU里每个TCP连接要唯一ActiveEstablishedFALSE表示被动监听即本机作为服务器等待别人来连RemoteAddress服务器的远程地址填0.0.0.0表示不限制对端RemotePort/LocalPortModbus TCP固定用502两边都填502顺便说一句后面做客户端时ActiveEstablished要改成TRUERemoteAddress填对端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_ServerMB_HOLD_REGVariant指向保持寄存器数据块HoldingRegsNDRBool新数据到达标志上位机写入后该位会置1DRBool数据已读完成标志上位机读走数据后置1ERRORBool通信错误标志ERROR为TRUE时看STATUSSTATUSWord错误状态码0表示无错误注意MB_SERVER的MB_HOLD_REG也可以指向位存储区但我建议单独建DB因为DB里的数据断电保持的设置更灵活而且程序里引用起来结构清晰。NDR和DR这两个标志位可以接到HMI上做通信状态显示也可以用它们做读写计数。3.5 用Modbus Poll做回环验证程序编译下载后打开Modbus Poll这个免费的调试工具按下面步骤配置Connection → Connect选TCP/IPRemote IP填PLC的IP端口填502Setup → Read/Write Definition功能码选03地址填40001长度填16点OK后观察数据区有没有数值跳动如果看到数据在变化说明MB_SERVER已经正常监听PLC侧的程序没有问题。如果超时先从两层排查ping不通PLC的IP检查VMware网络模式是不是桥接或者Windows防火墙有没有挡502端口ping通了但Modbus Poll超时检查Modbus Poll里的Unit ID是不是1MB_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置1BUSY为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读保持寄存器0340001起读取的寄存器字数1读输入寄存器0430001起读取的寄存器字数2写单个保持寄存器0640001起固定为13写多个保持寄存器1640001起写入的寄存器字数这里有个细节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要设为TRUERemoteAddress填对方IP。这里有几个从实际调试里积累出来的坑源码包里的注释通常不会写这么细对方设备没上电或者IP不对STATUS里会出现80C8这是连接建立失败的标志。这时候不要用100毫秒周期去重试程序会不停地在TCP层尝试握手机制加重CPU负载把重试周期放大到2到3秒。连接一旦建立成功就是长连接不要每轮询一次就断开重建。README里如果写了“自动重连”它指的应该是检测到断开后再重连而不是一次请求一次连接。威纶通触摸屏、KingSCADA这类组态软件做Modbus TCP服务器时它们的寄存器地址偏移和西门子不一样。有的从0开始有的从1开始连不上时先抓包看往返报文确认对方返回的是不是异常码。5. 拿到例程压缩包后先改这三处再下载到PLC5.1 第一处IP、端口和接口硬件标识符解压Open Modbus TCP.zip之后别急着把整个项目下载到PLC。先定位到连接描述块也就是那个TCON_IP_v4结构体。改三个值RemoteAddress改成对端设备IPLocalPort保持502InterfaceId改成你当前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), timeout3) req struct.pack(HHHBBHH, 0x0001, 0x0000, 0x0006, 0x01, 0x03, 0x0000, 0x000A) s.send(req) resp s.recv(256) print(resp.hex())这段代码做的事情就是第二章那条报文的实际发送事务ID1协议ID0长度6单元ID1功能码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这个抓包习惯能节省大量联调时间。本文还有配套的精品资源点击获取