LAT1500 如何通过 DMA 配合 CRC 功能
【应用笔记】LAT1500 如何通过DMA配合CRC功能
关键字:CRC, DMA,AHB,STM32G4,AN4187
LAT1500 – Rev 1.0– Jan. 2025
1. 概述
STM32 的 CRC 外设是用来实现 CRC 计算的硬件单元,硬件实现 CRC 计算相对于软件算法实现来说,可以大大提高运算速度。CRC 计算单元具有单个 32 位读 / 写数据寄存器 (CRC_DR)。它用于输入新数据 (写访问) 并保存以前的 CRC 计算结果 (读访问)。当 DMA 用作数据传输处理器时,可以极大地减轻 CPU 负荷。本文以 STM32G474 为例,介绍如何通过 DMA 配合 CRC 计算,以供读者参考。
2. 原理
2.1. 时间上的可能性
单次 DMA 传输包括两个 AHB 传输阶段,通过 DMA AHB 总线 master 实现:
- 通过内部当前外设 / 存储器地址寄存器进行寻址,从外设数据寄存器或存储器单元中读取单个数据 (字节、半字或字)。
- 通过内部当前外设 / 存储器地址寄存器进行寻址,向外设数据寄存器或存储器单元中写入单个数据 (字节、半字或字)。

从上面解释可以看出,单次 DMA 传输可以传输单个数据 (字节,半字或字),其中包含两个传输过程,一次 AHB 读传输过程以及一次 DMA 写传输过程。
从 AHB 协议的描述中可以看到,一次 AHB 传输分为两个阶段,分别是地址阶段和数据阶段,这两个阶段即使在最快的情况下也各需要一个 AHB 时钟周期。即一次 AHB 传输至少需要两个 AHB 时钟周期。
HWRITE 控制着与主设备之间的数据传输方向。因此:
- 当 HWRITE 为高电平时,表示写传输,主设备在写数据总线 HWDATA 上广播数据
- 当 HWRITE 为低电平时,执行读传输,从设备必须在读取数据总线 HRDATA 上生成数据
最简单的传输是无等待状态的传输,因此该传输包括一个地址周期和一个数据周期。

从上面的分析可以看出,单次 DMA 传输至少 4 个 AHB 时钟周期,如果加上寻址计算等操作,需要的时间可能更多。
而 CRC 单次计算,最多需要 4 个 AHB 时钟周期:
CRC_DR 寄存器可以按字、右对齐半字和右对齐字节访问。对于其他寄存器,仅允许 32 位访问。计算的持续时间取决于数据宽度:32 位为 4 个 AHB 时钟周期,16 位为 2 个 AHB 时钟周期,8 位为 1 个 AHB 时钟周期。

综上所述,在 AHB 总线上一次读 + 写就至少需要 4 个 AHB 时钟周期,这就已经可以满足 CRC 单次计算所需的时间。所以单次 DMA 数据传输之后即可进行下次数据传输 (背靠背传输),无需额外等待。
2.2. 内部连接的可能性
CRC 外设寄存器是挂在 AHB1 总线上的,具体地址映射如下:

总线矩阵提供从主器件 (master) 到从器件 (slave) 的访问。DMA 作为 Master,而 SRAM、FLASH 以及 AHB1 外设都是作为 Slave。所以 DMA 可以通过总线矩阵对 SRAM、FLASH 以及 CRC 外设进行访问,也即通过 DMA 传输数据到 CRC 是可行的。
系统架构主要由 32 位多层 AHB 总线矩阵组成,该矩阵互连:
最多五个主设备:带 FPU 内核的 Cortex-M4 I 总线、带 FPU 内核的 Cortex-M4 D 总线、带 FPU 内核的 Cortex-M4 S 总线、DMA1、DMA2;
最多九个从设备:ICode 总线上的内部闪存、DCode 总线上的内部闪存、内部 SRAM1、内部 SRAM2、内部 CCM SRAM、包含 AHB 到 APB 桥和 APB 外设的 AHB1 外设(连接到 APB1 和 APB2)、AHB2 外设、灵活静态存储器控制器 (FSMC)、四路 SPI 存储器接口 (QUADSPI)。
总线矩阵提供从主设备到从设备的访问,即使多个高速外设同时工作,也能实现并发访问和高效操作。

