STM32H7 的 D-Cache 与 DMA 一致性:地址对齐、Clean/Invalidate 时序与踩坑复盘
STM32H7 主频高达 480MHz,为了匹配内核速度,芯片内置了 L1 D-Cache。但 DMA 直接访问物理内存,不经过 Cache,若两者同时操作同一块内存,数据一致性就成了大问题。本文带你从原理到实践,彻底解决这个痛点。
一、为什么会有数据一致性问题?
- CPU 写数据:先写入 D-Cache,标记为 dirty,稍后才写回 SRAM。
- DMA 读数据:直接读 SRAM,此时 SRAM 里还是旧数据,导致 DMA 读到错误内容。
- DMA 写数据:直接写 SRAM,但 CPU 可能仍从 Cache 读旧值,导致 CPU 看不到新数据。
因此,在 DMA 传输前后,必须手动维护 Cache 与内存的一致性。
二、地址对齐:必须遵守的硬性规则
Cortex-M7 的 Cache 行大小为 32 字节。任何 Clean/Invalidate 操作都以 32 字节为单位。
- DMA 缓冲区必须 32 字节对齐,且大小最好是 32 的整数倍。
- 若缓冲区跨越两个 Cache 行,Invalidate 时可能误伤相邻数据,导致其他变量被丢弃。
推荐使用 __attribute__((aligned(32))) 定义缓冲区:
__attribute__((aligned(32))) uint8_t dma_buffer[256];
三、Clean 与 Invalidate 的时序
1. CPU 写 → DMA 读(发送数据)
- 先 Clean(写回)D-Cache,确保 SRAM 数据最新。
- 再启动 DMA 发送。
SCB_CleanDCache_by_Addr((uint32_t*)dma_buffer, sizeof(dma_buffer));
HAL_UART_Transmit_DMA(&huart1, dma_buffer, sizeof(dma_buffer));
2. DMA 写 → CPU 读(接收数据)
- DMA 接收完成前,不要提前 Invalidate。
- 在 DMA 完成中断中,先 Invalidate(丢弃)D-Cache,再读取数据。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
SCB_InvalidateDCache_by_Addr((uint32_t*)dma_buffer, sizeof(dma_buffer));
// 现在可以安全读取 dma_buffer
}
注意:Invalidate 会丢弃未写回的数据,若缓冲区同时被 CPU 写过,必须先 Clean 再 Invalidate。
四、完整代码示例(UART DMA 接收)
#include "stm32h7xx_hal.h"
__attribute__((aligned(32))) uint8_t rx_buffer[128];
void uart_dma_init(void) {
// 配置 UART 和 DMA,使能 DMA 接收
HAL_UART_Receive_DMA(&huart1, rx_buffer, sizeof(rx_buffer));
}
// DMA 接收完成回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART1) {
// 1. 无效化 D-Cache,确保读到 DMA 写入的最新数据
SCB_InvalidateDCache_by_Addr((uint32_t*)rx_buffer, sizeof(rx_buffer));
// 2. 处理数据
process_data(rx_buffer, sizeof(rx_buffer));
// 3. 重新启动接收
HAL_UART_Receive_DMA(&huart1, rx_buffer, sizeof(rx_buffer));
}
}
// 发送数据示例
void uart_send_data(uint8_t *data, uint16_t len) {
// 确保数据在 32 字节对齐的缓冲区中
__attribute__((aligned(32))) static uint8_t tx_buffer[128];
memcpy(tx_buffer, data, len);
// Clean D-Cache,将数据写回 SRAM
SCB_CleanDCache_by_Addr((uint32_t*)tx_buffer, len);
HAL_UART_Transmit_DMA(&huart1, tx_buffer, len);
}
五、踩坑复盘与注意事项
-
坑1:缓冲区未对齐。导致 Invalidate 时误伤相邻变量,程序跑飞。务必使用
aligned(32)。 - 坑2:Clean/Invalidate 顺序错误。发送前忘 Clean,接收后忘 Invalidate,数据错乱。
- 坑3:在 DMA 传输过程中操作 Cache。例如 DMA 还在写,CPU 就 Invalidate,可能读到半新半旧数据。务必在 DMA 完成中断中处理。
-
坑4:使用
SCB_InvalidateDCache()全局无效化。这会丢弃所有 Cache 数据,极大降低性能,应使用按地址操作。 - 坑5:MPU 配置不当。可将 DMA 缓冲区配置为 Write-Through 或 Non-Cacheable,但会牺牲性能。推荐仍用 Cache + 手动维护。
额外提醒:STM32H7 的 Cache 维护函数在 core_cm7.h 中,需包含 #include "stm32h7xx.h"。若使用 RTOS,注意临界区保护。
六、总结
D-Cache 与 DMA 的一致性并不复杂,核心就是:对齐、Clean 前、Invalidate 后。理解 32 字节 Cache 行的操作粒度,严格遵循时序,就能避开绝大多数坑。希望本文能帮你少走弯路,充分发挥 STM32H7 的性能。