Modbus通讯错误常用检测方式

Modbus通讯错误常用检测方式缩略图
本文目录
  1. 1. 一、错误检测的三层架构
  2. 2. 二、字符层:奇偶校验
  3. 3. 2.1 原理
  4. 4. 2.2 计算示例
  5. 5. 2.3 奇偶校验的局限
  6. 6. 三、帧层(一):LRC 校验 —— Modbus ASCII 模式
  7. 7. 3.1 LRC 是什么
  8. 8. 3.2 Modbus ASCII 帧结构
  9. 9. 3.3 LRC 算法
  10. 10. 3.4 LRC 的 C 语言实现
  11. 11. 3.5 LRC 的局限
  12. 12. 四、帧层(二):CRC-16 校验 —— Modbus RTU 模式
  13. 13. 4.1 CRC-16 Modbus 的数学定义
  14. 14. 4.2 CRC-16 Modbus 算法流程(逐位计算法)
  15. 15. 4.3 CRC-16 Modbus C 语言实现
  16. 16. 4.4 查表法 —— 嵌入式设备上的标准实现
  17. 17. 4.5 验证你的 CRC 实现
  18. 18. 4.6 CRC 的检错能力
  19. 19. 五、应用层:异常响应码
  20. 20. 5.1 异常响应的帧格式
  21. 21. 5.2 标准异常码速查表
  22. 22. 5.3 异常码的实际调试用法
  23. 23. 六、超时机制:主站的最后一道防线
  24. 24. 七、完整排障流程

来源:Modbus中文网(modbus.cn) —— 国内领先的Modbus通信协议技术社区

本文:Modbus 通讯错误的检测与诊断:从奇偶校验到异常码的完整体系 · 作者:modbus技术团队 · 发布于 2026-07-01

摘要:Modbus 协议的错误检测分为三个层次——字符级的奇偶校验、帧级的 LRC/CRC 校验、以及应用层的异常响应码和超时机制。本文逐一拆解每种检测方式的工作原理、算法实现(含可运行的 C/Python 代码)和实际调试用途,并给出完整的异常码速查表和排障流程。关键词:Modbus CRC、LRC 校验、奇偶校验、Modbus 异常码、Modbus 错误检测、CRC16 Modbus。


在 RS-485 总线上传输一个 Modbus 帧,就像在嘈杂的车间里喊话——喊出去的是 01 03 00 00 00 01,对端收到的可能变成了 01 03 00 00 00 03。一个比特翻转,温度读数就从 25°C 变成 26°C,或者某台阀门「为什么不动」的原因就埋在这里。

Modbus 从 1979 年诞生就带着一套分层错误检测机制:每个字符用奇偶校验挡住比特错误,整帧数据用 CRC(RTU 模式)或 LRC(ASCII 模式)兜底,应用层再通过异常响应码告诉主站「你的请求有问题」。这三层机制配合超时重发策略,构成了 Modbus 在工业现场存活近五十年不倒的可靠性基础。

坦率说,Modbus 的错误检测不是密码学级别的——CRC16 不能防篡改,LRC 对连续偶数位错误可能漏检。但在一个 9600bps、几十米 RS-485 总线的物理环境下,这些机制已经够用了。它们的价值不在于理论完美,而在于实现简单、计算开销低——一个 8 位单片机用不到 100 字节的代码就能完成 CRC16 计算。


一、错误检测的三层架构

Modbus 串行通信的错误检测分成三个层级,从上到下覆盖整个通信栈:

应用层:异常响应码 + 超时重发
   ↓
帧层  :CRC-16(RTU)或 LRC(ASCII)
   ↓
字符层:奇偶校验(Even/Odd/None)

字符层保护每个字节的传输正确性——一个 UART 帧里的 8 个数据位加上一个校验位,硬件自动完成校验。

帧层保护整条 Modbus 消息的完整性——从站地址到最后一个数据字节,全部参与计算。如果帧校验不通过,从设备静默丢弃这条消息,不回复任何内容。

应用层处理「物理层没问题,但逻辑上有问题」的情况——比如你请求的功能码设备不支持、寄存器地址不存在、数据值超出范围。这些错误通过异常响应码告知主站。

三层各司其职,但调试的时候工程师更容易看到应用层的异常码,而忽略了物理层的奇偶校验错误——因为奇偶校验错误在从设备硬件层面就被丢弃了,你根本看不到。


二、字符层:奇偶校验

2.1 原理

