5.5 CRC 使用说明
本章说明 N32G41x 标准外设库中 CRC 驱动的文件组成与 17 个公开函数,内容包括 CRC32 与 CRC16 校验值的计算、CRC32 独立数据寄存器与 CRC16 清除和大小端配置,以及 LRC 校验值的读写;示例 CalCRC 用软件算法复算硬件结果,CRC_DMA 用 DMA 搬运数据完成校验。生成多项式与 CRC32、CRC16 的使用流程请配合用户手册的“CRC 计算单元”章节阅读,芯片已知问题以勘误手册为准。
5.5.1 相关文件
| 相对路径 | 作用 |
|---|---|
firmware/n32g41x_std_periph_driver/inc/n32g41x_crc.h |
定义 CRC16 清除、清除屏蔽、结果清零值与大端小端四个宏,并按 CRC32 计算、CRC16 底层配置、CRC16 计算、CRC16 LRC 四个区段声明 17 个公开函数 |
firmware/n32g41x_std_periph_driver/src/n32g41x_crc.c |
实现上述 17 个函数:读写 CRC32 与 CRC16 的待校验数据、结果与独立数据寄存器,配置 CRC16 控制位,并读写 LRC 寄存器 |
projects/n32g41x_EVAL/examples/CRC/ |
CRC 示例根目录,含 CalCRC、CRC_DMA 2 个示例 |
projects/n32g41x_EVAL/examples/CRC/CalCRC/ |
该示例的工程目录组成:MDK-ARM/、EWARM/、inc/、src/、readme.txt |
projects/n32g41x_EVAL/bsp/ |
两个示例共用的板级支持文件:串口日志初始化与 printf 重定向(log.h、log.c) |
5.5.2 API 参考
以下 17 个公开函数按 n32g41x_crc.h 的声明顺序分为 4 组;每个条目给出功能摘要与 n32g41x_crc.c 中该函数正上方的注释原文。
CRC32 计算
CRC32_ResetCrc、CRC32_CalcCrc、CRC32_CalcBufCrc、CRC32_GetCrc、CRC32_SetIDat、CRC32_GetIDat
CRC16 底层配置
__CRC16_SetLittleEndianFmt、__CRC16_SetBigEndianFmt、__CRC16_SetCleanEnable、__CRC16_SetCleanDisable
CRC16 计算
__CRC16_CalcCrc、__CRC16_SetCrc、__CRC16_GetCrc、CRC16_CalcBufCrc、CRC16_CalcCRC
CRC16 LRC
__CRC16_SetLRC、__CRC16_GetLRC
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 位数据字的 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 位数据缓冲区的 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
使用提示: 实现逐字写入 CRC->CRC32DAT 并在循环结束后返回 CRC->CRC32D,函数内部不改写 CRC32CTRL,初值沿用复位默认值 0xFFFF_FFFF 或上一次运算的结果。
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)把 8 位数据存入独立数据(ID)寄存器。
name CRC32_SetIDat.
fun Stores an 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)把 8 位数据写入 CRC16 数据寄存器(CRC16DAT)。
name __CRC16_SetCrc.
fun Writes an 8-bit data to the CRC16 data register (CRC16DAT).
param Data :
- 8-bit data value to be written to the CRC16 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
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
使用提示: 实现先把 CRC->CRC16D 写为 CRC_CRC16D_CLEAR_VALUE(0x0000),再逐字节写入 CRC->CRC16DAT,最后返回 CRC->CRC16D。
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_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
5.5.3 Demo 示例说明
本节说明 CRC 的 2 个示例:CalCRC 用硬件与软件分别计算同一组数据的 CRC32、CRC16 并比较结果,CRC_DMA 用 DMA 通道搬运数据与读回结果来完成 CRC32、CRC16 与 LRC 校验。
5.5.3.1 CalCRC 硬件与软件 CRC 对比
工程目录: projects/n32g41x_EVAL/examples/CRC/CalCRC/
开启 CRC 时钟并初始化串口日志→硬件对 32 位数据缓冲区计算 CRC32→软件按同一多项式复算并比较→硬件对 8 位数据缓冲区计算 CRC16→软件复算并比较→经串口打印两项校验结论
| 项目 | 源码解读 |
|---|---|
| 功能 | 用硬件 CRC 与软件参考算法分别计算同一组数据的 32 位与 16 位校验值,逐一比较后经串口打印比较结论。 |
| 源码组成 | src/main.c:main、软件参考算法 GetCRC16_Software 与 GetCRC32_Software、数据缓冲区;inc/main.h:包含驱动与 BSP 头文件;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | BUFFER_SIZE 为 114,DataBuffer 为 114 个 32 位字,首字为 0x00001021;Buffer 为 8 字节 0x7e、0x11、0x36、0x4e、0x55、0x0a、0xf1、0x1a。软件 CRC16 多项式 CRC16_DFE_POLY 为 0x8005,软件 CRC32 多项式 CRC32_DFE_POLY 为 0x04C11DB7;软件 CRC32 初值传 0xffffffff,软件 CRC16 初值传 0x00。 系统时钟为 HSI+PLL,N32G412 为 80 MHz、N32G415 为 96 MHz; 打印串口为 PA9、115200 bps。 |
| 程序与数据路径 | main 先开 CRC 时钟并调用 log_init 初始化串口;CRC32_CalcBufCrc 对 DataBuffer 的 114 个字求硬件 CRC32,GetCRC32_Software 复算同一缓冲区,相等时打印 CRC32 calculation is correct!;随后 CRC16_CalcBufCrc 对 Buffer 的 8 字节求硬件 CRC16,GetCRC16_Software 复算,相等时打印 CRC16 calculation is correct!;最后进入空的 while (1)。 |
| 主要 API | CRC32_CalcBufCrc、CRC16_CalcBufCrc |
| 验证方法 | 复位运行后观察串口输出:应出现 CRC32 calculation is correct! 与 CRC16 calculation is correct!;出现以 error 结尾的提示说明硬件与软件结果不一致。 |
| 使用提示 | 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1,基于 N32G41x 评估板; 串口打印为 PA9、115200 bps。 |
核心代码解读与移植
示例让同一组数据分别跑硬件 CRC 与软件参考算法,通过比较两者结果验证硬件计算。
数据路径: DataBuffer、Buffer → CRC32_CalcBufCrc、CRC16_CalcBufCrc 写入 CRC->CRC32DAT、CRC->CRC16DAT 并读回结果 → GetCRC32_Software、GetCRC16_Software 逐位复算 → 比较结果后经 printf 输出
1. 定义校验数据与多项式。 数据规模与两条生成多项式由文件顶部的宏给出,硬件与软件两路使用同一组数据。
#define BUFFER_SIZE 114
#define CRC16_DFE_POLY 0x8005
#define CRC32_DFE_POLY 0x04C11DB7
移植到自己的数据时同步改 BUFFER_SIZE 与 DataBuffer、Buffer 的元素个数,更换生成多项式时同步改这两个宏。
2. 打开 CRC 时钟并先算硬件 CRC32。 main 打开 CRC 时钟、初始化串口后先算硬件 CRC32,再算软件 CRC32,两者都作用在 DataBuffer 上。
/* Enable CRC clock */
RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_CRC, ENABLE);
log_init();
/* Compute the 32-bit CRC of DataBuffer using hardware */
CRC32Value = CRC32_CalcBufCrc((uint32_t*)DataBuffer, BUFFER_SIZE);
/* Compute the 32-bit CRC of DataBuffer using software */
CRCValue = GetCRC32_Software((uint32_t*)DataBuffer, BUFFER_SIZE, 0xffffffff);
CRC32_CalcBufCrc 只逐字写入 CRC->CRC32DAT 再读回 CRC->CRC32D,函数内部不复位 CRC32,硬件初值取 CRC_CRC32DAT 的复位值 0xFFFF_FFFF;软件侧传入的初值 0xffffffff 必须与之一致,结果才有可比性。
3. 软件参考算法逐位复算。 GetCRC32_Software 对每个字按位左移并异或生成多项式,得到与硬件一致的 CRC32。
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;
}
}
这段只处理 32 位字;换成字节或半字数据时,字长、位宽与多项式都要与硬件侧同步调整。
4. 比较结果并打印结论。 把易失变量读入局部变量后比较,相等则打印正确、否则打印错误;16 位一路用同样的方式再比一次。
/* Read volatile values into local variables to avoid undefined access order */
crc32_hw = CRC32Value;
crc32_sw = CRCValue;
if (crc32_hw == crc32_sw)
{
printf(" CRC32 calculation is correct! \r\n");
}
else
{
printf(" CRC32 calculation error! \r\n");
}
/* Compute the 16-bit CRC of Buffer using hardware */
CRC16Value = CRC16_CalcBufCrc((uint8_t*)Buffer, 8);
/* Compute the 16-bit CRC of Buffer using software */
CRCValue = GetCRC16_Software((uint8_t*)Buffer, 8, 0x00);
CRC16_CalcBufCrc 会先把 CRC->CRC16D 写为 0x0000 再累加,软件侧初值 0x00 与之对应;移植时这一段的数据长度 8 要与 Buffer 的实际元素个数一致。
对应源码: src/main.c、inc/main.h
5.5.3.2 CRC_DMA 通过 DMA 搬运完成校验
工程目录: projects/n32g41x_EVAL/examples/CRC/CRC_DMA/
开启 CRC 与 DMA 时钟并初始化串口→复位 CRC32 计算单元→用 DMA 把 32 位数据送入 CRC32 计算→用 DMA 把 8 位数据送入 CRC16 计算→经 DMA 读回校验结果并与期望值比较→串口打印 CRC32、CRC16 与 LRC 结果
| 项目 | 源码解读 |
|---|---|
| 功能 | 用 DMA 通道把内存中的字、字节数据搬入 CRC 待校验数据寄存器,验证 CRC32、CRC16 与 LRC 的硬件计算结果,并把结果寄存器经 DMA 读回内存。 |
| 源码组成 | src/main.c:main 与 assert_failed;src/crc_dma.c:TestCrc32AndDma、TestCrc16AndDma 与两个数据缓冲区;inc/main.h:包含驱动与 BSP 头文件;src/n32g41x_it.c、inc/n32g41x_it.h:中断服务函数;readme.txt:功能、环境与使用说明;MDK-ARM/、EWARM/:Keil 与 IAR 工程。 |
| 关键配置 | CRC_original_word_data 为 114 个 32 位字,首字为 0x00001021;CRC_original_byte_data 为 456 字节,与字数据逐字节对应。DMA 使用 DMA_CH1,Mem2Mem 为 DMA_M2M_ENABLE、Priority 为 DMA_PRIORITY_VERY_HIGH、CircularMode 为 DMA_MODE_NORMAL;CRC32 搬运 MemDataSize 为 DMA_MemoryDataSize_Word、BufSize 为 114,CRC16 搬运 MemDataSize 为 DMA_MemoryDataSize_Byte、BufSize 为 456。CRC16 运算前选大端格式并带结果清除。 期望值为 CRC32 的 0x379E9F06、CRC16 的 0x5268、LRC 的 0x00。 |
| 程序与数据路径 | main 开 CRC 与 DMA 时钟、log_init 后打印 CRC DMA test start.,调用 CRC32_ResetCrc 复位 CRC32,再依次调用 TestCrc32AndDma、TestCrc16AndDma;前者两次配置 DMA_CH1,完成内存到 CRC32 待校验数据寄存器、结果寄存器到内存的搬运,读回后与期望值比较;后者先设 CRC16 初值与 LRC 初值、使能清除并选大端,再用 DMA 搬运 456 字节,读回 CRC16D 与 LRC 后比较。 |
| 主要 API | CRC32_ResetCrc、CRC32_GetCrc、__CRC16_SetCrc、__CRC16_SetLRC、__CRC16_SetCleanEnable、__CRC16_SetBigEndianFmt、__CRC16_GetCrc、__CRC16_GetLRC |
| 验证方法 | 复位运行后观察串口:应打印 CRC32 为 0x379e9f06、CRC16 为 0x5268、LRC 为 0x00,且不出现以 error 结尾的提示行。 |
| 使用提示 | 开发环境为 KEIL MDK-ARM V5.34 或 IAR EWARM 8.50.1,基于 N32G41x 评估板; 串口打印为 PA9、115200 bps; 示例依赖 DMA 驱动。 |
核心代码解读与移植
示例用 DMA 通道代替软件写寄存器,在数据缓冲区和结果寄存器之间来回搬运,再由读回的值判断校验结果。
数据路径: CRC_original_word_data、CRC_original_byte_data → DMA_Init、DMA_EnableChannel 搬入 CRC->CRC32DAT、CRC->CRC16DAT → CRC32_GetCrc、__CRC16_GetCrc、__CRC16_GetLRC 或 DMA 读回结果 → printf 打印并与期望值比较
1. 打开 CRC 与 DMA 时钟并复位 CRC32。 main 同时打开两个外设的 AHB 时钟,初始化串口后先复位 CRC32,再进入两个测试函数。
/* Enable CRC and DMA clock */
RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_CRC | RCC_AHB_PERIPH_DMA, ENABLE);
log_init();
printf(" CRC DMA test start.\r\n");
CRC32_ResetCrc();
TestCrc32AndDma();
TestCrc16AndDma();
两个测试函数都依赖 DMA,移植到其它工程时要同时把 CRC 与 DMA 驱动的源文件加入编译。
2. 用 DMA 把字数据搬入 CRC32。 DMA 通道把 CRC_original_word_data 的 114 个字逐个搬到 CRC 的 32 位待校验数据寄存器,搬运结束后由传输完成标志退出等待。
/* 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_MemoryDataSize_Word;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_WORD;
DMA_InitStructure.DMA_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_Init(DMA_CH1, &DMA_InitStructure);
DMA_EnableChannel(DMA_CH1, ENABLE);
while (!DMA_GetFlagStatus(DMA_FLAG_TC1, DMA));
DMA_DeInit(DMA_CH1);
外设地址固定为结果寄存器不动、存储地址自增,每个字依次进入 CRC;更换缓冲区时同步改 MemAddr 与 BufSize,字节数据要改用字节宽度。
3. 读回 CRC32 结果并与期望值比较。 搬运结束后用 CRC32_GetCrc 读结果寄存器,再与固定期望值比较,不符则打印错误。
CRC32_Value = CRC32_GetCrc();
printf("The CRC 32 code after Memory->CRC32DAT DMA transmission is %08x.\r\n", CRC32_GetCrc());
if (CRC32_Value != 0x379E9F06)
printf("The CRC 32 code computed through DMA transmission is error, it should be 0x379E9F06.\r\n");
期望值 0x379E9F06 对应这组固定的 114 字数据;更换数据或多项式后这个比较值必须同步重算。
4. 设定 CRC16 初值、清除与大端格式后再搬运。 进入 CRC16 搬运前先设初值、清除结果并选大端,再按字节宽度把 456 字节搬入。
__CRC16_SetCrc(0x00);
__CRC16_SetLRC(0x00);
__CRC16_SetCleanEnable();
__CRC16_SetBigEndianFmt();
/* Memory -> 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_MemoryDataSize_Byte;
DMA_InitStructure.PeriphDataSize = DMA_PERIPH_DATA_SIZE_BYTE;
DMA_InitStructure.DMA_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;
DMA_Init(DMA_CH1, &DMA_InitStructure);
DMA_EnableChannel(DMA_CH1, ENABLE);
while (!DMA_GetFlagStatus(DMA_FLAG_TC1, DMA));
DMA_DeInit(DMA_CH1);
__CRC16_SetBigEndianFmt 把 ENDIAN 位清 0,使待校验数据从高位到低位运算;__CRC16_SetCleanEnable 置清除位,结果寄存器先取默认值 0x0000。数据端序或初值不一致会直接改变 CRC16 结果。
5. 读回 CRC16 与 LRC 结果并比较。 搬运完成后分别读结果寄存器与 LRC 寄存器,与期望值比较。
CRC16_Value = __CRC16_GetCrc();
LRC_Value = __CRC16_GetLRC();
printf("The CRC 16 code after Memory->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->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");
CRC 与 LRC 计算同时进行,读 CRC->LRC 得到的是同一组数据的 LRC 校验值;本例数据对应的期望值为 CRC16 的 0x5268、LRC 的 0x00。
对应源码: src/main.c、src/crc_dma.c、inc/main.h
5.5.4 注意与排障
| 场景 | 检查与建议 | 来源 |
|---|---|---|
| CRC32 结果初值与复位 | CRC_CRC32CTRL.RESET 只能写 1,硬件自动清 0,置位后把 CRC_CRC32D 置为 0xFFFF_FFFF;不置位时初值沿用上一次运算的结果,新一轮校验前应复位或自行写入初值。 |
用户手册 |
| CRC16 结果的清除与数据端序 | CRC_CRC16CTRL.CLEAR 为只写清、软件置 1 仅维持一个周期(软件读恒为 0),置 1 后结果取默认值 0x0000;CRC_CRC16CTRL.ENDIAN 只作用于待校验数据,0 为高位到低位、1 为低位到高位。 |
用户手册 |
| LRC 的读取与清零 | CRC 与 LRC 计算同时进行,读 CRC_LRC 后下次运算前应软件清零,否则结果会延续上次的值。 |
用户手册 |
| 保留位与访问宽度 | CRC_CRC32CTRL 位 31:1、CRC_CRC32IDAT 位 31:8、CRC_CRC16CTRL 位 31:3 与位 0、CRC_CRC16DAT 位 31:8、CRC_CRC16D 位 31:16、CRC_LRC 位 31:8 为保留位,必须保持复位值;CRC16 相关寄存器支持 8 位、16 位、32 位操作。 |
用户手册 |
N32G412、N32G415 系列勘误手册 V1.0.0(2026.6.30)的勘误列表只列出一行 A 版芯片条目,勘误链接列为空,未列出 CRC 相关条目。