3、RTU模式详解:RTU帧结构、CRC校验原理与计算、RTU模式下的字符间隔与帧间隔

3.1 RTU帧结构——比ASCII更紧凑的“数据包裹”

RTU模式,说白了就是Modbus的“二进制模式”。它不像ASCII那样把每个字节拆成两个字符发送,而是直接发送原始字节。这样做的好处很明显——同样的数据量,RTU的传输效率比ASCII高一倍。

我个人习惯把RTU帧看作一个“数据包裹”,它由四个部分组成:

  • 地址域(1字节):从站地址,范围1-247。0是广播地址。
  • 功能码(1字节):告诉从站要干什么,比如03是读保持寄存器。
  • 数据域(N字节):具体的数据内容,长度可变。
  • CRC校验(2字节):16位循环冗余校验,低字节在前,高字节在后。

你想想看,整个帧没有起始标记,也没有结束标记。那接收方怎么知道一帧结束了?全靠“静默时间”来判断。这就是RTU和ASCII最大的不同点。

RTU帧结构示例(读保持寄存器):

从站地址: 0x01
功能码:   0x03
起始地址: 0x00 0x6B
寄存器数: 0x00 0x03
CRC校验:  0x?? 0x??

总共8个字节,干净利落。

我在项目中遇到过有人把RTU帧和ASCII帧搞混,结果通信死活不通。嗯,这里要注意——RTU帧里没有冒号、没有回车换行,全是二进制数据。

3.2 CRC校验原理——为什么Modbus选了这个算法?

CRC校验,全称是循环冗余校验。说白了,就是把整个报文当作一个巨大的二进制数,然后除以一个固定的“生成多项式”,余数就是CRC值。

Modbus RTU用的是CRC-16,生成多项式是0x8005(也就是x^16 + x^15 + x^2 + 1)。为什么选这个?因为它在16位校验中错误检测能力很强,而且硬件实现简单。

我记得刚入行时,总觉得CRC很神秘。后来自己手算了一遍,发现其实就是“异或”和“移位”的反复操作。没什么高深的。

CRC校验的核心步骤:

  1. 初始化CRC寄存器为0xFFFF
  2. 对每个字节,与CRC寄存器的低8位异或
  3. 右移1位,如果移出的位是1,则与0xA001异或
  4. 重复8次(一个字节处理完)
  5. 所有字节处理完后,CRC寄存器取反,得到最终CRC值

小技巧:实际工程中没人手算CRC。我建议直接用查表法,提前算好256个字节对应的CRC值,运行时直接查表。速度能快几十倍。

3.3 CRC计算实战——手把手教你算一遍

光讲理论没意思,我们来算一个实际的例子。假设我们要发送01 03 00 6B 00 03这6个字节,CRC怎么算?

我习惯用C语言写一个简单的CRC计算函数:

unsigned int crc16_modbus(unsigned char *buf, int len) {
    unsigned int crc = 0xFFFF;
    int i, j;
    
    for (i = 0; i < len; i++) {
        crc ^= buf[i];
        for (j = 0; j < 8; j++) {
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001;
            } else {
                crc = crc >> 1;
            }
        }
    }
    return crc;
}

01 03 00 6B 00 03丢进去算,得到CRC值是0x3C 0x88。注意,发送时低字节在前,所以实际发送的是88 3C

避坑指南:我曾经在项目里犯过一个低级错误——把CRC的高低位搞反了。结果从站一直回复异常码。查了两天才发现,原来是字节序的问题。记住:Modbus RTU的CRC是低字节在前,高字节在后

3.4 RTU模式下的字符间隔与帧间隔——时间就是一切

RTU模式没有帧头帧尾,那接收方怎么知道一帧结束了?答案是:靠时间间隔。

Modbus协议规定了两类时间间隔:

间隔类型 时间要求 说明
字符间隔 ≤ 1.5个字符时间 同一帧内相邻字符的最大间隔
帧间隔 ≥ 3.5个字符时间 两帧之间的最小间隔

“字符时间”怎么算?很简单:1个字符时间 = 11位 / 波特率。为什么是11位?因为Modbus RTU通常使用1个起始位 + 8个数据位 + 1个校验位 + 1个停止位,总共11位。

举个例子,9600波特率下:

  • 1个字符时间 = 11 / 9600 ≈ 1.146ms
  • 1.5个字符时间 ≈ 1.719ms
  • 3.5个字符时间 ≈ 4.010ms

你想想看,如果接收方在收到一个字节后,超过1.719ms还没收到下一个字节,它就认为这一帧结束了。然后开始检查CRC,看看数据是否完整。

实际工程中的经验值:

我个人习惯把帧间隔设得比协议要求稍微宽松一点,比如4ms(9600波特率下)。因为有些从站的时钟不太准,太严格反而容易丢帧。但字符间隔一定要严格,超过1.5个字符时间就视为帧结束。

3.5 避坑指南——RTU模式下的常见问题

做了这么多年Modbus,我总结了几条RTU模式下的血泪教训:

  • CRC计算要放在发送缓冲区里统一算:别边发边算,容易出错。
  • 接收时用定时器监控字符间隔:别用延时函数,会阻塞其他任务。
  • 帧间隔要留余量:3.5个字符时间是最低要求,实际可以设到5个字符时间。
  • 注意波特率偏差:主从站的波特率偏差不能超过±2%,否则字符间隔会乱。

我记得有一次在现场调试,从站老是丢帧。查了半天,发现是主站的晶振精度不够,9600波特率实际跑成了9500。偏差超过5%,字符间隔全乱了。后来换了高精度晶振,问题解决。

嗯,RTU模式就讲到这里。下一章我们聊聊ASCII模式,看看它和RTU到底该怎么选。