在很多的串口通讯中,会使用到CRC16校验。在TIdHashCRC16中,给我们提供了一种CRC校验码的生成方式:
TIdHashCRC16继承于TIdHash16这个类,其中覆盖实现了两个方法:
-
- { TIdHashCRC16 }
-
- procedure TIdHashCRC16.HashStart(var VRunningHash: UInt16);
- begin
- VRunningHash := 0;
- end;
-
- procedure TIdHashCRC16.HashByte(var VRunningHash: UInt16; const AByte: Byte);
- begin
- VRunningHash := (VRunningHash shr 8) xor CRC16Table[AByte xor (VRunningHash and $FF)];
- end;
然而,当我们直接使用TIdHashCRC16类生成校验码的时候,经常是与设备的校验不匹配的,其原因在于VRunningHash初始化时为0,而我们需要的初始化值应该为0xFFFF。所以,我们需要对这个类进行改造,其中最简单的办法就是找到IdHashCRC.pas文件,把VRunningHash:=0;这个修改为VRunningHash:=$FFFF;,然后把这个修改后的文件直接放到项目文件的根目录下就行了。
但这样的修改并不是最好的方式,所以,我们可以新建一个类,让他继承于TIdHashCRC16,并重写HashStart方法:
- uses
- IdGlobal, IdHash, IdHashCRC, System.SysUtils, System.Classes;
-
- Type
- TCRC16 = class(TIdHashCRC16)
- public
- procedure HashStart(var VRunningHash: UInt16); override;
- function GetCRC(vCRCBytes: TBytes): TBytes;
- end;
-
- implementation
-
- { TCRC16 }
-
- function TCRC16.GetCRC(vCRCBytes: TBytes): TBytes;
- var
- LStream: TStream;
- SS: TIdBytes;
- begin
- LStream := TMemoryStream.Create;
- try
- LStream.WriteBuffer(vCRCBytes, Length(vCRCBytes));
- LStream.Position := 0;
- SS := HashStream(LStream);
- move(SS[0], Result[0], 2);
- finally
- FreeAndNil(LStream);
- end;
- end;
-
- procedure TCRC16.HashStart(var VRunningHash: UInt16);
- begin
- VRunningHash := $FFFF;
- end;
上面代码中我们在重写HashStart方法的时候还增加了一个获取CRC值的函数,方便直接使用。
使用方法也比较简单:
- CRC16 := TCRC16.Create;
- vCRC16 := CRC16.GetCRC(vTmp);
- FreeAndNil(CRC16);
此外,我们还有一种生成CRC校验码的方式:
- function CalCRC16(AData: TBytes): TBytes;
- const
- GENP=$A001; //多项式公式X16+X15+X2+1(1100 0000 0000 0101)
- var
- vCRC:Word;
- i:Integer;
- vTmpByte:Byte;
- procedure CalOneByte(AByte:Byte); //计算1个字节的校验码
- var
- j:Integer;
- begin
- vCRC:=vCRC xor AByte; //将数据与CRC寄存器的低8位进行异或
- for j:=0 to 7 do //对每一位进行校验
- begin
- vTmpByte:=vCRC and 1; //取出最低位
- vCRC:=vCRC shr 1; //寄存器向右移一位
- vCRC:=vCRC and $7FFF; //将最高位置0
- if vTmpByte=1 then //检测移出的位,如果为1,那么与多项式异或
- vCRC:=vCRC xor GENP;
- vCRC:=vCRC and $FFFF;
- end;
- end;
- begin
- vCRC:=$FFFF; //将余数设定为FFFF
- for i:=0 to Length(AData)-1 do //对每一个字节进行校验
- CalOneByte(AData[i]);
-
- SetLength(Result,2);
- Result[0]:= trunc(vCRC mod 256);
- Result[1]:= trunc(vCRC div 256);
- end;
-
方法三的使用也非常简单,在需要计算CRC校验码的时候,直接调用函数就可以生成CRC校验码了。
这两种校验码的生成方式不同,结果是一样的。两种方法的使用,都是以TBytes的变量传递计算与返回值。