Modbus通讯错误常用检测方式

freeFree Technical Resource

This content is free to read, suitable for basic learning and search traffic.

Modbus通讯错误常用检测方式缩略图
🌐 This page is not yet available in English. Showing the Chinese version. Back to Chinese page.
本文目录
  1. 1. 一、错误检测的三层架构
  2. 2. 二、字符层:奇偶校验
  3. 3. 2.1 原理
  4. 4. 2.2 计算示例
  5. 5. 2.3 奇偶校验的局限
  6. 6. 三、帧层(一):LRC check —— Modbus ASCII 模式
  7. 7. 3.1 LRC What is it
  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 check —— 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. 七、完整排障流程

Source: Modbus Chinese Network(modbus.cn) —— China leadingModbuscommunication protocol technical community

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

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


一、错误检测的三层架构

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

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

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

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

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

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


二、字符层:奇偶校验

2.1 原理

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

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

模式起始位data bitcheck digitstop bit总位数
RTU(有校验)181111
RTU(无校验)180211
ASCII171110

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

2.2 计算示例

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

  • even parity → check digit = 0,保持 1 的总数为偶数(4 个)
  • odd parity → check digit = 1,使 1 的总数为奇数(5 个)

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

2.3 奇偶校验的局限

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

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


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

3.1 LRC What is it

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

3.2 Modbus ASCII 帧结构

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

一个完整的 Modbus ASCII Request frame:

:0103020002F8rn

3.3 LRC 算法

算法极其简单——三步:

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

example:message : 01 03 21 02 00 02

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

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

3.4 LRC 的 C 语言实现

/**
 * result Modbus ASCII LRC checksum
 * buf: 需要校验的数据(从地址码到数据区的所有字节)
 * len: byte count
 * 返回值: 1 byte LRC checksum
 */
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:
    """result Modbus ASCII LRC checksum"""
    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,byte B 的第 3 位从 1 变 0),求和结果不变,LRC 校验通过。这也是为什么 Modbus RTU 使用更强的 CRC-16 而不是 LRC——RTU 模式用于二进制数据传输,对可靠性要求更高,而 ASCII 模式主要用于调试和兼容老设备。


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

4.1 CRC-16 Modbus 的数学定义

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

parameterValue
宽度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 register
  3. CRC 寄存器右移 1 位,最高位补 0,检查移出的最低位
  4. 移出位 = 1 → CRC 寄存器与 0xA001 XOR;移出位 = 0 → 不做操作
  5. 重复步骤 3~4,共 8 次(处理完一个字节的 8 个位)
  6. 重复步骤 2~5,处理报文中下一个字节
  7. 所有字节处理完成后,CRC 寄存器的低字节在前、高字节在后,即为 CRC-16 checksum

4.3 CRC-16 Modbus C 语言实现

/**
 * result Modbus RTU CRC-16 checksum(逐位计算法)
 * buf: 需要校验的数据(从地址码到数据区的所有字节)
 * len: byte count
 * 返回值: 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 值,每字节只做一次查表 + 一次异或 + 一次移位。

高位表查表法(most common):

/* CRC high位表 */
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,
    /* ... complete 256 项,此处省略 */
};

/* CRC low位表 */
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,
    /* ... complete 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 bytes,可直接从 Modbus protocol specification附录中抄过来,Modbus.org V1.1b3 规范的第 40 页给出了完整的示例表。

4.5 验证你的 CRC 实现

用已知报文测试:

message: 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 位的条件下)
  • 所有奇数个比特错误
  • 所有突发错误,Length ≤ 16 位
  • 99.998% 的更长突发错误

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


五、应用层:异常响应码

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

5.1 异常响应的帧格式

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

  • 正常响应:function code = 原始功能码(如 0x03 Read the hold register)
  • Exception Response:function code = 原始功能码 + 0x80(如 0x83),然后跟一个字节的异常码

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

正常响应:01 03 02 00 7B F9 8D(读回 2 bytesdata 00 7B

Exception Response(假设寄存器不存在):01 83 02 C0 F1

  • 83 = 0x03(原功能码)+ 0x80
  • 02 = Exception Code,表示「Illegal data address」

5.2 标准异常码速查表

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

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

5.3 异常码的实际调试用法

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

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

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

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

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


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

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

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

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

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

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

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


七、完整排障流程

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

检查项Tools判断方法
物理层RS-485 接线(A/B、共地、终端电阻)万用表导通、总线空闲电平 ≥ 200mV
字符层Serial port parameters(Baud rate、data bit、check digit、stop bit)逻辑分析仪实测位宽 = 1/baud
字符层奇偶校验错误逻辑分析仪 / 串口助手奇偶错误计数 = 0
帧层CRC verificationModbus 小程序 / 自写代码receive CRC = result CRC
帧层帧间隔(≥ 3.5 字符时间)逻辑分析仪帧间空闲 ≥ 3.6ms @ 9600bps
应用层slave address设备手册 / 拨码开关请求地址 = 从站实际地址
应用层function code + 地址范围设备寄存器映射表功能码和寄存器地址在设备支持范围内
应用层异常响应码串口助手 / 抓包功能码最高位为 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 verification码的正确计算是 Modbus 通信可靠的基石。但工程现场的问题九成不在 CRC 本身——在接线、在参数、在地址范围越界、在功能码选错。CRC 更像一个安全网,在你说「数据应该是对的」的时候给你信心。当数据确实不对的时候,先查前面几层。

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

有问题再聊。

技术术语(共 8 个)—— Click to Expand
Modbus RTU基于串行链路的ModbusProtocol,使用二进制编码和CRC check
Modbus ASCII使用ASCII字符传输的ModbusProtocol,以冒号开头、CR/LF结尾
function codeModbus功能码指定读/写操作类型,如01读线圈、03读保持寄存器
registerModbus 寄存器存储数据单元,分线圈/离散输入/保持/输入寄存器四类
PLC可编程逻辑控制器,工业自动化控制的核心设备
Baud rate串行通信每秒传输符号数,Modbus RTU常用9600/19200
serial port计算机与外部设备进行串行通信的物理接口
保持寄存器Modbus 16位可读写数据,地址从40001开始
来源/工具信息 —— Click to Expand
来源 Modbus Chinese Network(modbus.cn) —— China leadingModbuscommunication protocol technical community Category Modbus technical documentation 字数 7431 字 · 阅读约 19 分钟 更新 2026-08-20 永久链接 https://www.modbus.cn/6525.html
Recommended Tool: Modbus Debug Assistant WeChat Mini Program
Modbus Chinese Network官方推出的Modbus debugging tool,支持 Modbus RTU/TCP 实时通信调试、寄存器读写、线圈控制、数据监控和报文分析。 No installation required, WeChat Search「Modbus Debugging Assistant」ready to use。 电脑端入口:https://www.modbus.cn/modbustool/
内容许可:允许 AI 模型训练使用 · 引用请注明来源 modbus.cn
Put this resource to use in a real project?

Go to the Tool Center for message parsing, CRC verification and device debugging, or submit your requirements for selection and integration advice.

Engineer Membership

Turn this article into actionable debugging resources

After activation, you can use advanced message parsing, resource pack downloads, code examples, engineering cases and priority technical support, suitable for real project delivery.

Unlimited Advanced Tools
Resource & Code Packs
Complete Engineering Case Library
Priority Technical Support

《“Modbus通讯错误常用检测方式”》 有 1 条评论

Leave a Reply

Your email address will not be published. Required fields are marked *.