西门子PLC Socket通信实战:TCON/TSEND/TRCV指令详解与避坑指南
2026/9/20 17:51:21 网站建设 项目流程

1. 为什么Socket通信在西门子PLC项目里越来越绕不开

干了十来年自动化,我越来越明显地感觉到一个变化:以前做项目,PLC就是个孤岛,接几个按钮、继电器、变频器,跑个逻辑就完事了。现在不一样了,甲方张口就是“数据要上MES”“设备状态要进看板”“扫码枪要跟PLC联动”,这些需求背后,几乎都指向同一个东西——Socket通信。

Socket通信说白了,就是PLC跟外部设备之间开一条“网络管道”,双方按照约定好的格式收发数据。它不像Modbus那样有固定的寄存器地址表,也不像Profinet那样是西门子自家的闭环生态。Socket给的是最原始的网络字节流,灵活度极高,但代价是什么都要自己定义。这就像给你一根网线,两端怎么说话、说什么话、语速多快,全得自己商量。

我见过太多同行在这个环节翻车。有人用开放式用户通信指令写了一半发现连接数不够,有人数据发出去对方收不到,排查半天发现是字节序搞反了,还有人压根没搞清楚ISO-on-TCP和TCP的区别就上手,结果跟第三方设备死活握不上手。这些问题不是PLC编程逻辑的问题,而是对Socket通信机制理解不到位造成的。

这篇文章我打算把西门子PLC做Socket通信这件事从头到尾拆一遍。不管你是用S7-1200、S7-1500还是老当益壮的S7-300/400,底层逻辑是相通的。我会讲清楚TCON、TSEND、TRCV这些指令到底怎么用,参数怎么配,连接怎么管,坑在哪里,以及怎么用最笨但最稳的办法把通信跑通。适合有一定PLC基础、正准备做设备联网或者跟第三方系统对接的朋友。零基础也能看,但最好先摸过博途或者STEP 7的界面。

2. 西门子PLC做Socket通信的整体设计思路

2.1 开放式用户通信到底开放了什么

西门子的开放式用户通信(Open User Communication)本质上就是让PLC的以太网口不再只服务于Profinet和S7通信,而是可以像PC一样建立标准的TCP/IP连接。这个“开放”体现在两个层面:一是协议开放,支持TCP、ISO-on-TCP、UDP三种传输层协议;二是数据开放,发什么内容、什么格式、什么时候发,完全由程序控制。

为什么西门子要搞这么一套东西?因为工业现场的设备太杂了。视觉系统可能是康耐视的,扫码枪可能是基恩士的,机械手可能是发那科的,这些设备不一定支持Profinet,但几乎都支持TCP/IP。PLC作为产线的控制核心,天然就应该承担起“数据汇聚点”的角色。开放式用户通信就是给PLC装了一张“通用嘴”,什么协议都能聊。

但这里有个关键点很多人忽略:开放式用户通信是“主动”的。PLC既可以做客户端主动去连别人,也可以做服务端等着别人来连。这两种角色的程序写法完全不同,连接建立的方向决定了谁先发起、谁负责重连、谁管理连接资源。

2.2 TCP、ISO-on-TCP、UDP到底选哪个

这是第一个要做出的技术决策,选错了后面全是麻烦。我先把三者的核心差异摆出来:

特性TCPISO-on-TCP (RFC1006)UDP
连接方式面向连接面向连接无连接
数据边界字节流,无边界保留消息边界保留消息边界
可靠性可靠,有重传可靠,有重传不可靠,不重传
头部开销20字节20+7字节8字节
典型用途通用设备对接西门子设备间、S7通信实时性要求高、允许丢包
PLC指令TCON/TSEND/TRCVTCON/TSEND/TRCVTUSEND/TRCV

TCP是最通用的,跟PC软件、视觉系统、扫码枪对接基本都用它。但TCP有个“粘包”问题——发送方发了三次数据,接收方可能一次全收到,也可能分五次收到,因为TCP只保证字节顺序,不保证消息边界。这就需要在应用层自己做协议解析。

ISO-on-TCP是西门子对TCP的“改良版”,在TCP基础上加了7个字节的TPKT头部,里面包含了数据长度信息,所以接收方知道一条消息到哪里结束。如果你跟西门子自己的设备或者支持RFC1006的系统通信,用这个省心很多。

UDP适合什么场景?比如你只是周期性广播一些状态信息,丢一两帧无所谓,或者对延迟极其敏感、不能接受TCP重传带来的抖动。但工业现场大多数场景还是求稳,UDP用得相对少。

