5.5 CRC 使用说明
本章说明 N32H49x 标准外设库中 CRC 驱动的文件组成与 16 个公开函数,以及 2 个示例。CRC 外设对 32 位或 16 位数据流计算校验值,并额外提供 8 位独立数据寄存器与 LRC 异或校验;初始值、字节序与清除方式的设置请配合用户手册的“循环冗余校验(CRC)”章节阅读。
5.5.1 相关文件
| 相对路径 | 作用 |
|---|---|
firmware/n32h49x_std_periph_driver/inc/n32h49x_crc.h |
定义 32 位与 16 位校验的寄存器位与掩码,声明 16 个公开函数 |
firmware/n32h49x_std_periph_driver/src/n32h49x_crc.c |
实现上述 16 个函数:32 位数据寄存器与结果读取、独立数据寄存器读写、CRC16 与 LRC 的格式配置、单字节与缓冲区计算及结果读取 |
projects/n32h49x_EVAL/examples/CRC/ |
CRC 示例根目录,含 CalCRC、CRC_DMA 2 个示例 |
projects/n32h49x_EVAL/examples/CRC/CalCRC/ |
该示例的工程目录组成:MDK-ARM/、EWARM/、inc/、src/、readme.txt |
projects/n32h49x_EVAL/bsp/ |
该示例引用的公共板级文件:串口日志函数 |
5.5.2 API 参考
以下 16 个公开函数按 n32h49x_crc.h 的声明顺序分为 4 组;每个条目给出功能摘要与 n32h49x_crc.c 中该函数正上方的注释原文。
32 位 CRC
CRC32_ResetCrc、CRC32_CalcCrc、CRC32_CalcBufCrc、CRC32_GetCrc
独立数据寄存器
CRC32_SetIDat、CRC32_GetIDat
16 位 CRC 与 LRC
__CRC16_SetLittleEndianFmt、__CRC16_SetBigEndianFmt、__CRC16_SetCleanEnable、__CRC16_SetCleanDisable、__CRC16_CalcCrc、__CRC16_SetCrc、__CRC16_GetCrc、__CRC16_SetLRC、__CRC16_GetLRC
16 位缓冲区
CRC16_CalcBufCrc
void CRC32_ResetCrc(void)复位 CRC 数据寄存器(DAT)。
name CRC32_ResetCrc.
fun Resets the CRC Data register (DAT).
param none
return none
uint32_t CRC32_CalcCrc(uint32_t Data)计算给定 32 位数据字的 CRC 值。
name CRC32_CalcCrc.
fun Computes the 32-bit CRC of a given data word(32-bit).
param Data :
- data word(32-bit) to compute its CRC
return 32-bit CRC
uint32_t CRC32_CalcBufCrc(const uint32_t pBuffer[], uint32_t BufferLength)计算给定 32 位数据缓冲区(数据字)的 CRC 值。
name CRC32_CalcBufCrc.
fun Computes the 32-bit CRC of a given buffer of data word(32-bit).
param pBuffer :
- pointer to the buffer containing the data to be computed
param BufferLength :
- length of the buffer to be computed
return 32-bit CRC
uint32_t CRC32_GetCrc(void)返回当前 CRC 值。
name CRC32_GetCrc.
fun Returns the current CRC value.
param none
return 32-bit CRC
void CRC32_SetIDat(uint8_t IDValue)向独立数据(ID)寄存器写入一个 8 位数据。
name CRC32_SetIDat.
fun Stores a 8-bit data in the Independent Data(ID) register.
param IDValue :
- 8-bit value to be stored in the ID register
return none
uint8_t CRC32_GetIDat(void)返回独立数据(ID)寄存器中存储的 8 位数据。
name CRC32_GetIDat.
fun Returns the 8-bit data stored in the Independent Data(ID) register.
param none
return 8-bit value of the ID register
void __CRC16_SetLittleEndianFmt(void)把参与计算的数据设为小端格式。
name __CRC16_SetLittleEndianFmt.
fun Set the calculate data to little endian format.
param none
return none
void __CRC16_SetBigEndianFmt(void)把参与计算的数据设为大端格式。
name __CRC16_SetBigEndianFmt.
fun Set the calculate data to big endian format.
param none
return none
void __CRC16_SetCleanEnable(void)使能 CRC16 值的清除。
name __CRC16_SetCleanEnable.
fun Enable Clean CRC16 value.
param none
return none
void __CRC16_SetCleanDisable(void)关闭 CRC16 值的清除。
name __CRC16_SetCleanDisable.
fun Disable Clean CRC16 value.
param none
return none
uint16_t __CRC16_CalcCrc(uint8_t Data)计算给定 8 位数据字的 16 位 CRC 值。
name __CRC16_CalcCrc.
fun Computes the 16-bit CRC of a given data word(8-bit).
param Data :
- data word(8-bit) to compute its CRC
return 16-bit CRC
void __CRC16_SetCrc(uint8_t Data)向 16 位 CRC 数据寄存器写入一个 8 位数据。
name __CRC16_SetCrc.
fun write a 8-bit data in the 16-bit CRC Data register.
param Data :
- 8-bit data value to be writed in the 16-bit CRC Data register
return none
uint16_t __CRC16_GetCrc(void)返回当前 CRC 值。
name __CRC16_GetCrc.
fun Returns the current CRC value.
param none
return 16-bit CRC
void __CRC16_SetLRC(uint8_t Data)写入 LRC 初始值。
name __CRC16_SetLRC.
fun Write LRC initial value.
param Data :
- 8-bit data
return none
uint8_t __CRC16_GetLRC(void)返回当前 LRC 值。
name __CRC16_GetLRC.
fun Returns the current LRC value.
param none
return 8-bit LRC
uint16_t CRC16_CalcBufCrc(const uint8_t pBuffer[], uint32_t BufferLength)计算给定 8 位数据缓冲区的 16 位 CRC 值。
name CRC16_CalcBufCrc.
fun Computes the 16-bit CRC of a given buffer of data word(8-bit).
param pBuffer :
- pointer to the buffer containing the data to be computed
param BufferLength :
- length of the buffer to be computed
return 16-bit CRC
5.5.3 Demo 示例说明
本节说明 CRC 的 2 个示例:CalCRC 用软件实现交叉验证硬件 CRC32 与 CRC16 的计算结果,CRC_DMA 用 DMA 把数据搬到 CRC 数据寄存器完成校验。
5.5.3.1 CalCRC 硬件与软件校验结果对比
工程目录: projects/n32h49x_EVAL/examples/CRC/CalCRC/
使能 CRC 时钟并初始化日志→用硬件 API 计算 32 位缓冲区校验→用软件函数算同一缓冲区的参考值→比较两者并打印结论→用同样方式验证 16 位缓冲区校验
| 项目 | 源码解读 |
|---|---|
| 功能 | 对 114 个 32 位字的 DataBuffer 用 CRC32_CalcBufCrc 计算校验,并与软件实现 GetCRC32_Software(初值 0xffffffff)比较;再对 8 字节的 Buffer 用 CRC16_CalcBufCrc 计算校验,并与 GetCRC16_Software(初值 0x00)比较;两次比较结果通过串口打印。 |
| 源码组成 | src/main.c:main、ReverseBits、GetCRC16_Software、GetCRC32_Software;inc/main.h:包含 n32h49x_crc.h 等头文件;src/n32h49x_it.c、inc/n32h49x_it.h:中断服务函数(仅异常处理);readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | 时钟:RCC_EnableAHBPeriphClk(RCC_AHB_PERIPHEN_CRC, ENABLE);数据: DataBuffer 为 114 个 32 位字,Buffer 为 8 个字节;软件参考:CRC32 初值 0xffffffff、CRC16 初值 0x00,多项式 CRC32_DFE_POLY、CRC16_DFE_POLY 只用于软件实现。 |
| 程序与数据路径 | main 使能 CRC 时钟并调用 log_init;CRC32_CalcBufCrc 得到硬件 32 位校验,GetCRC32_Software 得到参考值,比较后打印结论;随后 CRC16_CalcBufCrc 与 GetCRC16_Software 对 8 字节缓冲区重复同样的比较。 |
| 主要 API | CRC32_CalcBufCrc、CRC16_CalcBufCrc |
| 验证方法 | 串口打印 “CRC32 calculation is correct!” 与 “CRC16 calculation is correct!” 表示硬件结果与软件参考一致; 若不一致则打印对应的 error 提示。 |
| 使用提示 | 软件对照实现对同一缓冲区逐位计算,用于交叉验证硬件结果; 示例在主循环前完成两次比较,不依赖中断; 缓冲区长度以 BUFFER_SIZE 与字面量给出,移植时两处长度要保持一致。 |
核心代码解读与移植
示例用硬件 API 与软件实现分别计算同一缓冲区的校验值并比较,验证 CRC32 与 CRC16 两条计算通路。
数据路径: RCC_EnableAHBPeriphClk 使能 CRC 时钟 → log_init 初始化日志 → CRC32_CalcBufCrc 计算硬件 32 位校验 → GetCRC32_Software 计算参考值 → 比较并打印 → CRC16_CalcBufCrc 与 GetCRC16_Software 对 8 字节缓冲区重复比较
1. 使能 CRC 时钟。 main 先打开 CRC 外设时钟,随后初始化串口日志。
/* LED3 port is default used for JTAG, must remap first */
RCC_EnableAHBPeriphClk(RCC_AHB_PERIPHEN_CRC, ENABLE);
CRC 计算只用到外设时钟,不占用 GPIO,移植时保留这一行使能即可。
2. 计算并比较 32 位校验。 先调用 CRC32_CalcBufCrc 得到硬件结果,再用 GetCRC32_Software 以初值 0xffffffff 算出参考值,两者相等即打印正确。
/* Compute the 32bit CRC of "DataBuffer" */
CRC32Value = CRC32_CalcBufCrc((uint32_t*)DataBuffer, BUFFER_SIZE);
/* Compute the 32bit CRC of "DataBuffer" by software*/
CRCValue = GetCRC32_Software((uint32_t*)DataBuffer, BUFFER_SIZE, 0xffffffff);
if( CRC32Value == CRCValue )
{
printf(" CRC32 calculation is correct! \r\n");
}
else
{
printf(" CRC32 calculation error! \r\n");
}
软件初值要与硬件数据寄存器的默认值一致,否则两路结果不可直接比较。
3. 计算并比较 16 位校验。 8 字节缓冲区改用 CRC16_CalcBufCrc 计算,并以 GetCRC16_Software 从初值 0x00 算出参考值。
/* Compute the 16bit CRC of an array */
CRC16Value = CRC16_CalcBufCrc((uint8_t*)Buffer, 8);
/* Compute the 16bit CRC of an array by software*/
CRCValue = GetCRC16_Software((uint8_t*)Buffer, 8, 0x00);
if( CRC16Value == CRCValue )
{
printf("CRC16 calculation is correct! \r\n");
}
else
{
printf("CRC16 calculation error! \r\n");
}
16 位与 32 位用不同的多项式与初值,比较前要确认两者对应同一组参数。
4. 软件对照实现。 GetCRC32_Software 按位处理缓冲区,用标准多项式逐位更新校验值,作为硬件结果的参照。
for(i = 0; i < len; i ++)
{
xbit = 0x80000000;
for (bits = 0; bits < 32; bits++)
{
if (crc32 & 0x80000000)
{
crc32 <<= 1;
crc32 ^= CRC32_DFE_POLY;
}
else
{
crc32 <<= 1;
}
if (buf[i] & xbit)
{
crc32 ^= CRC32_DFE_POLY;
}
xbit >>= 1;
}
}
移植到其它校验标准时,多项式与移位方向都要随之调整,不能只改一个常量。
对应源码: src/main.c
5.5.3.2 CRC_DMA 通过 DMA 搬运数据计算校验
工程目录: projects/n32h49x_EVAL/examples/CRC/CRC_DMA/
使能 CRC 与 DMA1 时钟→复位 CRC 数据寄存器→用 DMA 把字缓冲区搬入 CRC32 数据寄存器并读回校验→用 DMA 搬入字节缓冲区完成 CRC16 与 LRC 计算→把 CRC16 与 LRC 结果读回内存并检查
| 项目 | 源码解读 |
|---|---|
| 功能 | 用 DMA 把数据搬到 CRC 数据寄存器完成校验:TestCrc32AndDma 搬入 114 个字得到 CRC32 结果 0x379E9F06;TestCrc16AndDma 搬入 456 个字节得到 CRC16 结果 0x5268 与 LRC 结果 0x00;每步结果都读回并打印。 |
| 源码组成 | src/main.c:main;src/crc_dma.c:TestCrc32AndDma、TestCrc16AndDma 与缓冲区 CRC_original_word_data、CRC_original_byte_data;inc/main.h:包含 n32h49x_crc.h、n32h49x_dma.h 等头文件;src/n32h49x_it.c、inc/n32h49x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | 时钟:使能 CRC 与 DMA1 时钟; DMA:DMA1_CH1, Mem2Mem 使能;字搬运用 WORD 宽度、字节搬运用 BYTE 宽度,写数据寄存器方向为内存到外设、读结果方向为外设到内存; 突发长度由参数 burst_mode_len(2~7)选择 DMA_BURST_LEN_2 到 DMA_BURST_LEN_7。 |
| 程序与数据路径 | main 使能时钟并调用 CRC32_ResetCrc 复位数据寄存器;TestCrc32AndDma(0, 2) 把 CRC_original_word_data 搬入 CRC->CRC32DAT,等待 DMA_FLAG_TC1 后用 CRC32_GetCrc 读取结果,再把 CRC->CRC32D 搬回内存;TestCrc16AndDma(0, 2) 先写初值,把 CRC_original_byte_data 搬入 CRC->CRC16DAT,读回 __CRC16_GetCrc 与 __CRC16_GetLRC,并把 CRC->CRC16D、CRC->LRC 搬回。 |
| 主要 API | CRC32_ResetCrc、CRC32_GetCrc、__CRC16_SetCrc、__CRC16_SetLRC、__CRC16_SetCleanEnable、__CRC16_SetBigEndianFmt、__CRC16_GetCrc、__CRC16_GetLRC |
| 验证方法 | 串口打印 DMA 传输后的 CRC32 结果(应为 0x379E9F06)、CRC16 结果(应为 0x5268)与 LRC 结果(应为 0x00); 任一结果不符时打印对应的 error 提示。 |
| 使用提示 | CRC32 用 114 个字写入 CRC->CRC32DAT,CRC16 用 456 个字节写入 CRC->CRC16DAT;CRC16 计算前先写初值 0x00,并开启清除与大端格式; 结果既可直接读取寄存器,也可用外设到内存的 DMA 搬回内存。 |
核心代码解读与移植
示例用 DMA 把数据搬到 CRC 数据寄存器,分别完成 CRC32、CRC16 与 LRC 的计算,再把结果读回内存校验。
数据路径: RCC_EnableAHBPeriphClk 使能 CRC 与 DMA1 时钟 → CRC32_ResetCrc 复位数据寄存器 → TestCrc32AndDma 用 DMA 搬入 CRC->CRC32DAT 并读回校验 → TestCrc16AndDma 写初值后用 DMA 搬入 CRC->CRC16DAT,读回 CRC16 与 LRC
1. 使能时钟并复位数据寄存器。 main 同时使能 CRC 与 DMA1 时钟,复位 CRC 数据寄存器后依次启动两个测试。
int main(void)
{
/* Enable CRC and DMA clock */
RCC_EnableAHBPeriphClk(RCC_AHB_PERIPHEN_CRC | RCC_AHB_PERIPHEN_DMA1, ENABLE);
log_init();
printf(" CRC DMA test start.\r\n");
CRC32_ResetCrc();
TestCrc32AndDma(0, 2);
TestCrc16AndDma(0, 2);
复位数据寄存器让每次测试都从明确的初值开始;两个测试参数分别为突发模式和突发长度。
2. 配置 DMA 把字缓冲区搬到 CRC32 数据寄存器。 TestCrc32AndDma 把外设地址设为 CRC->CRC32DAT、存储器地址设为 CRC_original_word_data,方向为内存到外设,并使用 Mem2Mem 与 WORD 宽度。
/*Memory->CRC32DAT*/
DMA_InitStructure.PeriphAddr = (uint32_t)&CRC->CRC32DAT;
DMA_InitStructure.MemAddr = (uint32_t)CRC_original_word_data;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
DMA_InitStructure.Mem2Mem = DMA_M2M_ENABLE;
DMA_InitStructure.MemDataSize = DMA_MEM_DATA_WIDTH_WORD;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_WIDTH_WORD;
DMA_InitStructure.MemoryInc = DMA_MEM_INC_ENABLE;
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE;
DMA_InitStructure.CircularMode = DMA_MODE_NORMAL;
DMA_InitStructure.Priority = DMA_PRIORITY_VERY_HIGH;
DMA_InitStructure.BufSize = 114;
DMA_InitStructure.BurstCmd = DMA_BURST_DISABLE;
外设地址不自增,保证每个字都写到同一个数据寄存器;缓冲区长度要与实际数据长度一致。
3. 启动 DMA 并读回 CRC32 结果。 使能通道后等待 DMA_FLAG_TC1,再用 CRC32_GetCrc 读取校验值并打印。
DMA_Init(DMA1_CH1, &DMA_InitStructure);
DMA_EnableChannel(DMA1_CH1, ENABLE);
while(!DMA_GetFlagStatus(DMA_FLAG_TC1, DMA1));
DMA_DeInit(DMA1_CH1);
CRC32_Value = CRC32_GetCrc();
printf("The CRC 32 code after Memory->CRC32DAT DMA transmission is %08x.\r\n", CRC32_GetCrc());
搬运完成后先确认传输完成标志再读结果,避免读到尚未更新的校验值。
4. 写入 CRC16 与 LRC 初值并配置字节搬运。 TestCrc16AndDma 先把 CRC16 与 LRC 初值写为 0x00、开启清除并设为大端,再把 456 个字节搬到 CRC->CRC16DAT。
/*CRC 16 and LRC big endian test wirh 0 initial value*/
__CRC16_SetCrc(0x00);
__CRC16_SetLRC(0x00);
__CRC16_SetCleanEnable();
__CRC16_SetBigEndianFmt();
/*Memory->CRC.CRC16DAT*/
DMA_InitStructure.PeriphAddr = (uint32_t)&CRC->CRC16DAT;
DMA_InitStructure.MemAddr = (uint32_t)CRC_original_byte_data;
DMA_InitStructure.Direction = DMA_DIR_PERIPH_DST;
DMA_InitStructure.Mem2Mem = DMA_M2M_ENABLE;
DMA_InitStructure.MemDataSize = DMA_MEM_DATA_WIDTH_BYTE;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_WIDTH_BYTE;
DMA_InitStructure.MemoryInc = DMA_MEM_INC_ENABLE;
DMA_InitStructure.PeriphInc = DMA_PERIPH_INC_DISABLE;
DMA_InitStructure.CircularMode = DMA_MODE_NORMAL;
DMA_InitStructure.Priority = DMA_PRIORITY_VERY_HIGH;
DMA_InitStructure.BufSize = 456;
字节搬运改用 BYTE 宽度,初值与清除设置在搬运前完成,顺序不能颠倒。
5. 读回 CRC16 与 LRC 结果。 搬运完成后读取 __CRC16_GetCrc 与 __CRC16_GetLRC 并与期望值比较。
CRC16_Value = __CRC16_GetCrc();
LRC_Value = __CRC16_GetLRC();
printf("The CRC 16 code after Memory->CRC.CRC16DAT DMA transmission is %04x.\r\n", __CRC16_GetCrc());
if(CRC16_Value != 0x5268)
{
printf("The CRC 16 code computed through DMA transmission is error,it should be 0x5268.\r\n");
}
printf("The LRC code after Memory->CRC.CRC16DAT DMA transmission is %02x.\r\n", __CRC16_GetLRC());
if(LRC_Value != 0x00)
{
printf("The LRC code computed through DMA transmission is error,it should be 0x00.\r\n");
}
期望值由数据内容与初值共同决定,换用其它数据集时这两个常量要一并更新。
对应源码: src/main.c、src/crc_dma.c
5.5.4 注意与排障
| 场景 | 检查与建议 | 来源 |
|---|---|---|
| CRC32 复位 | 向 RESET 位写 1 会把 CRC32R 复位为 0xFFFFFFFF,该位由硬件自动清除,无需手动清零。 | 用户手册 |
| CRC16 字节序 | ENDIAN 位配置 CRC16 数据的解析顺序,决定输入数据按大端还是小端解析。 |
用户手册 |
| 清除方式 | CLEAR 位自清除,把 CRC 值从 0xFFFF_FFFF 置为默认值 0x0000。 |
用户手册 |
| 独立数据寄存器 | CRC32IDAT 是通用数据寄存器,不受 RESET 位影响。 |
用户手册 |
| 计算初值 | 需要指定初值时,应先向 CRC_CRC16D、CRC_CRC32D 写入初始值,否则本次计算会沿用上一次的结果。 |
用户手册 |
| LRC 校验 | LRC 为异或校验,软件应在下一次计算前清除 LRC 初值。 | 用户手册 |
| 数据宽度 | CRC16DAT 支持 8 位、16 位与 32 位写操作。 |
用户手册 |