|  | 
 
| 计算法简单实现crc校验 
 前一段时间做协议转换器的时间用到CRC-16校验,查了不少资料发现都不理想。查表法要建表太麻烦,而计算法觉得那些例子太罗嗦。最后只好自己写了,最后发现原来挺简单嘛:)
 
 两个子程序搞定。这里用的多项式为:
 CRC-16    = X16 + X12 + X5 + X0 = 2^0+2^5+2^12+2^16=0x11021
 
 因最高位一定为“1”,故略去计算只采用0x1021即可
 
 CRC_Byte:计算单字节的CRC值
 CRC_Data:计算一帧数据的CRC值
 CRC_High  CRC_Low:存放单字节CRC值
 CRC16_High  CRC16_Low:存放帧数据CRC值
 
 ;<>-------------------------------------------------------------
 ;      Function:       CRC one byte
 ;      Input:             CRCByte
 ;      Output:           CRC_High CRC_Low
 ;<>-------------------------------------------------------------
 
 CRC_Byte:
 clrf         CRC_Low
 clrf         CRC_High
 movlw           09H
 movwf           v_Loop1
 movf              CRCByte, w
 movwf           CRC_High
 CRC:
 decfsz            v_Loop1                              ;8次循环,每一位相应计算
 goto        CRC10
 goto        CRCend
 CRC10
 bcf                STATUS, C
 rlf                  CRC_Low
 rlf                  CRC_High
 
 btfss              STATUS, C
 goto        CRC                                          ;为0不需计算
 movlw           10H                                    ;若多项式改变,这里作相应变化
 xorwf            CRC_High, f
 movlw           21H                                    ;若多项式改变,这里作相应变化
 xorwf            CRC_Low, f
 goto        CRC
 CRCend:
 nop
 nop
 return
 ;<>-------------------------------------------------------------
 ;      CRC one byte end
 ;<>-------------------------------------------------------------
 ;<>-------------------------------------------------------------
 ;      Function:       CRC date
 ;      Input:             BufStart(A,B,C)(一帧数据的起始地址) v_Count (要做CRC的字节数)
 ;      Output:           CRC16_High CRC16_Low(结果)
 ;<>-------------------------------------------------------------
 CRC_Data:
 
 clrf         CRC16_High
 clrf         CRC16_Low
 
 CRC_Data10
 
 movf              INDF, w
 xorwf            CRC16_High,w
 
 movwf           CRCByte
 call         CRC_Byte
 incf         FSR
 decf        v_Count                       ;需计算的字节数
 
 movf              CRC_High, w
 xorwf            CRC16_Low, w
 movwf           CRC16_High
 
 movf              CRC_Low, w
 movwf           CRC16_Low
 
 movf              v_Count, w                                          ;计算结束?
 btfss              STATUS, Z
 goto        CRC_Data10
 
 return
 
 ;<>-------------------------------------------------------------
 ;             CRC date end
 ;<>-------------------------------------------------------------
 
 
 
 说明: CRC 的计算原理如下(一个字节的简单例子)
 11011000 00000000 00000000  <- 一个字节数据, 左移 16b
 ^10001000 00010000 1         <- CRC-CCITT 多项式, 17b
 --------------------------
 1010000 00010000 10        <- 中间余数
 ^1000100 00001000 01
 -------------------------
 10100 00011000 1100
 ^10001 00000010 0001
 -----------------------
 101 00011010 110100
 ^100 01000000 100001
 ---------------------
 1 01011010 01010100
 ^1 00010000 00100001
 -------------------
 01001010 01110101  <- 16b CRC
 
 仿此,可推出两个字节数据计算如下:d 为数据,p 为项式,a 为余数
 dddddddd dddddddd 00000000 00000000 <- 数据 D ( D1, D0, 0, 0 )
 ^pppppppp pppppppp p                 <- 多项式 P
 -----------------------------------
 ...
 aaaaaaaa aaaaaaaa 0        <- 第一次的余数 A’ ( A’1, A’0 )
 ^pppppppp pppppppp p
 --------------------------
 ...
 aaaaaaaa aaaaaaaa <- 结果 A ( A1, A0 )
 
 由此与一字节的情况比较,将两个字节分开计算如下:
 先算高字节:
 dddddddd 00000000 00000000 00000000 <- D1, 0, 0, 0
 ^pppppppp pppppppp p                 <- P
 -----------------------------------
 ...
 aaaaaaaa aaaaaaaa          <- 高字节部分余数 PHA1, PHA0
 
 此处的部分余数与前面两字节算法中的第一次余数有如下关系,即 A’1 = PHA1 ^ D0, A’0 = PHA0:
 aaaaaaaa aaaaaaaa          <- PHA1, PHA0
 ^dddddddd                   <- D0
 -----------------
 aaaaaaaa aaaaaaaa          <- A’1, A’0
 
 低字节的计算:
 aaaaaaaa 00000000 00000000 <- A’1, 0, 0
 ^pppppppp pppppppp p        <- P
 --------------------------
 ...
 aaaaaaaa aaaaaaaa <- 低字节部分余数 PLA1, PLA0
 ^aaaaaaaa          <- A’0 , 即 PHA0
 -----------------
 aaaaaaaa aaaaaaaa <- 最后的 CRC ( A1, A0 )
 
 总结以上内容可得规律如下:
 设部分余数函数
 PA = f( d )
 其中 d 为一个字节的数据(注意,除非 n = 0 ,否则就不是原始数据,见下文)
 第 n 次的部分余数
 PA( n ) = ( PA( n - 1 ) << 8 ) ^ f( d )
 其中的
 d = ( PA( n - 1 ) >> 8 ) ^ D( n )
 其中的 D( n ) 才是一个字节的原始数据。
 
 公式如下:
 PA( n ) = ( PA( n - 1 ) << 8 ) ^ f( ( PA( n - 1 ) >> 8 ) ^ D( n ) )
 
 可以注意到函数 f( d ) 的参数 d 为一个字节,对一个确定的多项式 P, f( d ) 的返回值 是与 d 一一对应的,总数为 256 项,将这些数据预先算出保存在表里,f( d )就转换为一 个查表的过程,速度也就可以大幅提高,这也就是查表法计算 CRC 的原理。
 
 再来看 CRC 表是如何计算出来的,即函数 f( d ) 的实现方法。分析前面一个字节数据的 计算过程可发现,d 对结果的影响只表现为对 P 的移位异或,看计算过程中的三个 8 位 的列中只低两个字节的最后结果是余数,而数据所在的高 8 位列最后都被消去了,因其 中的运算均为异或,不产生进位或借位,故每一位数据只影响本列的结果,即 d 并不直接 影响结果。再将前例变化一下重列如下:
 11011000
 --------------------------
 10001000 00010000 1        // P
 ^ 1000100 00001000 01       // P
 ^  000000 00000000 000      // 0
 ^   10001 00000010 0001     // P
 ^    0000 00000000 00000    // 0
 ^     100 01000000 100001   // P
 ^      00 00000000 0000000  // 0
 ^       1 00010000 00100001 // P
 -------------------
 01001010 01110101
 
 现在的问题就是如何根据 d 来对 P 移位异或了,从上面的例子看,也可以理解为每步 移位,但根据 d 决定中间余数是否与 P 异或。从前面原来的例子可以看出,决定的条件是中间余数的最高位为0,因为 P 的最高位一定为1,即当中间余数与 d 相应位异或的最高位为1时,中间余数移位就要和 P 异或,否则只需移位即可。其方法如下例(上例的变形,注意其中空格的移动表现了 d 的影响如何被排除在结果之外):
 
 d --------a--------
 1 00000000 00000000 <- HSB = 1
 0000000 000000000 <- a <<= 1
 0001000 000100001 <-不含最高位的 1
 -----------------
 1 0001000 000100001
 001000 0001000010
 000100 0000100001
 -----------------
 0 001100 0001100011 <- HSB = 0
 01100 00011000110
 -----------------
 1 01100 00011000110 <- HSB = 1
 1100 000110001100
 0001 000000100001
 -----------------
 1 1101 000110101101 <- HSB = 0
 101 0001101011010
 -----------------
 0 101 0001101011010 <- HSB = 1
 01 00011010110100
 00 01000000100001
 -----------------
 0 01 01011010010101 <- HSB = 0
 1 010110100101010
 -----------------
 0 1 010110100101010 <- HSB = 1
 0101101001010100
 0001000000100001
 -----------------
 0100101001110101 <- CRC
 
 结合这些,前面的程序就好理解了。
 | 
 |