# STM32F4 系列 D-Cache 一致性维护:从原理到 DMA 传输实战 在 STM32F4 系列(特别是带有 D-Cache 的型号如 F429、F439)中,D-Cache 能显著提升 CPU 访问外部存储器(如 SDRAM)的速度,但同时也引入了与 DMA 之间的数据一致性问题。若处理不当,轻则数据错乱,重则系统崩溃。本文将从原理出发,结合实战代码,彻底讲透 D-Cache 一致性维护。 ## 一、D-Cache 原理与一致性问题 ### 1. 什么是 D-Cache? D-Cache(数据缓存)是 CPU 与主存(如 SDRAM、Flash)之间的一层高速缓存,用于暂存最近访问的数据。当 CPU 读取数据时,若命中 Cache,则直接返回,无需访问慢速主存;写入时,数据可能先写入 Cache,再延迟写回主存(写回策略)。 在 STM32F4 中,D-Cache 通常与外部存储器(如 SDRAM)配合使用,以提升性能。但 DMA 控制器直接访问主存,不经过 Cache,因此当 CPU 和 DMA 共享同一块内存区域时,就会产生一致性问题。 ### 2. 一致性问题的两种典型场景 - **CPU 写,DMA 读**:CPU 将数据写入内存(数据可能还留在 Cache 中),DMA 直接读取主存,可能读到旧数据。 - **DMA 写,CPU 读**:DMA 将数据写入主存,但 CPU 的 Cache 中可能还保留着旧副本,CPU 读到的仍是旧数据。 ### 3. 解决思路 - **Clean(清脏)**:将 Cache 中已修改的数据写回主存,确保主存数据最新。 - **Invalidate(失效)**:将 Cache 中的行标记为无效,下次读取时强制从主存加载。 ## 二、STM32F4 的 Cache 操作函数 CMSIS 提供了标准接口,位于 `core_cm4.h` 中,常用函数如下: - `SCB_CleanDCache(void)`:清空整个 D-Cache,将脏行写回主存。 - `SCB_InvalidateDCache(void)`:使整个 D-Cache 失效,丢弃所有行。 - `SCB_CleanDCache_by_Addr(uint32_t *addr, int32_t dsize)`:按地址范围清脏。 - `SCB_InvalidateDCache_by_Addr(uint32_t *addr, int32_t dsize)`:按地址范围失效。 注意:按地址操作时,地址必须对齐到 32 字节(Cache line 大小),且长度应为 32 的倍数,否则需手动调整。 ## 三、实战:ADC 采样 + DMA 传输 ### 1. 硬件与场景 - MCU:STM32F429(带 D-Cache) - 外设:ADC1 通道0,采样结果通过 DMA 传输到内存缓冲区 `adc_buffer[64]` - 需求:DMA 持续采样,CPU 周期性读取缓冲区计算平均值 ### 2. 配置步骤 1. **使能 D-Cache**:在系统初始化时调用 `SCB_EnableDCache()`。 2. **配置 DMA**:设置 DMA 为循环模式,内存地址为 `adc_buffer`,外设地址为 ADC 数据寄存器。 3. **定义缓冲区**:使用 `__attribute__((aligned(32)))` 确保缓冲区 32 字节对齐。 4. **在 DMA 传输前**:若 CPU 之前写过该缓冲区,需要先 Clean。 5. **在 CPU 读取前**:需要 Invalidate,使 Cache 失效,强制从主存读取最新数据。 ### 3. 完整代码示例 ```c #include "stm32f4xx.h" #include "core_cm4.h" // 缓冲区,32字节对齐 __attribute__((aligned(32))) uint16_t adc_buffer[64]; void ADC_DMA_Init(void) { // 使能时钟 RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // 配置 ADC1 通道0,采样时间等(略) ADC1->CR2 = ADC_CR2_ADON | ADC_CR2_DMA | ADC_CR2_DDS; ADC1->CR1 = 0; // 独立模式 ADC1->SQR3 = 0; // 通道0 ADC1->SMPR2 = 0; // 采样周期 // 配置 DMA2 Stream0,通道0(ADC1) DMA2_Stream0->CR = 0; DMA2_Stream0->PAR = (uint32_t)&ADC1->DR; DMA2_Stream0->M0AR = (uint32_t)adc_buffer; DMA2_Stream0->NDTR = 64; DMA2_Stream0->CR = DMA_SxCR_MINC | DMA_SxCR_CIRC | DMA_SxCR_PSIZE_0 | \ DMA_SxCR_MSIZE_0 | DMA_SxCR_PL_1 | DMA_SxCR_EN; // 启动 ADC ADC1->CR2 |= ADC_CR2_SWSTART; } int main(void) { // 使能 D-Cache SCB_EnableDCache(); ADC_DMA_Init(); uint32_t sum = 0; uint16_t avg; while (1) { // 等待 DMA 完成一轮(例如通过 DMA 中断或延时) delay_ms(100); // 在读取 DMA 写入的数据前,先使 Cache 失效 SCB_InvalidateDCache_by_Addr((uint32_t*)adc_buffer, sizeof(adc_buffer)); // 计算平均值 sum = 0; for (int i = 0; i < 64; i++) { sum += adc_buffer[i]; } avg = sum / 64; // 使用 avg(例如通过串口发送) } } ``` ### 4. 关键点解析 - **对齐**:`aligned(32)` 确保缓冲区起始地址是 32 的倍数,这是 Cache line 对齐要求。 - **Invalidate 时机**:每次 CPU 读取 DMA 数据前,必须调用 `SCB_InvalidateDCache_by_Addr`,否则可能读到旧数据。 - **Clean 时机**:如果 CPU 先写缓冲区,然后 DMA 读取(例如 DMA 发送),则需先调用 `SCB_CleanDCache_by_Addr`。 - **性能权衡**:频繁 Invalidate 会降低性能,但为了保证正确性,必须如此。对于高频数据,可考虑使用双缓冲或乒乓缓冲,交替操作。 ## 四、注意事项 - **地址对齐与长度**:`SCB_CleanDCache_by_Addr` 和 `SCB_InvalidateDCache_by_Addr` 的地址必须 32 字节对齐,长度应为 32 的倍数。若缓冲区长度不是 32 的倍数,需向上取整,并确保不越界。 - **中断上下文**:在中断服务函数中操作 Cache 时,注意优先级和原子性,避免与主循环冲突。 - **DMA 与 Cache 共存**:如果 DMA 频繁且数据量大,可考虑将缓冲区放置在 DTCM(Data Tightly Coupled Memory)中,该区域不经过 D-Cache,可避免一致性问题,但容量有限。 - **调试技巧**:使用调试器观察 Cache 操作前后内存值,验证一致性维护是否生效。 ## 五、总结 D-Cache 一致性是 STM32F4 高性能应用中的关键点。通过理解 Cache 的工作原理,正确使用 Clean 和 Invalidate 操作,可以确保 CPU 与 DMA 之间的数据同步。实战中,务必注意地址对齐、操作时机和性能影响。掌握这些技巧,你的嵌入式系统将更加健壮和高效。