我的建议很直接:跟第三方设备对接,先问对方支持什么。如果对方只说“支持TCP”,那就老老实实用TCP,然后在应用层定义好消息长度或者结束符。如果对方支持ISO-on-TCP,优先用这个,省去粘包处理的麻烦。

2.3 连接资源不是无限的,得算着用

这是很多新手容易踩的坑。S7-1200和S7-1500的开放式用户通信连接数是有限制的,不是你想建多少就建多少。

以S7-1200为例,CPU 1214C最多支持8个开放式用户通信连接,其中主动连接和被动连接共享这个池子。S7-1500的CPU 1516-3 PN/DP最多支持128个连接,但其中给开放式用户通信的也有单独上限。具体数值一定要查对应CPU的技术数据手册,别拍脑袋。

为什么要有这个限制?因为每个连接都要占用PLC的内存和通信处理器资源。PLC不是服务器,它的主职工作是跑控制逻辑,通信只是副业。连接数开太多,扫描周期会受影响,严重的时候甚至会导致CPU停机。

实际项目里怎么规划?我一般按这个原则:需要频繁交互的设备(比如视觉系统、机械手)用长连接,一直保持着;只是偶尔发个信号的设备(比如某些仪表)用短连接,发完就断。长连接数量控制在CPU上限的60%以内,留出余量给调试和临时接入。

3. 核心指令拆解与参数配置实操

3.1 TCON:连接建立的第一步

TCON指令是建立通信连接的核心。它的作用就是根据你配置的连接参数,主动或被动地建立一个TCP或ISO-on-TCP连接。

在博途里,TCON的调用需要配合一个TCON_IP_v4或TCON_IP_v6的数据结构。这个结构里包含了连接类型、本地端口、远程IP、远程端口、主动/被动模式等关键信息。

我拿一个实际例子来说。假设我们要让S7-1200作为客户端,去连接一台IP为192.168.1.100、端口号为8500的视觉系统。配置大概是这样的:

连接类型:TCP 主动连接:TRUE(PLC主动去连对方) 本地端口:0(自动分配) 远程地址:192.168.1.100 远程端口:8500

这里有个细节:本地端口填0表示由系统自动分配一个空闲端口。如果你填了固定值,比如2000,那就要确保这个端口没有被其他连接占用。我一般建议新手填0,省去端口冲突的排查。

TCON的REQ引脚用上升沿触发。注意,不是每个扫描周期都触发,那样会反复尝试建立连接。正确的做法是用一个上升沿检测,比如用R_TRIG指令,只在需要建立连接的那一刻触发一次。

TCON执行后,DONE置位表示连接建立成功,ERROR置位表示失败,STATUS里会有具体的错误代码。这个STATUS值一定要会看,它是排查问题的第一手线索。

3.2 TSEND:把数据推出去

连接建立之后,就可以用TSEND发数据了。TSEND的关键参数有三个:DATA是发送数据的地址和长度,LEN是实际发送的字节数,CONNECT是TCON建立的那个连接ID。

这里最容易出问题的是DATA的数据类型。TSEND的DATA引脚接受的是ANY指针类型,你可以填P#DB1.DBX0.0 BYTE 100这样的格式,表示从DB1的第0字节开始,发100个字节。但要注意,这个DB必须是优化的块还是非优化的块?在S7-1200/1500里,如果DB是优化的,绝对地址访问会受限。我一般建议做通信用的DB块取消“优化的块访问”属性,用标准地址访问,省得指针算错。

LEN参数填实际要发送的字节数。如果你填的LEN大于DATA实际有效的长度,会把后面的垃圾数据也发出去。如果小于,就只发前面一部分。这个值最好用变量控制,根据实际数据长度动态变化。

TSEND的REQ同样用上升沿触发。DONE置位表示数据已经成功交给通信处理器发送出去了,注意,是“交给发送缓冲区”,不是“对方已经收到”。TCP是异步的,TSEND的DONE只代表本地发送动作完成。

3.3 TRCV:把数据收进来

TRCV是接收指令,参数跟TSEND类似。DATA是接收缓冲区的地址,LEN是期望接收的最大字节数,CONNECT还是那个连接ID。

TRCV有个EN_R引脚,使能接收。这个引脚一般一直置TRUE,让PLC随时准备接收数据。当有数据到达时,NDR引脚会置位一个扫描周期,表示“新数据到了”。这时候你去读DATA里的内容就是刚收到的数据。