2.3. DMA 传输方式
由于 CRC 是一个简单的计算单元,计算完成时直接将结果放到 CRC_DR 寄存器中,所以没有信号来触发 DMA 传输。
DMA 控制器可以在没有外设请求触发的情况下运行,这种模式叫做 Memory-to-memory 模式,由软件发起传输。所以我们必须将 DMA 的传输模式设置为 Memory-to-memory 模式。DMA 在数据传输期间,CPU 仅在 CRC 和 DMA 配置期间以及 DMA 中断处理程序执行期间进行干预,极大的减少 CPU 负载。
Memory-to-memory 模式说明 :
DMA 通道可以在没有外设请求触发的情况下运行,此模式称为存储器到存储器模式,由软件启动。如果设置了 DMA_CCRx 寄存器中的 MEM2MEM 位,则该通道(如果启用)将启动传输,一旦 DMA_CNDTRx 寄存器达到零,传输将停止。
注意 :
存储器到存储器模式不得在循环模式下使用。在以存储器到存储器模式(MEM2MEM=1)启用通道之前,软件必须清除 DMA_CCRx 寄存器的 CIRC 位。

3. 实现
这里以 NUCLEO-G474RE 为例进行介绍,这里不对基本操作做过多介绍,只介绍与 DMA 和 CRC 相关的配置部分,详细配置可参考附件。
3.1. 创建工程
- 使用 STM32CubeMX 选择 STM32G474RET6 创建工程。然后选择 CRC 外设,点击 "Activated" 选项。本例的 "Input Data Format" 参数配置为 "Words",其他参数保持默认,实际应用中可根据需求配置,至此 CRC 外设配置完毕。

- 找到 DMA 外设,点击 "Add" 添加 DMA Request,并选择 "MemToMem" 模式。

- 配置保持默认即可,Mode 设置为 "Normal"。需注意 "Dst Memory" 不要勾选,因为 CRC_DR 寄存器的地址是固定的。"Data Width" 配置为 Byte、Half Word 或者 Word 都可以,本示例中配置为 Word。

- 配置 DMA 中断,在 "NVIC" 里面 Enable "DMA1 channel1 global interrupt",注意该配置与实际使用的 DMA 相关,本例中使用的是 DMA1。

