
去年做 STM32H7 项目的时候我为了把一段音频处理函数跑得更快决定把它挪进 ITCM。编译很顺畅链接也过了结果在 STM32CubeProgrammer 下载固件时直接弹出一个让我卡住一晚上的错误duplicate memory error。关键是报错指向的地址区间正好在 0x00000000 附近也就是 ITCM 的地址范围。那一瞬间我的第一反应是链接脚本写错了但看了一眼又觉得没什么大问题毕竟函数段分配确实是按 ITCM 来的。后来通过一步步排查才发现问题根本不在于“函数有没有放进 ITCM”而在于「放进去的方式」没有告诉烧录工具这个函数到底应该从哪里加载。这篇文章就把完整的排查过程、根因分析和修复方案写出来。如果你是刚接触 H7 系列、想把关键函数放进 ITCM/DTC 缓存区或者已经在 CubeProgrammer 里遇到过类似报错这篇应该能帮你省下不少时间。1. 报错现场编译通过下载时却卡在“重复内存”1.1 项目背景和环境先交代一下我的测试环境芯片STM32H743ZITx开发环境STM32CubeIDE 1.9.0GCC arm-none-eabi 工具链烧录工具STM32CubeProgrammer 2.8.0命令行和 GUI 都试过目标把一个 DSP 算法函数放到 ITCM利用它零等待、CPU 直接访问的优势当时我写的函数标记很简单就是用__attribute__((section(.itcm)))把目标函数放进自定义段__attribute__((section(.itcm))) void audio_process_fast(uint16_t *buf, uint32_t len) { for (uint32_t i 0; i len; i) { // 一些对时延敏感的运算 } }然后在链接脚本里给 ITCM 划过一个段区域原以为是“标准的操作”谁知道就是这一步埋了雷。1.2 报错信息与第一轮尝试在用 STM32CubeProgrammer 烧录.elf文件时工具给出的错误信息大概如下不同版本文本会有些差异但核心都是 duplicate memoryError: memory region overlap detected at address 0x00000000 Error: duplicate memory error第一次看到这报错我的直觉是「ITCM 地址和别的区域冲突了」。于是我先做了几轮常规排查查内存占用把链接脚本里各个段的大小打印出来确认 ITCM 没有溢出。关闭Verify after download无效报错出现在写入之前和校验无关。只烧.hex文件结果一样因为 hex 是从同一个 ELF 转出来的地址信息没变。换用 CubeIDE 的 Debug 模式这次报错变成了类似「Cannot access memory at 0x00000000」的提示总之还是卡在 ITCM 这个地址上。试了一大圈发现问题不在 CubeProgrammer 的配置而在 ELF 文件本身携带的地址映射信息有问题。2. 先搞清楚 ITCM 和 Flash 的“同地址不同名”关系2.1 为什么 H7 的 0x00000000 并不是“普通 RAM”很多教材讲 STM32H7 内存映射时会直接画一个表地址范围区域大小0x00000000 - 0x0000FFFFITCM-RAM64KB0x20000000 - 0x2001FFFFDTCM-RAM128KB0x08000000 - 0x080FFFFFFlash1MB这个表本身没错但很容易让人产生一个错误印象ITCM 就是一个普通的 64KB RAM想用就能用。实际在 STM32H7 上0x00000000 这个地址在复位后到底是访问内部 Flash 还是访问真正的 ITCM 物理 RAM取决于启动配置。具体来说当芯片配置为从 Flash 启动时这也是绝大多数应用场景0x00000000 区域会被映射到内部 Flash。也就是说CPU 访问 0x00000000 时实际读到的内容来自 Flash而不是一块独立的 RAM。这个设计是为了兼容早期的启动方式——不少旧代码会在 0x00000000 处放向量表然后从那里开始执行。真正要让 ITCM 物理 RAM 在 0x00000000 生效你需要通过系统控制块里的 ITCMCR 寄存器使能 ITCM并且确保 Flash 控制器不再把这段地址重映射到 Flash。STM32CubeH7 的SystemInit()里通常会有类似这样的代码SCB-ITCMCR (SCB-ITCMCR ~(SCB_ITCMCR_ASZ_Msk | SCB_ITCMCR_WRAP_Msk)) | SCB_ITCMCR_EN_Msk;如果你写的裸机代码没有调用SystemInit()就得自己在启动早期初始化 ITCM否则就算链接脚本写得对函数放到 0x00000000 后也无法按预期执行。2.2 理解 linker 里的 VMA 和 LMA这是全文最关键的认知很多人写链接脚本时只关注“这个段放在哪个地址”却忽略了一个重要概念运行地址VMA和加载地址LMA是两个完全不同的东西。VMAVirtual Memory Address程序运行时这个段实际所在的地址。对 ITCM 里的函数来说VMA 是 0x00000000。LMALoad Memory Address程序烧录时这段数据被存放在非易失存储器里的地址。对绝大多数从 Flash 启动的 MCU 来说LMA 是 0x08000000 之后的某个 Flash 地址。为什么会有这种区分因为 RAM 掉电就丢。你不可能把函数“烧”进 ITCMITCM 不是 Flash。正确做法是把函数的机器码首先存放在 Flash 里的某个位置LMA上电后由启动代码把它从 Flash 拷贝到 ITCMVMA然后 CPU 再去 ITCM 执行。GNU ld 中常见的写法是.itcm : { *(.itcm) *(.itcm*) } ITCM AT FLASH这里的 ITCM指定 VMA 放在 ITCM 区域AT FLASH指定 LMA 放在 Flash 区域。这行语法是整个问题的分水岭。很多人漏掉AT FLASH只写成.itcm : { *(.itcm) } ITCM两种写法在编译阶段都不会报错objdump出来的符号地址也一样。但 ELF 文件里携带的加载地址信息完全不同。而 CubeProgrammer 在烧录时恰恰是依据加载地址来决定往哪里写数据的。2.3 duplicate memory error 到底在检查什么CubeProgrammer 读取 ELF 时会解析里面的程序段program header / LOAD segment拿到每个段需要写入的地址范围然后检查这些范围是否存在“重复”或“冲突”。在 H7 这类带地址别名机制的芯片上工具的设备数据库知道 0x00000000 和 Flash 区域在特定启动模式下有映射关系。当你生成的 ELF 里同时存在以下情况时冲突就出现了.isr_vector、.text等正常代码段的 LMA 在 0x08000000 区域.itcm段因为漏写了AT FLASH导致 LMA 也是 0x00000000两个加载地址在工具的视角里都指向了同一个物理存储的别名区域但内容又是两份不同的数据于是判定为“重复内存”。这就是 duplicate memory error 的本源。3. objdump 一锤定音看 LMA/VMA 找出重复来源3.1 用 arm-none-eabi-objdump 查看 section 表当时我先怀疑是链接脚本里某个区域的 ORIGIN 写重了。但排查之后发现所有 MEMORY 区域定义都正常于是决定直接看 ELF 内部的 section 信息这是最直观的方式arm-none-eabi-objdump -h build/my_project.elf输出关键部分Sections: Idx Name Size VMA LMA File off Algn 0 .isr_vector 00000400 08000000 08000000 00010000 2**2 1 .text 00009abc 08000800 08000800 00010400 2**2 2 .rodata 00001a88 08009abc 08009abc 00019ebc 2**2 ... 6 .itcm 000000a8 00000000 00000000 0001b000 2**2看到第 6 行的时候问题基本就清楚了。.itcm段的 VMA 是00000000这没问题但 LMA 也是00000000这说明链接器认为这段数据应该被加载到 ITCM 地址。对烧录工具来说它不会“聪明地”帮你把数据放到 Flash 再运行时拷贝它只是忠实地想往 0x00000000 写数据。而 H7 的 0x00000000 在从 Flash 启动模式下根本不是一个可正常写入的独立 RAM于是冲突。正常的输出应该长这样6 .itcm 000000a8 00000000 0801b000 0001b000 2**2VMA 仍然是 0x00000000但 LMA 变成了 0x0801b000也就是 Flash 里的一个空闲位置。这样 CubeProgrammer 就知道这段代码虽然运行时在 ITCM但烧录时只需写入 Flash。3.2 用 readelf 验证加载段信息objdump -h看的是 section 视角如果想确认 ELF 最终生成的加载段信息可以用arm-none-eabi-readelf -l build/my_project.elf输出里会列出 LOAD 段每个 LOAD 段包含几个 section。重点看 PT_LOAD 段的 p_paddr物理地址/加载地址和 p_vaddr虚拟地址/运行地址。如果.itcm所在 LOAD 段的 p_paddr 出现在 0x00000000 附近就进一步确认了问题。3.3 对比“正确”和“错误”两种情况的实际差异我把修正前后两份 ELF 的 section 表整理成对比状态SectionVMALMA结果错误.itcm0x000000000x00000000CubeProgrammer 报 duplicate memory正确.itcm0x000000000x0801b000烧录正常运行时拷贝到 ITCM这一列出来根因已经不需要再猜了。问题就是AT FLASH的缺失导致 LMA 错误地落在了 ITCM 地址上。CubeProgrammer 无法判断这是一个“运行时映射”场景直接把它当作重复内存冲突。4. 修复链接脚本并实现 Flash 到 ITCM 的拷贝4.1 修好链接脚本修复方式就是补上AT FLASH。这是我最终使用的链接脚本片段MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 1024K ITCM (rwx) : ORIGIN 0x00000000, LENGTH 64K DTCM (rwx) : ORIGIN 0x20000000, LENGTH 128K } SECTIONS { .isr_vector : { KEEP(*(.isr_vector)) } FLASH .text : { *(.text*) } FLASH .itcm : { . ALIGN(4); __itcm_start__ .; *(.itcm) *(.itcm*) . ALIGN(4); __itcm_end__ .; } ITCM AT FLASH __itcm_load_start__ LOADADDR(.itcm); }几个关键点__itcm_start__和__itcm_end__是 ITCM 段的运行地址边界给拷贝代码用。__itcm_load_start__ LOADADDR(.itcm)导出这个段在 Flash 里的加载地址。这是启动代码里需要读取的源地址。. ALIGN(4)保证拷贝时按 32 位对齐避免非对齐访问带来的额外开销。.text段明确放进 Flash不要让普通代码也跑到 ITCM 里。ITCM 只有 64KB放太多函数很容易溢出。修完链接脚本后我在命令行验证了内存分布arm-none-eabi-ld --print-memory-usage build/my_project.elf输出里可以看到 Flash 区域和 ITCM 区域的占用情况ITCM 的地址被正确分配到 0x00000000 起始而对应的加载区在 Flash 中单独占用了一份空间。4.2 写 Flash 到 ITCM 的拷贝代码链接脚本只是告诉链接器“这段代码的加载地址在哪、运行地址在哪”真正把数据从 Flash 搬运到 ITCM 的还得靠你自己写的拷贝函数。这个函数不能放在.itcm段里否则就成“先有鸡还是先有蛋”的问题了——你应该把它放在.text段保证它一开始就在 Flash 里可执行。我加的拷贝逻辑extern uint32_t __itcm_start__; extern uint32_t __itcm_end__; extern uint32_t __itcm_load_start__; void itcm_init(void) { uint32_t *src __itcm_load_start__; uint32_t *dst __itcm_start__; while (dst __itcm_end__) { *dst *src; } __DSB(); __ISB(); }这里有两个容易被忽略的点用uint32_t指针做拷贝比逐字节拷贝效率高很多。链接脚本里已经对齐到 4 字节所以可以放心用 32 位读写。拷贝结束后必须加__DSB()和__ISB()。因为 ITCM 紧贴 CPU 核心改动它的内容后指令预取流水线里可能还残留旧指令__DSB()确保所有内存写操作完成__ISB()清空流水线让后续取指重新从 ITCM 中读取。调用时机也很有讲究。itcm_init()必须在任何.itcm段函数被调用之前执行。最稳的位置是在main()一开始或者更早的SystemInit()里。如果你使用了 RTOS还要确保在创建任务之前完成拷贝因为有些 RTOS 的调度器启动后任务代码可能立刻就会跳进 ITCM 函数。4.3 验证烧录和运行修改完链接脚本和启动代码之后重新编译再用 CubeProgrammer 烧录STM32_Programmer_CLI -c portSWD modeUR -w build/my_project.elf -v这次没有报 duplicate memory error。下载完成后复位运行用调试器在audio_process_fast入口打断点确认它在 0x00000000 之后的地址执行而 Flash 里确实多了一份它的机器码备份。我还做了个简单验证把itcm_init()里的拷贝逻辑注释掉再烧录程序跑到 ITCM 函数时直接 HardFault。这从反面说明ITCM 函数如果不手动拷贝CPU 在 0x00000000 执行时实际上会访问到 Flash 的别名区域拿到的内容和预期不一致程序自然崩溃。5. 实战中还会遇到的几个 ITCM 相关坑5.1 Cube