〇、参考文章
一、CRC原理
CRC
是对传输的数据进行一个差错校验
的,但是不对数据进行纠错,CRC
检测不通过的时候丢弃数据
CRC注意点
首先先明确几个点:
- 多项式的被除数
M
,也就是信息码(数据) - 多项式的除数
G
,也叫生成多项式 - 得到的结果冗余校验码
R
- 以及冗余校验码的宽度
W
- 规定:生成多项式对应的二进制位数比W多一位,但是把第1位忽略掉
比如:
生成多项式 x 4 + x 3 + 1 x^{4} + x^{3}+1 x4+x3+1 ,对应的二进制是 11001,那么所求的就是 CRC-4,是所求的CRC位数来决定多项式的
新的运算法则
CRC
算法的是以GF(2)
(2元素伽罗瓦域)多项式算术为数学基础的,注意,这是一种新的运算法则
法则规定,加减不考虑进位,所以加减是一致的,式子中不出现减号,以加号代替,因此可以用二进制的异或来运算其加法
(在运算异或的时候就是加法)
P1 = x^3 + x^2 + 1,P2 = x^3 + x^1 + 1,P1 + P2为:
x^3 + x^2 + 1 1101
+ x^3 + x + 1 ^ 1011
------------------------------ >> ------------------------------
x^2 + x 0110
了解一下异或的一些性质:(下面的代码中用到了交换律)
1. a ⊕ a = 0
2. a ⊕ b = b ⊕ a // 异或运算满足交换律
3. a ⊕ b ⊕ c = a ⊕ (b ⊕ c) = (a ⊕ b) ⊕ c; // 异或运算满足结合律
4. d = a ⊕ b ⊕ c 可以推出 a = d ⊕ b ⊕ c.
5. a ⊕ b ⊕ a = b.
CRC原理
假设信息 M M M,多项式 G G G,在 M M M 后面添加 W W W 个 0 0 0,也就是 M ′ = M ∗ x W M' = M*x^{W} M′=M∗xW ,除以多项式 G G G,的到商 Q Q Q,余下余数 R R R
M ′ ÷ G = Q ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ R M'÷G = Q······R M′÷G=Q⋅⋅⋅⋅⋅⋅R
比如说我们的需要发送的信息是0x01 0x02 0x03 0x04
,求CRC-8
,会把所有信息的二进制拼在一起,然后末尾添加8
个0
0x01 0x02 0x03 0x04
0000000100000010000000110000010000000000 =>M'
对M'
是多出了R
的,对M'
使用新的运算法则,(M'+R)
会把多余的R消除掉,因为a ⊕ a = 0
,所以有(M'+R)÷G = Q······0
- 发送方对信息进行
CRC
验算,然后把余数R(CRC)
附加在信息的后面0x01 0x02 0x03 0x04 R(CRC)
,相当于(M'+R)
,一起发给接收方 - 接收方接收到信息后,可以直接对多项式求余数,余数等于
0
,说明接收到的信息是正确的 - 当然也可以去掉
R(CRC)
,同样使用M'
对G
求余数,结果跟发过来的R(CRC)
一样说明接收的数据是正确的
CRC例子
下面是以对信息0x01 0x02 0x03 0x04
为例求CRC-16
的过程,商不重要,所以没有写
二、CRC检验C语言
实际如果信息字节数比较多,是没有办法用一个变量存储所有的字节的,这不现实
所以需要使用到异或的性质:交换律和结合律
对0x0102
,有0x0102 = 0x0100 ⊕ 0x02
对0x0102 ⊕ 0x1021
,有 0x0100 ⊕ 0x02 ⊕ 0x1021= (0x0100 ⊕ 0x1021) ⊕ 0x02
其中0x0100
只有高8
位是有数据的,所以只要逐个左移8
位去找到数据位为1
的开始异或,这里找到数据位为1
之后还要左移一次再异或就是因为隐藏了最高位的1
for (i = 0; i < 8; i++) // 一个字节8位,一位一位的去异或
{
if (crc & 0x8000) // 判断CRC的首位,如果是1的活,那么左移一位之后,其首位和除数被忽略的那个最高位的1异或结果位0,也可以忽略
crc = (crc << 1) ^ POLY; // 左移后进行异或
else // 首位为0,比如异或之后结果为0,应该选首位为1的位开始继续异或,直接跳过0位
crc <<= 1;
}
对0x0100
左移了8
位,异或多项式得到余数,所以后面的0x02
,也是需要左移8
位的,再异或余数的
crc = crc ^ (*addr++ << 8);
那么结合起来的总代码就是下面的这个
#define POLY 0x1021
/**
* @param: addr: 存放数据的数组地址
* lenth: 数组长度
* crc: crc初始化的值
*/
uint16_t crc16(unsigned char *addr, int lenth, uint16_t crc)
{
int i;
for (; lenth> 0; lenth--) // 数组有多少字节进行多少次计算
{
crc = crc ^ (*addr++ << 8);
for (i = 0; i < 8; i++)
{
if (crc & 0x8000)
crc = (crc << 1) ^ POLY;
else
crc <<= 1;
}
crc &= 0xFFFF; // 确保出来的CRC是16位,多余的清零,这个不是结果异或值 XOROUT
}
return(crc);
}
三、测试:
AA 55 12 AA AA AA 12 34 56 78