一、为什么 D-Cache 与 DMA 会冲突?
STM32H7 的 Cortex-M7 内核带有 16KB D-Cache,用于加速数据访问。当 CPU 访问内存时,数据可能被缓存在 D-Cache 中,而实际物理内存(如 SRAM、SDRAM)并未更新。DMA 控制器直接访问物理内存,不经过 Cache。因此:
- CPU 写数据后启动 DMA 发送:数据可能还在 Cache 中,DMA 读到的是旧数据。
- DMA 接收数据后 CPU 读取:Cache 中可能缓存了旧数据,CPU 读到的是旧值。
这就是数据一致性问题。解决手段是 Clean(将 Cache 写回内存)和 Invalidate(丢弃 Cache 内容,强制从内存重新加载)。
二、地址对齐:Cache 行与 DMA 边界
D-Cache 以 32 字节 Cache Line 为单位操作。Clean/Invalidate 必须按 Cache Line 对齐,否则会误伤相邻数据。
- DMA 缓冲区地址必须 32 字节对齐。
- 缓冲区大小必须是 32 字节的整数倍。
- 若无法满足,需使用
SCB_CleanDCache_by_Addr等函数,并确保地址和长度按 32 字节对齐。
// 推荐:使用 __attribute__((aligned(32))) 强制对齐
uint8_t dma_tx_buf[256] __attribute__((aligned(32)));
uint8_t dma_rx_buf[256] __attribute__((aligned(32)));
三、Clean 与 Invalidate 的正确时机
3.1 CPU 写 → DMA 读(发送)
- CPU 填充缓冲区。
- Clean 缓冲区(写回内存)。
- 启动 DMA 发送。
SCB_CleanDCache_by_Addr((uint32_t*)dma_tx_buf, sizeof(dma_tx_buf));
HAL_UART_Transmit_DMA(&huart1, dma_tx_buf, sizeof(dma_tx_buf));
3.2 DMA 写 → CPU 读(接收)
- 启动 DMA 接收。
- DMA 完成中断中 Invalidate 缓冲区(丢弃旧 Cache)。
- CPU 读取数据。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
SCB_InvalidateDCache_by_Addr((uint32_t*)dma_rx_buf, sizeof(dma_rx_buf));
// 处理数据
}
注意:Invalidate 前必须确保 CPU 没有未写回的数据,否则会丢失。通常先 Clean 再 Invalidate 更安全。
四、完整代码示例(UART DMA 收发)
#include "stm32h7xx_hal.h"
#define BUF_SIZE 256
uint8_t tx_buf[BUF_SIZE] __attribute__((aligned(32)));
uint8_t rx_buf[BUF_SIZE] __attribute__((aligned(32)));
void send_data(void) {
// 填充数据
for (int i = 0; i < BUF_SIZE; i++) tx_buf[i] = i;
// Clean D-Cache
SCB_CleanDCache_by_Addr((uint32_t*)tx_buf, BUF_SIZE);
// 启动 DMA 发送
HAL_UART_Transmit_DMA(&huart1, tx_buf, BUF_SIZE);
}
void start_receive(void) {
// 启动 DMA 接收
HAL_UART_Receive_DMA(&huart1, rx_buf, BUF_SIZE);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
// Invalidate D-Cache
SCB_InvalidateDCache_by_Addr((uint32_t*)rx_buf, BUF_SIZE);
// 此时 rx_buf 中为最新数据
process_data(rx_buf, BUF_SIZE);
}
五、实测踩坑记录
-
坑1:忘记 Clean 导致发送数据错乱。现象:DMA 发送的数据部分为旧值。解决:发送前调用
SCB_CleanDCache_by_Addr。 - 坑2:Invalidate 未对齐导致相邻变量被破坏。现象:接收后其他变量值突变。解决:确保缓冲区 32 字节对齐且长度对齐。
- 坑3:在中断中频繁 Clean/Invalidate 影响性能。建议:合理划分缓冲区,减少操作次数。
-
坑4:使用
SCB_InvalidateDCache全量操作。全量 Invalidate 会丢弃所有 Cache,影响系统性能,应使用按地址操作。
六、注意事项
- 使用
SCB_CleanDCache_by_Addr和SCB_InvalidateDCache_by_Addr时,地址和长度必须 32 字节对齐。 - 若使用 MPU 配置内存属性为 Write-Through 或 Non-Cacheable,可简化一致性处理,但会降低性能。
- DMA 传输期间避免 CPU 访问同一缓冲区,否则可能引发竞争。
- 对于双向 DMA(如 SPI 全双工),需同时处理发送和接收的 Cache 操作。
- 调试时可通过查看内存窗口与 Cache 内容对比,验证一致性。
掌握以上要点,即可在 STM32H7 上安全高效地使用 D-Cache 与 DMA。