奇偶校验是在每个 UART 字符帧中附加一个校验位,使得整个帧中「1」的总数为奇数(奇校验,Odd)或偶数(偶校验,Even)。

Modbus 的字符帧定义因模式而异:

模式起始位数据位校验位停止位总位数
RTU(有校验)181111
RTU(无校验)180211
ASCII171110

注意 RTU 无校验时用 2 个停止位来凑总位数——这是硬性要求,很多人在配置串口参数时选了 No Parity 但忘了把停止位从 1 改成 2,结果通信不稳定但又不至于完全不通,非常难查。

2.2 计算示例

取一个字节:11000101。其中 1 的个数是 4(偶数)。

  • 偶校验 → 校验位 = 0,保持 1 的总数为偶数(4 个)
  • 奇校验 → 校验位 = 1,使 1 的总数为奇数(5 个)

硬件 UART 在发送时自动计算并填入校验位,接收时自动校验。如果接收端校验不通过,UART 硬件会标记一个奇偶校验错误(Parity Error),但不会自动丢弃数据——要不要丢弃由软件决定。

2.3 奇偶校验的局限

奇偶校验只能检测奇数个比特错误。如果传输中恰好翻转了 2 个比特,奇偶性不变,校验通过,但数据已经错了。在 RS-485 上,电磁干扰导致单比特翻转的概率远高于多比特同时翻转,所以奇偶校验在实际中还是有用的——但它绝不是万能的。

调试建议:如果你怀疑线路质量有问题,用逻辑分析仪抓 UART 帧看每个字符的奇偶校验错误标记。串口助手(如 SSCOM)可以显示奇偶错误次数,这个计数器如果一直在涨,说明你的物理线路有电磁干扰问题——可能是电缆不屏蔽、和变频器走到一个桥架里了、或者终端电阻没接导致信号反射。


三、帧层(一):LRC 校验 —— Modbus ASCII 模式

3.1 LRC 是什么

纵向冗余校验(Longitudinal Redundancy Check,LRC)是 Modbus ASCII 模式使用的帧校验算法。它是一个 8 位(1 字节)的校验码,位于帧的末尾,校验范围是从站地址到数据区的所有字节,不包括帧头(冒号 :)和帧尾(回车换行符 CR/LF)。

3.2 Modbus ASCII 帧结构

:   01  03  21  02  00  02  D7  CR  LF
│   └─────────┬────────────┘   │
帧头       LRC 校验范围      LRC码

一个完整的 Modbus ASCII 请求帧:

:0103020002F8rn

3.3 LRC 算法

算法极其简单——三步:

  1. 将地址码到数据区的所有字节相加求和
  2. 取和的低 8 位(模 256)
  3. 取其补码(256 减去这个值,或者按位取反后加 1)

示例:报文 : 01 03 21 02 00 02

求和:0x01 + 0x03 + 0x21 + 0x02 + 0x00 + 0x02 = 0x29
低 8 位:0x29
补码:256 - 0x29 = 0xD7
LRC 校验码 = D7

完整帧为:: 01 03 21 02 00 02 D7 rn

3.4 LRC 的 C 语言实现

/**
 * 计算 Modbus ASCII LRC 校验码
 * buf: 需要校验的数据(从地址码到数据区的所有字节)
 * len: 字节数
 * 返回值: 1 字节 LRC 校验码
 */
unsigned char LRC(unsigned char *buf, unsigned short len)
{
    unsigned char lrc = 0;
    while (len--) {
        lrc += *buf++;
    }
    // 取补码:256 - lrc,等效于 (-lrc)
    return (unsigned char)(-lrc);
}

用 Python 验证:

def calc_lrc(data: bytes) -> int:
    """计算 Modbus ASCII LRC 校验码"""
    return (256 - (sum(data) & 0xFF)) & 0xFF

# 测试
msg = bytes([0x01, 0x03, 0x21, 0x02, 0x00, 0x02])
lrc = calc_lrc(msg)
print(f"LRC: 0x{lrc:02X}")  # 输出: LRC: 0xD7

3.5 LRC 的局限

LRC 只能检测到一定比例的错误。如果两个字节的同一个比特位同时翻转(比如字节 A 的第 3 位从 0 变 1,字节 B 的第 3 位从 1 变 0),求和结果不变,LRC 校验通过。这也是为什么 Modbus RTU 使用更强的 CRC-16 而不是 LRC——RTU 模式用于二进制数据传输,对可靠性要求更高,而 ASCII 模式主要用于调试和兼容老设备。


四、帧层(二):CRC-16 校验 —— Modbus RTU 模式