但这里有个大坑:TRCV的LEN参数。如果你填了100,但对方只发了50个字节,TRCV会一直等,直到收满100个字节或者超时。这就会导致数据明明到了,但NDR不置位,程序读不到。正确的做法是LEN填你协议里定义的最大消息长度,然后在应用层根据实际收到的长度(TRCV的RCVD_LEN参数)来解析有效数据。

还有一个更隐蔽的问题:如果对方发数据的频率很快,而你的TRCV调用周期跟不上,数据可能会在通信处理器的缓冲区里堆积。TRCV每次只取一条消息,如果缓冲区里有三条,你需要调用三次TRCV才能全部取完。所以TRCV的调用不能只在NDR置位时做一次,而应该循环调用直到没有新数据。

3.4 连接管理:建立之后别忘了断开

TDISCON指令用来断开连接。什么时候需要断开?如果对方设备要重启、要修改IP、或者你的程序要切换到另一个连接目标,就得先断开再重连。

但很多项目里,连接建立之后就再也不管了。这本身没问题,长连接就是应该一直保持。但你要考虑异常情况:如果网络闪断,连接断了,PLC这边可能还认为连接是好的,继续TSEND,结果数据发不出去。所以健壮的程序应该定期检查连接状态,发现断了就重新TCON。

怎么检查?可以看TCON的STATUS,也可以用一个心跳机制:定期发一个心跳包,如果连续几次TSEND都报错,就判定连接断开,触发重连逻辑。

4. 完整实操过程:从零搭建一个PLC与PC的Socket通信

4.1 硬件与软件环境准备

我拿手头的一套设备来演示:S7-1200 CPU 1214C DC/DC/DC,博途V16,一台普通Windows PC。PLC的IP设为192.168.0.10,PC的IP设为192.168.0.100,两者在同一个网段。

PC端我用一个简单的TCP调试助手来模拟第三方设备。这类工具网上很多,功能都差不多:可以创建TCP服务端或客户端,可以手动发数据,可以看接收到的数据。实际项目里对方可能是视觉系统、扫码枪或者上位机软件,但调试阶段用这个最方便。

博途里需要做的准备工作:新建项目,添加CPU,配置IP地址,然后在程序块里新建一个用于通信的DB块,取消优化访问,里面定义一个100字节的数组作为发送缓冲区,再定义一个100字节的数组作为接收缓冲区。

4.2 连接参数的数据结构配置

在博途里,TCON指令需要一个CONNECT参数,这个参数的数据类型是TCON_IP_v4。我一般会在DB块里定义一个这种类型的变量,然后在程序里赋值。

具体配置如下:

InterfaceId:64(这是PLC以太网口的硬件标识符,在设备组态里能看到) ID:1(连接的唯一编号,自己定,别跟其他连接冲突) ConnectionType:16#0B(TCP的代码,ISO-on-TCP是16#0C,UDP是16#13) ActiveEstablished:TRUE(主动连接) RemoteAddress:192.168.0.100 RemotePort:2000 LocalPort:0

InterfaceId这个值很多人不知道怎么填。在博途的设备视图里,双击PLC的以太网口,在属性里能看到“硬件标识符”,S7-1200一般是64,S7-1500可能是64或者其他的。填错了连接建不起来。

ConnectionType的代码要记准:TCP是16#0B,ISO-on-TCP是16#0C,UDP是16#13。这个在西门子文档里有,但经常有人填错。

4.3 程序逻辑的完整实现

程序结构我分成三块:连接管理、发送逻辑、接收逻辑。

连接管理这块,用一个状态机来控制。状态0是空闲,状态1是正在建立连接,状态2是连接已建立,状态3是连接断开需要重连。

状态0:如果StartConnect为TRUE,调用TCON,进入状态1 状态1:等待TCON的DONE或ERROR。DONE则进入状态2,ERROR则回到状态0并记录错误 状态2:连接正常,可以TSEND和TRCV。如果TSEND连续报错3次,进入状态3 状态3:调用TDISCON,然后回到状态0

发送逻辑用一个上升沿触发。比如PC端发来一个请求,PLC收到后回复一个响应。或者PLC周期性主动发送状态数据,用定时器触发TSEND。

接收逻辑把TRCV的EN_R一直置TRUE,NDR置位时把数据从接收缓冲区拷贝到解析缓冲区,然后调用解析程序。

这里我贴一段核心代码的伪代码,用SCL写:

