问题现象
用 MicroLink 调试 AT32F403A 时,想直接用 SEGGER RTT 做打印输出,工具却报了这么个错:
|
|
明明是按 RTT 的标准流程配的,为什么找不到?
排查思路
第一步:理解 RTT 的工作原理
SEGGER RTT 的核心是一个叫做 _SEGGER_RTT 的控制块,里面存着 "SEGGER RTT" 标识字符串。调试器(或 MicroLink 这类工具)需要在 MCU 的内存里找到这个控制块,才能读取打印数据。
找控制块的方式有两种:
| 工具 | 扫描方式 |
|---|---|
| J-Link | 自动扫描整个 SRAM 范围,找到标识符就停 |
| MicroLink | 从固定地址 0x20000000 开始扫描 |
问题根源就在这里 —— MicroLink 不会自己找,它只认 0x20000000。但 _SEGGER_RTT 控制块是链接器自由分配的,不一定恰好落在 SRAM 开头。
第二步:尝试固定地址
既然 MicroLink 只认 0x20000000,那把控制块固定放到那里不就行了?
第一反应是用 AC6 链接器的 __attribute__((at(0x20000000))):
|
|
编译,报错:
|
|
为什么? AC6 链接器对 ZI(零初始化)和 RW(有初值)数据在同一个执行区域有严格限制。_SEGGER_RTT 没给初值,链接器认为它是 ZI 类型,而 main.c 里其他数据是 RW 类型,混在一个区域就冲突了。
第三步:给初值试试
那把初值设成 0 总行了吧?
|
|
还是报同样的错。原因是 AC6 对全零初值做了优化,仍然识别为 ZI。
第四步:scatter 文件方案
理论上可以在 scatter 文件里专门划一个 UNINIT 区域来放 RTT 控制块:
|
|
这个方案确实可行,但 scatter 文件一碰就容易牵一发而动全身,能不动最好不动。
最终方案:非零初值 + at()
核心思路:让链接器把 RTT 相关变量识别为 RW 类型,这样就不会和 ZI 冲突。
在 SEGGER_RTT.c 中修改三个变量:
|
|
关键点逐个解释
| 写法 | 作用 |
|---|---|
at(0x20000000) |
AC6 链接器自动从该地址开始依次排列三个变量 |
{'\0', 1} |
acID[0]='\0' 满足 INIT() 触发 _DoInit() 的条件;acID[1]=1 非零 → RW 类型 |
{1} |
数组首字节非零 → RW 类型,避开 ZI/RW 混合错误 |
为什么 acID[0] 必须是 '\0'?
RTT 的 INIT() 宏里面有这样一段逻辑:
|
|
如果 acID[0] 不是 '\0',_DoInit() 不会被调用,控制块就不会被正确初始化(包括写入 "SEGGER RTT" 标识)。所以虽然我们要给非零初值来骗过链接器,但第一个字节必须留 '\0',让 _DoInit() 有机会执行。
内存布局
|
|
总计约 1160 字节 固定在 SRAM 开头。AT32F403A 有 96KB SRAM,这点占用完全可以忽略。
踩坑过程一览
| 尝试 | 方案 | 结果 | 原因 |
|---|---|---|---|
| ❌ | __attribute__((section(".ARM.__at_0x20000000"))) |
ZI/RW 混合报错 | ZI 类型与 RW 数据冲突 |
| ❌ | 初值 = {0} |
同上 | AC6 优化全零初值为 ZI |
| ⚠️ | 修改 scatter 文件 | 可行但麻烦 | 不想碰链接器脚本 |
| ✅ | at() + 非零初值 |
成功 | RW 类型,scatter 不用改 |
验证
修改后重新编译,下载程序,用 MicroLink 连接:
|
|
RTT 打印正常输出,问题解决。
总结
这个问题本质上是工具行为差异(J-Link 会扫描全 SRAM,MicroLink 只认固定地址)加上链接器特性(AC6 对 ZI/RW 混合很敏感)叠加出来的。
如果你也遇到类似情况,记住这几条:
- 先确认工具的扫描策略 —— 是自动扫描还是固定地址?
- AC6 链接器用
at()固定地址时,一定要给非零初值 —— 否则会被优化成 ZI acID[0]必须保持'\0'—— 否则 INIT() 不会触发_DoInit()- 能不动 scatter 就不动 —— 用属性语法更干净
环境:AT32F403A + MDK-ARM (AC6) + MicroLink