3. 通信协议解析方法
上位机与工业设备之间的通信,本质上是在一条字节流上“按约定切分并解释数据”。协议解析就是把这串字节还原成有意义的字段:谁是指令、谁是地址、谁是数据、谁负责校验。若解析规则不统一,即使物理链路完全正常,也会出现“收到数据但控制不动作”“数值跳变”“偶发丢帧”等现象。本节以典型设备协议为例,说明手工解析的完整方法。
一、报文结构:先定边界,再定字段
一条完整报文通常由帧头、地址/功能码、数据区、校验区和帧尾组成。帧头用于在连续字节流中定位报文起点,帧尾用于确认报文结束。以某类工业仪表常用的 Modbus RTU 风格帧为例,其结构可表示为:
[帧头 0xAA][设备地址 1B][功能码 1B][数据长度 1B][数据区 NB][校验 2B][帧尾 0x55]
注意:标准 Modbus RTU 并没有 0xAA 帧头和 0x55 帧尾,这里是为了教学演示而构造的“带帧头帧尾的类 Modbus 帧”。真实项目中,帧头帧尾可能由厂商自定义,也可能完全依赖静默间隔(如 3.5 字符时间)来划分帧边界。解析前必须先确认协议文档中帧边界的定义方式,否则后续字段全部错位。
二、帧头帧尾:定位与转义
帧头帧尾的核心作用是“找起点、找终点”。如果数据区中恰好出现与帧头相同的字节,接收方可能误判为新帧开始,因此部分协议会引入转义机制,例如将数据中的 0xAA 替换为 0x7D 0x5E,将 0x7D 替换为 0x7D 0x5D。手工解析时,必须按“先转义还原、再按字段切分”的顺序处理,否则长度和校验都会算错。
若协议不使用帧头帧尾,而是靠超时断帧,则解析逻辑应改为:每收到一个字节就刷新定时器,当静默时间超过阈值(例如 3.5 个字符时间)时,把缓冲区中的字节视为一帧。这种方式对波特率和系统调度敏感,上位机应避免在接收线程中做耗时操作。
三、校验方式:判断这帧能不能用
常见校验包括累加和、异或校验、CRC16 等。以 CRC16/MODBUS 为例,它对帧头之后、校验之前的全部字节进行计算,低字节在前、高字节在后。手工解析时,先按字段长度取出待校验区域,再计算 CRC,与报文中的校验字段比较。若不一致,直接丢弃该帧,不要尝试“修正”后使用。
下面给出一个可手工验算的示例帧(示例数据,非实测):
AA 01 03 02 00 64 55
帧头: AA
地址: 01
功能码: 03
数据长度: 02
数据: 00 64 → 十进制 100
校验: 55(此处仅为占位示例,真实 CRC 为 2 字节)
实际工程中,校验字段通常为 2 字节,且不包含帧头帧尾。若文档写“校验范围从地址到数据区结束”,就不能把帧头算进去。这一点在手工解析时最容易出错。
四、字节序与位域:数值怎么拼、状态怎么拆
字节序决定多字节数值的拼接顺序。大端(Big-Endian)是高字节在前,小端(Little-Endian)是低字节在前。例如数据区为 00 64,按大端解释为 0x0064 = 100;若按小端解释则为 0x6400 = 25600。协议文档必须明确字节序,上位机解析代码中也要统一约定,不能一部分字段大端、一部分字段小端而不加说明。
位域用于在一个字节或一个字中表达多个开关量或状态。例如状态字节 0x0D,二进制为 0000 1101,可定义:bit0 运行、bit1 故障、bit2 就绪、bit3 报警。解析时用“与运算”提取:status & 0x01 判断运行,status & 0x02 判断故障。位域定义必须写明每一位的含义、有效电平以及保留位处理方式。
五、手工解析的完整示例
假设上位机收到一帧:AA 01 03 02 00 64 55(示例帧,校验字段为占位)。按上述规则手工解析:
- 定位帧头 0xAA、帧尾 0x55,确认帧边界完整。
- 读取地址 0x01,功能码 0x03,数据长度 0x02。
- 取数据区
00 64,按大端拼接为 0x0064,即十进制 100。 - 若该字段表示温度,且协议规定单位 0.1℃,则实际值为 10.0℃。
- 校验字段按协议计算并比对,一致则接受,不一致则丢弃并记录。
若状态字节为 0x0D,按位域定义:bit0=1 表示运行,bit1=0 表示无故障,bit2=1 表示就绪,bit3=1 表示报警。上位机可据此更新界面状态灯,而不是把 0x0D 直接显示为“13”。
- 帧头帧尾是否唯一?数据区是否可能包含相同字节?是否需要转义?
- 长度字段是否包含帧头帧尾和校验?
- 校验范围从哪个字节开始、到哪个字节结束?
- 多字节数值是大端还是小端?单位与缩放系数是多少?
- 位域每一位的含义、有效电平、保留位如何处理?
掌握以上方法后,面对任何新协议,都可以先画出一张“字节—字段—含义”对照表,再用一帧已知数据手工走一遍解析路径。只有手工解析能走通,代码实现才有可靠的判断依据。