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个字节,放在帧的最后。它是对地址码、功能码、数据区所有字节做循环冗余校验的结果。

计算过程不复杂,但手算很麻烦。我一般直接用现成的函数库。不过你得知道原理:

  1. 初始化CRC为0xFFFF
  2. 对每个字节与CRC低8位异或
  3. 右移1位,如果移出的是1,就与0xA001异或
  4. 重复8次,处理下一个字节
  5. 最终结果低字节在前,高字节在后

看个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把关。把这四个部分搞清楚了,暖通设备的通讯调试就成功了一大半。

下一节咱们聊聊实际项目中,怎么用这些知识排查通讯故障。到时候我会分享几个我踩过的坑,保证让你少走弯路。