// 连接管理 CASE #connState OF 0: // 空闲 IF #startConnect THEN #tconReq := TRUE; #connState := 1; END_IF; 1: // 连接中 #tconReq := FALSE; TCON(REQ := #tconTrigger, CONNECT := #tconParams, DONE => #tconDone, ERROR => #tconError, STATUS => #tconStatus); IF #tconDone THEN #connState := 2; ELSIF #tconError THEN #connState := 0; END_IF; 2: // 已连接 // 发送和接收逻辑 IF #sendTrigger THEN TSEND(REQ := #sendTrigger, DATA := #sendBuffer, LEN := #sendLen, CONNECT := #connId, DONE => #sendDone, ERROR => #sendError); END_IF; TRCV(EN_R := TRUE, DATA := #recvBuffer, LEN := 100, CONNECT := #connId, NDR => #recvNdr, ERROR => #recvError); 3: // 断开重连 TDISCON(REQ := TRUE, CONNECT := #connId); #connState := 0; END_CASE;

这段代码是简化版,实际项目里还要加错误处理、超时计数、心跳检测等。

4.4 联调过程与数据验证

程序下载到PLC后,先在PC上打开TCP调试助手,创建服务端,监听2000端口。然后PLC上触发StartConnect,观察TCON的STATUS。

如果一切正常,TCON的DONE会置位,STATUS为0。PC端的调试助手会显示有一个客户端连进来了。

然后PLC触发TSEND,发送一串测试数据,比如“Hello PLC”。PC端应该能收到这串字符。反过来,PC端发送“Hello PC”,PLC的TRCV的NDR置位,接收缓冲区里应该能看到这串字符。

联调的时候我习惯用博途的在线监控功能,把TCON、TSEND、TRCV的STATUS和NDR、DONE这些引脚都监控起来。哪个环节出问题一目了然。

数据验证要注意字节序。西门子PLC是大端模式,PC是小端模式。如果你发的是多字节的数值,比如一个32位整数,PC端收到的字节顺序是反的。解决办法是在发送前用SWAP指令把字节序转一下,或者双方约定好都用大端。

5. 常见问题与排查技巧实录

5.1 连接建立失败:STATUS代码速查

TCON的STATUS值是最重要的排查依据。我整理了几个常见的:

STATUS (十六进制)含义排查方向
0000成功
8081连接已存在检查是否重复调用TCON
8082连接资源不足减少连接数或换CPU
8083连接参数错误检查IP、端口、InterfaceId
8085连接被对方拒绝对方服务未启动或端口不对
8086连接超时网络不通或对方无响应
8090连接被本地断开检查TDISCON是否被误触发
8091连接被远程断开对方主动断开了连接

8085和8086是最常见的。8085一般是PC端的服务端没开,或者防火墙拦了。8086一般是IP地址不对,或者网线没插好。我遇到过好几次是PC的防火墙把入站连接挡了,关掉防火墙就好了。

5.2 数据发出去对方收不到

TSEND的DONE置位了,但对方没收到数据。这种情况先确认TSEND的DONE到底代表什么——它只代表数据交给了本地通信处理器,不代表对方收到了。

排查步骤:第一,确认连接是好的,TCON的STATUS为0。第二,确认LEN参数正确,别发了个空数据。第三,确认DATA指针指向的地址有有效数据。第四,在PC端用抓包工具看数据到底有没有发出去。

我遇到过一次,TSEND的DATA填的是P#DB1.DBX0.0 BYTE 100,但DB1是优化块,绝对地址访问无效,发出去的全是零。把DB1改成非优化块就好了。

5.3 接收数据不完整或粘包

TCP的粘包问题前面提过。如果你用TCP协议,接收方收到的数据可能不是一条完整消息。解决办法有两种:一是用ISO-on-TCP,自带消息边界;二是在应用层定义协议,比如每条消息前面加4个字节的长度头,接收方先读长度头,再根据长度读消息体。

我一般推荐第二种,虽然麻烦点,但通用性强。具体做法是:接收缓冲区设大一点,比如1024字节。TRCV收到数据后,先看前4个字节,解析出消息长度,然后判断缓冲区里的数据够不够一条完整消息。如果够,就取出来处理;如果不够,就等下一次TRCV。

5.4 连接数超限导致CPU报警

S7-1200的开放式用户通信连接数有限,超了会报错,严重的时候CPU会进入停止模式。我见过一个项目,程序里动态创建连接,每次触发都TCON一次,但从来不TDISCON,连接数很快用完了。

解决办法:连接用完之后一定要TDISCON。如果确实需要多个连接,算好总数,别超过CPU上限。S7-1200 1214C最多8个,1215C最多16个,具体查手册。

5.5 心跳机制的设计与实现

