3. Modbus RTU协议详解:RTU帧结构、地址域、功能码、数据域、CRC校验原理
Modbus RTU(Remote Terminal Unit)是工业现场最常用的串行通信协议之一。它以紧凑的帧结构、高效的二进制编码和强大的CRC校验,在PLC、变频器、传感器、仪表等设备之间建立了可靠的“对话”规则。理解RTU帧结构是进行串行总线调试和故障排查的基础。
3.1 RTU帧结构总览
Modbus RTU的报文以“帧”为单位进行传输。每一帧由四个基本域组成:地址域、功能码、数据域和CRC校验域。帧与帧之间通过至少3.5个字符时间的静默间隔(Silent Interval)进行分隔。
3.2 地址域(Address Field)
地址域占用1个字节,取值范围为 0 ~ 247。其中:
- 1 ~ 247:分配给从站设备的唯一地址。主站通过地址域指定要通信的从站。
- 0:广播地址。所有从站都会接收并执行命令,但不需要回复。
| 地址值 | 目标设备 | 响应要求 | 典型应用场景 |
|---|---|---|---|
| 0 | 所有从站(广播) | 不响应 | 同步时钟、启动批量操作 |
| 1 ~ 247 | 指定从站 | 必须响应 | 读取传感器数据、写入控制参数 |
| 248 ~ 255 | 保留 | — | 协议扩展或厂商自定义 |
3.3 功能码(Function Code)
功能码占用1个字节,用于告诉从站要执行什么操作。功能码分为公共功能码、用户自定义功能码和保留功能码三类。在厂务数据采集中,最常用的是以下公共功能码:
3.4 数据域(Data Field)
数据域是帧中长度可变的部分,具体内容取决于功能码。数据域通常包含:
- 起始地址:要操作的寄存器或线圈的起始编号(2字节,高字节在前)。
- 数量:要读取或写入的寄存器/线圈数量(2字节,高字节在前)。
- 数据值:写入操作时携带的具体数值。
以读取保持寄存器(功能码 0x03)为例,请求帧的数据域格式如下:
| 字节偏移 | 内容 | 长度 | 示例值 | 说明 |
|---|---|---|---|---|
| 0 | 起始地址高字节 | 1 Byte | 0x00 | 寄存器地址 = 0x0064 = 100 |
| 1 | 起始地址低字节 | 1 Byte | 0x64 | |
| 2 | 寄存器数量高字节 | 1 Byte | 0x00 | 读取数量 = 0x000A = 10 个寄存器 |
| 3 | 寄存器数量低字节 | 1 Byte | 0x0A |
对应的响应帧数据域则包含:
- 字节计数:后续数据的字节数(1字节)。
- 寄存器数据:每个寄存器占2字节,高字节在前,低字节在后。
3.5 CRC校验原理
CRC(Cyclic Redundancy Check,循环冗余校验)是Modbus RTU保证数据完整性的核心机制。它使用16位CRC,多项式为 0x8005(x¹⁶ + x¹⁵ + x² + 1),初始值为 0xFFFF。
CRC校验覆盖整个报文(地址域 + 功能码 + 数据域),但不包括CRC本身。接收方计算收到的报文CRC,并与帧尾的CRC值比较,若不一致则丢弃该帧并返回异常响应。
CRC计算的Python实现示例:
def crc16_modbus(data: bytes) -> int:
"""
计算 Modbus RTU CRC-16 校验值
:param data: 待校验的字节数据(地址 + 功能码 + 数据域)
:return: 16位 CRC 值
"""
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
# CRC 高低字节交换
return ((crc & 0xFF) << 8) | ((crc >> 8) & 0xFF)
# 示例:计算报文 01 03 00 64 00 0A 的 CRC
frame = bytes([0x01, 0x03, 0x00, 0x64, 0x00, 0x0A])
crc_value = crc16_modbus(frame)
print(f"CRC = 0x{crc_value:04X}") # 输出: CRC = 0xBCB5
3.6 完整报文示例
下面是一个完整的Modbus RTU请求与响应报文示例,主站读取地址为01的从站,从寄存器地址100(0x0064)开始读取10个保持寄存器。
| 方向 | 地址域 | 功能码 | 数据域 | CRC (低字节) | CRC (高字节) |
|---|---|---|---|---|---|
| 请求 (主站→从站) | 0x01 | 0x03 | 0x00 0x64 0x00 0x0A | 0xB5 | 0xBC |
| 响应 (从站→主站) | 0x01 | 0x03 | 0x14 (20字节数据)... | 0x?? | 0x?? |
响应帧的数据域中,0x14 表示后续有20个字节的数据(10个寄存器 × 2字节/寄存器)。如果从站检测到错误(如寄存器地址不存在),则会返回异常响应帧:
- 功能码:原功能码 + 0x80(例如 0x83)。
- 数据域:1字节的异常码,指示错误类型(如 0x02 表示非法数据地址)。