# 一、问题现象与背景 某产品基于 STM32F407,使用 DMA 接收串口数据,并启用 D-Cache 提高 CPU 性能。调试时发现:接收数据偶尔出现乱码或丢失,且仅在优化等级较高时出现,逻辑分析仪显示数据正确,但 CPU 读取的缓冲区内容错误。 ## 根因分析 - STM32F4 的 Cortex-M4 内核具有 4KB D-Cache,默认在复位后关闭,但若通过 `SCB_EnableDCache()` 启用,CPU 访问 SRAM 时会先经过 Cache。 - DMA 控制器(如 DMA2)不经过 Cache,直接访问物理 SRAM。 - 当 CPU 读取 DMA 刚写入的数据时,若 Cache 中已有旧数据(脏行),CPU 会读到过期数据;反之,若 CPU 先写入数据再由 DMA 读取(如 DMA 发送),Cache 中的新数据可能未回写,DMA 读到旧数据。 # 二、解决方案:MPU 配置非缓存区域 MPU(内存保护单元)可以设置内存区域的属性,包括 Cache 策略。将 DMA 缓冲区所在的 SRAM 区域配置为“非缓存(Non-cacheable)”或“写透(Write-through)”,即可保证 CPU 与 DMA 访问的一致性。 ## 配置步骤(基于 CubeMX + HAL 库) 1. 在 CubeMX 中启用 MPU:`System Core > MPU`,勾选 Enable。 2. 添加一个 Region,设置: - Region Base Address:例如 `0x20000000`(SRAM1 起始地址) - Region Size:根据需要,如 16KB(覆盖缓冲区) - TEX=1, C=0, B=0(非缓存)或 TEX=0, C=1, B=1(写透,但推荐非缓存) - Enable Region 勾选。 3. 生成代码后,在 `main()` 中调用 `MPU_Config()`(CubeMX 自动生成),并确保在 `SCB_EnableDCache()` 之前调用。 ## 代码示例 ```c // 自定义 DMA 缓冲区(必须位于 MPU 配置的区域内) #define BUF_SIZE 256 __attribute__((section(".ARM.__at_0x20000000"))) uint8_t dma_rx_buf[BUF_SIZE]; // 在 main 函数中初始化顺序: void main(void) { HAL_Init(); SystemClock_Config(); // 1. 配置 MPU(必须在使能 D-Cache 之前) MPU_Config(); // 2. 使能 D-Cache SCB_EnableDCache(); // 3. 初始化外设和 DMA MX_GPIO_Init(); MX_DMA_Init(); MX_USART2_UART_Init(); // 启动 DMA 接收 HAL_UART_Receive_DMA(&huart2, dma_rx_buf, BUF_SIZE); while (1) { // 主循环中处理数据 } } // CubeMX 生成的 MPU 配置函数(可手动调整) void MPU_Config(void) { MPU_Region_InitTypeDef MPU_InitStruct = {0}; HAL_MPU_Disable(); // 配置 SRAM1 区域为非缓存(地址 0x20000000,大小 16KB) MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.BaseAddress = 0x20000000; MPU_InitStruct.Size = MPU_REGION_SIZE_16KB; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1; MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); } ``` # 三、注意事项 - **MPU 配置必须在使能 D-Cache 之前**,否则无效。 - 缓冲区地址必须落在 MPU 配置的区域内,且大小匹配。若使用多个缓冲区,可配置多个 Region 或扩大区域。 - 非缓存区域会降低访问速度,但仅对 DMA 缓冲区影响较小;若追求性能,可使用“写透”策略(TEX=0, C=1, B=1),但需注意写透仍可能产生读不命中,一致性不如非缓存。 - 若使用 `__attribute__((section))` 指定地址,需确保链接脚本中该段未被其他变量占用。 - 对于 DMA 发送(CPU 写,DMA 读),即使配置了非缓存,也建议在启动 DMA 前调用 `SCB_CleanDCache()` 确保数据回写(非缓存区域无需,但写透区域需要)。 - 调试时可用 `SCB_InvalidateDCache_by_Addr()` 强制失效缓存行,但根治方案仍是 MPU 配置。 # 四、总结 通过 MPU 将 DMA 缓冲区配置为非缓存区域,从根本上避免了 D-Cache 与 DMA 的数据一致性问题。本案例中,配置后串口接收数据稳定,不再出现乱码。对于使用 STM32F4 且启用 D-Cache 的开发者,建议在项目初期就规划好内存区域属性,避免后期排查的困扰。