关于STM32F4xx的硬件CRC32校验

关于STM32F4xx的硬件CRC32校验

一、概述

前段时间由于项目所需,要对MCU上某些数据进行CRC32校验,MCU选用的是STM32F4系列,以前看到过STM32有硬件CRC32校验功能,决定采用硬件CRC32校验,于是成功入坑。STM32硬件CRC32校验的结果跟预期的值并不一致,参考了CSDN大神的方法,根据项目加以改进,校验成功。STM32硬件CRC使用相关资料在网上是相对较少的,所以这里做个总结。

环境:MDK5或IAR8
参考:https://blog.csdn.net/lan120576664/article/details/47156067
申明:以下部分内容来自上述网站

二、STM32硬件CRC32与PC端计算结果的差异

STM32硬件CRC32计算结果不一致,也是使用时最大的坑,其原因为:

  1. STM32的硬件CRC32的每个字节的位序相反,STM32是按32位,高位在先,而主流实例每字节里面是从低位起的。
  2. 最终计算结果主流实例与0xFFFFFFFF进行了异或,而STM32并没有。

三、具体实现

首先对硬件CRC32初始化,主要是开启时钟,这里用的是标准库

void CRC32_Init(void)
{
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_CRC, ENABLE);
	CRC->CR = CRC_CR_RESET;		//复位CRC寄存器
}

根据前面叙述可知,要使其计算结果与主流计算结果一致,需要做以下几步:

  1. 改变字节序,可以理解为将32位的小端数据转为大端数据(或将32位大端数据转为小端数据);
  2. 将要计算的数据按位颠倒,如1110变为0111,不过这里是32位数据;
  3. 硬件CRC32计算结果与0xFFFFFFFF异或。

所以先实现以下函数

/**
 *	@brief	将一个32位数据按位颠倒函数,如0011->1100
 *	@param	data:要颠倒的数据
 *	@retval	颠倒后的数据
 */
u32 Revbit(u32 data) //
{
	u32 uRevData = 0,i = 0;
	uRevData |= ((data >> i) & 0x01);
	for(i = 1; i < 32; i++)
	{
		uRevData <<= 1;
		uRevData |= ((data >> i) & 0x01);
	}
	return uRevData;
}

/**
 *	@brief	32位数据改变字节序
 *	@param	data:源数据
 *	@retval	改变后的数据
 */
u32 Revswap (u32 data) // 自己写的32字节序转换函数
{
	u8 *p = (u8 *)&data;
	u8 *q = p + 3;
	
	while(p < q)	
	{
		*p = *p^*q;
		*q = *p^*q;
		*p = *p^*q;
		p++;
		q--;
	}
	return data;
}

有了上面两个函数就可以得到与PC端保持一致的计算结果了,代码如下(这里只计算了一个数据)

u32 CRC32_Cal(u32 data)
{
	CRC->DR = Revbit(Revswap(data));
	return (Revbit(CRC->DR)^0xFFFFFFFF);
}

执行代码测试,确实能得到与PC结果一致的CRC32校验值

我开始想,计算一个数据就要几十次的循环,感觉跟软件计算CRC差不了多少,那用硬件CRC的意义何在,还有必要吗,更何况校验一个较大的buffer呢?好在本人有些ARM汇编基础,当回头再看参考博客的描述,Revbit函数与Revswap貌似用了汇编指令,激动中的我马上查了下ARM相关的汇编指令,果然如此查到了两条汇编指令,rbit和rev,接下来进行一下改进:

对于MDK环境:

__asm u32 Revbit (u32 data)	
{
	RBIT	R0, R0
	BX		LR
}

__asm u32 Revswap(u32 data)
{
	REV	R0, R0
	BX		LR
}

对于IAR环境

u32 Revbit (u32 data)	
{
	asm("RBIT	R0, R0 \n"
		"BX		LR");
}

u32 Revswap (u32 data)
{
	asm("REV	R0, R0 \n"
		"BX		LR");
}

对于Linux gcc环境

u32 Revbit (u32 data)	
{
	__ASM volatile("RBIT	R0, R0 \n"
				   "BX		LR");
}

u32 Revswap (u32 data)
{
	__ASM volatile("REV	R0, R0 \n"
				   "BX		LR");
}

最终两个复杂的函数被两条汇编指令干掉了,这里解释下这两个汇编指令:

  1. RBIT:把一个32位的数据按位倒置,效果如下:

    原数据 01000001 01000010 01000011 01000100,0x41424344
    执行后 00100010 11000010 00100100 10000010,0x42c22482

  2. REV:把一个32位数据的字节序改变(若芯片是小端则转为大端),效果如下

    原数据 0x12345678
    执行后 0x78563412

在MDK中编码时,输入RBIT时提示了一个__rbit()函数,我才意识到这个汇编指令在CM4相关头文件实现了,对应的在IAR中有__RBIT()函数,同样REV指令也一样,MDK中是__rev(),IAR中是__REV()函数,哈哈,谁让咱是小白呢。

接下来校验buffer就只需要传入buffer地址和长度就行

u32 CRC32_Cal_Buffer(u32 *buffer, u32 length)
{
	u32 i = 0;
	CRC->CR = CRC_CR_RESET;//复位CRC寄存器
	for(i = 0; i < length - 1; i++)
	{
		CRC32_Cal(buffer[i]);
	}
	return CRC32_Cal(buffer[i]);	//返回最后一次计算的值
}

到此,在我的项目中硬件CRC32校验成功进行。若有什么不正确的地方,欢迎各位大神指正。

四、注意

  1. 由于在我的项目中只用到32位CRC校验,且数据都是32位的,所以没有做不满32位数据的CRC32校验,以减少不必要的消耗,若要做8位数据(或说不满32位的数据)的CRC32校验请参考:https://blog.csdn.net/lan120576664/article/details/47156067
  2. 另外,根据自己项目需求,并没有支持大小端的转换,若是大端数据校验,另当别论。
上一篇:从0到1学习FreeRTOS:FreeRTOS 内核应用开发:(一)移植FreeRTOS到STM32第二部分


下一篇:stm32F4xx中文参考手册