关于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计算结果不一致,也是使用时最大的坑,其原因为:
- STM32的硬件CRC32的每个字节的位序相反,STM32是按32位,高位在先,而主流实例每字节里面是从低位起的。
- 最终计算结果主流实例与0xFFFFFFFF进行了异或,而STM32并没有。
三、具体实现
首先对硬件CRC32初始化,主要是开启时钟,这里用的是标准库
void CRC32_Init(void)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_CRC, ENABLE);
CRC->CR = CRC_CR_RESET; //复位CRC寄存器
}
根据前面叙述可知,要使其计算结果与主流计算结果一致,需要做以下几步:
- 改变字节序,可以理解为将32位的小端数据转为大端数据(或将32位大端数据转为小端数据);
- 将要计算的数据按位颠倒,如1110变为0111,不过这里是32位数据;
- 硬件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");
}
最终两个复杂的函数被两条汇编指令干掉了,这里解释下这两个汇编指令:
-
RBIT:把一个32位的数据按位倒置,效果如下:
原数据 01000001 01000010 01000011 01000100,0x41424344
执行后 00100010 11000010 00100100 10000010,0x42c22482 -
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校验成功进行。若有什么不正确的地方,欢迎各位大神指正。
四、注意
- 由于在我的项目中只用到32位CRC校验,且数据都是32位的,所以没有做不满32位数据的CRC32校验,以减少不必要的消耗,若要做8位数据(或说不满32位的数据)的CRC32校验请参考:https://blog.csdn.net/lan120576664/article/details/47156067
- 另外,根据自己项目需求,并没有支持大小端的转换,若是大端数据校验,另当别论。