• 正文
  • 相关推荐
申请入驻 产业图谱

STM32 DMA 错误避坑:编译器 RAM 分配陷阱!CCRAM 不可访问问题终极解决

12/23 09:04
259
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

STM32F429 增大 FreeRTOS 堆内存后 Ethernet DMA 报错、ping 不通,核心原因是Keil 编译器随机将 DMA 缓冲区分配到了 CCRAM(内核独占 RAM) ——DMA 无法访问 CCRAM,触发致命总线错误(FBES)。解决方案:要么禁用 CCRAM 分配,要么显式将 DMA 缓冲区固定到 SRAM 范围,彻底规避编译器随机分配风险。

资料获取:开发经验 | LAT1565 编译器随机分配RAM地址导致DMA错误

1. 问题背景:诡异的 DMA 错误触发条件

客户基于 STM32F429IGT6 开发产品,使用 FreeRTOS+Ethernet+USB,遇到关键异常:

  1. 异常现象:FreeRTOS 的Total_Heap_Size增大后,Ethernet DMA 出错,PC 无法 ping 通;减小堆内存后恢复正常,且未超出芯片最大 RAM 容量(剩余几十 KB);
  2. 故障定位:Ethernet 的 DMASR 寄存器FBES位(bit13)=1(致命总线错误),EBS位(25:23)显示 “descriptor/data buffer 读错误”,说明 DMA 从内存读取数据失败;
  3. 编译器差异:IAR 工程无论如何调整堆大小均正常,仅 Keil 工程出现偶发错误。

2. 根源拆解:CCRAM 访问限制与编译器分配机制差异

2.1 核心矛盾:CCRAM 是内核独占 RAM,DMA 无法访问

STM32F429 的 CCRAM(地址 0x10000000~0x1000FFFF,64KB)是Cortex-M4 内核专属 RAM,仅支持内核访问,DMA 控制器(包括 Ethernet DMA)无法通过 AHB 总线访问该区域。

2.2 编译器 RAM 分配机制差异(Keil vs IAR)

编译器 RAM 分配规则 问题风险
Keil 默认包含 SRAM(0x20000000~)和 CCRAM(0x10000000~),调整堆 / 栈大小时,编译器会随机分配地址到任一区域 DMA 缓冲区可能被分到 CCRAM,触发访问错误
IAR CubeMX 生成的工程默认仅使用 SRAM,CCRAM 未被启用或未分配数据 DMA 缓冲区始终在 SRAM,无访问风险

2.3 问题触发逻辑

  • Keil 中增大Total_Heap_Size后,编译器会重新规划 RAM 分配;
  • 若 Ethernet DMA 的缓冲区(如DMARxDiscrTabDMATxDiscrTab)被随机分配到 CCRAM;
  • DMA 访问该缓冲区时,因总线权限不足触发致命错误,Ethernet 功能瘫痪。

3. 解决方案:两种落地方式,彻底规避分配陷阱

3.1 方案 1:禁用 CCRAM,让 Keil 仅使用 SRAM(推荐)

核心是让 Keil 编译器只在 DMA 可访问的 SRAM 范围内分配地址,步骤如下:

  1. 打开 Keil 工程,点击「Project」→「Options for Target」→「Target」选项卡;
  2. 在「Read/Write Memory Areas」中,删除IRAM2(CCRAM)配置,仅保留IRAM1(SRAM):
    • IRAM1:Start=0x20000000,Size=0x30000(根据芯片实际 SRAM 大小调整);
    • 清空 IRAM2 的 Start 和 Size,取消勾选启用;
  3. 重新编译工程,编译器会将所有数据(包括 DMA 缓冲区、FreeRTOS 堆)分配到 SRAM,DMA 访问无权限问题。

3.2 方案 2:显式固定 DMA 缓冲区到 SRAM 地址(灵活适配场景)

若需保留 CCRAM 供内核使用,可通过代码强制指定 DMA 缓冲区的存储地址,避免编译器随机分配:

(1)Keil 编译器(使用__attribute__

// 固定DMA接收描述符到SRAM(0x20010000~0x2001FFFF范围)
__attribute__((at(0x20010000))) uint8_t DMARxDiscrTab[288];
// 固定DMA发送描述符到SRAM
__attribute__((at(0x20011000))) uint8_t DMATxDiscrTab[288];

// FreeRTOS堆也可固定到SRAM,避免占用DMA缓冲区地址
__attribute__((at(0x20020000))) uint8_t ucHeap[1024*100]; // 100KB堆
#define configTOTAL_HEAP_SIZE (sizeof(ucHeap))

(2)IAR 编译器(使用#pragma location

// 指定缓冲区位于SRAM地址0x20010000
#pragma location=0x20010000
uint8_t DMARxDiscrTab[288];
#pragma location=0x20011000
uint8_t DMATxDiscrTab[288];

3.3 验证方法

  • 编译后查看 map 文件,确认DMARxDiscrTabDMATxDiscrTab等 DMA 相关变量的地址在0x20000000~0x2002FFFF(SRAM 范围),而非0x10000000~(CCRAM 范围);
  • 增大 FreeRTOS 堆内存后,Ethernet 可正常 ping 通,DMASR 寄存器无 FBES 错误标志。

4. 扩展注意:其他 STM32 系列的类似陷阱

除了 STM32F4 的 CCRAM,以下 RAM 区域同样存在 DMA 访问限制,需避免分配 DMA 缓冲区:

  • STM32H7 的 DTCMRAM(0x20000000~0x2001FFFF):仅内核可访问,DMA 无法访问;
  • STM32L4 的 CCM SRAM(0x10000000~):部分型号为内核独占,DMA 无访问权限;
  • 核心原则:DMA 缓冲区必须分配到 “DMA 可访问的 RAM 区域”,具体可参考对应型号参考手册的 “系统架构” 章节。

5. 开发经验小结

  1. 编译器分配风险:Keil、GCC 等编译器默认可能使用所有可用 RAM,包括内核独占区域,DMA / 外设缓冲区需显式指定地址或限制 RAM 范围;
  2. 优先禁用无用 RAM:若不使用 CCRAM/DTCMRAM,直接在编译器中禁用该区域分配,从根源规避错误;
  3. 显式地址绑定:关键缓冲区(DMA、外设 FIFO)建议用__attribute__#pragma固定地址,确保在 DMA 可访问范围;
  4. map 文件排查:遇到 DMA 访问错误,优先查看 map 文件,确认缓冲区地址是否在合法范围。

相关推荐