第三章 通信协议入门:Modbus RTU、Modbus TCP 与 OPC UA

各位同学,大家好。这一章我们来聊聊通信协议。说实话,做EMS系统,通信协议就是你的“耳朵”和“嘴巴”。设备采集不上来数据,或者指令发不下去,整个系统就是一堆废铁。我刚开始接触工业通信时,也被各种协议搞得头大。但别怕,咱们一个一个来,把最常用的三个讲透。

本章核心脉络:从物理层到应用层,理解Modbus RTU(串口)、Modbus TCP(以太网)和OPC UA(跨平台互操作)的定位与区别。

EMS通信协议知识体系 Modbus RTU Modbus TCP OPC UA RS232/485 串口 主从问答模式 适合短距离、低速 以太网 TCP/IP 主从/多客户端 适合中高速网络 跨平台、面向对象 数据建模+安全 适合复杂系统集成 从RTU → 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。这样既兼容老设备,又方便未来扩展。你想想看,是不是这个理?


专注资料整理