一、为什么 STM32H7 上 DMA 会“翻车”
STM32H7 的 Cortex-M7 内核带有 D-Cache(数据缓存),默认开启。CPU 读写内存时,实际访问的是 Cache,而不是直接访问 SRAM。DMA 控制器则绕过 Cache,直接读写 SRAM。
这就导致两个经典问题:
- CPU 写、DMA 读:CPU 写入的数据可能还在 Cache 里(Write-Back 策略),SRAM 中还是旧值,DMA 读走旧数据。
- DMA 写、CPU 读:DMA 把新数据写入 SRAM,但 CPU 读到的却是 Cache 中的旧数据(Cache 命中)。
因此,必须在合适的时机对 Cache 做 Clean(写回) 或 Invalidate(无效化)。
二、Clean 与 Invalidate 到底做什么
- Clean(清理):把 Cache 中“脏”数据写回 SRAM,保证 SRAM 与 Cache 一致。
- Invalidate(无效化):把 Cache 行标记为无效,下次 CPU 读取时会从 SRAM 重新加载。
关键点:Invalidate 会丢弃 Cache 中尚未写回的数据。如果顺序错了,就会丢数据。
三、DMA 发送(内存 -> 外设)
数据流向:CPU 写缓冲区 -> DMA 从 SRAM 读到外设。
正确顺序:
- CPU 填充发送缓冲区。
- Clean 发送缓冲区对应的 Cache 行。
- 启动 DMA 发送。
#define TX_LEN 64
__attribute__((aligned(32))) uint8_t tx_buf[TX_LEN];
void uart_dma_send(void)
{
for (int i = 0; i < TX_LEN; i++) {
tx_buf[i] = i;
}
/* 关键:把 CPU 写入的数据写回 SRAM */
SCB_CleanDCache_by_Addr((uint32_t *)tx_buf, TX_LEN);
HAL_UART_Transmit_DMA(&huart1, tx_buf, TX_LEN);
}
注意:发送前只需要 Clean,不需要 Invalidate。Invalidate 反而可能丢掉刚写的数据。
四、DMA 接收(外设 -> 内存)
数据流向:DMA 把外设数据写入 SRAM -> CPU 读取。
正确顺序:
- 准备接收缓冲区(可先 Invalidate,防止 Cache 中有旧数据)。
- 启动 DMA 接收。
- DMA 完成中断中,Invalidate 接收缓冲区。
- CPU 读取数据。
#define RX_LEN 64
__attribute__((aligned(32))) uint8_t rx_buf[RX_LEN];
void uart_dma_start_recv(void)
{
/* 启动前无效化,避免 Cache 命中旧数据 */
SCB_InvalidateDCache_by_Addr((uint32_t *)rx_buf, RX_LEN);
HAL_UART_Receive_DMA(&huart1, rx_buf, RX_LEN);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
/* 接收完成后无效化,让 CPU 读到 DMA 写入的新数据 */
SCB_InvalidateDCache_by_Addr((uint32_t *)rx_buf, RX_LEN);
/* 此时读取 rx_buf 才是安全的 */
process_data(rx_buf, RX_LEN);
}
五、最容易踩的坑:顺序与地址对齐
- 坑 1:接收完成后先读再 Invalidate。CPU 读到的是 Cache 旧值,数据看似“没更新”。
- 坑 2:发送前做了 Invalidate。可能把尚未写回的数据丢掉,DMA 发出错误内容。
- 坑 3:地址未按 Cache Line 对齐。Cortex-M7 的 Cache Line 为 32 字节,操作长度也应是 32 的倍数,否则会误伤相邻数据。
- 坑 4:在中断中频繁 Clean/Invalidate 大块内存。会影响实时性,建议只操作实际使用的缓冲区。
六、更稳妥的替代方案
如果不想手动维护 Cache,可以考虑:
- 把 DMA 缓冲区放到 非 Cache 区域(如通过 MPU 配置为 Device 或 Non-cacheable 内存)。
- 使用 MPU 将特定 SRAM 区域设为 Write-Through 或 Non-cacheable。
- 使用 ST 提供的
SCB_*DCache_by_Addr函数,并确保 32 字节对齐。
七、总结
记住一句话:
- CPU 写、DMA 读:先 Clean,再启动 DMA。
- DMA 写、CPU 读:DMA 完成后先 Invalidate,再读数据。
Cache 与 DMA 一致性问题的本质是“谁先看到数据”。只要顺序正确、地址对齐,STM32H7 的高性能 DMA 就能稳定工作。