4、通信协议解析:RS485/Modbus RTU协议基础、读取参考温度计数据、读取待校准设备数据、数据帧解析与校验
好,咱们进入实战环节最核心的一步——通信协议解析。
说实话,做测温系统校准,硬件连好了只是第一步。真正让数据跑起来的,是通信协议。我见过太多工程师,硬件焊得漂漂亮亮,结果卡在协议解析上,一卡就是好几天。嗯,咱们今天就把这块硬骨头啃下来。
4.1 RS485物理层:为什么是它?
先聊聊RS485。你可能会问:为什么工业现场清一色用RS485,而不是RS232?
说白了,RS485是差分信号传输。两根线(A和B)传数据,抗干扰能力比RS232强太多了。我在一个电机房做校准项目时,RS232线拉长了就丢数据,换成RS485,30米稳稳的。
RS485关键参数:
- 传输距离:最远1200米(实际建议控制在800米以内)
- 节点数量:一条总线最多挂256个设备
- 信号方式:半双工,同一时刻只能收或发
- 电平标准:A-B > 200mV 为逻辑1,A-B < -200mV 为逻辑0
我的小习惯:布线时A和B线一定要双绞,终端电阻120Ω焊上。别省这个电阻,省了它,长距离通信时波形反射能让你怀疑人生。
4.2 Modbus RTU协议:工业界的通用语言
RS485是物理层,Modbus RTU是应用层。两者配合,就是工业测温的黄金搭档。
Modbus RTU的数据帧结构很简单,就四个部分:地址码、功能码、数据区、CRC校验。
| 字段 | 长度 | 说明 |
|---|---|---|
| 地址码 | 1字节 | 设备地址,范围1-247 |
| 功能码 | 1字节 | 03读保持寄存器,06写单个寄存器 |
| 数据区 | N字节 | 寄存器地址+数据内容 |
| CRC校验 | 2字节 | 低字节在前,高字节在后 |
举个例子。我要读取地址为0x01的参考温度计,它的温度值存放在寄存器地址0x0000处。发送的指令就是:
发送:01 03 00 00 00 01 84 0A
解析:
01 → 设备地址
03 → 功能码(读保持寄存器)
00 00 → 起始寄存器地址
00 01 → 读取1个寄存器
84 0A → CRC校验
设备回复的数据长这样:
接收:01 03 02 01 2C B9 8A
解析:
01 → 设备地址
03 → 功能码
02 → 数据长度(2字节)
01 2C → 温度值(十六进制,0x012C = 300,除以10得30.0℃)
B9 8A → CRC校验
注意:不同厂家的温度计,数据格式可能不一样。有的直接传整数,有的要除以10,有的甚至要补码转换。我曾经遇到过一台设备,温度值是反字节序存的,折腾了我两个小时才排查出来。
4.3 读取参考温度计数据
参考温度计是校准的基准,数据必须准确。我个人习惯用Python的pymodbus库来操作,代码简洁,调试方便。
from pymodbus.client import ModbusSerialClient
# 初始化串口
client = ModbusSerialClient(
method='rtu',
port='COM3',
baudrate=9600,
bytesize=8,
parity='N',
stopbits=1,
timeout=1
)
# 连接
client.connect()
# 读取参考温度计(地址0x01),寄存器0x0000,长度1
result = client.read_holding_registers(0x0000, 1, slave=0x01)
if not result.isError():
raw_value = result.registers[0]
temperature = raw_value / 10.0 # 假设精度为0.1℃
print(f"参考温度计读数:{temperature:.1f}℃")
else:
print("读取失败,检查接线和地址")
client.close()
这段代码我用了很多项目,基本没出过问题。唯一要注意的是,有些廉价的USB转485模块,波特率设高了会丢包。我建议先用9600波特率调试,稳定了再提速。
4.4 读取待校准设备数据
待校准设备通常也是Modbus RTU协议,但地址和寄存器可能不同。比如某款工业温度变送器,温度值存在寄存器0x0001,地址是0x02。
# 读取待校准设备(地址0x02),寄存器0x0001,长度1
result = client.read_holding_registers(0x0001, 1, slave=0x02)
if not result.isError():
raw_value = result.registers[0]
# 有些设备数据需要转换
# 比如:实际温度 = (raw_value - 5000) / 100.0
temperature = (raw_value - 5000) / 100.0
print(f"待校准设备读数:{temperature:.2f}℃")
else:
print("读取失败,检查设备地址和寄存器")
避坑指南:我曾经遇到一个设备,手册上写寄存器地址是0x0001,但实际读出来全是0。后来用Modbus扫描工具一查,发现温度值藏在0x0010。所以,拿到新设备,先用扫描工具把所有寄存器读一遍,确认数据位置,别完全信手册。
4.5 数据帧解析与CRC校验
CRC校验是Modbus RTU的灵魂。没有它,你根本不知道收到的数据对不对。
CRC-16(Modbus)的算法不复杂,但手算很麻烦。Python里可以用crcmod库,或者自己写一个函数。
def crc16_modbus(data: bytes) -> int:
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc
# 验证发送帧
send_frame = bytes([0x01, 0x03, 0x00, 0x00, 0x00, 0x01])
crc_value = crc16_modbus(send_frame)
print(f"CRC校验值:0x{crc_value:04X}")
# 输出:0x840A,与前面例子一致
收到数据后,也要做CRC校验。把收到的数据(包括CRC本身)重新算一遍,如果结果不是0,说明数据在传输过程中被污染了。
def verify_crc(frame: bytes) -> bool:
if len(frame) < 3:
return False
received_crc = (frame[-1] << 8) | frame[-2]
calculated_crc = crc16_modbus(frame[:-2])
return received_crc == calculated_crc
# 验证接收帧
recv_frame = bytes([0x01, 0x03, 0x02, 0x01, 0x2C, 0xB9, 0x8A])
if verify_crc(recv_frame):
print("CRC校验通过,数据可靠")
else:
print("CRC校验失败,数据可能损坏")
重要提醒:CRC校验不通过时,不要直接丢弃数据。我建议重试3次,如果还是失败,再报错。因为工业现场偶尔会有干扰,重试往往能解决问题。
4.6 本章知识体系
下面这张图,是我梳理的本章核心逻辑。从物理层到应用层,再到数据校验,一条线串下来。
嗯,到这里,通信协议这块就讲完了。你想想看,从RS485的接线,到Modbus RTU的帧结构,再到Python代码实现和CRC校验,是不是一条清晰的链路?
我个人觉得,协议解析没什么高深的,就是细心加耐心。多读几遍手册,多写几行调试代码,踩过的坑都会变成经验。
本章要点回顾:
- RS485用差分信号,抗干扰强,适合工业现场
- Modbus RTU帧结构:地址+功能码+数据+CRC
- 读取设备数据前,先用扫描工具确认寄存器位置
- CRC校验必须做,重试机制要加上