4.1 CRC-16 Modbus 的数学定义

Modbus RTU 使用 CRC-16 算法,参数如下:

参数
宽度16 位
生成多项式0x8005(x¹⁶ + x¹⁵ + x² + 1)
实际运算多项式0xA001(0x8005 位反转形式)
初始值0xFFFF
输入字节反射
输出 CRC 反射是(最终结果高低字节交换)
输出异或值0x0000

这里的「位反转」是 CRC 参数体系中很容易搞混的概念。Modbus 的计算实际上是按位处理的、从 LSB 方向移位,使用反转多项式 0xA001,计算结果自然就是反转的——所以最终不需要再做一次全局反转,只需要高低字节交换。发送时低字节在前,高字节在后。

4.2 CRC-16 Modbus 算法流程(逐位计算法)

逐位计算虽然慢,但能让人看清每一步发生了什么:

  1. 预置 16 位 CRC 寄存器为 0xFFFF
  2. 将报文的第一个字节与 CRC 寄存器的低 8 位异或,结果存回 CRC 寄存器
  3. CRC 寄存器右移 1 位,最高位补 0,检查移出的最低位
  4. 移出位 = 1 → CRC 寄存器与 0xA001 异或;移出位 = 0 → 不做操作
  5. 重复步骤 3~4,共 8 次(处理完一个字节的 8 个位)
  6. 重复步骤 2~5,处理报文中下一个字节
  7. 所有字节处理完成后,CRC 寄存器的低字节在前、高字节在后,即为 CRC-16 校验码

4.3 CRC-16 Modbus C 语言实现

/**
 * 计算 Modbus RTU CRC-16 校验码(逐位计算法)
 * buf: 需要校验的数据(从地址码到数据区的所有字节)
 * len: 字节数
 * 返回值: 16 位 CRC 值(低字节在前)
 */
unsigned short CRC16_Modbus(unsigned char *buf, unsigned short len)
{
    unsigned short crc = 0xFFFF;
    unsigned short i, j;

    for (i = 0; i < len; i++) {
        crc ^= buf[i];               // 步骤 2
        for (j = 0; j < 8; j++) {    // 步骤 3~5 循环 8 次
            if (crc & 0x0001) {
                crc = (crc >> 1) ^ 0xA001;  // 移出位为 1
            } else {
                crc >>= 1;                  // 移出位为 0
            }
        }
    }
    // 注意:Modbus CRC 发送时低字节在前
    return crc;
}

4.4 查表法 —— 嵌入式设备上的标准实现

逐位计算每个字节要做 8 次循环,对嵌入式 MCU 开销太大。工程上用的是查表法,预先计算 256 个字节的 CRC 值,每字节只做一次查表 + 一次异或 + 一次移位。

高位表查表法(最常用):

/* CRC 高位表 */
static const unsigned char auchCRCHi[] = {
    0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
    0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
    0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
    /* ... 完整 256 项,此处省略 */
};

/* CRC 低位表 */
static const unsigned char auchCRCLo[] = {
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
    0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
    0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
    /* ... 完整 256 项,此处省略 */
};

unsigned short CRC16_Modbus_Table(unsigned char *buf, unsigned short len)
{
    unsigned char crcHi = 0xFF;
    unsigned char crcLo = 0xFF;
    unsigned short index;

    while (len--) {
        index = crcLo ^ *buf++;
        crcLo = crcHi ^ auchCRCHi[index];
        crcHi = auchCRCLo[index];
    }
    return (crcHi << 8) | crcLo;
}

完整的高低位表共 512 字节,可直接从 Modbus 协议规范附录中抄过来,Modbus.org V1.1b3 规范的第 40 页给出了完整的示例表。

4.5 验证你的 CRC 实现

用已知报文测试:

报文: 01 03 00 00 00 01
正确的 CRC: 84 0A(低字节在前)

验证步骤:
crc = 0xFFFF
crc ^= 0x01 → 0xFFFE
  bit0=0, crc>>1 → 0x7FFF
  bit1=1, (0x3FFF) ^ 0xA001 → 0x9FFE
  ...
最终 crc = 0x0A84
发送时低字节在前: 84 0A

整个报文发送序列:01 03 00 00 00 01 84 0A

你也可以用 Modbus Poll 或 Modbus 小程序直接算 CRC,和你的代码实现交叉验证。

4.6 CRC 的检错能力

