第三章 通信协议入门:Modbus RTU、Modbus TCP 与 OPC UA
各位同学,大家好。这一章我们来聊聊通信协议。说实话,做EMS系统,通信协议就是你的“耳朵”和“嘴巴”。设备采集不上来数据,或者指令发不下去,整个系统就是一堆废铁。我刚开始接触工业通信时,也被各种协议搞得头大。但别怕,咱们一个一个来,把最常用的三个讲透。
本章核心脉络:从物理层到应用层,理解Modbus RTU(串口)、Modbus TCP(以太网)和OPC UA(跨平台互操作)的定位与区别。
3.1 Modbus RTU 协议详解
Modbus RTU,说白了就是串口通信的“老大哥”。它诞生于1979年,到现在还在大量使用。为什么?因为它简单、可靠、成本低。我见过很多老旧的电表、PLC、变频器,都只支持RTU。
物理层:通常走RS232或RS485。RS232只能点对点,距离15米左右。RS485可以挂32个设备,距离能到1200米。我个人习惯,只要距离超过10米,一律用RS485加屏蔽双绞线。
数据帧结构:RTU是二进制传输,不是ASCII。每个报文包含:
- 地址码(1字节):从站地址,范围1-247。0是广播地址。
- 功能码(1字节):比如03读保持寄存器,06写单个寄存器。
- 数据区(N字节):寄存器地址、数量、具体数值。
- CRC校验(2字节):循环冗余校验,保证数据完整性。
小技巧:我曾经调试一个RTU设备,死活读不到数据。后来发现是CRC算错了。很多初学者会忽略CRC,直接用现成库。但如果你要自己写驱动,CRC算法必须手撸一遍。我推荐用查表法,速度快。
功能码速查表:
| 功能码 | 名称 | 作用 |
|---|---|---|
| 01 | 读线圈状态 | 读取DO(数字量输出) |
| 02 | 读离散输入 | 读取DI(数字量输入) |
| 03 | 读保持寄存器 | 读取AO(模拟量输出/参数) |
| 04 | 读输入寄存器 | 读取AI(模拟量输入) |
| 05 | 写单个线圈 | 控制单个DO |
| 06 | 写单个寄存器 | 设置单个AO/参数 |
| 15 | 写多个线圈 | 批量控制DO |
| 16 | 写多个寄存器 | 批量设置AO/参数 |
代码示例:读取保持寄存器(Python + minimalmodbus)
import minimalmodbus
# 初始化串口设备
instrument = minimalmodbus.Instrument('COM3', 1) # 串口COM3,从站地址1
instrument.serial.baudrate = 9600
instrument.serial.bytesize = 8
instrument.serial.parity = 'N'
instrument.serial.stopbits = 1
instrument.serial.timeout = 0.5
try:
# 读取从地址0开始的2个保持寄存器
values = instrument.read_registers(0, 2, functioncode=3)
print(f"寄存器值: {values}")
except Exception as e:
print(f"读取失败: {e}")
避坑指南:我曾经遇到一个坑——RTU的字节序问题。有些设备是大端(Motorola),有些是小端(Intel)。比如一个16位整数0x1234,大端发送0x12 0x34,小端发送0x34 0x12。如果你发现读出来的数值不对,先检查字节序!
3.2 Modbus TCP 协议详解
Modbus TCP,其实就是把RTU的报文封装到TCP/IP包里。它去掉了CRC校验(因为TCP本身有校验),加了一个MBAP报文头。说白了,RTU是“老式电话线”,TCP是“互联网宽带”。
MBAP报文头(7字节):
- 事务标识符(2字节):用于匹配请求和响应。我习惯用递增计数器。
- 协议标识符(2字节):固定为0x0000,表示Modbus协议。
- 长度(2字节):后续数据的字节数。
- 单元标识符(1字节):相当于RTU的从站地址,用于网关路由。
与RTU的核心区别:
- TCP可以多客户端同时连接(RTU只能一问一答)。
- TCP默认端口502,需要防火墙放行。
- TCP没有CRC,但依赖TCP的可靠性。
代码示例:Modbus TCP 读取数据(Python + pymodbus)
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient('192.168.1.100', port=502)
client.connect()
try:
# 读取从地址0开始的3个保持寄存器
result = client.read_holding_registers(0, 3, unit=1)
if not result.isError():
print(f"寄存器值: {result.registers}")
else:
print("读取错误")
finally:
client.close()
经验之谈:我做过一个项目,现场有50多个Modbus TCP设备。一开始用轮询方式,结果发现响应越来越慢。后来改成异步并发,每个设备一个协程,速度提升了10倍。记住,TCP可以并发,别浪费这个特性。
3.3 OPC UA 协议简介
OPC UA(统一架构),是工业4.0的“明星协议”。它不像Modbus那样只传裸数据,而是把数据、类型、方法、历史记录都封装成对象模型。说白了,Modbus是“发短信”,OPC UA是“发带附件的邮件”。
为什么需要OPC UA?
- 跨平台:Windows、Linux、嵌入式都能跑。
- 安全:支持证书加密、用户认证。
- 信息模型:可以定义设备的结构,比如“电机”包含“转速”、“温度”、“启停方法”。
- 发现机制:客户端可以自动扫描服务器上的节点。
核心概念:
- 节点(Node):一切皆节点。变量、对象、方法都是节点。
- 引用(Reference):节点之间的关联,比如“电机”引用“转速”。
- 地址空间(AddressSpace):所有节点组成的树形结构。
注意:OPC UA不是OPC DA的升级版。OPC DA依赖COM/DCOM,只能在Windows上用,而且防火墙配置极其痛苦。我当年配置DCOM权限,折腾了两天。OPC UA彻底解决了这个问题,一个库搞定所有平台。
代码示例:OPC UA 客户端读取变量(Python + opcua-asyncio)
from opcua import Client
client = Client("opc.tcp://192.168.1.200:4840")
client.connect()
try:
# 获取根节点
root = client.get_root_node()
# 读取变量(路径根据实际地址空间)
temp = client.get_node("ns=2;s=Temperature")
value = temp.get_value()
print(f"温度值: {value} °C")
finally:
client.disconnect()
避坑指南:我曾经在OPC UA连接时遇到“证书不匹配”错误。原因是服务器和客户端的证书没有互相信任。解决办法:要么在服务器端配置“允许匿名连接”(不安全),要么正确导入证书。生产环境一定要用证书,别图省事。
3.4 如何选择协议?
嗯,这里我直接给建议:
- 老旧设备、简单传感器:用Modbus RTU。成本低,兼容性好。
- 新设备、以太网环境:用Modbus TCP。速度快,易调试。
- 系统集成、跨平台、需要安全:用OPC UA。虽然学习曲线陡,但一劳永逸。
我个人习惯,在EMS系统中,底层采集用Modbus RTU/TCP,上层数据汇聚用OPC UA。这样既兼容老设备,又方便未来扩展。你想想看,是不是这个理?