3、通信协议基础(下):Modbus TCP/IP协议、与Modbus RTU的异同、TCP Socket编程基础、工业以太网选型

好,咱们接着聊。上一节我们把Modbus RTU和ASCII扒了个底朝天,这一节重点聊聊Modbus TCP/IP。说实话,现在新建的项目,十有八九都是走TCP/IP了。为什么?因为便宜、好布线、速度快。但便宜归便宜,坑也不少。

3.1 Modbus TCP/IP协议:其实就是RTU穿了个马甲

Modbus TCP/IP,说白了就是把Modbus RTU的报文,直接塞进TCP的数据包里。它去掉了RTU里的CRC校验,因为TCP/IP协议栈自己会做校验。你想想看,TCP已经保证了数据包不丢、不乱序、不出错,我再加个CRC不是脱裤子放屁吗?

它的报文结构很简单:

| 事务处理标识符(2字节) | 协议标识符(2字节) | 长度(2字节) | 单元标识符(1字节) | 功能码(1字节) | 数据(N字节) |

这里我重点说几个容易搞混的地方:

  • 事务处理标识符:客户端发请求时随便填个序号,服务器回复时原样返回。我习惯用递增的计数器,方便排查丢包。
  • 协议标识符:固定为0x0000,表示这是Modbus协议。别问为什么,标准就是这么定的。
  • 长度:从单元标识符开始算起,到报文结束的总字节数。注意,不包括前面的6个字节。
  • 单元标识符:相当于RTU里的从站地址。但在TCP里,一个IP端口只对应一个设备,所以这个字段很多时候填0xFF或者0x00。不过我在项目里遇到过,有些老外做的PLC必须填1,否则不认。

关键区别:Modbus TCP没有从站地址的概念,用的是IP地址+端口号。单元标识符更多是为了兼容老设备,或者通过网关桥接到RTU网络时使用。

3.2 Modbus TCP与Modbus RTU的异同

很多新手问我:能不能把RTU设备直接接到以太网上?答案是不能。但你可以加个网关,网关负责把TCP转成RTU。我做过一个项目,现场有50多台老式仪表,全是RS485接口,硬是用了5个网关全部转成TCP,统一接到上位机。

咱们用表格对比一下:

对比项 Modbus RTU Modbus TCP
物理层 RS232/RS485 以太网(RJ45、光纤)
传输方式 半双工,主从轮询 全双工,可并发
地址范围 1-247(从站地址) IP地址+端口号
校验方式 CRC16 TCP/IP协议栈校验
报文头 无额外头 MBAP头(7字节)
最大节点数 32(RS485典型值) 理论上无限制
典型速率 9.6kbps - 115.2kbps 10/100/1000Mbps

嗯,这里要注意:虽然TCP支持并发,但很多PLC的Modbus TCP服务器端是单线程处理的。你并发发100个请求,它还是一个一个处理。所以别以为用了TCP就能随便狂轰滥炸,该做的延时还是得做。

3.3 TCP Socket编程基础:别怕,其实就那几步

很多搞自动化的人一听Socket编程就头疼,觉得那是IT的事。其实没那么玄乎。我当年第一次写上位机采集程序,也是硬着头皮啃的。说白了,TCP Socket就三步:连上、发数据、收数据。

下面我用Python举个例子,因为Python最直观,适合快速验证:

import socket
import struct

# 1. 创建Socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 连接PLC(假设IP是192.168.1.100,端口502)
client.connect(('192.168.1.100', 502))

# 3. 构造Modbus TCP请求报文(读保持寄存器,起始地址0,读2个寄存器)
transaction_id = 1
protocol_id = 0
length = 6          # 后面6个字节
unit_id = 1
function_code = 0x03
start_addr = 0x0000
quantity = 0x0002

# 打包成二进制
req = struct.pack('>HHHBBHH', 
                  transaction_id, protocol_id, length,
                  unit_id, function_code, start_addr, quantity)

# 4. 发送
client.send(req)

# 5. 接收响应(至少收8个字节的MBAP头,再根据长度字段收完)
resp_header = client.recv(8)  # 先收头
# 解析长度字段
_, _, resp_len, _, _ = struct.unpack('>HHHBB', resp_header)
# 收完剩余数据
resp_data = client.recv(resp_len - 2)  # 减掉已经收的单元标识符和功能码

# 6. 关闭
client.close()

我的经验:实际项目中,千万别用阻塞式recv不加超时。我曾经有个程序,PLC突然断电,recv一直卡着不动,整个采集线程都挂了。一定要加socket.settimeout(3),3秒收不到就重试。

为什么会这样?因为TCP是流式协议,你发的数据可能被拆成多个包发出去,也可能多个小包合并成一个。所以接收的时候一定要按Modbus TCP的报文格式来解析,不能想当然地认为一次recv就能收到完整报文。

3.4 工业以太网选型:别图便宜,血的教训

说到工业以太网,我得先泼盆冷水。别以为家里用的网线、交换机就能用到工厂里。我见过一个项目,现场用了普通交换机,结果车间里电焊机一开,网络就断。为什么?电磁干扰太强了。

工业以太网选型,我建议关注这几点:

  • 防护等级:至少IP30以上,最好IP65。粉尘、油污、震动,普通交换机扛不住。
  • 工作温度:工业现场夏天40度、冬天零下10度很正常。家用交换机标称0-40度,到了冬天就罢工。
  • 冗余功能:关键链路一定要支持环网冗余(RSTP/ERPS)。我有个项目,一条生产线因为交换机坏了停了2小时,损失几十万。后来全部改成环网,断一条线自动切换。
  • 电源:工业交换机一般支持双电源冗余,DC 24V供电。别用那种插个电源适配器的,现场震动几下就松了。
  • 网线:至少超五类,最好六类屏蔽线。屏蔽层要单端接地,否则反而引入干扰。

避坑指南:我曾经在一个化工厂,用了号称工业级的交换机,结果半年坏了3台。拆开一看,里面电路板没有做三防漆处理,被腐蚀性气体搞短路了。所以选型时一定要问清楚:有没有做三防处理?有没有抗硫化能力?

最后说一句,工业以太网不是越贵越好,但一定不能图便宜。你想想看,一个交换机几百块,一条生产线几百万,哪个更重要?

好了,这一节就到这里。下一节我们聊聊OPC UA,那个才是真正的大杀器。