CRC16(Cyclic Redundancy Check,循环冗余校验)是一种用于检测数据传输或存储过程中是否出现错误的校验算法。它通过计算数据的校验码(通常为16位,即两个字节)来实现。在通信协议(如Modbus)中,CRC16校验被广泛用于确保数据的完整性。
CRC16的基本原理
多项式除法:CRC16使用一个特定的生成多项式(例如Modbus中使用的是0x8005)对数据进行除法运算(在GF(2)域,即模2除法),得到的余数就是校验码。初始值:计算前通常有一个初始值(如0xFFFF)。结果异或值:计算完成后,有时会将结果与某个值(如0x0000)进行异或。输出反转:有些CRC16算法要求将最终结果按位反转。
Modbus RTU中的CRC16
在Modbus RTU协议中,CRC16的计算遵循以下规范:
多项式(Polynomial):0x8005(二进制:10000000000000101)初始值(Initial Value):0xFFFF输入反转(Input Reflected):True(按字节反转,即每个字节的位序颠倒)输出反转(Output Reflected):True(整个16位结果位序颠倒)结果异或值(XOR Out):0x0000
CRC16计算步骤(Modbus)
以计算数据01 03 00 00 00 01的CRC16为例:
初始化CRC寄存器为0xFFFF。依次处理每一个数据字节(包括地址和功能码等):
a. 将当前数据字节与CRC寄存器的低8位进行异或。
b. 对异或后的结果进行8次循环移位操作(每次移一位),在移位过程中根据移出的位决定是否与多项式0xA001(0x8005按位反转后的形式)进行异或。处理完所有字节后,得到的CRC寄存器中的值就是CRC16校验码(注意,Modbus要求将最终结果按小端序放置,即低字节在前,高字节在后)。
C#实现Modbus CRC16
以下是C#中计算Modbus CRC16的典型实现:
public static byte[] ComputeModbusCrc16(byte[] data)
{
ushort crc = 0xFFFF; // 初始值
foreach (byte b in data)
{
crc ^= b; // 与当前字节异或
for (int i = 0; i < 8; i++)
{
bool lsb = (crc & 0x0001) != 0; // 检查最低位是否为1
crc >>= 1; // 右移1位
if (lsb)
crc ^= 0xA001; // 如果移出位为1,则与多项式0xA001异或
}
}
// 将CRC拆分为两个字节(小端序:低字节在前,高字节在后)
return new byte[] { (byte)(crc & 0xFF), (byte)((crc >> 8) & 0xFF) };
}
示例
计算数据帧 01 03 00 00 00 01 的CRC16:
数据:01 03 00 00 00 01计算CRC:使用上述函数,得到两个字节:0x84 和 0x0A完整帧:01 03 00 00 00 01 84 0A
在Modbus通信中的作用
发送方:在数据帧末尾附加计算出的CRC16(低字节在前)。接收方:对接收到的数据(不包括最后的CRC部分)重新计算CRC16,然后与接收到的CRC16比较。如果一致,则数据正确;否则,认为传输错误,丢弃该帧。
注意事项
字节顺序:Modbus RTU中,CRC16的结果以低字节在前(小端序)的方式附加在帧尾。性能优化:实际应用中,通常使用预计算的CRC表(查表法)来提高计算速度,尤其当数据量较大时。
查表法优化
下面是使用查表法优化后的CRC16计算(在Modbus通信中更高效):
public static class ModbusCrc16
{
private static readonly ushort[] CrcTable = new ushort[256];
static ModbusCrc16()
{
// 初始化CRC表
for (ushort i = 0; i < 256; i++)
{
ushort value = 0;
ushort temp = (ushort)(i << 8);
for (byte j = 0; j < 8; j++)
{
if (((value ^ temp) & 0x8000) != 0)
value = (ushort)((value << 1) ^ 0x8005);
else
value <<= 1;
temp <<= 1;
}
CrcTable[i] = value;
}
}
public static byte[] Compute(byte[] data)
{
ushort crc = 0xFFFF;
foreach (byte b in data)
{
byte index = (byte)(crc ^ b);
crc = (ushort)((crc >> 8) ^ CrcTable[index]);
}
return new byte[] { (byte)crc, (byte)(crc >> 8) };
}
}
使用查表法可以大幅提高CRC16的计算效率,特别适合在嵌入式系统或高频通信中使用。
总结
CRC16是Modbus RTU协议中用于错误检测的关键机制,确保数据传输的可靠性。理解其原理和实现对于开发工业通信应用至关重要。
CRC16(循环冗余校验)详解
CRC16(Cyclic Redundancy Check,循环冗余校验)是一种广泛用于数据通信和存储中检测传输错误的校验算法。它通过计算数据的校验值(Checksum)来验证数据的完整性。
CRC16的核心原理
多项式除法:
将数据视为一个大型二进制数
用预定义的多项式(如Modbus使用0x8005)除这个数
得到的余数就是CRC校验码
位操作实现:
通过移位和异或(XOR)操作高效计算
不需要实际的除法运算
Modbus RTU中的CRC16规范
特性 值 说明
多项式 0x8005 x¹⁶ + x¹⁵ + x² + 1
初始值 0xFFFF 计算开始时的种子值
输入反转 是 每个字节的位顺序反转
输出反转 是 最终结果的位顺序反转
结果异或 0x0000 最终结果不需要额外异或
校验码长度 16位 2字节
CRC16计算步骤(以Modbus为例)
初始化
ushort crc = 0xFFFF; // 初始值
处理每个字节
foreach (byte b in data)
{
crc ^= b; // 当前字节与CRC异或
for (int i = 0; i < 8; i++) // 处理每个bit
{
bool lsb = (crc & 0x0001) != 0; // 检查最低位
crc >>= 1; // 右移一位
if (lsb) // 如果移出的位是1
crc ^= 0xA001; // 与多项式异或 (0x8005的反转)
}
}
最终处理`
// 输出反转 (大端序转小端序)
byte crcLow = (byte)crc;
byte crcHigh = (byte)(crc >> 8);
CRC16在Modbus帧中的位置
完整的Modbus RTU帧结构:
[地址][功能码][数据][CRC低字节][CRC高字节]
示例帧(读取寄存器):
01 03 00 00 00 01 84 0A
└─┬┘ └─────┬────┘ └──┬──┘
│ │ └── CRC16校验码 (0x0A84)
│ └────────── 数据部分
└────────────────── 从站地址和功能码
为什么需要CRC16
错误检测能力:
检测所有单比特错误
检测所有双比特错误
检测奇数个错误
检测大多数突发错误(最长16位)
计算效率高:
适合嵌入式系统
硬件实现简单
广泛支持:
工业标准(Modbus, CAN, USB等)
文件校验(ZIP, RAR等)
CRC16算法实现(C#)
public static byte[] CalculateModbusCRC16(byte[] data)
{
ushort crc = 0xFFFF; // 初始值
foreach (byte b in data)
{
crc ^= b; // 异或当前字节
for (int i = 0; i < 8; i++) // 处理每个bit
{
bool lsb = (crc & 0x0001) != 0;
crc >>= 1; // 右移一位
if (lsb)
crc ^= 0xA001; // 0xA001是0x8005的位反转
}
}
// 返回小端序 [低字节, 高字节]
return new byte[] { (byte)(crc & 0xFF), (byte)(crc >> 8) };
}
CRC16验证过程
接收方验证数据的完整性:
接收完整数据帧(包括CRC)
计算接收数据的CRC(不包括最后2字节)
将计算结果与接收的CRC比较
匹配则数据正确,不匹配则数据有误
public bool VerifyCRC(byte[] frame)
{
// 分离数据和CRC
byte[] data = frame.Take(frame.Length - 2).ToArray();
byte[] receivedCrc = frame.Skip(frame.Length - 2).Take(2).ToArray();
// 计算数据的CRC
byte[] calculatedCrc = CalculateModbusCRC16(data);
// 比较CRC
return receivedCrc.SequenceEqual(calculatedCrc);
}
常见CRC16变体
名称 多项式 初始值 应用领域
Modbus 0x8005 0xFFFF 工业控制
CCITT 0x1021 0xFFFF XMODEM, Bluetooth
USB 0x8005 0xFFFF USB数据包
CRC-16-IBM 0x8005 0x0000 SDLC协议
实际通信示例
主站请求读取寄存器:
// 请求帧: 01 03 00 00 00 01
byte[] request = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x01 };
byte[] crc = CalculateModbusCRC16(request); // 返回 [0x84, 0x0A]
// 完整发送帧: 01 03 00 00 00 01 84 0A
byte[] fullFrame = request.Concat(crc).ToArray();
_serialPort.Write(fullFrame, 0, fullFrame.Length);
从站响应:
// 接收响应: 01 03 02 01 42 39 E5
byte[] response = ReadSerialData(); // 实现读取串口数据
// 验证CRC
if (!VerifyCRC(response))
throw new Exception("CRC校验失败");
// 提取有效数据: [0x01, 0x42]
byte[] validData = response.Skip(3).Take(response[2]).ToArray();
C16的重要性
数据可靠性:确保工业环境中数据传输的准确性
安全性:防止错误数据导致设备误操作
协议完整性:Modbus RTU标准要求
错误定位:帮助识别通信线路问题
理解CRC16的原理和实现对于开发可靠的工业通信系统至关重要,特别是在Modbus RTU这类没有内置错误纠正机制的协议中,CRC16是确保数据完整性的第一道防线。