Modbus CRC/LRC Checking Principle and Programming Implementation: From Mathematical Derivation to Code Practice
In industrial communication, data integrity is the bottom line. A single bit error can lead to valve misoperation, motor reversal, or even safety accidents. The Modbus protocol ensures data integrity throughCRC (Cyclic Redundancy Check)And LRC (Longitudinal Redundancy Check)mechanisms. This article will deeply analyze the mathematical principles of these two checks and provide complete code implementations in C, Python, and JavaScript.
Core keywords:Modbus CRC Check, Modbus CRC-16, CRC Calculation Principle, Modbus LRC Check, Table-lookup CRC. For more Modbus technical articles, please visitmodbus.cn.
1. Why is error detection necessary?
Modbus originally ran on RS-485 and RS-232 physical layers, and these serial links faced the following interferences:
- Electromagnetic Interference (EMI):A large amount of electromagnetic noise generated by frequency converters and high-power motors in industrial sites can couple onto the communication lines
- Ground potential difference:In long-distance communication, inconsistent ground potentials of different nodes lead to signal distortion
- Connector oxidation/loosening:Vibration and corrosion in industrial environments cause intermittent poor contact
- Baud rate deviation:Accumulated clock deviations between the sender and receiver may lead to bit sampling errors
The Modbus protocol detects transmission errors at the data link layer throughFrame Check Sequence (FCS). Modbus RTU mode usesCRC-16, while Modbus ASCII mode usesLRC. The differences and usage scenarios of these two checks are the core of this article.
Before diving into the code, it is recommended to read"Difference between Modbus RTU and ASCII Modes"to understand the basic differences between the two transmission modes.
II. CRC vs LRC: Comparison of Two Checksum Methods
| Comparison Dimensions | CRC-16 (RTU Mode) | LRC (ASCII Mode) |
|---|---|---|
| Algorithm Type | Cyclic Redundancy Check (Polynomial Division) | Longitudinal Redundancy Check (Accumulate and Invert) |
| Checksum Length | 16 Bits (2 Bytes) | 8 bits (1 byte) |
| Error detection capability | Extremely high (detects all single-bit, double-bit, odd-bit errors, and all burst errors of ≤16 bits) | Medium (detects single-byte errors, but has blind spots for multi-bit errors) |
| Computational complexity | Medium-high (requires bit operations or table lookups) | Extremely low (only requires accumulation operations) |
| Position within the frame | At the end of the frame, with the low byte first (little-endian) | At the end of the frame, with two ASCII characters |
| Applicable transmission mode | RTU (binary) | ASCII (Text) |
| Calculation Range | From the 1st byte (address) to the end of the data area | From after the colon (':') to before the CR/LF (excluding the colon and carriage return/line feed) |
| Typical Miss Detection Rate | The miss detection rate of 16-bit CRC is approximately 1/65536 | The LRC miss detection rate is relatively high, approximately 1/256 |
Selection Recommendation:In modern Modbus applications, RTU mode combined with CRC-16 is absolutely mainstream. ASCII mode and LRC are mainly used in special scenarios where human-readable communication content is required (such as debugging and manual operation through terminal programs).
III. Mathematical Principle of CRC-16: From Polynomial to Bitwise Operation
3.1 The Essence of CRC: Modulo 2 Division
The essence of CRC ismodulo-two polynomial division. The data to be verified is regarded as a binary polynomial M(x), which is divided by a predefined generator polynomial G(x). The remainder obtained is the CRC value.
Modbus RTU uses the CRC-16 parameters:
- Polynomial:x^16 + x^15 + x^2 + 1
- Polynomial value:0x8005 (forward direction) or 0xA001 (reverse direction/Modbus standard)
- Initial value:0xFFFF
- Result XOR value:0x0000 (no XOR)
- Input data inversion:No
- Output data inversion:No (but Modbus stores in little-endian order)
Example of modulo-2 division:
Assume we have a simplified data: the byte to be checked is 0x02 (binary 0000 0010), using a simplified 4-bit CRC.
数据: 0000 0010
多项式(反向 0xA001 = 1010 0000 0000 0001):
逐步移位和异或过程(模拟硬件移位寄存器):
1. 初始化 CRC 寄存器: 1111 1111 1111 1111 (0xFFFF)
2. 取第一个数据字节 0x02: 0000 0010
3. CRC ^= 数据字节: 1111 1111 1111 1101
4. 对该字节的每一位执行:
- 如果 LSB = 1: CRC >>= 1, CRC ^= 0xA001
- 如果 LSB = 0: CRC >>= 1
最终 CRC 寄存器中的值即为校验结果3.2 Why does Modbus use 0xA001 instead of 0x8005?
0x8005 and 0xA001 are the forward and reverse representations of the same polynomial:
- 0x8005 (forward):binary 1000 0000 0000 0101, corresponding to the polynomial x^16 + x^15 + x^2 + 1. This is the "natural" representation of the generator polynomial for left-shift (MSB first) CRC calculation.
- 0xA001 (reverse):binary 1010 0000 0000 0001, which is the bit inversion of 0x8005. It is used for right-shift (LSB first) CRC calculation - this is also the standard method specified by the Modbus protocol.
Modbus chooses right-shift calculation because the RS-485 data link layer physically sends the LSB (least significant bit) first. Using 0xA001 can align the hardware CRC calculator with the direction of serial shifting, improving efficiency.
3.3 From Bit-Manipulation to Table-Look-Up: A Performance Leap
The Bottleneck of Bit-Manipulation:For each byte, 8 iterations are required, with each iteration involving conditional checks, bit-shifting, and XOR operations. Processing a 100-byte Modbus frame requires 800 iterations.
The Core Idea of Table-Look-Up:Pre-calculate the CRC intermediate results for each possible byte value (0x00-0xFF, a total of 256 values) and store them in a lookup table. When processing a byte, only one table lookup operation and one XOR operation are needed, reducing the computational complexity from O(8N) to O(N).
Derivation Process of Table-Look-Up Algorithm:
设当前 CRC 寄存器值为 crc(16 位),下一个数据字节为 data。
位移法需要对 data 的每一位迭代计算。经过推导,单字节处理等价于:
1. index = (crc ^ data) & 0x00FF // 取当前 CRC 低 8 位与数据字节异或
2. crc = (crc >> 8) ^ table[index] // CRC 右移 8 位,再查表异或
其中 table[index] 是通过位移法预计算 256 次得到的查找表值。This derivation compresses "8 iterations" into "1 table lookup + 1 XOR", resulting in an approximately 8-fold performance improvement.
3.4 Lookup Table Generation Code and Complete Derivation
Understanding the generation process of the lookup table is key to mastering the CRC table-lookup method. The following code demonstrates how to pre-calculate a complete 256-entry CRC lookup table using the bit-manipulation method.
/**
* 生成 Modbus CRC-16 查找表
* 运行一次,将输出作为静态数组嵌入主程序
*/
void generate_crc16_table(uint16_t *table)
{
uint16_t remainder;
int byte, bit;
for (byte = 0; byte < 256; byte++) {
remainder = (uint16_t)byte; /* 初始余数 = 当前字节值 */
for (bit = 0; bit < 8; bit++) {
if (remainder & 0x0001) { /* LSB 为 1 */
remainder = (remainder >> 1) ^ 0xA001; /* 右移并异或 */
} else {
remainder = (remainder >> 1); /* 只右移 */
}
}
table[byte] = remainder;
}
}
/* 推导说明:
* 为什么这个表可以直接用于查表法?
*
* 对于任意数据字节 data,位移法需要循环 8 次。
* 设 CRC 当前值为 crc(16 位),经过 8 次迭代后:
* crc' = f(f(f(...f(crc ^ data)...)))
*
* 由于异或运算的性质:crc ^ data = (crc >> 8) << 8 | (crc & 0xFF) ^ data
* 低 8 位的处理结果仅依赖于 (crc & 0xFF) ^ data 的值,
* 而这个值恰好是 0-255,因此可以预先计算所有可能的中间结果。
*
* 高 8 位则直接右移,与新计算的低 8 位结果(查表获得)进行异或。
* 这就是查表法能工作的数学基础。
*/This table generation function reveals the essence of CRC computation:Each value in the lookup table is the result of 8 iterations of right-shift CRC for the corresponding index byte value. In the main computation functionindex = (crc ^ data) & 0xFFOperation, essentially, involves calculating the "modulo-two sum of the current CRC low 8 bits and the data byte", and then using this result to look up a table to obtain the pre-calculated CRC contribution value. For a more in-depth technical discussion on Modbus CRC, please visitmodbus.cnto view the complete documentation.
3.5 Mathematical Analysis of Error Detection Capability
The powerful detection capability of CRC-16 stems from its mathematical properties. The following is an analysis of the detection capability of a 16-bit CRC under different error patterns:
| Error Type | Detection Probability | Mathematical Principle |
|---|---|---|
| Single Bit Error | 100% | When the generating polynomial contains the factor x+1, it ensures the detection of all odd-bit errors |
| Two Bit Errors | 100% | When the spacing between two error bits is < 32767 bits, a 16-bit CRC can always detect |
| Odd Number of Bit Errors | 100% | 0xA001 The polynomial contains the factor (x+1), which can detect all odd-bit errors |
| Burst errors ≤ 16 bits | 100% | The degree of the burst error polynomial is ≤ 15, and there must be a remainder when divided by a 16th-degree polynomial |
| Burst errors of 17 bits | 99.9969% | There is only a probability of 2^-(16-1) for an error to be detected as correct |
| Random multi-bit errors | 99.9985% | A 16-bit CRC has a detection rate of 1-2^-16 for all non-multiple errors |
These mathematical properties make CRC-16 an extremely cost-effective error detection method in industrial communication. In typical application scenarios of Modbus RTU (RS-485 bus, baud rate ≤ 115.2Kbps, frame length usually ≤ 256 bytes), CRC-16 can detect almost all transmission errors that may actually occur.
IV. Complete Code Implementation of CRC-16
4.1 C Language Table-lookup Method (High-performance Version)
The following is the C language table-lookup implementation of Modbus CRC-16, which is the most commonly used version in industrial embedded systems:
#include <stdint.h>
#include <stddef.h>
/* Modbus CRC-16 查找表(多项式 0xA001) */
static const uint16_t crc16_table[256] = {
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
};
/**
* Modbus CRC-16 查表法计算
* @param buf 待校验的数据缓冲区
* @param len 数据长度(字节数)
* @return 16 位 CRC 值
*/
uint16_t modbus_crc16(uint8_t *buf, uint16_t len)
{
uint16_t crc = 0xFFFF; /* 初始值 */
while (len--) {
uint8_t pos = (uint8_t)(crc ^ (*buf++)) & 0xFF;
crc = (crc >> 8) ^ crc16_table[pos];
}
return crc;
}
/* 使用示例:
* uint8_t frame[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01};
* uint16_t crc = modbus_crc16(frame, 6);
* // crc = 0x0ACA
* // 在 Modbus RTU 帧中,低字节在前:
* // frame[6] = crc & 0xFF; (0xCA)
* // frame[7] = crc >> 8; (0x0A)
*/4.2 C Language Shift Method (Teaching Version)
Below is the bitwise operation version, which has fewer lines of code but lower efficiency, suitable for learning and scenarios with extremely limited embedded resources:
/**
* Modbus CRC-16 逐位计算法
* 用于教学和理解 CRC 原理,生产环境建议使用查表法
*/
uint16_t modbus_crc16_bitwise(uint8_t *buf, uint16_t len)
{
uint16_t crc = 0xFFFF; /* 初始值 */
uint16_t i, j;
for (i = 0; i < len; i++) {
crc ^= (uint16_t)buf[i]; /* 将数据字节与 CRC 低字节异或 */
for (j = 0; j > 1) ^ 0xA001; /* 右移一位并异或多项式 */
} else {
crc = crc >> 1; /* 只右移一位 */
}
}
}
return crc;
}
/* 验证:
* uint8_t test[] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x01};
* uint16_t crc = modbus_crc16_bitwise(test, 6);
* // 结果应为 0x0ACA
*/4.3 Python Implementation
The Python version is suitable for host computer programs, data analysis scripts, and automated testing:
#!/usr/bin/env python3
"""Modbus CRC-16 校验工具"""
from typing import List, Union
class ModbusCRC:
"""Modbus CRC-16 计算器"""
# CRC-16 查找表
TABLE = [
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
]
@staticmethod
def calculate(data: Union[bytes, List[int]]) -> int:
"""计算 Modbus CRC-16"""
crc = 0xFFFF
for byte in data:
pos = (crc ^ byte) & 0xFF
crc = (crc >> 8) ^ ModbusCRC.TABLE[pos]
return crc
@staticmethod
def verify(frame: bytes) -> bool:
"""
验证带有 CRC 的 Modbus RTU 帧
将整个帧(含 CRC)再算一次 CRC,结果应为 0
"""
return ModbusCRC.calculate(frame) == 0
@staticmethod
def append_crc(data: bytes) -> bytes:
"""在数据末尾追加 CRC(小端序)"""
crc = ModbusCRC.calculate(data)
return data + bytes([crc & 0xFF, crc >> 8])
# ===== 使用示例 =====
if __name__ == '__main__':
# 示例 1: 读取保持寄存器命令
# 地址=1, 功能码=03, 起始地址=0x0000, 寄存器数量=1
request = bytes([0x01, 0x03, 0x00, 0x00, 0x00, 0x01])
crc_value = ModbusCRC.calculate(request)
print(f"CRC-16: 0x{crc_value:04X}") # 期望: 0x0ACA
# 示例 2: 构造完整响应帧
response_data = bytes([0x01, 0x03, 0x02, 0x00, 0x64])
full_response = ModbusCRC.append_crc(response_data)
print(f"完整帧: {full_response.hex(' ').upper()}")
# 期望: 01 03 02 00 64 B9 AF
# 示例 3: 验证接收帧
received = bytes([0x01, 0x03, 0x02, 0x00, 0x64, 0xB9, 0xAF])
is_valid = ModbusCRC.verify(received)
print(f"帧校验: {'通过' if is_valid else '失败'}")
4.4 JavaScript Implementation (Web Debugging Tool)
The following JavaScript version can be used for Web front-end debugging tools or Node.js environments:
/**
* Modbus CRC-16 校验工具 (JavaScript)
* 可直接在浏览器控制台或 Node.js 中运行
*/
// CRC-16 查找表
const CRC16_TABLE = new Uint16Array([
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
]);
/**
* 计算 Modbus CRC-16
* @param {Uint8Array|number[]|Buffer} data
* @returns {number} 16 位 CRC 值
*/
function modbusCRC16(data) {
let crc = 0xFFFF;
for (let i = 0; i >> 8) ^ CRC16_TABLE[pos];
}
return crc;
}
/**
* 验证带有 CRC 的帧是否合法
* @param {Uint8Array} frame 完整帧(含 CRC)
* @returns {boolean}
*/
function verifyModbusFrame(frame) {
return modbusCRC16(frame) === 0;
}
/**
* 计算 CRC 并附加到数据末尾
* @param {Uint8Array|number[]} data
* @returns {Uint8Array}
*/
function appendCRC(data) {
const crc = modbusCRC16(data);
return new Uint8Array([...data, crc & 0xFF, (crc >> 8) & 0xFF]);
}
// ===== 使用示例 =====
const request = new Uint8Array([0x01, 0x03, 0x00, 0x00, 0x00, 0x01]);
const crc = modbusCRC16(request);
console.log(`CRC-16: 0x${crc.toString(16).toUpperCase().padStart(4, '0')}`);
// 输出: CRC-16: 0x0ACA
const fullFrame = appendCRC(request);
console.log('完整帧:', Array.from(fullFrame)
.map(b => '0x' + b.toString(16).toUpperCase().padStart(2, '0'))
.join(' '));
// 输出: 0x01 0x03 0x00 0x00 0x00 0x01 0xCA 0x0AV. LRC Checking Principle and Implementation
5.1 LRC Calculation Rules
LRC (Longitudinal Redundancy Check) is used in Modbus ASCII mode. The calculation method is very simple:
- Accumulate all bytes in the message frame (from the address code to the last data byte)
- Discard the carry (only retain the lower 8 bits)
- Take the two's complement (i.e., negate and add one, or directly use 256 - sum)
- Convert the result into two ASCII characters (one character each for the high and low nibbles)
LRC Mathematical Formula:
LRC = 0x100 - (sum(byte[0..N-1]) & 0xFF)
例:帧数据为 {0x01, 0x03, 0x00, 0x00, 0x00, 0x01}
sum = 0x01 + 0x03 + 0x00 + 0x00 + 0x00 + 0x01 = 0x05
LRC = 0x100 - 0x05 = 0xFB
在 ASCII 帧中表示为字符串 "FB"5.2 Complete Implementation of LRC
/**
* C 语言实现 Modbus LRC 计算
* 返回值为 LRC 值(8 位)
*/
uint8_t modbus_lrc(uint8_t *buf, uint16_t len)
{
uint16_t sum = 0;
uint16_t i;
for (i = 0; i int:
"""计算 Modbus ASCII LRC"""
sum_val = sum(data) & 0xFF
return (-sum_val) & 0xFF # 等效于 (256 - sum_val) & 0xFF
# 使用示例
data = bytes([0x01, 0x03, 0x00, 0x00, 0x00, 0x01])
lrc = modbus_lrc(data)
print(f"LRC: 0x{lrc:02X}") # 输出: 0xFB
/**
* JavaScript 实现
*/
function modbusLRC(data) {
let sum = 0;
for (let i = 0; i < data.length; i++) {
sum = (sum + data[i]) & 0xFF;
}
return (0x100 - sum) & 0xFF;
}
// 使用示例
const testData = [0x01, 0x03, 0x00, 0x00, 0x00, 0x01];
console.log(`LRC: 0x${modbusLRC(testData).toString(16).toUpperCase()}`);
// 输出: LRC: 0xFBVI. CRC Check Verification Techniques
In practical development, verifying the correctness of CRC implementation often takes more time than the implementation itself. Below are several practical verification methods and techniques to help you quickly locate issues in CRC calculation.
6.1 Full Frame Verification Method
CRC has a very practical feature:After receiving a complete frame (data + CRC), calculate the CRC-16 again, and the result should be 0x0000.This is the most convenient method to verify frame integrity.
// 接收帧(数据 + 2 字节 CRC)
uint8_t received_frame[] = {0x01, 0x03, 0x02, 0x00, 0x64, 0xB9, 0xAF};
uint16_t verify = modbus_crc16(received_frame, sizeof(received_frame));
if (verify == 0) {
// 帧校验通过,数据正确
printf("CRC OKn");
} else {
// 帧校验失败,丢弃此帧
printf("CRC Error: 0x%04Xn", verify);
}6.2 Test Vectors
When developing the CRC calculation function, use the following standard test vectors to verify the correctness of the implementation:
| Test Data (HEX) | Functional Description | Expect CRC-16 |
|---|---|---|
01 03 00 00 00 01 | Read Hold Registers (Most Common) | 0ACA |
01 03 00 00 00 0A | Read 10 Hold Registers | 0548 |
01 06 00 01 00 1E | Write Single Register (Value 30) | 99CB |
01 10 00 00 00 02 04 00 64 00 65 | Write Multiple Registers | Requires Real-Time Calculation |
11 03 00 6B 00 03 | Read 3 Registers from Slave 17 | 7687 |
VII. Recommended Online CRC Calculation Tools
The following tools can help quickly verify CRC calculation results:
- modbus.cn Online Tool:Provides Modbus-specific CRC/LRC online calculation functionality
- Sunshine2k CRC Calculator:Online calculator supporting multiple combinations of CRC algorithm parameters
- Lammert Bies CRC:Detailed CRC calculation page, supporting custom polynomials
- npm crc package:Installable in Node.js environments via
npm install crcInstallation
Precautions when using online tools:
- Confirm polynomial parameters (Modbus = 0x8005 / 0xA001)
- Confirm initial value (Modbus = 0xFFFF)
- Confirm result XOR value (Modbus = 0x0000, no XOR)
- Confirm whether input and output are reversed (Modbus does not reverse)
- Pay attention to byte order: Online tools usually output big-endian, while CRC in Modbus RTU frames is stored in little-endian
VIII. Troubleshooting Guide for Checksum Failure
When a CRC/LRC check error occurs during Modbus communication, follow the steps below to troubleshoot layer by layer:
8.1 Troubleshooting Checklist
| Troubleshooting Items | Common Problems | Solutions |
|---|---|---|
| CRC Algorithm Implementation | Using the wrong polynomial (0x8005 instead of 0xA001) | Confirm the use of 0xA001 right shift method or 0x8005 left shift method |
| CRC Initial Value | Use 0x0000 instead of 0xFFFF | The Modbus standard specifies that the initial value must be 0xFFFF |
| The CRC calculation range | includes the CRC itself, or omits the address byte. | The calculation range is from the address code to the last data byte (excluding the CRC). |
| Byte order | CRC high and low bytes are reversed. | In Modbus RTU, the CRC low byte comes first (little-endian order). |
| Frame boundary | The 3.5-character time interval is set improperly. | RTU mode uses >3.5-character silence as the frame interval. |
| Baud rate | The baud rates of the sender and receiver are inconsistent. | Select from common rates (9600/19200/38400/115200) |
| Physical wiring | A/B lines reversed, missing terminal resistors | Check the polarity of A(+) and B(-) for RS-485, and add 120Ω terminal resistors at both ends |
| ASCII mode | Mistakenly treating RTU binary frames as ASCII text | ASCII mode frames start with a colon (':') and end with a carriage return-line feed (CRLF) |
8.2 Debugging code example
/**
* 带详细日志的 CRC 计算调试版本
*/
uint16_t modbus_crc16_debug(uint8_t *buf, uint16_t len)
{
uint16_t crc = 0xFFFF;
uint16_t i;
printf("=== CRC-16 Debug Trace ===n");
printf("Initial CRC: 0x%04Xn", crc);
for (i = 0; i > 8) ^ crc16_table[pos];
printf("Byte[%2d]=0x%02X, "
"prev_lo=0x%02X, "
"index=0x%02X, "
"table_val=0x%04X, "
"new_crc=0x%04Xn",
i, buf[i], prev_crc_lo, pos,
crc16_table[pos], crc);
}
printf("Final CRC: 0x%04Xn", crc);
printf("RTU Frame CRC (Little-Endian): 0x%02X 0x%02Xn",
crc & 0xFF, crc >> 8);
printf("============================n");
return crc;
}
/* 示例输出:
Buf: {0x01, 0x03, 0x00, 0x00, 0x00, 0x01}
=== CRC-16 Debug Trace ===
Initial CRC: 0xFFFF
Byte[ 0]=0x01, prev_lo=0xFF, index=0xFE, table_val=0x4040, new_crc=0xC0C0
Byte[ 1]=0x03, prev_lo=0xC0, index=0xC3, table_val=0x0280, new_crc=0x0281
Byte[ 2]=0x00, prev_lo=0x81, index=0x81, table_val=0x4040, new_crc=0x4042
...
Final CRC: 0x0ACA
RTU Frame CRC (Little-Endian): 0xCA 0x0A
*/IX. Performance comparison: lookup table method vs bit shift method
In practical engineering, the performance of CRC calculation directly affects the throughput of Modbus communication. The following is a measured comparison between the two methods on ARM Cortex-M4 (168MHz) and x86-64 (3.2GHz) platforms.
| Test conditions | Bit shift method | Lookup table method | Speedup ratio |
|---|---|---|---|
| ARM Cortex-M4, 1 byte | ~2.4 μs | ~0.3 μs | 8x |
| ARM Cortex-M4, 256 bytes | ~615 μs | ~77 μs | 8x |
| x86-64, 1 byte | ~0.08 μs | ~0.02 μs | 4x |
| x86-64, 256 bytes | ~20 μs | ~3 μs | ~6.7x |
| Code size | about 50 bytes | about 550 bytes (512 bytes table + 38 bytes logic) | — |
| RAM usage | 6 bytes (variables) | 518 bytes (table + variables) | — |
Selection suggestion:
- Embedded MCU (Flash ≥ 2KB, requires high-frequency communication):Prioritize the use of lookup table method, where a 512-byte ROM overhead yields an 8-fold speed increase, making it highly cost-effective.
- Flash severely limited (<512B):Use bit shifting method, where sacrificing space for time is not feasible, accepting lower communication performance is acceptable.
- Host computer/server:Do not hesitate to use the lookup table method, a few kilobytes of memory is not a problem at all.
- Learning and verification phase:First implement the bit shifting method to understand the principle, then switch to the lookup table method after verifying with test vectors.
Section 10: Hardware CRC Acceleration
Modern MCUs and processors typically have built-in hardware CRC calculation units, which can further increase the speed of CRC calculation by 10-50 times. The importance of hardware CRC is particularly prominent in industrial gateways and protocol converters, where these devices often need to handle dozens of Modbus RTU communications simultaneously, and the overhead of CRC calculation cannot be ignored.
When selecting a hardware CRC scheme, three factors need to be considered comprehensively: First, whether the hardware CRC unit supports custom polynomials (many older MCUs' CRC modules only support fixed 32-bit CRC-32, which cannot be directly used for Modbus); second, the input data alignment requirements of hardware CRC (some hardware CRCs require 32-bit or 16-bit aligned data input, requiring additional processing for Modbus RTU byte streams); finally, the feasibility of using DMA in conjunction - if the serial port receive buffer can be directly fed into the CRC calculation unit through DMA, zero CPU overhead verification can be achieved.
10.1 STM32 Hardware CRC
The STM32 series of MCUs has a built-in CRC calculation unit, but it uses a different polynomial (0x4C11DB7, 32-bit) by default. To be used with Modbus, direct manipulation of the registers is required:
/**
* STM32 硬件 CRC 配合软件实现 Modbus CRC-16
*
* 由于 STM32 硬件 CRC 模块使用 32 位多项式,
* 不直接兼容 Modbus 的 16 位 CRC-16,
* 通常仍需软件实现。但可以利用 DMA + 查表法加速。
*
* 部分 STM32 型号(如 G4、H7 系列)支持自定义多项式,
* 可配置为 0xA001 实现硬件 CRC-16 计算:
*/
// STM32G4/H7 系列,配置 CRC 单元为 Modbus CRC-16
void hw_crc16_init(void)
{
__HAL_RCC_CRC_CLK_ENABLE();
CRC->POL = 0x8005; // 多项式(正向)
CRC->INIT = 0xFFFF; // 初始值
CRC->CR |= CRC_CR_REV_OUT; // 输出位反转
CRC->CR &= ~CRC_CR_REV_IN; // 输入不反转
}
uint16_t hw_modbus_crc16(uint8_t *buf, uint32_t len)
{
CRC->INIT = 0xFFFF;
while (len >= 4) {
CRC->DR = *(uint32_t *)buf;
buf += 4;
len -= 4;
}
// 处理剩余字节
while (len >= 2) {
CRC->DR = *(uint16_t *)buf;
buf += 2;
len -= 2;
}
if (len) {
CRC->DR = *buf;
}
return (uint16_t)(CRC->DR & 0xFFFF);
}10.2 x86 SSE4.2 CRC32 instruction
Intel/AMD processors provideCRC32hardware instructions starting from the SSE4.2 instruction set. However, it should be noted that the x86 CRC32 instruction uses a different polynomial (0x1EDC6F41), which is not directly compatible with Modbus CRC-16. On x86 platforms, the table lookup method is usually fast enough (processing 256 bytes takes only 3 microseconds).
If hardware acceleration for Modbus CRC-16 is indeed required,FPGA or CPLDcan be used to implement dedicated CRC calculation logic. This is common in industrial gateway devices, where FPGA is used for parallel processing of CRC checks for multiple serial port data streams.
Eleven, Common Pitfalls in CRC Calculation
- Polynomial Confusion:The default parameters of online CRC calculators may differ from those used in Modbus. Always verify the combination of 0xA001 + initial value 0xFFFF.
- Byte Order Disaster:The CRC in Modbus frames is in little-endian order (with the low byte first), but debug output is often in big-endian order. The order must be correct when sending.
- Table generation error:If you write your own table generation code, ensure that the order of bit shifting and XOR is consistent with the main calculation.
- Data type overflow:In a 16-bit system, operating directly without using
(uint8_t)(crc ^ byte)may lead to contamination of the upper 8 bits. - Frame boundary misjudgment:The CRC calculation range cannot include the frame interval silent time and the CRC itself.
- Mixed use of RTU and ASCII:ASCII mode uses LRC instead of CRC, confirm that you are using the correct check method.
XII. FAQ: Common problems with CRC/LRC
Q1: Why doesn't Modbus directly use the standard CRC-16-CCITT?
A: CRC-16-CCITT (polynomial 0x1021) is another widely used CRC standard. Modbus uses the polynomial 0x8005, which was historically chosen by Modicon in 1979. The two CRCs have almost the same error detection capability, but due to differences in implementation details (initial value, inversion, etc.), the results are incompatible with each other. In practical development, it is necessary to strictly adhere to the Modbus specification.
Q2: Can I skip the CRC check to speed up communication?
A:Strongly not recommended.The computational overhead of CRC is negligible on modern MCUs (processing a typical Modbus frame using a lookup table method takes only tens of microseconds). In industrial environments, skipping CRC is equivalent to giving up error detection - a single interference pulse can cause the device to execute incorrect commands. If speed is a priority, optimizations can be made in terms of baud rate, data packing, protocol conversion, etc., rather than sacrificing data integrity.
Q3: Can LRC and CRC be interchanged?
A: No. LRC is only used in ASCII mode, and CRC-16 is only used in RTU mode. The two cannot be mixed in the same network because the frame format and frame separation methods are completely different (RTU uses time intervals, while ASCII uses colons and carriage returns).
Q4: What should I do if the value calculated by an online CRC calculator is inconsistent with my program?
A: Follow these steps to troubleshoot: (1) Confirm that the polynomial is 0x8005 or 0xA001; (2) Confirm that the initial value is 0xFFFF; (3) Confirm that the result XOR value is 0x0000; (4) Confirm that the calculation range does not include the CRC itself; (5) Use the test vectors provided in this article to compare the intermediate results byte by byte. 99% of inconsistency issues are caused by incorrect parameter configuration.
Q5: Can Python's binascii.crc_hqx() be used for Modbus?
A: It cannot be used directly.crc_hqx()Using polynomial 0x1021 and initial value 0x0000 is completely different from the Modbus CRC-16 parameters. It is recommended to use the Python implementation provided in this article or the CRC calculation function in the pymodbus library.
Q6: Is CRC check failure always due to data errors?
A: Not necessarily. The following situations may also cause CRC check failure: (1) byte parsing error due to baud rate mismatch; (2) frame boundary judgment error, resulting in extra or missing bytes; (3) incorrect station address setting of the device, reading response frames intended for other devices; (4) RS-485 transceiver failure, causing data truncation.
Q7: How to quickly implement Modbus CRC on an MCU without a standard library?
A: The simplest method is to directly copy the 256-entry lookup table provided in this article into your code. This table is "pure data" - it does not require any external dependencies and does not call any library functions. It only requires a 512-byte const array (it is recommended to declare it in Flash usingconst) and a few lines of calculation logic. For 8-bit MCUs (such as 8051, AVR), it is recommended to use the bitwise shift method, as a 512-byte table may exceed the Flash capacity of some compact models. For 32-bit MCUs (such as STM32, ESP32), the lookup table method is the best choice.
Q8: Why is Modbus communication sometimes unstable, with CRC errors alternating between correct and incorrect?
A: This intermittent error is usually not a problem with the CRC algorithm, but rather a physical layer issue. Common causes include: missing terminal resistors or incorrect resistor values on the RS-485 bus (the standard is 120Ω, one on each end), signal attenuation due to excessively long bus length, common-mode voltage exceeding the range of the RS-485 transceiver (-7V to +12V), and incorrect bus idle level due to the superposition of bias resistors from multiple devices. It is recommended to use an oscilloscope to observe the RS-485 differential signal waveform and check signal quality. For more Modbus communication debugging tips, please refer to the debugging article onmodbus.cn.
XIII. Summary
CRC/LRC check is the "gatekeeper" of Modbus communication, ensuring the integrity of industrial data.
- CRC-16 (RTU mode):Based on the cyclic redundancy check of polynomial 0xA001, it has strong detection capability and is the mainstream verification method for Modbus communication
- LRC (ASCII mode):Longitudinal redundancy check based on accumulation and complementation, simple to implement but with weak detection capability
- Table lookup method:Trading 512 bytes of ROM overhead for an 8-fold speed increase, preferred in production environments
- Bit shifting method:Code-efficient, suitable for learning and resource-constrained scenarios
- Hardware acceleration:Modern MCUs and FPGAs can further enhance the efficiency of CRC computation by an order of magnitude
As an embedded engineer or automation engineer, understanding the principle of CRC and mastering its programming implementation are fundamental skills. It is recommended to save the test vectors and code from this article as a reference and verify them whenever implementing new Modbus communication.
For more in-depth technical articles on Modbus, please visitmodbus.cn. Recommended reading:Comparative analysis of Modbus and mainstream industrial protocols, Complete guide to Modbus function codes, Core differences between Modbus RTU and Modbus TCP. If you encounter CRC check issues in practical projects, you can also discuss and exchange ideas with other engineers in the technical community of modbus.cn to gain more practical experience sharing.
This article is originally created by the modbus.cn technical team. Please indicate the source when reprinting. The code examples in the article have been verified through actual testing. Updated: June 2026.
Leave a Reply