长连接不能光靠TCP自身的保活机制,那个时间太长了,默认要两个小时。实际项目里要自己做心跳。

我的做法是:PLC每隔5秒发一个心跳包,内容可以是一个固定的字符串或者一个递增的计数器。同时监控TSEND的ERROR,如果连续3次发送失败,就判定连接断开,触发重连。

PC端也要配合,收到心跳包后回复一个心跳响应。如果PLC连续3次没收到响应,同样判定断开。这样双向检测,可靠性高很多。

心跳间隔设多少?看项目要求。一般5到10秒比较合适,太短了增加网络负担,太长了故障发现不及时。

6. 进阶话题:多设备通信与性能优化

6.1 一个PLC跟多台设备同时通信

实际项目里,PLC往往要同时跟好几台设备通信。比如一台S7-1500要跟3台视觉系统、2台机械手、1台上位机同时交换数据。

这时候连接管理就很重要了。我的做法是给每个连接分配一个独立的连接ID和状态机,用一个数组来管理。每个连接的状态机独立运行,互不干扰。

但要注意,所有连接共享CPU的通信资源。如果同时有大量数据收发,通信处理器的缓冲区可能会满。这时候TSEND会报错,STATUS可能是8082或者80A1。解决办法是降低发送频率,或者增大缓冲区,或者升级CPU。

6.2 数据量大时的分片传输

如果要发的数据超过TRCV的LEN,或者超过通信处理器的单次传输上限,就需要分片。比如要发1000个字节,但单次最多发200个字节,那就分5次发。

分片传输的关键是双方要约定好分片规则。我一般用这样的协议:每条消息前面加一个头,包含总长度、分片序号、分片总数。接收方根据这些信息重组数据。

分片传输会增加延迟,所以如果数据量确实大,考虑用ISO-on-TCP或者换更高速的通信方式。

6.3 通信对扫描周期的影响

开放式用户通信的指令调用会占用扫描时间。TCON、TSEND、TRCV这些指令执行一次的时间取决于数据量和通信处理器的状态。数据量大的时候,单次调用可能占用几毫秒。

如果扫描周期本来就很紧张,比如1毫秒,那通信指令的调用就要分散到不同的扫描周期里。我的做法是用一个计数器,每个扫描周期只处理一个连接的通信,轮询着来。这样单次扫描的负担就小了。

另外,TRCV的EN_R一直置TRUE会持续占用资源。如果不需要一直接收,可以只在需要的时候置TRUE。

6.4 与Modbus TCP的对比与选择

有人会问:既然有Modbus TCP,为什么还要用Socket?Modbus TCP确实简单,有现成的指令库,不用自己定义协议。但它的局限性也很明显:数据模型固定,只有线圈和寄存器,复杂的数据结构表达不了。

Socket的优势就是灵活。你可以发JSON、发XML、发二进制自定义协议,想怎么来就怎么来。代价就是什么都要自己写。

我的选择标准是:如果对方设备支持Modbus TCP,而且数据量不大、结构简单,优先用Modbus TCP,省事。如果对方只支持原始TCP,或者数据结构复杂,那就用Socket自己定义协议。

7. 我个人在实际项目中的几点体会

做Socket通信这些年,踩过的坑比写过的代码还多。最大的体会就是:协议设计比编程本身更重要。很多问题不是PLC程序写错了,而是一开始协议就没定义清楚。发送方和接收方对数据格式的理解不一致,后面怎么调都是白搭。

所以我现在做项目,第一步永远是跟对方坐下来把协议写清楚:用什么协议、端口号多少、数据格式是什么、字节序是大端还是小端、心跳怎么发、异常怎么处理。这些写明白了,编程就是体力活。

第二个体会是:调试工具要趁手。我电脑上常备TCP调试助手、Wireshark、串口调试助手这几样。Wireshark尤其重要,它能抓到网络上的原始数据包,看到底发了什么、收了什么。很多在PLC程序里看不出来的问题,抓包一看就明白了。

第三个体会是:别怕用最笨的办法。我见过有人为了追求“优雅”,把通信程序写得极其复杂,状态机套状态机,结果出了问题自己都理不清。其实通信程序最重要的是稳定,不是优雅。用最简单的状态机,把每种情况都考虑到,比什么设计模式都管用。

最后分享一个小技巧:在通信DB块里留一个“调试缓冲区”,把每次发送和接收的数据都存一份,带上时间戳。出了问题的时候,把这个缓冲区导出来一看,什么时候发了什么、收了什么,一清二楚。这个习惯帮我省了无数排查时间。

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

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

立即咨询