基于 Cortex-M7 内核的 MPU 配置不当导致 DMA 传输数据损坏的排查方法
👁 1 阅读 · 2026-08-27 · 嵌入式
在基于 Cortex-M7 内核的高性能 MCU(如 STM32H7)开发中,MPU(内存保护单元)配置不当可能引发 DMA 传输数据损坏,表现为随机数据错误或系统崩溃。本文从 MPU 与 DMA 的交互原理出发,分析典型故障场景,提供系统性排查方法,并给出完整配置示例与注意事项,帮助开发者快速定位并解决问题。
# 基于 Cortex-M7 内核的 MPU 配置不当导致 DMA 传输数据损坏的排查方法
## 一、问题现象与背景
在 STM32H7 等 Cortex-M7 内核 MCU 上,开发者常使用 DMA 进行高速数据传输(如 ADC 采样、UART 接收、外部存储器读写)。然而,当启用 MPU 后,DMA 传输的数据可能随机出现错误,例如:
- 接收缓冲区中部分字节被改写为 0x00 或 0xFF
- 传输完成后校验和错误
- 系统进入 HardFault 或 BusFault
这些现象往往与 MPU 配置不当直接相关。
## 二、MPU 与 DMA 的交互原理
### 1. Cortex-M7 的 MPU 特性
Cortex-M7 的 MPU 支持最多 16 个区域,每个区域可配置基地址、大小、访问权限、缓存属性(Cacheability)和共享属性(Shareability)。关键点在于:
- MPU 控制 CPU 对内存的访问,但 DMA 控制器通常不经过 MPU,而是直接通过总线矩阵访问内存。
- 然而,MPU 设置的内存属性(如 Cacheable、Bufferable)会影响 CPU 对同一内存区域的缓存行为,进而导致 CPU 与 DMA 看到的数据不一致。
### 2. 缓存一致性问题
Cortex-M7 内置 L1 Cache(I-Cache 和 D-Cache)。当 DMA 写入内存时,如果 CPU 的 D-Cache 中缓存了该区域的旧数据,CPU 读取时会命中缓存,得到旧值,而 DMA 写入的新值被忽略。反之,如果 DMA 读取内存,而 CPU 刚写入了数据但还在 Cache 中,DMA 可能读到旧数据。
MPU 的作用之一就是定义内存区域的缓存策略。如果配置不当(例如将 DMA 缓冲区配置为 Cacheable),就会引发缓存一致性问题。
## 三、典型故障场景分析
### 场景 1:DMA 缓冲区被配置为 Cacheable
```c
// 错误配置示例:将 DMA 缓冲区设为 Cacheable
MPU_Region_InitTypeDef MPU_InitStruct;
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = (uint32_t)rx_buffer;
MPU_InitStruct.Size = MPU_REGION_SIZE_256B;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL_0; // TEX=0, C=1, B=1 => Cacheable
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
```
后果:DMA 写入 rx_buffer 后,CPU 读取时可能命中 D-Cache 中的旧数据,导致数据错误。
### 场景 2:MPU 区域未覆盖 DMA 缓冲区
如果 DMA 缓冲区所在内存区域未被 MPU 覆盖,则默认属性可能为 Cacheable(取决于系统默认设置),同样会导致问题。
### 场景 3:MPU 区域大小或对齐错误
MPU 要求区域大小必须是 2 的幂,且基地址对齐到区域大小。若配置错误,可能导致部分缓冲区未被保护或属性不一致。
## 四、系统性排查方法
### 步骤 1:确认 DMA 缓冲区是否被 Cache 影响
- 检查 DMA 缓冲区地址是否在 D-Cache 可缓存范围内(通常 SRAM 区域)。
- 使用调试器查看 DMA 写入后的内存值,与 CPU 读取的值对比。
### 步骤 2:检查 MPU 配置
- 列出所有 MPU 区域,确认 DMA 缓冲区所在区域是否被正确配置。
- 验证区域的 TEX、C、B 位组合。建议使用以下组合:
- 非 Cacheable、非 Bufferable:TEX=0, C=0, B=0(适用于 DMA 缓冲区)
- 非 Cacheable、Bufferable:TEX=0, C=0, B=1(适用于外设寄存器)
### 步骤 3:使用 Cache 维护操作
如果必须使用 Cacheable 区域(例如大块数据),则需要在 DMA 传输前后执行 Cache 清理/无效化操作:
- 在 DMA 写入前:清理(Clean)CPU 缓存,确保旧数据写回内存。
- 在 DMA 写入后:无效化(Invalidate)CPU 缓存,强制重新从内存读取。
### 步骤 4:检查总线互连
Cortex-M7 的 DMA 可能通过 AXI 或 AHB 总线访问内存,确保 MPU 配置不影响总线事务(通常 MPU 不直接作用于 DMA,但系统级缓存策略可能影响)。
## 五、正确配置示例(以 STM32H7 为例)
以下代码展示了如何将 DMA 缓冲区配置为 Non-Cacheable,并启用 MPU 保护:
```c
// 正确配置:DMA 缓冲区设为 Non-Cacheable
void MPU_Config(void)
{
MPU_Region_InitTypeDef MPU_InitStruct;
// 禁用 MPU
HAL_MPU_Disable();
// 配置 SRAM 区域(例如 0x30000000,大小 256KB)为 Non-Cacheable
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL_0;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
// 使能 MPU
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
// DMA 缓冲区定义(需放在 0x30000000 区域)
__attribute__((section(".noncacheable"))) uint8_t rx_buffer[256];
```
注意:链接脚本中需将 `.noncacheable` 段分配到 0x30000000 起始的 SRAM。
## 六、完整代码示例:DMA 接收 + 缓存维护
```c
// 假设使用 UART DMA 接收
uint8_t rx_buffer[256];
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
// 无效化 D-Cache,确保读取最新 DMA 数据
SCB_InvalidateDCache_by_Addr((uint32_t *)rx_buffer, sizeof(rx_buffer));
// 处理数据...
}
}
void Start_DMA_Receive(void)
{
// 清理 D-Cache,确保 CPU 写入的缓冲区内容(如果有)写回内存
SCB_CleanDCache_by_Addr((uint32_t *)rx_buffer, sizeof(rx_buffer));
HAL_UART_Receive_DMA(&huart1, rx_buffer, sizeof(rx_buffer));
}
```
## 七、注意事项
- **MPU 区域大小必须为 2 的幂,且基地址对齐**。例如 256B 区域,基地址必须 256B 对齐。
- **不要将 DMA 缓冲区与频繁访问的变量混放在同一 Cacheable 区域**,否则性能与正确性难以兼顾。
- **使用 Cache 维护函数时,地址必须 32 字节对齐**(Cortex-M7 的 Cache line 大小通常为 32 字节)。
- **在启用 MPU 前,先确保所有外设和内存区域配置正确**,否则可能引发总线错误。
- **调试时,可临时禁用 D-Cache 或 MPU 来验证问题是否与缓存相关**。
## 八、总结
MPU 配置不当导致 DMA 数据损坏,本质是缓存一致性问题。通过正确配置 MPU 区域为 Non-Cacheable,或在使用 Cacheable 区域时执行必要的缓存维护操作,可以彻底解决。排查时,建议从 MPU 配置、缓存操作、内存布局三方面入手,结合调试器观察内存变化,快速定位根因。
掌握这些技巧,能显著提升 Cortex-M7 项目的稳定性,避免隐蔽的数据错误。