问题现象与背景
在STM32H7系列MCU上开发以太网应用时,许多开发者会遇到一个棘手问题:系统初始化正常,但一旦启动以太网DMA传输(发送或接收),立即触发HardFault。调试器显示故障发生在DMA描述符访问阶段,而描述符本身位于SRAM中,看似一切正常。
根本原因往往在于MPU(内存保护单元)配置不当,导致DMA无法正确访问描述符内存区域,或Cache与DMA之间产生数据一致性问题,进而引发总线错误,最终表现为HardFault。
原理分析:MPU、Cache与DMA的三角关系
STM32H7的存储架构
STM32H7采用Cortex-M7内核,具有:
- L1 Cache:I-Cache和D-Cache,提升CPU访问性能
- MPU:可配置内存区域的访问权限、Cache策略、共享属性
- DMA:直接访问物理内存,不经过Cache
关键矛盾点
- CPU通过Cache访问SRAM,DMA直接访问SRAM
- 若描述符区域被配置为Cacheable且未正确维护一致性,DMA可能读到旧数据
- 若MPU将描述符区域配置为不可访问或错误属性,DMA访问将产生总线错误
- STM32H7的以太网DMA描述符必须位于可被DMA访问且属性正确的内存中
典型错误配置
- 描述符区域被MPU配置为
Device或Strongly Ordered,DMA访问受限 - 描述符区域未配置MPU,默认属性导致Cache一致性问题
- 描述符区域被配置为
Non-shareable,多主设备访问冲突
排查路径
第一步:确认HardFault来源
使用调试器查看CFSR(Configurable Fault Status Register)和BFAR(BusFault Address Register):
void HardFault_Handler(void) {
volatile uint32_t cfsr = SCB->CFSR;
volatile uint32_t bfar = SCB->BFAR;
volatile uint32_t hfsr = SCB->HFSR;
// 在此设置断点,观察cfsr和bfar
while(1);
}
- 若
CFSR的BFARVALID置位,BFAR给出出错地址,通常指向描述符区域 - 若
CFSR的IMPRECISERR置位,说明是写缓冲导致的非精确总线错误
第二步:检查MPU配置
查看MPU区域配置,确认描述符所在SRAM区域的属性:
void MPU_Config(void) {
HAL_MPU_Disable();
MPU_Region_InitTypeDef MPU_InitStruct = {0};
// 描述符区域:0x30000000 - 0x30003FFF (SRAM1, 16KB)
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30000000;
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; // 关键:禁止Cache
MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE; // 关键:共享
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
要点:
-
IsCacheable = NOT_CACHEABLE:避免Cache一致性问题 -
IsShareable = SHAREABLE:确保DMA与CPU访问一致 -
AccessPermission = FULL_ACCESS:允许读写 - 区域大小必须覆盖所有描述符
第三步:验证链接脚本与内存分配
确保描述符数组被放置在正确的MPU区域:
// 在链接脚本中定义段
__attribute__((section(".eth_desc"))) ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT];
__attribute__((section(".eth_desc"))) ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT];
链接脚本片段:
.eth_desc (NOLOAD) : {
. = ALIGN(32);
*(.eth_desc)
} > RAM_D2
第四步:检查DMA可访问性
STM32H7中,以太网DMA只能访问特定内存区域(如D2域SRAM)。确认描述符位于D2 SRAM(0x30000000起),而非DTCM(0x20000000),因为DTCM不支持DMA。
完整配置示例
#include "stm32h7xx_hal.h"
// 描述符定义,强制32字节对齐
__attribute__((aligned(32), section(".eth_desc")))
ETH_DMADescTypeDef DMARxDscrTab[ETH_RX_DESC_CNT];
__attribute__((aligned(32), section(".eth_desc")))
ETH_DMADescTypeDef DMATxDscrTab[ETH_TX_DESC_CNT];
// MPU配置函数
void MPU_Config(void) {
HAL_MPU_Disable();
MPU_Region_InitTypeDef MPU_InitStruct = {0};
// 配置D2 SRAM为Non-Cacheable, Shareable
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB; // 覆盖整个D2 SRAM
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_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}
// 以太网初始化中调用
void ETH_Init(void) {
MPU_Config();
// ... 其他初始化
HAL_ETH_Init(&heth);
}
注意事项
-
Cache维护:若必须使用Cacheable内存,需在DMA传输前后调用
SCB_CleanDCache_by_Addr()和SCB_InvalidateDCache_by_Addr() - 对齐要求:描述符必须32字节对齐,否则DMA可能访问错误
- MPU区域重叠:避免多个MPU区域重叠导致属性冲突
- DMA可访问域:确认使用D2域SRAM,DTCM和ITCM不支持以太网DMA
-
调试技巧:在HardFault中打印
CFSR和BFAR,快速定位总线错误地址 - HAL库版本:不同版本HAL库MPU配置API略有差异,注意兼容性
通过以上排查路径和正确配置,可彻底解决STM32H7以太网DMA描述符访问导致的HardFault问题,确保网络通信稳定可靠。