- 然后点击 "GENERATE CODE" 生成代码。
3.2. 软件配置
本例中将 114 个 32-bit 数据放在 "aDataBuffer" 数组中,使用 CRC 外设默认参数的计算结果为 "0x379E9F06"。
c
运行
#define BUFFER_SIZE 114
static const uint32_t aDataBuffer[BUFFER_SIZE] = {
0x00001021, 0x20423063, 0x408450a5, 0x60c670e7, 0x9129a14a, 0xb16bc18c, 0xd1ade1ce, 0xf1ef1231, 0x32732252, 0x52b54294, 0x72f762d6, 0x93398318, 0xa35ad3bd, 0xc39cf3ff, 0xe3de2462, 0x34430420, 0x64e674c7, 0x44a45485, 0xa56ab54b, 0x85289509, 0xf5cfc5ac, 0xd58d3653, 0x26721611, 0x063076d7, 0x569546b4, 0xb75ba77a, 0x97198738, 0xf7dfe7fe, 0xc7bc48c4, 0x58e56886, 0x78a70840, 0x18612802, 0xc9ccd9ed, 0xe98ef9af, 0x89489969, 0xa90ab92b, 0x4ad47ab7, 0x6a961a71, 0x0a503a33, 0x2a12dbfd, 0xfbbfeb9e, 0x9b798b58, 0xbb3bab1a, 0x6ca67c87, 0x5cc52c22, 0x3c030c60, 0x1c41edae, 0xfd8fcdec, 0xad2abd0b, 0x8d689d49, 0x7e976eb6, 0x5ed54ef4, 0x2e321e51, 0x0e70ff9f,
0xefbedfdd, 0xcffcbf1b, 0x9f598f78, 0x918881a9, 0xb1caa1eb, 0xd10cc12d,
0xe16f1080, 0x00a130c2, 0x20e35004, 0x40257046, 0x83b99398, 0xa3fbb3da,
0xc33dd31c, 0xe37ff35e, 0x129022f3, 0x32d24235, 0x52146277, 0x7256b5ea,
0x95a88589, 0xf56ee54f, 0xd52cc50d, 0x34e224c3, 0x04817466, 0x64475424,
0x4405a7db, 0xb7fa8799, 0xe75ff77e, 0xc71dd73c, 0x26d336f2, 0x069116b0,
0x76764615, 0x5634d94c, 0xc96df90e, 0xe92f99c8, 0xb98aa9ab, 0x58444865,
0x78066827, 0x18c008e1, 0x28a3cb7d, 0xdb5ceb3f, 0xfb1e8bf9, 0x9bd8abbb,
0x4a755a54, 0x6a377a16, 0x0af11ad0, 0x2ab33a92, 0xed0fdd6c, 0xcd4dbdaa,
0xad8b9de8, 0x8dc97c26, 0x5c644c45, 0x3ca22c83, 0x1ce00cc1, 0xef1fff3e,
0xdf7caf9b, 0xbfba8fd9, 0x9ff86e17, 0x7e364e55, 0x2e933eb2, 0x0ed11ef0
};
/**
* @ Brief Expected CRC Value(Default configuration)
* @ Width : 32
* @ Poly : 0x04C11DB7
* @ Init : 0xFFFFFFFF
* @ RefIn : False
* @ RefOut : False
* @ XorOut : 0x00000000
*/
const uint32_t uwExpectedCRCValue = 0x379E9F06;
由于已经配置了 DMA,STM32CubeMX 生成代码时会生成相应的句柄,本例中生成的 DMA 句柄为hdma_memtomem_dma1_channel1。

这里我们添加一个 DMA 中断传输完成回调函数,中断传输完成时 HAL 库中断处理函数会调用该回调函数,传输完成后可通过读取 CRC_DR 寄存器获取计算结果(本例使用uwCRCValue获取 CRC 计算结果)。
c
运行
/**
* @brief DMA CRC transmit process complete callback.
* @param hdma DMA handle.
* @retval None
*/
static void UART_DMACrcTransferCpltCallback(DMA_HandleTypeDef *hdma) {
if(hdma->Instance == DMA1_Channel1) {
/* Get the CRC computed value */
uwCRCValue = hcrc.Instance->DR;
}
}
在开始计算之前,首先使用HAL_DMA_RegisterCallback将上述回调函数注册到 DMA 句柄中,然后重置 CRC 数据寄存器,最后开启 DMA 传输。
c
运行
/* USER CODE BEGIN 2 */
/* Set the DMA CRC transfer complete callback */
HAL_DMA_RegisterCallback(&hdma_memtomem_dma1_channel1,
HAL_DMA_XFER_CPLT_CB_ID, UART_DMACrcTransferCpltCallback);
/* Reset CRC Calculation Unit (hcrc->Instance->INIT is written in hcrc->Instance->DR) */
__HAL_CRC_DR_RESET(&hcrc);
/* Start the DMA Transfer with interrupt enabled */
HAL_DMA_Start_IT(&hdma_memtomem_dma1_channel1, (uint32_t)aDataBuffer,
(uint32_t)&(hcrc.Instance->DR), BUFFER_SIZE);
/* USER CODE END 2 */
3.3. 运行结果
运行结果与理论值一致,可成功获取 CRC 计算结果。

4. 小结
本文对通过 DMA 配合 CRC 应用从原理上进行了可行性分析,并且介绍了该功能的具体实现过程,希望能对读者有所帮助。