干自动化项目,尤其是用西门子S7-1200/1500做设备间通信或对接上位机的时候,TSEND_C指令基本是绕不开的。它是TIA Portal(博图)里开放式以太网通信的核心发送指令,底层走TCP或UDP协议,负责把PLC中的数据块内容发给PC服务器、第三方控制器或者另一台PLC。功能本身不算复杂,真正让人血压升高的是它那一长串STATUS错误代码:0x8003、0x80A1、0x80C5、0x8090……每一条背后都对应一种故障场景,查手册费劲,网上答案又经常各说各话。这篇文章就结合我自己的项目经历,把TSEND_C常见错误代码从头到尾理一遍,附带排错思路和实战复盘,适合正在被通信问题折腾的电气工程师、自动化调试人员,也适合刚接触博图通信的新手参考。
1. 指令轮廓与错误代码产生的底层逻辑
1.1 TSEND_C指令参数速览与调用方式
TSEND_C在博图里的位置一般是“指令 - 通信 - 开放式通信 - 其他”下面,和TRCV_C是一对搭档。它的标准输入输出参数不多,但每个都直接影响排错方向:
- REQ:发送请求,必须在上升沿时触发,不能持续为1。
- LEN:发送数据长度,单位是字节。
- CONNECT:连接描述,是一个数据块参数,在里面配置协议类型、IP地址、端口号、本地端口、连接ID等。
- DONE:发送完成标志,为1表示本次发送成功。
- BUSY:忙碌标志,为1表示指令正在执行中。
- ERROR:错误标志,为1表示本次调用出错,需要查看STATUS。
- STATUS:状态字,返回具体错误代码或当前状态。
调用时TSEND_C会自动生成一个背景DB,用于保存连接状态和内部参数。相比老式的TCON加TSEND组合,TSEND_C把“建立连接”这件事隐藏到了内部,第一次调用时自动发起连接,后续调用直接在这个连接上发送数据,确实省了很多编程量。但这也带来一个问题:连接状态不可见,一旦STATUS报错,很多人第一反应就是懵,不知道是参数问题、网络问题还是对端问题。
所以排错第一步,先把这几个输出参数的时序关系搞清楚。DONE、BUSY、ERROR三者不会同时为1,每个扫描周期最多只有一个为1。BUSY为1时STATUS通常是0x7000,这表示指令正在处理,不是错误。只有ERROR为1时,STATUS的值才具有“错误代码”意义,这时候需要把STATUS记录下来并按代码分类排查。
1.2 状态字(STATUS)的读取方法:从DONE、ERROR、BUSY入手
很多初学者拿到程序后,第一个动作是盯着STATUS看。这个方向没错,但容易漏掉上下文。STATUS本身是一把“万能钥匙”,但同一个值在不同阶段含义不一样。比如0x7000代表“作业正在处理中”,如果你在BUSY=1时看到它,这是完全正常的;如果在ERROR=1时看到0x7000,那才是异常。
我的习惯是做一个通信诊断数据块,把每次调用后的DONE、ERROR、BUSY、STATUS、LEN全部记录下来,同时加一个触发计数器。这样调设备的时候不用一直盯着在线监视,等故障出现后直接看诊断DB里的历史记录,能快速锁定是哪一次调用出了问题。尤其在现场设备偶发断线的情况下,这个习惯能省下大量蹲在现场等故障重现的时间。
另外STATUS的值在博图在线监视里可以用十六进制显示,也可以切换成十进制。S7-1200/1500里STATUS是WORD类型,有些指令返回的是Integer格式,比如-32766对应的实际值就是16#8002。判断的时候我建议统一按十六进制看,因为手册里的错误代码表全是十六进制,避免换算错误。
1.3 为什么同一个错误码在不同固件版本里含义不同
这一点容易被忽略。TSEND_C在S7-1200和S7-1500上虽然指令名字一样,但底层实现有差异,错误代码定义也不完全一致。比如某些0x80B0段的内部错误,在老固件版本上不会出现,新固件版本增加了更细的状态分类。甚至同一个0x8003,在某些旧手册里是“连接被拒绝”,在新版本手册里则细化为“连接被对端关闭”。
所以排错的第一原则是:以你当前使用的TIA Portal版本和CPU固件版本对应的在线帮助为准。不要拿着网上搜来的老错误码表硬套,尤其是跨CPU系列的情况。我在项目里遇到过S7-1200报0x80C5而S7-1500同样场景报0x8003的情况,如果只认一个版本的表,很容易走弯路。实在不确定时,可以直接在博图指令帮助里搜索STATUS,查看当前版本自带的错误代码说明。
2. 错误代码分类与逐项解析
2.1 0x7000段:这些不是错误,只是状态
先把最容易误判的一段拿出来说。STATUS以0x7开头的值,基本都属于“过程状态”,不代表发送失败。常见的有:
- 16#0000:无任务或任务未激活。
- 16#7000:作业正在处理中,对应BUSY=1。
- 16#7001:连接已建立。
- 16#7002:连接正在建立中。
- 16#7003:连接已关闭。
- 16#7004:连接正在关闭。
- 16#7005:参数错误,但指令未捕获为ERROR。
- 16#7006:连接正在重新建立。
这段状态码的实用意义在于:你可以通过STATUS判断当前连接处于什么阶段。比如第一次调用TSEND_C后STATUS长时间停在0x7002,说明TCP三次握手一直没完成,问题大概率在网络或对端监听上,而不是发送数据本身。如果STATUS从0x7002变成0x7001,说明连接已经OK,后续发送正常的话STATUS会频繁出现0x7000。
注意区分0x7003和0x7004:0x7003表示连接已经关闭,0x7004表示正在执行关闭动作。如果在程序运行中经常看到0x7003,说明对端主动关闭了连接,或者PLC侧触发了断开逻辑。S7-1200的TSEND_C在连接关闭后不会自动重建连接,需要再次触发REQ或调用相应连接管理指令。
2.2 0x8000段:连接层常见的报错与处置
STATUS以0x8开头的值才是真正的错误代码。0x8000段集中在连接层,也就是TCP/UDP连接本身出了问题。这部分错误在现场最常见,我把高频的几个逐个说一遍。
- 16#8001:连接尚未建立。通常出现在你调用TSEND_C发送数据,但之前连接建立失败或连接已经被关闭。处理方法:先确认对端IP和端口是否可达,再检查连接描述参数。
- 16#8002:参数错误。CONNECT指向的连接描述数据有误,比如协议类型字段写错、连接ID重复、IP地址格式错误等。这类错误只要仔细核对连接DB里的每一项配置就能解决。
- 16#8003:连接被对端关闭或重置。这是TCP通信里的典型故障,对端socket主动close或异常退出都会触发。如果数据发送过程中频繁报0x8003,要重点检查对端程序是否超时断开、心跳机制是否正常。
- 16#8004:连接中断。链路层面出问题,比如网线松动、交换机掉电、对端网卡重启。和0x8003的区别是,0x8003通常是对端主动关闭,0x8004更像是物理链路或中间设备导致的断开。
- 16#8080:协议类型错误。比如连接描述配置的是UDP,但实际调用TSEND_C时使用了TCP参数,或者反过来。这类错误在改过连接DB后特别容易发生。
- 16#8081:目标IP地址错误。IP不可达或者不在同一网段。先ping一下对端,如果ping不通,问题基本在网络配置。
- 16#8082:连接ID已被占用。连接描述里的ID和PLC里其他连接重复了,需要改成唯一值。
- 16#8083:本地端口已被占用。常见于多个连接共用一个本地端口,或者程序里重复调用了同一个连接实例。
- 16#8084:远程端口无法访问。IP能通,但对端端口没有监听,或者被防火墙拦了。用telnet测一下端口就知道。
- 16#8085/16#8086:连接描述符错误。一般是连接描述数据被破坏,或者背景DB被意外修改。
- 16#8089:连接数量已达到上限。CPU支持的最大连接数已经用完,需要释放不用的连接,或者换更高型号的CPU。
- 16#8090:没有可用的连接资源。这个和8089有点类似,但更偏向底层资源耗尽,比如连接控制块不够。检查程序里是否有重复建立的连接,是否有TSEND_C实例忘记释放。
2.3 0x80A0/0x80C0段:数据层与资源类错误
过了连接层,就到了数据层。这类错误往往不是网络问题,而是程序参数配置问题。
- 16#80A1:数据长度无效。LEN参数大于实际可用的数据区长度,或者LEN为0。比如你DB块里定义了一个100字节的数组,但LEN填了120,就会报这个错误。这是新手最容易踩的坑之一。
- 16#80A2:数据缓冲区无效。发送缓冲区指向了系统存储区、I/O映像区等不允许访问的区域,或者指针越界。
- 16#80A3:指针错误。发送数据区的指针类型不对,比如使用ANY指针时指向了未初始化的数据块。
- 16#80B0:内部软件错误。一般是程序逻辑问题,比如多个TSEND_C同时操作同一个连接。
- 16#80B1:内部硬件错误。比较少出现,如果反复报这个码,可以先下载空程序测试CPU通信是否正常。
- 16#80B2:资源错误。系统资源不足,可能是通信负载过高。
- 16#80B3:连接正在建立中。这种情况通常配合ERROR=1出现,说明发送请求来得太快,连接还没准备好。
- 16#80C0:连接ID错误。TSEND_C本身没有R_ID参数,但如果你在程序里用旧版TSEND指令的思维去操作,容易在连接管理上搞混。
- 16#80C1:连接ID已被占用。同8082。
- 16#80C2:本地端口已被占用。同8083。
- 16#80C3:无可用连接资源。注意这个码在多次反复重连、频繁建立和断开连接时特别容易出现。TCP连接关闭后,端口会进入TIME_WAIT状态,短时间内大量重连会把资源耗尽。
- 16#80C4:远程地址错误。地址格式不正确,比如端口号填成0。
- 16#80C5:连接被拒绝。对端IP可达,但端口没人监听,或者对端防火墙直接发送了RST包。这个码在对接第三方设备时很常见。
- 16#80C6:本地端口无效。本地端口填0或超过65535时会出现。
再往下是0x80D0段,主要围绕发送作业本身:
- 16#80D1:发送作业被占用。同一个连接上已经有另一个发送任务在执行,冲突了。
- 16#80D2:接收缓冲区溢出。这个更多出现在TRCV_C配合时,如果接收缓冲区比实际到达数据小,则可能丢数据或报错。
- 16#80D3:LEN长度大于有效数据长度。和80A1类似,但更偏向于发送缓冲区有效范围不够。
- 16#80D4:数据指针无法访问。发送区指向了被保护的数据块,或数据块未加载。
- 16#80D5:SEND参数错误。LEN、缓冲区、连接描述这三者之间的配合出了问题。
2.4 错误代码速查表
自己动手把高频错误码做了一张速查表,现场调试时直接对照,效率高很多。
| 错误代码 | 含义 | 常见原因 | 处理办法 |
|---|---|---|---|
| 16#7000 | 作业处理中 | 正常状态,BUSY=1 | 等待指令完成 |
| 16#7001 | 连接已建立 | 正常状态 | 无需处理 |
| 16#7002 | 连接建立中 | 正在三次握手 | 检查网络和对端监听 |
| 16#8001 | 连接尚未建立 | 连接失败或已关闭 | 先排查连接建立 |
| 16#8002 | 参数错误 | 连接描述配置有误 | 核对CONNECT参数 |
| 16#8003 | 连接被对端关闭 | 对端断开socket | 检查对端程序和心跳 |
| 16#8004 | 连接中断 | 网络链路故障 | 检查网线、交换机 |
| 16#8080 | 协议类型错误 | TCP/UDP配置不匹配 | 修改连接描述 |
| 16#8081 | 目标IP错误 | IP不可达 | ping对端 |
| 16#8082 | 连接ID重复 | ID冲突 | 修改连接ID |
| 16#8083 | 本地端口占用 | 端口冲突 | 修改本地端口 |
| 16#8084 | 远程端口不可达 | 端口未监听或被防火墙拦截 | telnet测试端口 |
| 16#8089 | 连接数超上限 | CPU连接数耗尽 | 释放连接或换CPU |
| 16#8090 | 连接资源不足 | 资源耗尽 | 检查重复建连 |
| 16#80A1 | 数据长度无效 | LEN过大或为0 | 修正LEN |
| 16#80A2 | 数据缓冲区无效 | 指针越界 | 检查数据区定义 |
| 16#80B0 | 内部软件错误 | 多实例冲突 | 检查程序逻辑 |
| 16#80C3 | 无可用连接资源 | 频繁重连耗尽资源 | 增加重连间隔 |
| 16#80C5 | 连接被拒绝 | 端口无人监听或防火墙RST | 检查对端服务 |
| 16#80D1 | 发送作业被占用 | 同连接并发发送 | 确保互斥调用 |
3. 实战排错流程与案例复盘
3.1 通用排错五步法
遇到TSEND_C报错,不要急着改程序,更不要迷信“重启一下就好”。我给自己定了一套排错流程,按顺序走,大部分问题都能在半小时内定位。
第一步,确认网络可达性。在电脑上ping PLC的IP,再从PLC侧ping对端(S7-1500支持PING指令,S7-1200部分型号也可以)。ping不通的话,网线、交换机、IP网段挨个查。 第二步,确认端口可达性。TCP通信必须确认对端端口正在监听。Windows上用telnet命令测端口,命令格式是“telnet 目标IP 端口”,能进入黑屏或返回连接成功就代表端口通。 第三步,确认连接描述参数。在博图里打开TSEND_C对应的连接DB,逐项核对协议类型、IP地址、端口号、本地端口、连接ID。有时候只是端口号一个数字之差,就能折腾半天。 第四步,记录STATUS变化过程。不看单个状态的截图,要看一段时间内STATUS的序列变化。比如反复出现0x7002、0x7003、0x8003的循环,基本可以断定是对端主动断开。 第五步,用抓包工具看TCP层。Wireshark抓包是最直接的手段,重点看SYN包有没有回应、有没有RST包、数据包有没有重传。抓包不需要太深,能看到TCP三次握手和四次挥手基本就够了。
这套流程看起来简单,但真的能解决八成以上的通信问题。我一直觉得,通信调试最忌讳“猜”,每一步都要有依据。
3.2 案例一:S7-1200作为客户端连接不上服务器
这个案例来自一套设备数据采集项目,S7-1200作为客户端,要把设备状态数据主动发给PC端的上位机软件。现场现象是TSEND_C一直报错,STATUS在0x8001和0x8084之间反复跳。
我先ping了上位机IP,网络是通的。再用telnet测试上位机的监听端口,发现端口不通,返回“无法打开到主机的连接”。到这一步基本明确,问题不在PLC侧,而在PC服务器的端口监听或防火墙。
上位机软件确认端口确实有在监听,那剩下的嫌疑就是防火墙。查看Windows防火墙入站规则,发现程序监听的是TCP 5000端口,但入站规则里只开放了TCP 102端口(西门子PLC通信默认端口)。给5000端口添加入站规则后,TSEND_C立刻恢复正常,STATUS稳定在0x7000到0x7001之间。
这个案例看起来简单,但很有代表性。很多人一看到TSEND_C报错,第一反应是改PLC程序,折腾半天发现是电脑防火墙的问题。记住一个原则:先测端口,再查程序。
3.3 案例二:发送能通但偶尔断线,STATUS为0x8003
另一个项目里,PLC与PC通信已经调通,数据能正常上传,但运行几个小时后偶尔会出现一次断线,STATUS报0x8003,之后再也连不上,必须重启PLC程序或手动触发重连才能恢复。
0x8003表示连接被对端关闭,说明PC端主动断开了socket。我第一反应是上位机软件有没有设置空闲超时时间,查了一圈发现没有。再抓包看,发现PC端每隔一段时间会发送FIN包主动关闭连接,而PLC侧因为没有处理连接关闭后的重连逻辑,就一直卡在断线状态。
问题的根源是上位机软件在接收端设置了“无数据超时断开”的机制,超过一定时间没有收到数据就主动关socket。但PLC这边的数据发送周期是不固定的,忙的时候数据很频繁,闲的时候可能超过超时时间。
解决方法是两边的配合:上位机侧把超时时间改大,或者改成不主动断开;PLC侧加一个重连状态机,检测到ERROR=1且STATUS为0x8003时,延时触发一次REQ重建连接。两件事都做了之后,断线问题彻底消失。
3.4 案例三:多数据块轮询发送时出现0x80A1
第三个案例是PLC需要循环发送多个数据块给上位机,程序里用了一个状态机,按顺序切换发送不同的DB块。运行一段时间后偶发0x80A1数据长度错误。
排查时发现,状态机切换数据块的逻辑没问题,但在一个扫描周期内,TSEND_C的REQ信号和LEN信号被同时修改了。前一个数据块还没发送完,LEN就变成了下一个数据块的长度,导致TSEND_C内部校验时发现LEN比当前缓冲区可用长度大,于是报0x80A1。
这类问题本质上是数据一致性被破坏。解决办法很简单:把LEN和发送数据区的指针绑定成同一个结构体,用同一个索引切换;或者在REQ置位的那一个周期,不允许修改LEN。用“先切数据,再发REQ,发送完成后再切换下一个”的顺序,就能避免偶发错码。
4. 常见问题与避坑心得
4.1 网络层面的隐藏坑:防火墙、ARP缓存和子网规划
网络层面的问题在TSEND_C排错里占比非常高。除了前面提到的Windows防火墙,还要注意交换机端口隔离、VLAN划分、公司网络安全策略这些大环境因素。现场设备如果和上位机不在同一个VLAN,即使IP在同一网段也可能不通。
ARP缓存也是一个隐藏坑。PLC更换IP后,电脑或交换机里可能还缓存着旧的MAC地址和IP对应关系。遇到ping不通的情况,可以在电脑上用“arp -d”清空缓存再试,或者重启交换机。
另外,S7-1200/1500的以太网口如果同时配置了Profinet和TCP通信,连接资源会被Profinet占用一部分。CPU型号越入门级,可用连接数越少。选型时就要算好同时在线连接数,别等到现场报0x8089再换CPU。
4.2 程序层面的坑:REQ、LEN与指针
REQ触发方式是我见过最多人写错的地方。TSEND_C的REQ必须用上升沿触发,不能直接在OB1里把REQ接成常1或者每个周期都置1。我见过一个设备,程序里把REQ直接接成了M0.0,结果每个扫描周期都在触发发送,通信卡死,STATUS一直报0x80D1发送作业被占用。
正确做法是用P_TRIG或自己写一个上升沿检测。比如用两个M点做边沿判断,前一个周期的状态存下来,当前周期为1且前一个周期为0时,才给REQ置1,发送完成后复位。
LEN的坑主要在单位上,TSEND_C的LEN单位是字节,不是位也不是字。发送一个WORD数组时,LEN要填“数组长度乘以2”。发送字符串时,如果用的是String类型,要注意西门子String的前两个字节是长度信息,实际发送的字符数据需要从偏移2开始取,或者直接用Byte数组来承载报文,避免格式混乱。
数据区指针方面,建议给TSEND_C单独建一个发送缓冲区DB,定义为固定长度的Byte数组,所有发送数据都先拷贝到这个缓冲区再发。这样LEN和指针都是可控的,不会因为源数据区结构变化导致TSEND_C参数错误。这个方法牺牲一点内存,但能把偶发故障率压到很低。
4.3 与TRCV_C配合时的常见误区
TSEND_C和TRCV_C经常成对出现,一个发一个收。但很多人在配置接收端时踩坑。
TRCV_C的LEN参数是“最大接收长度”,不是“固定接收长度”。如果对端发送的数据长度小于LEN,接收缓冲区里只有实际收到的数据有效,实际长度通过RCVD_LEN输出。如果对端发送的数据长度大于LEN,多出来的部分会被丢弃,而且可能报0x80A2接收缓冲区溢出。
所以接收缓冲区的长度要按最大报文长度定义,不能为了省内存而设得太小。同时接收侧要做好报文帧头、帧尾或长度字段的校验,TCP是流式传输,没有报文边界的概念,一次发送可能被拆成多包,多次发送也可能被合并成一包。这个特性不掌握,最容易出现数据错位。
还有个容易忽略的点:PLC里如果同时有多个TRCV_C实例,每个实例必须使用不同的背景DB和连接描述,不能共用一个连接。否则后一个实例会抢掉前一个实例的连接资源,导致两边都报错。
4.4 几个容易被忽略的细节
- 连接描述DB的“优化块访问”属性建议关闭。优化块访问开启后,数据没有固定的偏移地址,某些指令在访问连接描述时会出问题。虽然不是百分百报错,但关闭后更稳。
- 背景DB不能“仅存储在装载内存”。如果勾选了“仅存储在装载内存”,PLC掉电后背景DB的数据不会被保持,通信参数会丢失,上电后TSEND_C可能直接报参数错误。
- 频繁重连时,注意TCP的TIME_WAIT状态。连接关掉后,本地端口会在一段时间内处于TIME_WAIT,不能立即被新连接复用。如果程序里反复断开重连,可能报0x8083或0x80C3,解决办法是重连间隔至少设到5秒以上。
- S7-1200和S7-1500的TSEND_C行为不完全一致。S7-1500在连接断开后会自动尝试重连,S7-1200则需要程序里处理重连逻辑。写程序前先确认目标CPU的行为,别把1500的程序结构原封不动搬到1200上。
- 在线监视时修改连接参数,必须重新下载并复位。有些参数是初始化时写入背景DB的,在线改不会生效,反而可能导致DB数据不一致。
5. 写在最后的几点体会
做了这么多年自动化项目,我的感受是:通信排错的门槛不在指令本身,而在对“时序”和“状态”的理解。TSEND_C的每一个错误代码,本质上都是在告诉你“当前连接走到了哪一步、哪一步出了问题”。把这个思想理顺了,再复杂的通信故障也能拆解成一个个小问题逐个击破。
最后分享一个个人习惯:现场调试通信程序时,我一般会在HMI上留一个“通信状态”页面,把关键连接的STATUS、DONE、ERROR、BUSY和最近一次错误时间显示出来。这样不管是自己调试还是后续维保人员排查,都不用打开博图在线监视,看一眼HMI就能知道故障方向。省下的时间,足够把整个系统的稳定性再打磨一遍。