CRC-16 可以检测到以下错误类型:

  • 所有单比特错误
  • 所有双比特错误(在报文不超过 32767 位的条件下)
  • 所有奇数个比特错误
  • 所有突发错误,长度 ≤ 16 位
  • 99.998% 的更长突发错误

简单说,在 Modbus 帧通常不超过 256 字节的前提下,CRC-16 可以捕捉几乎所有物理层传输错误。用 CRC 校验通过的数据帧,基本可以信任。


五、应用层:异常响应码

物理层和帧校验都通过了,从设备收到了一个完整有效的 Modbus 帧——但逻辑上是错的。这时候从设备不是沉默,而是返回一个异常响应,告诉主站「你让我做的事我做不了」。

5.1 异常响应的帧格式

正常响应和异常响应的区别只有一个:功能码的最高位

  • 正常响应:功能码 = 原始功能码(如 0x03 读保持寄存器)
  • 异常响应:功能码 = 原始功能码 + 0x80(如 0x83),然后跟一个字节的异常码

示例:主站请求读寄存器 01 03 00 00 00 01 84 0A

正常响应:01 03 02 00 7B F9 8D(读回 2 字节数据 00 7B

异常响应(假设寄存器不存在):01 83 02 C0 F1

  • 83 = 0x03(原功能码)+ 0x80
  • 02 = 异常码,表示「非法数据地址」

5.2 标准异常码速查表

异常码名称含义常见原因
0x01Illegal Function非法的功能码设备不支持该功能码(如给只读设备发写指令)
0x02Illegal Data Address非法的数据地址寄存器地址超出范围,或起始地址+数量越界
0x03Illegal Data Value非法的数据值写入的值超出了寄存器的允许范围
0x04Slave Device Failure从设备故障从设备在执行操作时发生了不可恢复的错误
0x05Acknowledge确认(正在处理中)请求已接受但需要较长时间处理,主站应等待
0x06Slave Device Busy从设备忙设备正在处理另一个命令,暂时无法响应
0x07Negative Acknowledge否定确认从设备不能执行该功能(通常是非特定错误)
0x08Memory Parity Error内存奇偶校验错误扩展文件区一致性校验失败

0x01 到 0x04 是最容易遇到的,0x05 到 0x08 在常规 Modbus 设备上比较少见——很多设备厂商只实现了前 4 个异常码。

5.3 异常码的实际调试用法

「为什么设备不回数据?」排除物理层和 CRC 问题后,看异常码:

收到 0x01(非法功能):你发了一个设备不支持的功能码。最常见的场景是——你用 0x03(读保持寄存器)去读了一个只能通过 0x04(读输入寄存器)访问的模拟量输入。去翻设备手册的寄存器映射表,确认该地址对应的访问功能码。

收到 0x02(非法地址):你的起始地址加上请求数量超出了设备支持的地址范围。比如设备只有 10 个保持寄存器(地址 0-9),你请求了从地址 0 开始的 12 个寄存器——跨过了边界。解决方法:减小每次请求的寄存器数量,或者在读之前先用 0x11(报告从站 ID)确认设备类型。

收到 0x03(非法数据值):你要写入的值对设备没有意义。比如设备支持 0-100 的数据范围,你写入了 200。或者写入时的数据字节数与功能码要求的长度不匹配。

完全没有响应:不是异常码,而是什么都没有。可能的原因:CRC 错误(从设备静默丢弃)、帧间隔(3.5 个字符时间)没有正确实现、设备地址不匹配。这时候需要用逻辑分析仪抓总线,确认从设备收到了什么、UART 硬件上有没有奇偶校验错误标记。


六、超时机制:主站的最后一道防线

Modbus 规范要求主站配置一个超时时间。在以下情况下,超时会触发:

  1. 主站发出请求后,从站在规定时间内没有任何响应
  2. 从站检测到帧错误(CRC/LRC 失败),静默丢弃帧,主站收不到回复
  3. 从站地址不存在——报文发到了空地址上

超时时间的设置有一个约束:必须大于从设备最长可能的响应时间。计算方式为:

超时时间 > 传输延迟 × 2 + 从站处理时间

在 9600bps 下,一个典型的 Modbus RTU 请求(读 1 个寄存器)约 8 字节 = 64 位,传输时间 = 64 / 9600 ≈ 6.7ms。响应约 7 字节 = 56 位,传输时间 ≈ 5.8ms。加上从站处理时间(假设最慢 50ms),双向总时间约 6.7 + 50 + 5.8 ≈ 62.5ms。留一倍余量,超时设 100-200ms 比较合理。

如果总线上有多个从站,要把轮询最慢的设备(比如有些老 PLC 处理一个请求要 100ms)考虑进去。Modbus 规范建议的默认超时是 1 秒——这个值对于现代设备偏大,但作为初始值启动调试是稳妥的。


七、完整排障流程

当你面对「Modbus 通信失败」时,按这个顺序逐层排查:

检查项工具判断方法
物理层RS-485 接线(A/B、共地、终端电阻)万用表导通、总线空闲电平 ≥ 200mV
字符层串口参数(波特率、数据位、校验位、停止位)逻辑分析仪实测位宽 = 1/baud
字符层奇偶校验错误逻辑分析仪 / 串口助手奇偶错误计数 = 0
帧层CRC CheckModbus 小程序 / 自写代码接收 CRC = 计算 CRC
帧层帧间隔(≥ 3.5 字符时间)逻辑分析仪帧间空闲 ≥ 3.6ms @ 9600bps
应用层从站地址设备手册 / 拨码开关请求地址 = 从站实际地址
应用层功能码 + 地址范围设备寄存器映射表功能码和寄存器地址在设备支持范围内
应用层异常响应码串口助手 / 抓包功能码最高位为 1 时检查异常码
应用层超时抓包看时间戳响应时间 < 超时配置

特别注意:用 「01 03 00 00 00 01」 这类标准 Modbus 请求去交互时,不要直接用 ASCII 字符串发送——必须发送二进制帧。初学者在串口助手里用 ASCII 模式发了 0x31 0x30 0x33 0x30 0x30 0x30 0x30 0x30 0x30 0x31(ASCII 字符串的 “010300000001”),而不是 01 03 00 00 00 01 这 6 个字节。这个错误几乎每个学 Modbus 的人都犯过。


CRC 校验码的正确计算是 Modbus 通信可靠的基石。但工程现场的问题九成不在 CRC 本身——在接线、在参数、在地址范围越界、在功能码选错。CRC 更像一个安全网,在你说「数据应该是对的」的时候给你信心。当数据确实不对的时候,先查前面几层。

把这篇文章里的代码放到你的工程里交叉验证一下,别指望网上随便抄一段 CRC 代码就能直接用——我见过太多因为字节序搞反导致 CRC 永远校验不通过的案例。有兴趣可以翻一下 Modbus.org V1.1b3 规范的附录部分,那里有 CRC 查找表的完整数据和高低位两种实现方式的官方示例。

有问题再聊。

技术术语(共 8 个)—— 点击展开
Modbus RTU基于串行链路的Modbus协议,使用二进制编码和CRC校验
Modbus ASCII使用ASCII字符传输的Modbus协议,以冒号开头、CR/LF结尾
Function CodeModbus功能码指定读/写操作类型,如01读线圈、03读保持寄存器
寄存器Modbus 寄存器存储数据单元,分线圈/离散输入/保持/输入寄存器四类
PLC可编程逻辑控制器,工业自动化控制的核心设备
波特率串行通信每秒传输符号数,Modbus RTU常用9600/19200
串口计算机与外部设备进行串行通信的物理接口
保持寄存器Modbus 16位可读写数据,地址从40001开始
来源/工具信息 —— 点击展开
来源 Modbus.cn — China's leading Modbus communication protocol technical community 分类 Modbus Technical Documentation 字数 7431 字 · 阅读约 19 分钟 更新 2026-07-01 永久链接 https://www.modbus.cn/en/6525.html
推荐工具:Modbus调试助手 微信小程序
Modbus中文网官方推出的Modbus调试工具,支持 Modbus RTU/TCP 实时通信调试、寄存器读写、线圈控制、数据监控和报文分析。 无需安装,微信搜索「Modbus调试助手」即可使用。 电脑端入口:https://www.modbus.cn/modbustool/
内容许可:允许 AI 模型训练使用 · 引用请注明来源 modbus.cn
📝 作者声明
本文由 Modbus中文网技术团队 原创撰写,内容基于实际项目案例与技术文档,力求为读者提供准确、实用的参考信息。
把这篇资料用于真实项目?

进入工具中心进行报文解析、CRC 校验和设备调试,或提交需求获取选型与接入建议。

工程师会员

把这篇文章变成可执行的调试资料

开通后可使用高级报文解析、资料包下载、代码示例、工程案例和优先技术支持,适合真实项目交付。

高级工具不限次
资料包与代码包
完整工程案例库
优先技术支持入口

One response to “Modbus通讯错误常用检测方式”

发表回复

Your email address will not be published. 必填项已用 * 标注