YTM32的循环冗余校验CRC外设模块详解
引言
在串行通信帧中,为了保证数据在传输过程中的完整性,通常采用一种指定的算法对原始数据进行计算,得出的一个校验值。接收方接收到数据时,采用同样的校验算法对原始数据进行计算,若计算结果和接收到的校验值一致,说明数据校验正确,该帧数据可用,若不一致,说明传输过程中出现了差错,丢弃该帧数据,请求重发。常用的校验算法有奇偶校验、校验和、CRC,还有LRC、BCC等不常用的校验算法。
在诸多检错手段中,CRC是最著名的一种。CRC的全称是循环冗余校验,其特点是:检错能力强,开销小,易于用编码器及检测电路实现。从其检错能力来看,它所不能发现的错误的几率仅为0.0047%以下。从性能上和开销上考虑,均远远优于奇偶校验及算术和校验等方式。因而,在数据存储和数据通讯领域,CRC无处不在:著名的通讯协议X.25的FCS(帧检错序列)采用的是CRC-CCITT,WinRAR、NERO、ARJ、LHA等压缩工具软件采用的是CRC32,磁盘驱动器的读写采用了CRC16,通用的图像存储格式GIF、TIFF等也都用CRC作为检错手段。
YTM32B1ME微控制器中提供了硬件的CRC计算引擎,用以加速和简化用户程序中的CRC运算。值得注意的是,YTM32B1ME微控制器中还集成了SENT通信外设,SENT通信协议中也用到了CRC校验。可以想见,在具体应用中,CRC外设可以配合SENT外设,或者其他在协议中使用CRC校验算法的通信过程提供硬件计算引擎的支持。
YTM32的硬件CRC外设模块,用户通过AHB总线向CRC外设送数参与计算,然后通过AHB总线送出实时的计算结果。CRC外设内部实现了CRC4(CRC-ITU)、CRC16(CRC-CCITT)、CRC32(CRC-ethernet)等多项算式的计算引擎,并可配置输出和输出数据的位交换,通过8b、16b、32b等带宽方式,向CRC计算引擎送数。如图x所示。
原理与机制
CRC算法简介
CRC的全称是循环冗余校验(Cyclic Redundancy Check),其目的是保证数据的完整性,具体应用方法,是在发送数据的后面再补充若干位的数据,使得接收方使用同样的CRC计算方法,检查接收到的数据的CRC计算结果是否为0,从而判定接收数据的完整性。
CRC的核心算法,就是通过一串“发送数据”,来计算“需要补充的若干数据”。CRC的计算过程是用除法求余数。不同于十进制的除法运算,CRC用的是2进制的除法运算,2进制的除法运算,属于模2运算,模2运算的特点是:没有进位。
- 模2加法:0+0=0,0+1=1,1+0=1,1+1=0。
- 模2减法:0-0=0,0-1=1,1-0=1,1-1=0。模2加减法实际上就是异或操作,这也是CRC很容易在实际中应用的数学基础。
- 模2乘法:0x0=0,0x1=0,1x0=0,1x1=1。
- 模2除法:是模2乘法的逆运算,参见下图示例。
通过除法算式可以形象地想见,当将一串输入数据,再在右侧补入N个空位,作为被除数,除以CRC多项式所表示的N位除数时,若不能除尽,则在除法竖式的最后一个环节,在最右边产生一个同补入空位同位宽的余数,那这个余数就是CRC的计算结果。此处用一个例子说明CRC的计算过程。如图x所示。
在这个算例中:
- CRC8的多项式是
x8+x2+x+1
,对应的除数就是二进制数100000111
- 被除数是
0x1C
,转化成二进制就是00011100
- CRC8为8位,被除数后面补8个0,也同时对应CRC计算的结果也是8位
- 最后的计算结果是
0x54
在CRC解算时,若将CRC计算的结果换入补充的空位,在进行CRC计算,相当于是将之前CRC计算的余数同自己相加,根据CRC计算的加法中0+0=1
和1+1=0
的规则,刚好可以得到0的计算结果,从而验证数据在传输过程的完整性。
使用在线的计算器(http://www.ip33.com/crc.html),也能算得同样的结果。如图x所示。
细心的读者可能看到了,这里面还有初始值,结果异或值,输入数据反转和输出数据反转,这些名词都是指什么呢?
- 初始值,是给CRC计算前提供一个初始值(Offset,预计算值),大部分情况下设定为0,也可以根据选定的特定算法指定其他值,有时也被称为CRC计算的种子(Seed)。结果异或值,是把计算结果再异或某一个值。使用初始值和结果异或值,的目的是防止在某个计算环节中,算得全0的中间结果,导致后续的CRC余数一直为0。
- 输入数据反转,是指输入数据以字节为单位按位逆序处理;输出数据反转,是指CRC计算结果整体按位逆序处理;这么做的目的(一个合理的解释)是右移比左移更容易计算,效率高,它跟大小端无关。
特别注意,最基本的计算过程大多不需要额外设定这些参数(配置成false或者0即可),但有些面向典型应用的场景,会对应一些特别配置组合(典型配置),产生了不同的算法。如图x所示。
从CRC算法到CRC硬件外设
示例CRC计算的过程中,使用的是1个8位的数作为被除数,但实际计算数据帧的数据大多是一个数组,这个扩展过程,可以理解为将整个数据帧的一串数据并排放在一起,当成一个大数进行计算。实际上,CRC的除法计算,也是一个串行移位的计算过程,可以从左边算到右边。在使用CRC硬件外设进行计算时,对应可以逐个向CRC_DATA
寄存器中送数,CRC会使用上次计算结果和新送入的数据位,得到本次计算的结果,并继续参与后续的计算。CRC硬件外设的CRC_DATA
寄存器,支持用户已8位、16位、32位的位宽向CRC计算引擎送数。
YTM32的硬件CRC支持三种算式:
CRC硬件外设可以配置初始值(种子,寄存器CRC_INIT
),支持输入和数据的位序反转,不支结果持异或计算,但支持结果位翻转。通过结果寄存器CRC_RESULT
可以读取实时计算的结果。
需要注意的是,YTM32手册上列写的支持CRC多项式的名字,主要还是描述算子,硬件还是使用基本算法执行计算。若需要使用某些具体算法配套特定的位序反转、初始值等,还是需要用户自行配置CRC硬件外设。
应用要点(软件)
在arm-mcu-sdk
软件仓库中,为ytm_crc_0
驱动设计的样例工程crc_basic
中,设计了计算CRC-16和CRC-32的演示用例。
CRC16 用例
在crc16_test
用例中,实现计算数列{0x1234, 0x5678, 0x5A5A, 0xA5A5}
的CRC16值,有源码如下 :
CRC_Init_Type crc16_ccitt =
{
.EnableOutputBitInv = false,
.EnableOutputBitSwap = false,
.EnableInputBitSwap = false,
.CalcMode = CRC_CalcMode_Crc16,
.InitData = 0x0000
};
#define CRC_DATA_LEN 4u
const uint16_t crc16_data_arr[CRC_DATA_LEN] = {0x1234, 0x5678, 0x5A5A, 0xA5A5};
#define CRC16_RESULT 0x4BDCu /* The result CRC calculator with CCITT 32 bits standard. */
void crc16_test(void)
{
/* setup the crc calculator. */
CRC_Init(BOARD_CRC_PORT, &crc16_ccitt);
for (uint32_t i = 0u; i < CRC_DATA_LEN; i++)
{
CRC_SetData16b(BOARD_CRC_PORT, crc16_data_arr[i]);
}
printf("crc16_test ... ");
if (CRC16_RESULT == CRC_GetResult(BOARD_CRC_PORT))
{
printf("succ.");
}
else
{
printf("fail.");
}
printf("\r\n");
}
使用在线的CRC计算器计算,可以得到相同的计算结果。如图x所示。
这里要注意,CRC在线计算器中定义的CRC-16/CCITT
是输入数据反转和输出数据反转的,而样例代码中指定的crc16_ccitt
配置参数,实际对应CRC在线计算器的CRC-16/XMODEM
计算配置。如果在样例代码中,设定配置参数.EnableOutputBitSwap = true
和.EnableInputBitSwap = true
,也可以得到同CRC在线计算器定义的CRC-16/CCITT
算式相同的计算结果。
这里显然对算式的名字存在了误解。开发者如果不确定各名字预设的配置,也可以使用“自定义”的参数模型,此时可人为指定各参数。如图x所示。
CRC32 用例
在crc32_test
用例中,实现计算数列{0x12345678, 0x56781234, 0x55AA55AA, 0xA5A5A5A5}
的CRC32值,有源码如下 :
CRC_Init_Type crc32_enet =
{
.EnableOutputBitInv = false,
.EnableOutputBitSwap = false,
.EnableInputBitSwap = false,
.CalcMode = CRC_CalcMode_Crc32,
.InitData = 0xffffffff
};
#define CRC_DATA_LEN 4u
const uint32_t crc32_data_arr[CRC_DATA_LEN] = {0x12345678, 0x56781234, 0x55AA55AA, 0xA5A5A5A5};
#define CRC32_RESULT (0x57738169U) /* The result CRC calculator with CRC-32 standard. */
void crc32_test(void)
{
/* setup the crc calculator. */
CRC_Init(BOARD_CRC_PORT, &crc32_enet);
for (uint32_t i = 0u; i < CRC_DATA_LEN; i++)
{
CRC_SetData(BOARD_CRC_PORT, crc32_data_arr[i]);
}
printf("crc32_test ... ");
if (CRC32_RESULT == CRC_GetResult(BOARD_CRC_PORT))
{
printf("succ.");
}
else
{
printf("fail.");
}
printf("\r\n");
}
使用在线的CRC计算器计算,可以得到相同的计算结果。如图x所示。
总结
YTM32的CRC硬件外设模块能够执行CRC计算,同在线CRC计算器的结果能够对应上。
YTM32的手册中描述的CRC16-CCITT
和CRC32-ENET
等对CRC计算典型配置的别称,实际可不必参考。CRC硬件外设的中CRC计算引擎还是执行基本CRC计算,典型配置实际可通过配置相关计算寄存器位的实现,最终可以支持各种各样的CRC典型算式。
参考文献
- YTM32B1MEx.pdf
- 史上解释CRC最清楚的文章
https://zhuanlan.zhihu.com/p/396165368 - CRC校验原理及实现
https://zhuanlan.zhihu.com/p/256487370 - 关于校验和:此CRC实现对具有种子值意味着什么?
https://www.codenong.com/30654238/ arm-mcu-sdk
,一个基于众多arm核MCU的驱动软件仓库,勇哥的SDK设计与开发的试验场
https://gitee.com/suyong_yq/arm-mcu-sdk