STM32F10x 学习笔记3之CRC计算单元

本文深入探讨STM32F系列单片机内置CRC32计算单元的操作流程及与传统CRC32算法的差异,包括复位、数据输入、结果获取等步骤,并通过实例代码展示如何实现CRC32计算。同时,解释了STM32F计算结果与传统算法不同的原因,涉及字节序调整以确保结果一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STM32F 系列的单片机内部带了CRC32 计算单元。这个内置CRC模块的方法使用非常简单。其操作如下图所示。


图 1 CRC计算单元框图

归纳起来有如下几步操作:

1. 开启CRC单元的时钟。RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE)
2. 复位CRC模块(设置CRC_CR=0x01),这个操作把CRC余数初始化为0xFFFFFFFF
3. 把要计算的数据按逐个地写入CRC_DR寄存器
4. 写完所有的数据字后,从CRC_DR寄存器读出计算的结果

STM32F10x StdPeriph Driver 中提供了几个函数。

CRC_ResetDR(void) 
用来复位CRC模块。

uint32_t CRC_CalcCRC(uint32_t Data)
将一个数据写入CRC_DR寄存器,返回值为计算结果。

uint32_t CRC_CalcBlockCRC(uint32_t pBuffer[], uint32_t BufferLength)
计算一个数组的CRC 值。

uint32_t CRC_GetCRC(void)
读取CRC_DR寄存器的结果。

另外,CRC 模块中还有个独立数据寄存器(CRC_IDR)。这是个单字节的寄存器,用于临时存放1字节的数据,不受复位操作影响。相应的操作函数有两个。

void CRC_SetIDRegister(uint8_t IDValue)
uint8_t CRC_GetIDRegister(void)
分别是写CRC_IDR和读 CRC_IDR 寄存器。

虽然STM32F 上的CRC 单元用起来很简单,但是似乎它计算出来的结果与传统的CRC32算法得到的结果有些不同。
下面是个简单的例子。
[cpp]  view plain copy
  1. #include "stm32f10x.h"  
  2.   
  3. int main(void)  
  4. {  
  5.     uint32_t j;  
  6.     uint32_t str[11] = {'1''2''3''4''5''6''7''8''9'' '};  
  7.       
  8.     SystemInit();  
  9.     RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE);  
  10.     CRC_ResetDR();  
  11.   
  12.     str[9] = CRC_CalcBlockCRC(str, 1);  
  13.     CRC_ResetDR();  
  14.     CRC_CalcCRC(0xA5A5A5A5);  
  15.     j = CRC_GetCRC();  
  16.     CRC_CalcCRC(j);  
  17.     for(;;)  
  18.     {  
  19.     }     
  20. }  
。还指出了这个CRC 单元计算的结果与常见的CRC32 算法得到的结果不相同。但是为什么不相同,是什么原因造成的却没有写出来。这里再补一篇,把这些都说清楚。

下面先给个crc32的计算函数,这个函数计算的结果与STM32F 单片机上硬件单元的计算结果相同。

[cpp]  view plain copy
  1. uint32_t crc32(uint32_t *addr, int num, uint32_t crc)  
  2. {  
  3.     int i;  
  4.     for (; num > 0; num--)                
  5.     {  
  6.         crc = crc ^ (*addr++);       
  7.         for (i = 0; i < 32; i++)               
  8.         {  
  9.             if (crc & 0x80000000)              
  10.                 crc = (crc << 1) ^ POLY;     
  11.             else                            
  12.                 crc <<= 1;                   
  13.         }                               
  14.         crc &= 0xFFFFFFFF;              
  15.     }                                 
  16.     return(crc);                     
  17. }  

