一、为什么 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 读(发送)

  1. CPU 填充缓冲区。
  2. Clean 缓冲区(写回内存)。
  3. 启动 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 读(接收)

  1. 启动 DMA 接收。
  2. DMA 完成中断中 Invalidate 缓冲区(丢弃旧 Cache)。
  3. 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_AddrSCB_InvalidateDCache_by_Addr 时,地址和长度必须 32 字节对齐。
  • 若使用 MPU 配置内存属性为 Write-Through 或 Non-Cacheable,可简化一致性处理,但会降低性能。
  • DMA 传输期间避免 CPU 访问同一缓冲区,否则可能引发竞争。
  • 对于双向 DMA(如 SPI 全双工),需同时处理发送和接收的 Cache 操作。
  • 调试时可通过查看内存窗口与 Cache 内容对比,验证一致性。

掌握以上要点,即可在 STM32H7 上安全高效地使用 D-Cache 与 DMA。