3、Modbus RTU协议帧结构:地址码、功能码、数据区、CRC校验详解
好,咱们今天来聊聊Modbus RTU的帧结构。说白了,就是设备之间通信时,数据到底长什么样,怎么打包,怎么拆包。
我记得刚入行那会儿,拿着示波器看总线上的波形,一头雾水。后来师傅丢给我一句话:「你就记住,RTU帧就是四个部分:谁找我、找我干啥、带啥数据、对不对。」嗯,这句话我一直记到现在。
3.1 地址码——设备的名字
地址码占1个字节,范围是1到247。0是广播地址,所有设备都得听,但不回应。248到255是保留的,别乱用。
我在一个大型商业综合体项目里,遇到过两台冷机地址设成了同一个。结果一读数据,两台同时回,总线直接乱套。排查了整整一个下午,最后发现是施工队抄错了标签。所以我的习惯是:每个设备上电前,先核对一遍地址拨码开关。
地址码要点:
- 1字节,范围1~247
- 0为广播地址,设备不应答
- 248~255保留,不建议使用
- 同一总线上地址必须唯一
3.2 功能码——告诉设备做什么
功能码也是1个字节。暖通设备里,最常用的就那几个:
| 功能码 | 含义 | 暖通场景 |
|---|---|---|
| 01 | 读线圈状态 | 读取风机启停状态 |
| 02 | 读离散输入 | 读取水流开关状态 |
| 03 | 读保持寄存器 | 读取温度、压力、频率设定值 |
| 04 | 读输入寄存器 | 读取实际温度、压力测量值 |
| 05 | 写单个线圈 | 控制风机启停 |
| 06 | 写单个寄存器 | 修改温度设定值 |
| 15 | 写多个线圈 | 批量控制多个阀门 |
| 16 | 写多个寄存器 | 批量设定PID参数 |
你想想看,功能码03和04有什么区别?我经常被问到这个问题。简单说:03读的是你设的值,04读的是实际测的值。比如你给冷机设了7℃出水,03读到的是7,04读到的是传感器测回来的实际温度,可能是7.2或者6.8。
我的小技巧:调试时先用功能码04读一遍实际值,确认传感器正常,再用03核对设定值。两步走,能省不少排查时间。
3.3 数据区——真正干活的部分
数据区长度不固定,取决于功能码和要读写的数量。结构是这样的:
- 读操作:主机发「起始地址 + 数量」,从机回「字节数 + 数据」
- 写操作:主机发「起始地址 + 数量 + 数据」,从机回「起始地址 + 数量」确认
举个例子,我要读一台冷机的出水温度(寄存器地址40001):
主机发送:01 03 00 00 00 01 84 0A
从机回应:01 03 02 00 46 39 8C
拆开看:
- 01:地址码,冷机1号
- 03:功能码,读保持寄存器
- 00 00:起始地址,从0开始(对应40001)
- 00 01:读1个寄存器
- 84 0A:CRC校验
从机回应的02表示数据长度2字节,00 46就是数据,换算成十进制是70。如果这个寄存器代表0.1℃,那实际温度就是7.0℃。
注意:Modbus的数据是大端模式,高位在前。00 46就是0x0046,不是0x4600。我见过有人搞反了,读出来的温度变成179.2℃,吓得以为冷机出故障了。
3.4 CRC校验——数据对不对
CRC校验占2个字节,放在帧的最后。它是对地址码、功能码、数据区所有字节做循环冗余校验的结果。
计算过程不复杂,但手算很麻烦。我一般直接用现成的函数库。不过你得知道原理:
- 初始化CRC为0xFFFF
- 对每个字节与CRC低8位异或
- 右移1位,如果移出的是1,就与0xA001异或
- 重复8次,处理下一个字节
- 最终结果低字节在前,高字节在后
看个C语言实现:
unsigned int crc16_modbus(unsigned char *buf, int len) {
unsigned int crc = 0xFFFF;
for (int i = 0; i < len; i++) {
crc ^= buf[i];
for (int j = 0; j < 8; j++) {
if (crc & 0x0001) {
crc = (crc >> 1) ^ 0xA001;
} else {
crc >>= 1;
}
}
}
return crc;
}
我曾经在一个项目里,发现冷机偶尔会回错数据。查了半天,最后发现是CRC校验没做。从机收到错误帧也照常处理,导致控制逻辑乱跳。从那以后,我要求所有设备必须做CRC校验,不校验的一律不接入系统。
避坑指南:
- CRC计算时,数据区包含所有字节,不包括CRC本身
- 发送时低字节在前,高字节在后(和Modbus其他数据相反)
- 接收端一定要校验CRC,不要偷懒
- 如果CRC不对,直接丢弃整帧,不要尝试解析
3.5 帧间间隔——别把两帧混一起
RTU还有个特点:帧与帧之间要有至少3.5个字符时间的静默期。波特率9600时,大约是3.5ms。如果两帧间隔太短,接收端会认为是一帧数据,解析必然出错。
嗯,这里要注意:有些便宜的网关设备,发送间隔控制不好,容易把两帧粘在一起。我建议你在程序中加一个超时判断,如果收到字节后超过1.5个字符时间没新数据,就认为一帧结束了。
说白了,Modbus RTU的帧结构并不复杂。地址码找人,功能码派活,数据区干活,CRC把关。把这四个部分搞清楚了,暖通设备的通讯调试就成功了一大半。
下一节咱们聊聊实际项目中,怎么用这些知识排查通讯故障。到时候我会分享几个我踩过的坑,保证让你少走弯路。