资讯动态

ADS1.2 ARM裸机开发全流程:编译链接、AXD调试与Boot/App分离

发布时间:2026/10/15 23:15:35 来源:尧图企业网站定制
简介一份面向ARM初学者的经典集成开发环境资源包聚焦ADS1.2工具链的使用与嵌入式开发实践。压缩包内共1287个文件包含编译器、链接器、调试器相关组件以及大量htm说明文档、cpp/c/h源码示例、mcp工程文件、s汇编代码、pdf教程和avi操作录屏另有部分配置文件与演示程序可支撑从环境搭建到源码调试的完整学习流程。已有588人学习下载资源包整体约122.52MB适合正在接触ARM7等经典内核、需要上手老牌工具链或维护旧项目的开发者。通过阅读教程文档与工程示例能够了解GCC编译器、链接器、调试器的基本用法掌握创建工程、添加源码、编译链接、下载调试的操作路径结合内置录屏还能直观还原典型开发环节降低入门门槛积累嵌入式系统开发经验。1. 提到 ADS1.2 ARM开发软件先别急着把它归为“考古工具”提到 ADS1.2 ARM开发软件很多新入行的开发者第一反应是这不是早就退出历史舞台的老古董吗实际上在 ARM7/ARM9 老产品的产线维护、存量代码迁移和毕业设计里ADS1.2 依然是很多团队唯一跑得通的编译调试链路。它能把一份老工程从源码一路编译成可烧写的 BIN 文件同时提供硬件断点、内存观察和反汇编定位能力——这些能力在裸机调试中至今不可替代。适合谁接手老项目需要从源码构建固件的工程师、被历史代码绑定不能随便换编译器的团队以及想搞懂 ARM 工具链底层工作方式的初学者。下面这套流程是从安装到避坑的完整落地路径。2. 从 CodeWarrior 到 ARM 编译器ADS1.2 的组件全景与选型理由2.1 一个 IDE 背后五套独立工具ADS1.2 的组件结构ADS1.2 表面上是一个带图形界面的开发套件但真正干活的其实是五个独立工具。CodeWarrior IDE 只是一个壳负责管理工程文件、调用外部编译器和收集输出armcc 是 C 编译器armasm 是汇编器armlink 是链接器armar 是静态库管理器AXD 是调试器。这五者之间的关系是流水线式的armcc 把 .c 编译成 .oarmasm 把 .s 汇编成 .oarmlink 把多个 .o 和库文件按分散加载描述合并成 .axfAXD 再加载 .axf 做源码级调试最后 fromelf 工具把 .axf 转换成可烧写的 .bin 或 .hex。这种分离结构在现代 IDE 里被隐藏了但在 ADS 里必须手动理解因为编译错误、链接错误、调试连接失败分别对应不同工具的责任区。例如 L6220E 这类链接错误只会在 armlink 阶段出现如果你在 CodeWarrior 的编译日志里看到它说明目标文件已经生成问题出在地址分配或段属性上而 C2933E 这类是 armcc 的语义检查错误根本走不到链接阶段。我一般会让团队成员先分清这两类错误再动手改代码否则容易在错误的方向上花掉半天时间。组件的版本也需要注意。ADS1.2 发行后有过补丁包不装补丁时 armcc 在某些优化级别下会产生错误代码这些代码在调试时表现正常一进 Release 就翻车。判断当前组件版本的方式是打开命令行输入 armcc --version输出里能看到编译器的构建号如果你拿到的构建号是早期版本建议先把补丁打上再开始干活。2.2 Thumb 指令与 ARM 指令切换为什么 ADS 里混用模式容易出问题ARM 处理器支持两套指令集ARM 指令是 32 位定长Thumb 指令是 16 位定长。ADS1.2 时代常见的做法是中断服务函数和性能敏感代码用 ARM 指令集普通逻辑用小体积的 Thumb 指令集以节省 Flash 空间。这种混用表面上只是加一个编译选项但实际涉及调用约定的切换——从 ARM 状态调用 Thumb 函数时必须通过 BX 指令且目标地址最低位为 1链接器需要知道哪些函数是 Thumb 代码并生成正确的跳转序列。在 ADS 里开启混用模式有两条路一是在 CodeWarrior 的编译器选项里勾选 Interworking二是在 armlink 命令里加 --interwork。两条路必须同时生效只开一边会链接成功但运行时直接跑飞。我见过一个真实案例有人在工程里里用 armcc -thumb 编译单个文件但没有在链接器里加 --interwork结果调试时发现只要主程序调用那个 Thumb 函数PC 就跳到一个非法地址反汇编窗口里看到的完全是乱码。如果代码里用了内联汇编那么切换就是手动控制的。下面的汇编片段展示了一个标准的从 ARM 跳转到 Thumb 的序列; 从 ARM 状态跳转到 Thumb 函数 LDR r0, thumb_func ORR r0, r0, #1 ; 最低位置 1指示进入 Thumb 状态 BX r0这段代码的逻辑是先把 Thumb 函数地址加载到 r0然后将地址最低位置 1最后用 BX 跳转。处理器在 BX 执行时会检查最低位如果是 1 就切换到 Thumb 状态如果是 0 就保持在 ARM 状态。这个最低位不是真正的地址位而是状态标识所以实际跳转地址是 r0 清掉最低位后的值。在调试时如果你看到 PC 的地址奇偶异常先怀疑这里的 ORR 是不是丢了。2.3 选型理由为什么老代码还要绑在 ADS1.2 上很多人问既然 Keil 和 GCC 都能编译 ARM 代码为什么不直接迁移答案是历史代码里有两类资产和 ADS 绑定很深一类是分散加载描述文件 .scl它的语法和 GCC 的 linker script、Keil 的 sct 都不一样另一类是代码里大量使用的 ADS 专有关键字比如 __irq、__softfp、__value_in_regs这些关键字在现代编译器里要么被移除要么语义完全不同。我遇到过一份老代码中断函数写作__irq void timer_handler(void) { /* 中断处理逻辑 */ }__irq 会让编译器自动生成中断返回指令和寄存器保护代码迁移到 GCC 时如果直接删掉这个关键字函数的入栈出栈行为就错了中断现场会被破坏。这类关键字散落在几十个文件里时迁移成本会指数上升。因此如果产品的生命周期还有三年以上但代码只有几千行且没有奇怪的硬件依赖那迁移是值得的如果代码有几十万行同时还依赖分散加载和多段地址映射乖乖留在 ADS 反而是更经济的决策。下面是三个常见工具链的对比方便你评估自己的项目到底值不值得迁对比维度ADS1.2Keil MDKGCC for ARM分散加载语法.scl 文件执行域/加载域分离.sct 文件语法相似但不兼容linker script完全不同的描述模型专有关键字__irq / __softfp兼容部分 ARM 关键字需用 attribute 重写调试器配套AXDKeil uVision 内置依赖第三方调试器维护现状已停止更新持续更新持续更新结论是如果只是维护不改需求留在 ADS如果要做功能迭代且代码量可控尽早迁移。这个决策本身也是项目风险的一部分犹豫越久积累的 ADS 专有语法就越多。3. 搭建一个可烧录的最小工程ADS1.2 编译与链接全流程3.1 最小工程目录与启动代码先让 PC 跑起来ADS 工程虽然没有现代 IDE 那种自动生成的工程模板但一个最小工程只需要三个文件startup.s、main.c 和一个 .scl 分散加载文件。startup.s 负责设置堆栈指针、初始化硬件环境并跳转到 mainmain.c 放业务代码.scl 告诉链接器代码放在哪里、数据放在哪里。这个结构从 ARM7 时代传承至今理解它可以打通后续所有 ARM 裸机开发。启动代码是最容易写错的部分。这里给一个适配 ARM7TDMI 的最小启动序列; 最小 ARM 启动代码 AREA Reset, CODE, READONLY ENTRY LDR sp, Top_Stack ; 设置堆栈指针 BL main ; 跳转到 C 主函数 Loop B Loop ; main 返回后死循环 ENDENTRY 声明了整个映像的入口点链接器会把 Reset 段放在加载域的最前面。LDR sp, Top_Stack 是伪指令Top_Stack 这个符号来自分散加载文件里定义的堆栈顶部地址真正的值由链接器解析。BL main 会把返回地址存入 LR 后跳转到 C 代码main 结束后会回到 Loop 死循环。这里有个细节如果你的 main 是永不返回的裸机主循环那么 Loop 永远执行不到但它必须存在——因为编译器默认认为 main 会返回没有这个循环的话链接器可能认为入口后面不可达而对启动代码做错误优化。3.2 CodeWarrior 工程配置ARM 模式、优化级别与段属性在 CodeWarrior 里新建工程时选择 ARM Executable Image 模板然后手动添加 startup.s 和 main.c。编译选项里最关键的三个设置ARM/Thumb 模式、优化级别、字节序。ARM 模式是默认选项Thumb 模式需要额外勾选优化级别建议开发期用 -O0发布前用 -O2字节序按硬件实际端序设ARM7 大多是小端。工程设置对话框中需要确认的还有汇编器选项。启动代码的汇编器默认是 armasm不要在工程里用 armcc 去编译 .s 文件否则汇编器会把 ARM 指令当 C 语法解析。CodeWarrior 会按文件扩展名自动选择工具但如果你把启动文件命名为 start.s 而不是 startup.s某些版本会识别异常。我在实际项目中会把启动文件统一命名为 startup.s规避这类识别问题。调试阶段建议关闭 CodeWarrior 的 Linker 选项里的 Use Optimization 之外的代码优化因为优化器会重排指令和变量导致 AXD 单步执行时看到的 C 代码行和实际执行位置不匹配。这不是 ADS 独有的问题现代编译器一样有但 ADS 的调试信息质量在老工具链里算中等偏上前提是你在编译选项里打开了 -g。3.3 armlink 链接参数与分散加载文件地址映射是硬功夫链接阶段是把编译产物映射到物理地址的关键环节。ADS 的 armlink 支持两种地址描述方式简单命令行参数和分散加载文件。裸机单段程序用命令行参数就够了但凡是带外部 RAM、Nor Flash、Boot 和 App 分离的程序都必须使用 .scl 分散加载文件。下面是一个单加载域的双执行域配置示例LOAD_REGION 0x00000000 0x00080000 { ER_ROM 0x00000000 0x00080000 { startup.o (Reset, First) main.o (RO) } RW_ROM 0x00080000 { *(RW) *(ZI) } }LOAD_REGION 定义了整个镜像的加载地址和最大容量这里是从 0x0 开始的 512KB。ER_ROM 是第一个执行域startup.o 的 Reset 段被 First 放到最前面这是为了确保复位向量位于地址 0x0main.o (RO) 表示 main.o 里所有只读段都放在这个执行域。RW_ROM 是运行域存放可读写数据和 ZI 段ZI 段由 C 运行时库的 __main 函数负责清零。链接器会根据这个文件自动布局所有目标文件不需要手动指定每个 .o 的地址。对应命令行方式的等价写法是armlink -ro-base 0x00000000 -rw-base 0x00080000 -entry Reset_Handler -o project.axf startup.o main.o-ro-base 指定只读段的起始地址-rw-base 指定可读写段的基地址-entry 入口指定为 Reset_Handler。这个方式适合快速验证但它无法描述多个执行域和嵌套地址映射正式工程还是用分散加载文件更清晰。我一般会两种方式都试一遍先用命令行参数确认目标文件没问题再写 .scl 文件做精细地址控制。3.4 从 AXF 到 BINfromelf 转换与烧写前检查链接器输出的 .axf 文件是 ELF 格式包含了调试信息和地址符号表但不能直接烧到 Flash。烧写前需要用 fromelf 工具把 axf 转换成纯二进制或 Intel HEX 格式。命令如下fromelf -c -bin -o project.bin project.axf这条命令里 -c 表示解析代码和数据段并生成二进制内容-bin 指定输出格式为纯二进制-o 指定输出文件。转换完成后用文本编辑器或 hexdump 检查 project.bin 的头部确认文件开头是启动代码里 Reset 段的内容而不是某个 C 函数的随机指令。这里有个血泪经验如果 .axf 里有多个加载域fromelf 生成 bin 时只会输出第一个加载域的连续地址空间内容多域工程要用 -m32 或者分别转换各加载域再拼接否则烧进去的程序会缺失 App 段。烧写工具通常直接接受 .bin 或 .hex但有些老式编程器只接受 .hex。fromelf 也支持输出 hex 格式把 -bin 换成 -i32 即可生成 Intel 32 位 HEX 文件。无论是哪种格式烧写前都要核对基地址是否和分散加载文件一致烧进去之后再用 AXD 读出来对比一次这样能过滤掉大部分因烧写配置造成的“假损坏”。4. AXD 调试器在没有在线仿真器的时代用它定位崩溃和异常4.1 从加载 AXF 到命中第一个断点AXD 是 ADS 套件里最容易让新手发怵的工具界面朴素操作方式和现代 IDE 差异很大但它的核心能力一点不弱。调试裸机程序的标准流程是打开 AXD → 在 Options 里配置目标类型 → 用命令行加载 axf → 设置断点 → 运行。如果你用的是模拟器配置目标类型时选 ARMUL如果接的是开发板选 Multi-ICE 或指定 JTAG 调试插件。加载目标文件的命令是在 AXD 底部的命令行输入load project.axf这条命令会解析 axf 里的代码段和数据段把代码加载到目标 CPU 的地址空间同时在源码窗口显示 main.c 的内容。加载之后在需要停顿的位置设置断点比如 main 函数的第一行。运行按钮和所有调试器一样是绿色三角但新手经常忽略 AXD 启动时默认停在复位地址 0x0直接按运行会从复位向量开始执行如果启动代码没有正确初始化时钟和内存程序会在几毫秒内跑飞或者进入异常。我习惯在 startup.s 的 BL main 那行设置断点让 CPU 先执行完启动代码确认堆栈和时钟就绪后再进入 C 环境。这个做法在接实际硬件时尤其重要因为如果跳过这一步你很可能会在 main 的第一行看到 PC 正确、SP 却指向 0x0 的诡异现象其实是启动代码的堆栈设置根本没跑。4.2 寄存器窗口、内存窗口与反汇编窗口的组合排错法AXD 的排错核心不是单步而是三个窗口相互印证。寄存器窗口看 R0-R12、SP、LR、PC 和 CPSR内存窗口看指定地址的内容反汇编窗口看当前 PC 处的实际指令。当一个 C 函数崩溃时先看 CPSR 里的模式位和中断标志然后看 LR 寄存器——LR 里保存的是函数返回地址如果 LR 的值落在某个函数的范围内就能推断是谁调用了当前函数。下面是一段刻意制造的除零崩溃代码用于演示排错组合int divide(int a, int b) { return a / b; /* 当 b 传入 0 时触发除零异常 */ } int main(void) { volatile int result; result divide(10, 0); while (1); }在 AXD 中运行后CPU 会进入异常向量表PC 指向 0x04 附近。此时不要慌张先在寄存器窗口找到 R14_und未定义指令异常模式的 LR它的值会指示异常发生时正在执行的指令地址。再到反汇编窗口输入这个地址就能看到是哪条汇编指令触发了异常。对 ARM7TDMI 来说除零不是硬件异常而是未定义指令陷阱——编译器会把定值除法转换成乘法加移位序列而变量除法会调用库函数。库函数在检测到除数为零时执行未定义指令从而触发异常。理解了这条链路你就能顺着 LR 往回找 C 代码而不是在异常向量表里瞎猜。4.3 断点数量有限Flash 断点和 RAM 断点的真实差异ARM 处理器通常没有 Flash 内联断点支持断点的实现方式是在目标地址处插入一条未定义指令当 CPU 取指到这条指令时触发异常断点管理器再恢复现场。如果断点设置在 Flash 里调试器就无法改写 Flash 内容只能依赖 CPU 的硬件断点寄存器而 ARM7 这类老内核的硬件断点通常只有两个。当你设置第三个断点时AXD 会弹出提示说断点不可用这时不是软件坏了而是硬件限制。解决办法是让程序在 RAM 中运行或者只在最需要停顿的两个位置设置断点。我一般会把关键函数全部安排在 RAM 执行域里然后把数据断点用在这些函数上。数据断点和指令断点不同它在内存地址被读或写时触发这对定位“某个变量被谁意外修改”的问题非常有效。设置方式是在 AXD 的断点窗口里新建一个数据观察点指定地址和访问类型。记住硬件断点数量只有两个之后你就不会在排查过程中浪费时间去“多设几个断点试试看”了。5. ADS1.2 避坑记录五条高发故障的现象、原因与解决5.1 链接器报 L6220E分散加载地址重叠现象工程编译通过但 armlink 阶段报 L6220E错误信息提示某个执行域的地址范围与另一个执行域重叠。常见于往既有工程里添加新的 .o 文件之后。原因分散加载文件里两个执行域的地址范围写得不严丝合缝新加入的目标文件的只读段超过了执行域容量链接器发现 ER_ROM 和 RW_ROM 的地址出现交叉。解决打开 .scl 文件逐段计算每个执行域的容量确认添加新文件后 RO 段总量没有超出 ER_ROM 大小。如果超出把 RW_ROM 的起始地址整体后移或者调整 LOAD_REGION 的大小。这个错误是最好解决的但最容易忽略的是你新增的 .o 如果被链接器自动归入 *(RO) 而不是指定段它的位置可能不在你预想的执行域里。5.2 中文路径AXD 加载后源码窗口一片空白现象编译和链接都正常AXD 输入 load 命令也能执行但源码窗口不显示任何 C 代码只能看到反汇编。原因工程所在路径包含中文或空格字符调试信息里的文件名和路径编码与 AXD 的解析器不兼容导致源码映射失败。这类问题在 ADS 里出现频率极高因为老工具链没有做 Unicode 路径处理。解决把整个工程目录移到纯英文路径下路径中不要有空格目录层级不要超过两层。这个规则应当作为工程创建时的强制规范写进团队文档。已经有项目因为路径问题浪费了整整一个下午最后发现只是目录名里带了一个中文“案”字。5.3 Flash 断点设置失败硬件断点数量不足现象AXD 中可以运行程序但设置第三个断点时提示 “Breakpoint resource exhausted”程序单步正常断点失效。原因ARM7 内核的硬件断点寄存器只有两个Flash 里的代码无法使用软件断点所以第三个断点必然失败。解决把频繁需要断点的函数放到 RAM 执行域运行这样调试器可以使用软件断点断点数量不受限制。或者在分散加载文件里划分一个 RAM 执行域给调试专用函数调试完成后这个区域还能回收给业务代码用。开发期用 RAM 跑关键代码、发布前改回 Flash 执行是 ARM7 时代标准的调试策略。5.4 烧写后上电不运行BIN 文件开头少了向量表现象AXD 调试一切正常但把从 fromelf 生成的 bin 烧到 Flash 后上电程序不跑用示波器看片选信号发现 CPU 根本没有取指动作。原因分散加载文件里 startup.o 的启动段没有设置 First 属性链接器把其他目标文件的只读段排到了最前面导致 Flash 地址 0x0 处不是复位向量CPU 上电后从 0x0 取到的是一条无效指令。解决打开 .scl确认启动段所在的目标文件必须有 First 属性。如果多个执行域都有代码确保你的复位向量在第一个执行域的最前面。检查方法是用 hexdump 查看 bin 文件第一个 word正常应该是跳转指令的机器码比如 0xEA000000 系列。5.5 AXD 连接开发板失败JTAG 配置的时序玄学现象AXD 选择 Multi-ICE 插件后点击连接提示无法识别 CPUJTAG 相关寄存器读不到偶尔多试几次又能成功。原因开发和调试的 JTAG 时钟频率过高或者复位信号时序和 CPU 上电时序不匹配导致调试器在 CPU 尚未完全复位时读取 ID 寄存器失败。这类问题在现场偶尔出现容易被认为是开发板硬件故障。解决在调试器的配置里降低 JTAG 时钟频率到 100kHz 甚至更低确保上电后先复位再连接。AXD 的 Multi-ICE 配置窗口里有一项用于设置目标时钟频率把它降到 500kHz 以下通常能解决大部分间歇性连接失败。如果还不行检查调试目标板的复位引脚是否有外部电容影响上电时序。这块的排查很考验耐心但基本都能通过降频解决。6. 用分散加载把 Boot 与 App 分离一个可直接套用的地址映射方案6.1 Boot 区与 App 区的划分与跳转实现Boot/App 分离的价值在于可以让产品通过串口或网络升级应用层而不需要每次都用仿真器擦写全片。ADS 的分散加载文件天然支持这种方案同一份工程里可以定义两个执行域分别对应 Flash 的不同地址区间。下面是一个典型的双区布局LOAD_REGION 0x00000000 0x00080000 { ER_BOOT 0x00000000 0x0001000 { boot.o (Reset, First) *(RO) } ER_APP 0x00001000 0x0007000 { app.o (RO) } RW 0x00010000 { *(RW) *(ZI) } }ER_BOOT 从 0x0 开始大小 4KB包含启动代码和所有被链接进 Boot 的函数ER_APP 从 0x1000 开始大小 28KB存放 App 代码。RW 区域就是统一的数据区。编译时Boot 和 App 可以放在同一个工程里编译也可以拆成两个独立工程。如果拆成两个工程App 工程的分散加载文件只保留 ER_APP 和 RWBoot 工程里留一个固定入口地址的跳转函数就行了。Boot 跳转到 App 的核心代码如下#define APP_START_ADDR 0x1000 typedef void (*pFunction)(void); void jump_to_app(void) { pFunction app_entry; volatile unsigned int temp; app_entry (pFunction)(*(volatile unsigned int *)(APP_START_ADDR 4)); temp *(volatile unsigned int *)APP_START_ADDR; if ((temp 0xFFFF0000) ! 0xEA000000) { return; /* 向量表无效拒绝跳转 */ } app_entry(); }这段代码先从 App 起始地址读出第一个 word 作为向量表校验再读出第二个 word 作为 App 的复位入口。正常编译的 ARM 工程中地址 0x1000 处是跳转指令0x1004 处存放 Reset_Handler 的地址。校验向量表的目的是防止 Flash 里的 App 区域还是空的或数据损坏导致系统跑飞。这段逻辑虽然简单但救过不少现场设备因为我见过太多没做校验就直接跳转的 bootloader最后把设备彻底变砖了。6.2 验证 Boot 跳转是否正常验证 Boot/App 跳转的最佳方法不是仿真而是实打实烧进去看结果。先在烧写器里只烧 Boot 区上电确认 Boot 不跳转向量表校验未通过用串口打印一条提示然后把 App 烧进 0x1000 地址复位后 Boot 校验通过并跳转如果看见 App 的初始化日志或引脚电平翻转说明方案可行。我早期做这类跳转时曾经把 App 的起始地址写成了 0x1001跳转后程序跑了几秒就进异常——因为地址最低位的奇偶性被解释成了 Thumb 状态切换标志CPU 错误地进入 16 位指令解码整个执行全乱掉。那次排查让我养成了习惯凡是涉及跳转地址的地方第一眼先看地址对齐和最低位其次才看函数指针类型。这个习惯一直保留到今天希望也能帮你在 Boot/App 分离这条路上少踩一次坑。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