STM32H7 的 DCache 与 DMA 数据一致性:地址对齐、Clean/Invalidate 失效场景与实测复现

STM32H7 主频高达 480MHz,Cortex-M7 内核自带 L1 D-Cache(默认 16KB)。开启 DCache 后,CPU 访问 SRAM 可能命中缓存,而 DMA 直接读写物理 SRAM,两者看到的数据可能不一致。本文从原理到实测,帮你彻底搞懂 DCache 与 DMA 的配合。

一、为什么 DCache 会让 DMA “翻车”

  • CPU 视角:读写变量时,数据可能只在 Cache 中,未写回 SRAM。
  • DMA 视角:直接操作 SRAM,看不到 Cache 里的新数据。
  • 典型现象:DMA 发送缓冲区数据错乱、接收数据被旧值覆盖、偶发丢包。

根本原因:Cache 与 SRAM 之间没有硬件自动同步机制,必须由软件在恰当时机执行 Clean(写回)或 Invalidate(无效化)。

二、地址对齐:32 字节是硬性要求

Cortex-M7 的 Cache 行(Cache Line)为 32 字节。Clean/Invalidate 操作以整行为单位,因此:

  • DMA 缓冲区地址必须 32 字节对齐。
  • 缓冲区大小建议为 32 字节的整数倍。
  • 若不对齐,Invalidate 可能误伤相邻变量,导致“莫名其妙”的数据丢失。
// 推荐:使用编译器属性强制对齐
__attribute__((aligned(32))) uint8_t dma_tx_buf[256];
__attribute__((aligned(32))) uint8_t dma_rx_buf[256];

三、Clean 与 Invalidate 的正确时机

| 方向 | 操作 | 时机 | |------|------|------| | CPU 写 → DMA 读(发送) | Clean | 启动 DMA 前 | | DMA 写 → CPU 读(接收) | Invalidate | DMA 完成后、CPU 读之前 |

注意:Invalidate 会丢弃 Cache 中未写回的数据。若 CPU 在 DMA 传输期间改写了同一缓冲区,Invalidate 会丢失这些修改。

四、完整代码示例(HAL 库)

#include "stm32h7xx_hal.h"

__attribute__((aligned(32))) uint8_t tx_buf[256];
__attribute__((aligned(32))) uint8_t rx_buf[256];

// 发送:CPU 填数据 → Clean → 启动 DMA
void dma_send(void)
{
    for (int i = 0; i < 256; i++) tx_buf[i] = i;

    SCB_CleanDCache_by_Addr((uint32_t *)tx_buf, 256);

    HAL_UART_Transmit_DMA(&huart1, tx_buf, 256);
}

// 接收完成回调:Invalidate → CPU 读
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    SCB_InvalidateDCache_by_Addr((uint32_t *)rx_buf, 256);

    // 此时读取 rx_buf 才是 DMA 写入的真实数据
    process_data(rx_buf, 256);
}

五、实测复现:不 Clean 会怎样?

实验环境:STM32H743,UART DMA 发送 256 字节递增序列。

  • 不调用 Clean:示波器/串口助手收到数据前 32 字节正确,后续出现旧值或 0。原因:部分数据仍在 Cache 中未写回。
  • 调用 Clean 后:数据完全正确。

接收侧实验:DMA 接收后不 Invalidate,CPU 读到的是 Cache 中的旧数据,表现为“数据不更新”。调用 Invalidate 后恢复正常。

六、注意事项与最佳实践

  • 优先使用 MPU 配置 DMA 缓冲区为 Write-Through 或 Non-Cacheable,可从根本上避免一致性问题。
  • 若必须使用 Cacheable 内存,严格遵循 Clean/Invalidate 时机。
  • 避免在 DMA 传输过程中 CPU 访问同一缓冲区。
  • 使用 SCB_CleanDCache_by_Addr 和 SCB_InvalidateDCache_by_Addr 时,地址必须 32 字节对齐。
  • 对于双向传输,可先 Clean 再 Invalidate,但需确保无 CPU 并发写。
  • 调试时若现象“时好时坏”,优先怀疑 Cache 一致性。

七、总结

DCache 是 STM32H7 性能的利器,也是 DMA 的“隐形杀手”。记住三点:地址 32 字节对齐、发送前 Clean、接收后 Invalidate。更稳妥的做法是用 MPU 将 DMA 缓冲区设为 Non-Cacheable。掌握这些,你的 H7 DMA 将不再“玄学”。