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. 配置步骤
-
使能 D-Cache:在系统初始化时调用
SCB_EnableDCache()。 -
配置 DMA:设置 DMA 为循环模式,内存地址为
adc_buffer,外设地址为 ADC 数据寄存器。 -
定义缓冲区:使用
__attribute__((aligned(32)))确保缓冲区 32 字节对齐。 - 在 DMA 传输前:若 CPU 之前写过该缓冲区,需要先 Clean。
- 在 CPU 读取前:需要 Invalidate,使 Cache 失效,强制从主存读取最新数据。
3. 完整代码示例
#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 之间的数据同步。实战中,务必注意地址对齐、操作时机和性能影响。掌握这些技巧,你的嵌入式系统将更加健壮和高效。