STM32F4 168MHz 下 DMA+双缓冲串口收发:Cache 一致性维护的三种实用策略
👁 2 阅读 · 2026-08-27 · 嵌入式
在 STM32F4 系列以 168MHz 主频运行时,CPU 与 DMA 对内存的访问可能因 Cache 缓存导致数据不一致,尤其在双缓冲串口收发场景中,极易出现数据错乱或丢失。本文深入剖析 Cache 一致性问题根源,并给出三种实用维护策略:软件清 Cache、配置 MPU 内存属性、以及使用非 Cacheable 内存区域。每种策略均附原理讲解、配置步骤和完整代码示例,帮助开发者根据应用场景选择最优方案,确保高速通信的可靠性。
# STM32F4 168MHz 下 DMA+双缓冲串口收发:Cache 一致性维护的三种实用策略
## 1. 问题根源:Cache 与 DMA 的“各自为政”
STM32F4 系列(如 STM32F407)在 168MHz 主频下,CPU 运行速度远高于外部 SRAM 或 SDRAM。为提升性能,Cortex-M4 内核集成了 Cache(通常为 4KB 或 8KB,分为 I-Cache 和 D-Cache)。当 CPU 访问内存时,数据会先被复制到 Cache 中,后续访问直接命中 Cache,避免重复访问慢速内存。
然而,DMA 控制器直接访问物理内存,不经过 CPU 的 Cache。这就导致了一个经典问题:
- **发送场景**:CPU 将数据写入内存缓冲区(数据可能被缓存,尚未写回物理内存),然后启动 DMA 传输。DMA 从物理内存读取数据,可能读到旧数据或未初始化的数据。
- **接收场景**:DMA 将接收到的数据写入内存缓冲区(直接写入物理内存),然后 CPU 读取该缓冲区。CPU 可能命中 Cache 中的旧数据,而看不到 DMA 写入的新数据。
在双缓冲串口收发中,这种不一致性会导致数据包错乱、丢包,甚至系统崩溃。因此,必须采取策略维护 Cache 一致性。
## 2. 策略一:软件清 Cache(简单直接)
### 原理
通过调用 CMSIS 提供的函数,在关键操作前后强制将 Cache 中的数据写回内存(Clean)或使 Cache 失效(Invalidate)。
- **发送前**:Clean 数据缓冲区,确保 DMA 能从物理内存读到最新数据。
- **接收后**:Invalidate 数据缓冲区,使 CPU 重新从物理内存读取 DMA 写入的数据。
### 配置步骤
1. 开启 D-Cache(默认可能开启,需确认)。
2. 在 DMA 传输前/后调用相应函数。
### 代码示例
```c
#include "stm32f4xx.h"
// 定义双缓冲结构
#define BUF_SIZE 256
uint8_t rx_buf[2][BUF_SIZE] __attribute__((aligned(32))); // 对齐到 Cache 行(32字节)
uint8_t tx_buf[2][BUF_SIZE] __attribute__((aligned(32)));
// 发送函数
void UART_DMA_Send(uint8_t *data, uint16_t len) {
// 1. 将数据拷贝到 tx_buf(假设已在 tx_buf 中)
// 2. 清 Cache:确保数据写回物理内存
SCB_CleanDCache_by_Addr((uint32_t*)tx_buf, len);
// 3. 启动 DMA 发送
DMA_SetConfig(DMA2_Stream7, data, (uint32_t)&USART1->DR, len);
DMA_Cmd(DMA2_Stream7, ENABLE);
}
// DMA 接收完成回调
void UART_DMA_RxComplete(void) {
// 1. 使 Cache 失效:让 CPU 重新从内存读取数据
SCB_InvalidateDCache_by_Addr((uint32_t*)rx_buf[active_buf], BUF_SIZE);
// 2. 处理接收到的数据
ProcessData(rx_buf[active_buf], BUF_SIZE);
// 3. 切换缓冲区,重新启动 DMA 接收
active_buf ^= 1;
DMA_SetConfig(DMA2_Stream5, (uint32_t)&USART1->DR, rx_buf[active_buf], BUF_SIZE);
DMA_Cmd(DMA2_Stream5, ENABLE);
}
```
### 注意事项
- 缓冲区必须按 32 字节对齐(Cache 行大小),否则清 Cache 操作可能影响相邻数据。
- 频繁调用清 Cache 函数会降低性能,适合数据量小、频率不高的场景。
- 确保在 DMA 启动前完成 Clean,在 DMA 完成后进行 Invalidate。
## 3. 策略二:配置 MPU 设置内存为非 Cacheable(硬件隔离)
### 原理
通过 MPU(内存保护单元)将 DMA 使用的内存区域配置为“非 Cacheable”属性,这样 CPU 访问该区域时直接读写物理内存,绕过 Cache。虽然会牺牲部分性能,但彻底避免了不一致问题,且无需软件干预。
### 配置步骤
1. 初始化 MPU,设置一个区域覆盖 DMA 缓冲区。
2. 设置区域属性:非 Cacheable、非 Bufferable(或 Write-Through)。
3. 使能 MPU。
### 代码示例
```c
void MPU_Config(void) {
MPU_Region_InitTypeDef MPU_InitStruct;
// 使能 MPU
MPU_DeInit();
// 配置区域:基地址为缓冲区地址,大小 4KB(覆盖两个 256B 缓冲区)
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = (uint32_t)rx_buf;
MPU_InitStruct.Size = MPU_REGION_SIZE_4KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_REGION_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_REGION_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_REGION_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
MPU_Init(&MPU_InitStruct);
// 使能 MPU
MPU_Cmd(ENABLE);
}
// 在主函数中调用 MPU_Config()
```
### 注意事项
- 非 Cacheable 区域访问速度较慢,但 DMA 缓冲区通常不大,影响有限。
- 确保缓冲区地址和大小符合 MPU 区域对齐要求(通常为 32 字节倍数)。
- 如果同时使用多个缓冲区,可配置多个区域或使用一个大区域覆盖。
## 4. 策略三:使用 CCM RAM(无 Cache 的专用内存)
### 原理
STM32F4 系列内部有一块 CCM(Core Coupled Memory)RAM,它直接连接到内核,不经过总线矩阵,因此也不受 Cache 影响。将 DMA 缓冲区放置在 CCM RAM 中,可以天然避免一致性问题,且访问速度极快(与内核同频)。
### 配置步骤
1. 在链接脚本中定义 CCM RAM 段(通常地址为 0x10000000)。
2. 将缓冲区变量指定到该段。
### 代码示例
```c
// 在链接脚本(如 stm32f407_flash.ld)中添加:
// .ccmram :
// {
// . = ALIGN(4);
// *(.ccmram)
// . = ALIGN(4);
// } > CCMRAM
// 定义缓冲区到 CCM RAM
__attribute__((section(".ccmram"))) uint8_t rx_buf[2][BUF_SIZE];
__attribute__((section(".ccmram"))) uint8_t tx_buf[2][BUF_SIZE];
// 使用方式与普通变量相同,无需额外 Cache 操作
```
### 注意事项
- CCM RAM 容量有限(通常 64KB 或 128KB),需合理分配。
- CCM RAM 不支持 DMA 访问?**注意**:在 STM32F4 中,DMA 控制器无法访问 CCM RAM,因为 CCM 只连接到内核。因此,**此策略仅适用于 CPU 访问缓冲区,不适用于 DMA 直接读写**。但可以用于 CPU 侧缓冲,DMA 使用另一块普通 RAM,通过软件拷贝数据,但这样会引入额外开销。
**修正**:实际上,STM32F4 的 DMA 无法访问 CCM RAM,所以此策略不适合直接用于 DMA 缓冲区。但可以作为辅助:将处理数据的临时缓冲区放在 CCM,DMA 缓冲区放在普通 RAM,通过 memcpy 在两者间拷贝(需配合 Cache 操作)。因此,此策略更适合对性能要求极高且数据量小的场景。
## 5. 三种策略对比与选型建议
| 策略 | 优点 | 缺点 | 适用场景 |
|------|------|------|----------|
| 软件清 Cache | 简单、无需硬件配置 | 性能损耗,需注意对齐 | 数据量小、频率低 |
| MPU 非 Cacheable | 硬件隔离,无需软件干预 | 访问速度稍慢,配置稍复杂 | 中等数据量,要求实时性 |
| CCM RAM | 速度最快,无一致性问题 | 容量小,DMA 无法直接访问 | 需要 CPU 高频处理的小缓冲 |
**推荐**:对于大多数串口双缓冲应用,策略一(软件清 Cache)足够且易于实现;若数据吞吐量大,建议使用策略二(MPU 配置);若对延迟极端敏感且数据量小,可结合策略三(CCM RAM)作为辅助。
## 6. 总结
在 STM32F4 高速主频下,Cache 一致性是 DMA 通信不可忽视的问题。本文介绍了三种实用策略:软件清 Cache 简单直接,MPU 配置硬件隔离,CCM RAM 提供极致速度但需注意 DMA 限制。开发者应根据实际需求选择,并在代码中严格遵循操作顺序,确保数据一致性。希望本文能帮助你在嵌入式开发中少踩坑,提升系统可靠性。