摘要:为什么全局变量被放在了 RAM 末尾?为什么 DMA 非要访问 0x24000000?不是编译器任性,而是 链接文件(Linker Script)​ 决定了这一切。本文手把手拆解 .ld文件,教你如何像上帝一样掌控内存布局。


一、问题现象:为什么我要改链接文件?

**DMA 传输失败,提示地址错误;

把变量定义成 const后,Flash 反而溢出了;

想预留一段内存给 FPGA 用,编译器却把它占了。**

这些都不是代码问题,而是 内存规划权​ 的问题。


二、核心概念:链接文件是干什么的?

链接文件(.ld)告诉链接器两件事:

  1. MEMORY:芯片有哪些内存块?(Flash、RAM、CCM、SDRAM)

  2. SECTIONS:代码和数据分别塞进哪块内存?

1. 物理内存视图(以 STM32H7 为例)

0x0000_0000 ─┬─ Flash (ITCM) 1MB
0x0800_0000 ─┘
0x2000_0000 ─┬─ DTCM-RAM 128KB (给内核用,最快)
0x2400_0000 ─┬─ AXI-SRAM 512KB (给 DMA 用,最稳)
0x3000_0000 ─┬─ SRAM1/2/3 288KB
0x3800_0000 ─┬─ SRAM4 64KB

痛点:DMA 不能访问 DTCM,只能访问 AXI-SRAM。如果不改链接文件,DMA 必死。


三、实战拆解:一步步写 .ld文件

1. 定义 MEMORY(画地图)

MEMORY
{
  FLASH (rx)    : ORIGIN = 0x08000000, LENGTH = 1024K
  DTCM_RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
  AXI_RAM (xrw)  : ORIGIN = 0x24000000, LENGTH = 512K
  SDRAM (xrw)    : ORIGIN = 0xC0000000, LENGTH = 8192K
}
  • rx:只读(代码、常量)

  • xrw:可读可写(变量、栈、堆)

2. 定义 SECTIONS(分房子)

SECTIONS
{
  .isr_vector :
  {
    . = ALIGN(4);
    KEEP(*(.isr_vector))
    . = ALIGN(4);
  } > FLASH

  .text :
  {
    . = ALIGN(4);
    *(.text)
    *(.text*)
    . = ALIGN(4);
  } > FLASH

  .rodata :
  {
    . = ALIGN(4);
    *(.rodata)
    *(.rodata*)
    . = ALIGN(4);
  } > FLASH

  .data :
  {
    . = ALIGN(4);
    _sdata = .;
    *(.data)
    *(.data*)
    _edata = .;
    . = ALIGN(4);
  } > DTCM_RAM AT > FLASH

重点解释:

  • > DTCM_RAM:运行时在 DTCM(快)。

  • AT > FLASH:烧录时存在 Flash(省 RAM)。

  • _sdata/ _edata:给启动文件用,用于上电拷贝数据。

3. 自定义段(给工程师用)

这是最实用的技巧。定义一个给 DMA 用的专用段:

.dma_buffer :
{
  . = ALIGN(32); /* DMA 要求 32 字节对齐 */
  _sdma = .;
  *(.dma_buffer)
  _edma = .;
  . = ALIGN(32);
} > AXI_RAM

在 C 代码中这样用:

#pragma location = ".dma_buffer"
uint8_t dma_tx_buffer[2048];

结果dma_tx_buffer必定在 0x24000000开始的 AXI-RAM 中,DMA 绝对不会报错。


四、工程级应用案例

案例 1:解决 DMA 地址错误

错误HAL_DMA_Start(&hdma, (uint32_t)&var, ...)返回 HAL_ERROR

解决:把 var放进 .dma_buffer段,映射到 AXI-RAM。

案例 2:把 GUI 帧缓冲放进 SDRAM

.frame_buffer :
{
  *(.frame_buffer)
} > SDRAM
#pragma location = ".frame_buffer"
uint16_t lcd_fb[480][272];

案例 3:预留内存给 Bootloader

.boot_flag (NOLOAD) :
{
  . = ALIGN(4);
  *(.boot_flag)
} > DTCM_RAM

NOLOAD表示该段不初始化(断电不丢,但上电不清零)。


五、常见坑位 Checklist

  • [ ] 是否把 DMA 缓冲区放在了 DTCM 里?(必死

  • [ ] 是否忘了 AT > FLASH导致 RAM 溢出?

  • [ ] 是否定义了 _sdata/ _edata但启动文件没用?

  • [ ] 自定义段的地址是否对齐(通常 4 或 32 字节)?

  • [ ] 是否把中断向量表放在了 ITCM 或 Flash 开头?


六、写在最后(终极总结)

我是 gqqsherry666,一个拒绝调包、专注底层逻辑的嵌入式架构师。

如果说 Map 文件是 X 光片,那 Linker Script 就是手术刀。

不懂链接文件,你永远只是代码的搬运工;

懂了链接文件,你才是芯片的主人。


原创文章,转载请注明出处!

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