在我写的文章《写给嵌入式程序员的循环冗余校验(CRC)算法入门引导》(http://blog.youkuaiyun.com/liyuanbhu/article/details/7882789) 中给了个利用查表法计算crc 的程序。那个程序稍微修改一点就能计算CRC32。下面给出改动后的程序。

[cpp]  view plain copy
  1. //crc32.h  
  2.   
  3. #ifndef CRC32_H_INCLUDED  
  4. #define CRC32_H_INCLUDED  
  5.   
  6. #ifdef __cplusplus  
  7. #if __cplusplus  
  8. extern "C"{  
  9.     #endif  
  10.     #endif /* __cplusplus */  
  11.   
  12.   
  13. #include<stdint.h>  
  14.   
  15. /* 
  16. * The CRC parameters. Currently configured for CRC32. 
  17. * CRC32=X32+X26+X23+X22+X16+X12+X11+X10+X8+X7+X5+X4+X2+X1+X0 
  18. */  
  19.   
  20. #define POLYNOMIAL          0x04C11DB7  
  21. #define INITIAL_REMAINDER   0xFFFFFFFF  
  22. #define FINAL_XOR_VALUE     0x00000000  
  23.   
  24. /* 
  25. * The width of the CRC calculation and result. 
  26. * Modify the typedef for an 8 or 32-bit CRC standard. 
  27. */  
  28. typedef uint32_t width_t;  
  29. #define WIDTH (8 * sizeof(width_t))  
  30. #define TOPBIT (1 << (WIDTH - 1))  
  31.   
  32. /** 
  33.  * Initialize the CRC lookup table. 
  34.  * This table is used by crcCompute() to make CRC computation faster. 
  35.  */  
  36. void crcInit(void);  
  37.   
  38. /** 
  39.  * Compute the CRC checksum of a binary message block. 
  40.  * @para message, 用来计算的数据 
  41.  * @para nBytes, 数据的长度 
  42.  * @note This function expects that crcInit() has been called 
  43.  *       first to initialize the CRC lookup table. 
  44.  */  
  45. width_t crcCompute(unsigned char * message, unsigned int nBytes, width_t remainder);  
  46.   
  47.   
  48. #ifdef __cplusplus  
  49.     #if __cplusplus  
  50. }  
  51. #endif  
  52. #endif /* __cplusplus */  
  53.   
  54. #endif // CRC32_H_INCLUDED  

对应的C程序如下:

[cpp]  view plain copy
  1. #include "crc32.h"  
  2. /* 
  3. * An array containing the pre-computed intermediate result for each 
  4. * possible byte of input. This is used to speed up the computation. 
  5. */  
  6. static width_t crcTable[256];  
  7.   
  8. /** 
  9.  * Initialize the CRC lookup table. 
  10.  * This table is used by crcCompute() to make CRC computation faster. 
  11.  */  
  12. void crcInit(void)  
  13. {  
  14.     width_t remainder;  
  15.     width_t dividend;  
  16.     int bit;  
  17.     /* Perform binary long division, a bit at a time. */  
  18.     for(dividend = 0; dividend < 256; dividend++)  
  19.     {  
  20.         /* Initialize the remainder.  */  
  21.         remainder = dividend << (WIDTH - 8);  
  22.         /* Shift and XOR with the polynomial.   */  
  23.         for(bit = 0; bit < 8; bit++)  
  24.         {  
  25.             /* Try to divide the current data bit.  */  
  26.             if(remainder & TOPBIT)  
  27.             {  
  28.                 remainder = (remainder << 1) ^ POLYNOMIAL;  
  29.             }  
  30.             else  
  31.             {  
  32.                 remainder = remainder << 1;  
  33.             }  
  34.         }  
  35.         /* Save the result in the table. */  
  36.         crcTable[dividend] = remainder;  
  37.     }  
  38. /* crcInit() */  
  39.   
  40. /** 
  41.  * Compute the CRC checksum of a binary message block. 
  42.  * @para message, 用来计算的数据 
  43.  * @para nBytes, 数据的长度 
  44.  * @note This function expects that crcInit() has been called 
  45.  *       first to initialize the CRC lookup table. 
  46.  */  
  47. width_t crcCompute(unsigned char * message, unsigned int nBytes, width_t remainder)  
  48. {  
  49.     unsigned int offset;  
  50.     unsigned char byte;  
  51.     //width_t remainder = INITIAL_REMAINDER;  
  52.     /* Divide the message by the polynomial, a byte at a time. */  
  53.     for( offset = 0; offset < nBytes; offset++)  
  54.     {  
  55.         byte = (remainder >> (WIDTH - 8)) ^ message[offset];  
  56.         remainder = crcTable[byte] ^ (remainder << 8);  
  57.     }  
  58.     /* The final remainder is the CRC result. */  
  59.     return (remainder ^ FINAL_XOR_VALUE);  
  60. /* crcCompute() */  

不过用这个程序直接计算得到的CRC 值与STM32 给出的并不相同。之所以会这样是因为字节序的原因。可以举个例子来说明这个问题。比如我们有一片内存区域要计算CRC值。这片内存区域的起始地址是 0x1000,共有8个字节。用 crcCompute() 函数计算时是按照地址顺序依次传入各个字节。也就是先计算0x1000 处的字节,再计算0x0001 处的字节,以此类推最后计算0x1007 地址处的字节。而 STM32 的硬件CRC单元是以32位的字为单位计算的。我们知道CRC 实际上是个多项式的除法运算,而除法运算是从高位算起的。也就是相当于它是按照 0x10030x10020x10010x1000 这个顺序计算第一个字,然后按照0x10070x10060x1005x1004 的顺序计算第二个字。因此。我们要是预先将字节序调换一下得到结果就没有问题了。这就有了下面的改造。其中 remainder 传入 0xffffffff。因为STM32 中的CRC余数初始值为0xffffffff

[cpp]  view plain copy
  1. uint32_t stm32crc32(uint32_t * message, unsigned int nWords, uint32_t remainder)  
  2. {  
  3.     unsigned int offset;  
  4.     unsigned char byte;  
  5.     unsigned char *p = (unsigned char *)message;  
  6.     //width_t remainder = INITIAL_REMAINDER;  
  7.     /* Divide the message by the polynomial, a byte at a time. */  
  8.     for( offset = 0; offset < nWords; offset++)  
  9.     {  
  10.         byte = (remainder >> (WIDTH - 8)) ^ p[3];  
  11.         remainder = crcTable[byte] ^ (remainder << 8);  
  12.   
  13.         byte = (remainder >> (WIDTH - 8)) ^ p[2];  
  14.         remainder = crcTable[byte] ^ (remainder << 8);  
  15.   
  16.         byte = (remainder >> (WIDTH - 8)) ^ p[1];  
  17.         remainder = crcTable[byte] ^ (remainder << 8);  
  18.   
  19.         byte = (remainder >> (WIDTH - 8)) ^ p[0];  
  20.         remainder = crcTable[byte] ^ (remainder << 8);  
  21.   
  22.         p += 4;  
  23.     }  
  24.     /* The final remainder is the CRC result. */  
  25.     return (remainder);  
  26. /* crcCompute() */  

大家可以验证这个函数的计算结果与STM32上的结果完全一样。

写到这里本该就结束了,不过我要多说一句,之所以要这么麻烦的调换字节序,都是小端(little endian)惹的祸。要是都采用大端格式就没这些麻烦的转换了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值