ARMCC 5.06 安装注册与 Keil 老工程编译环境恢复

发布时间:2026/11/6 4:38:38
ARMCC 5.06 安装注册与 Keil 老工程编译环境恢复

简介:ARMCompiler 5.06(960) Windows x86 安装包面向使用 Keil MDK 与 ARM 架构芯片的嵌入式开发者,提供 armcc 编译工具链,适合维护、复现或迁移基于 ARM/Thumb/Thumb-2 指令集的项目。压缩包共 3 个文件,包含 msi 安装主体、html 版本说明和 exe 安装引导程序,整体约 80.52MB,类型简洁、用途明确。已有 3609 人学习,说明该版本在嵌入式硬件开发中仍有稳定需求。读者可据此获得经 Plum Hall 验证的 ANSI C 编译能力、代码大小与运行速度优化选项,以及与 Keil 调试链配合的排错基础;版本说明页还可用于核对 b960 的更新、修复和已知问题,便于在 Windows x86 环境完成工具链配置并投入实际项目验证。

1. 接了老板子先别改代码,先确认编译器版本

接手一块跑了七八年的板子,最容易出问题的环节不是改代码,而是编译。工程在别人的机器上一切正常,到自己这儿一打开就满屏报语法错误,查到最后发现编译器被换成了 AC6。嵌入式硬件这行里,芯片厂给的 SDK、启动文件、分散加载文件大多按 armcc 写,换 arm编译器 不是点一下下拉框就完事:汇编语法、内联汇编写法、链接脚本的段描述都可能对不上。ARMCompiler_506_Windows_x86_b960.msi就是 ARMCC 5.06(build 960)的 Windows x86 安装包,作用很直接——把 armcc、armasm、armlink、fromelf 这套 AC5 工具链装回机器,让 Keil 工程和命令行构建重新指回旧版本。它适合三类人:还在维护 Cortex-M 老项目、需要复现客户现场编译环境、以及不想因为一次编译器升级就把整个 MDK 回退的人。

2. 拆开这个 MSI:AC5 工具链里到底装了什么

2.1 安装后该看到什么目录结构

双击 MSI 走完安装流程,绝大多数人会卡在“东西到底装哪里去了”。默认路径通常是C:\Program Files (x86)\ARM_Compiler_5.06这类形式,但 MSI 允许你在向导里改路径,这就埋下了后面 uVision 找不到编译器的伏笔。装完后最重要的是会有一个bin目录,里面平铺着全部可执行文件;与之并列的还有include(标准头文件和 CMSIS 相关头文件)、lib(编译器自带的运行时库,比如microlib和标准 C 库的.lib变体)、sw或类似的辅助目录(部分版本带 IDE 集成相关文件)。

我一般第一步不看工程,先在文件管理器里确认三件事:bin\armcc.exe存在、bin\armlink.exe存在、include\stdio.h之类的标准头文件存在。这三个都在,说明安装本身没被安全软件拦掉半截。接下来开一个 cmd,把bin目录临时加进 PATH,验证版本号:

# 在 cmd 里临时把编译器目录加到 PATH,只对当前窗口生效 set PATH=C:\ARM\ARMCC506\bin;%PATH% # 对版本,重点看 Product 和 build 号 armcc --version

这条命令的输出里会明确写出Product: ARM Compiler 5.06,以及对应的 build 号。安装包名里的 b960 就是 build 960,命令行输出必须和它对得上。这一步不要省——同名的 5.06 安装包在流传过程中有多个 update 版本,混着用会在链接阶段报一些看起来完全不相干的符号错误,这是较难排查的问题之一。

提示:把编译器目录永久写进系统 PATH 不是好主意。机器上同时存在 AC5 和 AC6 时,PATH 顺序一乱,你的armcc有可能被换成armclang的软链接或脚本,编译行为直接跑偏。要用的时候临时 set,或者写进构建脚本里。

2.2 armcc、armasm、armlink、fromelf 谁是干什么的

很多人用了几年 Keil,只知道点一下 Build,真让他脱离 IDE 说清楚每个工具负责什么,就说不出来了。这套工具链的分工其实很清楚,你把这个表记住,后面命令行构建和排错都能直接用:

可执行文件职责典型输入典型输出
armccC/C++ 编译.c/.cpp.o目标文件
armasmARM 语法汇编.s启动文件.o目标文件
armar静态库打包多个.o.lib
armlink链接与地址分配.o+.lib+.sct.axf(ELF)
fromelf镜像转换与反汇编.axf.bin/.hex/ 反汇编文本

这里有一个新手最容易混淆的点:armasm吃的是 ARM 自己的汇编语法,不是 GNU as 语法。芯片厂提供的startup_xxx.s大多是这种格式,里面会出现AREA、EXPORT、DCD、__main这类指令。如果你把一个本来是给 GCC 写的启动文件塞给armasm,它会从第一行开始报错。反过来,如果你习惯看 Linux 下的汇编,头一次读这种启动文件会觉得像另一个物种。

armlink是整条链上最容易被低估的一环。它不是简单地“把 .o 拼起来”,而是按分散加载文件(scatter,扩展名.sct)里描述的加载域和执行域,把代码、只读数据、可读写数据、零初始化数据摆到具体的 Flash 和 RAM 地址上。后面第 4 章会专门讲怎么读它生成的 map 文件。

fromelf则是收尾工具:把链接产物转成烧录用的 bin,或者反汇编回来看某段代码到底被编译成了什么指令。排查性能问题时,反汇编比看源码有效得多。

2.3 AC5 与 AC6 的差别:为什么不能简单换编译器

AC6 底子是 Clang/LLVM,AC5 是 ARM 自家那套老编译器。两者在同一份代码上的行为差异,远不止“报错信息长得不一样”这么简单。

先是语言特性开关。AC5 对 C99 的支持需要显式打开--c99,否则for (int i = 0; ...)这种写法直接报错;AC6 默认就按较新的 C 标准走。C++ 方面 AC5 用--cpp,而且对 C++11 之后的特性支持有限。很多老工程为了适配 AC5,代码里塞满了各种兼容写法,换成 AC6 之后这些写法反而会触发新的警告甚至错误。

然后是内联汇编。AC5 用的是__asm { ... }这种块状语法,AC6 支持的是 GCC 风格的__asm volatile("...")。一个用了大量寄存器级操作的驱动文件,换编译器基本等于重写。

再往下是链接脚本和启动流程。AC5 的 scatter 文件语法、__main到main之间的库初始化流程、microlib的启用方式,和 AC6 都有差异。尤其microlib这个精简 C 库,AC5 下是一个勾选框,AC6 下是--library_type=microlib这类链接参数,混用会导致printf直接不进串口,或者堆栈初始化跑飞。

所以结论很明确:老工程要动的不是编译器版本下拉框,而是要么整套留在 AC5,要么做一次彻底的迁移评估。这个 MSI 的价值就在前者——它让你在不动工程一行代码的前提下,把编译环境恢复成当年那套。

3. 让 Keil 认出这个编译器:安装、注册与工程参数

3.1 安装路径必须满足的两个条件

安装路径的选择不是随便点下一步的事。根据我踩过的坑,路径必须满足两个条件:纯 ASCII 字符,以及不含空格。

中文路径的杀伤力在于,MSI 安装阶段往往不报错,等到armlink去解析 scatter 文件路径、或者fromelf去写输出文件时才失败,报错信息还不是“路径非法”,而是“cannot open file”。空格的杀伤力小一点,但会出现在 Makefile 和各种脚本里,每次都得加引号,加漏一处就断。

我在 Windows 上习惯的目录结构是这样:

C:\ARM\ARMCC506\ <- 5.06 安装根目录 bin\ <- armcc.exe 等 include\ lib\ C:\ARM\ARMCC6\ <- 万一以后要装 AC6,平级放

平级放的好处是切换时只改一个路径变量,而不是在两棵完全不同的目录树里跳。

另外提醒一句:不要把 AC5 装进 MDK 安装目录下的ARM\ARMCC子目录里“覆盖式”安装。这个位置是 uVision 默认扫描的位置,看起来方便,但一旦你哪天重装或升级 MDK,整个目录会被清掉,之前的配置全丢。独立目录 + 手动注册更稳。

3.2 在 uVision 里注册并切换目标编译器

装好之后打开 Keil,直接建工程,你会发现 Options for Target → Target 选项卡里的 ARM Compiler 下拉框里,可能压根看不到 5.06 这一项。这时候要走注册流程。

常见做法是打开Project → Manage → Project Items,切到Folders/Extensions选项卡,找到 ARM Compiler 那一行,点后面的按钮添加一个新的编译器版本,把路径指向你的安装根目录(注意是根目录,不是bin子目录)。不同 MDK 小版本的界面文字会有出入,有的版本叫Add another ARM Compiler Version,有的直接是一个文件夹图标,但逻辑都一样:告诉 uVision 这套工具链住在哪。

注册完之后回到 Options for Target,下拉框里应该能看到类似ARM Compiler 5.06 (build 960)的条目。选中它,然后做一件事——把整个工程的Objects和Listings目录清空,全量重编。这一步不能省,因为旧的.o文件是另一个编译器产出的,残留下来会引发后面第 5 章会讲的混链错误。

还有一种情况:下拉框里明明有 5.06 选项,选中后编译却报找不到armcc。这通常是注册时路径指错了层级,或者TOOLS.INI里留下了上一次安装的旧路径。处理方式是关掉 uVision,去 MDK 安装目录下找到TOOLS.INI,搜索ARMCC相关段落,把失效路径改成当前路径,保存后重开。

3.3 用一个最小工程验证工具链真的通了

注册完不要急着开你那个几千行的老工程,先建一个最小的验证工程。目的只有一个:确认从源码到 axf 的全链路是通的,把工具链问题和工程代码问题分开。

新建一个main.c:

/* 最小验证程序:不依赖任何芯片头文件,只验证编译链路 */ volatile unsigned int counter = 0; int main(void) { unsigned int i; for (i = 0; i < 100; i++) { counter += i; /* 用 volatile 防止被优化掉 */ } while (1) { /* 停在这里,方便调试器观察 counter */ } return 0; }

这段代码刻意不包含stm32f10x.h之类的厂商头文件,就是为了排除头文件路径的干扰。编译通过,说明armcc本身可用;如果连这个都过不了,问题一定在安装或注册环节,跟你的老工程无关。

编译成功后,去Objects目录下确认生成了.axf文件,再看一眼 Build Output 窗口最后那行内存占用统计(Code / RO-data / RW-data / ZI-data)。如果 Code 只有几百字节,说明优化和链接都正常;如果 Code 突然几万字节,多半是某个库被意外全量链接进来了。

3.4 四个必须手动确认的工程开关

老工程从别的机器拷过来,很多开关是藏在.uvprojx里的,打开就能用。但如果你是新建工程或者迁移过,下面四个位置必须逐个确认:

第一个是 C99 模式。Options for Target → C/C++ 选项卡里勾上C99 Mode,等价于命令行加--c99。不勾的话,代码里任何一处for (int i都会报error: #268。

第二个是microlib。同样在 Target 选项卡里,Use MicroLIB这个勾选框。用不用取决于你的工程有没有依赖完整的 C 库特性(比如某些printf浮点格式化)。用microlib能省几 KB RAM 和 Flash,但它的printf对浮点支持有限,串口输出浮点数会变成空白,这个现象非常隐蔽,我第一次遇到时查了大半天。

第三个是优化等级。老工程常见的配置是-O0或-O1,因为高等级优化会引入时序变化,让本来靠空循环凑出来的延时全部失效。如果你接手的是一个带软件延时函数的工程,直接开-O2或-O3,大概率会出现“程序跑飞”的情况:逻辑没错,就是时序变了。

第四个是分散加载文件。Options for Target → Linker 选项卡,取消Use Memory Layout from Target Dialog,手动指定.sct文件。老工程如果改过 Flash/RAM 分区,比如加了 bootloader 或者把参数区放到特定地址,这个文件是必须手动指定的。

4. 脱离 IDE 用 armcc:命令行三段式与分散加载

4.1 环境准备与调用入口

命令行构建的价值在于可重复和可自动化:CI 机器上不用装 uVision,只要一份工具链加一个 Makefile,就能出固件。对需要同时维护多个分支、或者要给客户交付可复现构建的场景,这一点很关键。

先把环境变量理清楚。除了 PATH,AC5 还认几个环境变量,其中ARMCC5_TOOLS或类似的名字在不同版本里不一致,最稳妥的做法是不依赖它们,直接在脚本里写死工具链前缀:

# 工具链前缀,脚本里所有调用都基于它 TOOLCHAIN=C:/ARM/ARMCC506/bin # 验证 "$TOOLCHAIN/armcc" --version "$TOOLCHAIN/armlink" --version

在 Windows 上跑 Git Bash 或 MSYS2 时要注意路径分隔符,统一用正斜杠能省掉很多转义麻烦。如果你用的是纯 cmd 批处理,就把上面的写法和set TOOLCHAIN=...对应起来。

注意:命令行构建时,License 是绕不开的一环。AC5 在 uVision 里使用时用的是 MDK 的授权,脱离 IDE 后它需要能找到独立的 license 配置。如果运行时报LICENSING ERROR或类似的提示,说明授权没被识别,先把这部分解决,否则后面所有命令都会在启动阶段就失败。

4.2 编译、汇编、链接三步走通

以一个典型的 Cortex-M 工程为例,源文件分成三类:C 源码、启动汇编、以及可能存在的库文件。三步命令如下:

# 第一步:C 源码编译成目标文件 "$TOOLCHAIN/armcc" --c99 -c --cpu Cortex-M3 -O1 -g \ -I ./inc -I ./cmsis \ -D USE_STDPERIPH_DRIVER \ -o ./obj/main.o ./src/main.c # 第二步:启动文件用 armasm 汇编,注意是 ARM 语法 "$TOOLCHAIN/armasm" --cpu Cortex-M3 -g \ -o ./obj/startup.o ./src/startup_stm32f10x_md.s # 第三步:链接,指定分散加载文件和 map 输出 "$TOOLCHAIN/armlink" --cpu Cortex-M3 \ --scatter ./mcu.sct \ --map --list ./out/demo.map \ -o ./out/demo.axf \ ./obj/main.o ./obj/startup.o

逐个参数说清楚,因为这些参数直接决定你排错时能不能定位问题。

--c99是语言标准开关,不加就会出现前面说的for循环变量声明报错。-c表示只编译不链接,这是armcc必须显式加的参数,不加它默认走的是编译加链接的混合模式,行为会很奇怪。--cpu Cortex-M3决定了指令集和寄存器可用范围,写错成Cortex-M0会导致生成的指令在老核上跑不了,或者用了不该用的指令。

-O1是优化等级,-g生成调试信息。这两个参数在老工程上建议保守配置,前面提过原因。-I是头文件搜索路径,可以写多个;-D是宏定义,等价于在代码里写#define。

--scatter指向分散加载文件,这个参数在链接阶段是决定性的。--map --list会输出一份详细的地址分配报告,后面小节专门讲怎么看。-o指定输出文件。

三步走完,./out/demo.axf就是最终产物。如果中间任何一步失败,错误信息会明确指出是哪个文件哪一行,按提示往回查即可。

4.3 分散加载文件与 map 文件怎么读

.sct文件是链接的“地图”,它告诉 armlink 把哪些内容放到哪个地址区间。一个简化的例子如下:

; 简化版分散加载描述 LR_IROM1 0x08000000 0x00040000 { ; 加载域:Flash 起始 0x08000000,大小 256KB ER_IROM1 0x08000000 0x00040000 { ; 执行域:代码和只读数据 *.o (RESET, +First) ; 中断向量表放最前面 *(InRoot$$Sections) ; 库初始化代码 .ANY (+RO) ; 其余只读内容 } RW_IRAM1 0x20000000 0x00010000 { ; 可读写数据放 RAM .ANY (+RW +ZI) } }

读这个文件的关键是分清加载域(LR)和执行域(ER)。绝大多数情况下两者地址相同,但如果你有代码需要从 Flash 拷贝到 RAM 里执行(比如某些高频中断处理或者 Flash 擦写函数),两者就会不同,这时候要在启动代码里补一段拷贝逻辑,很多人漏掉这一步,结果是程序在 Flash 里跑得挺好,一切到 RAM 执行就进入 HardFault。

map 文件是排错的“黑匣子”。链接完成后打开.map,重点看三块内容:一是Memory Map of the image,看各个段的起始地址和大小,确认没有越界;二是Image component sizes,看每个.o贡献了多少 Code、RO、RW、ZI,如果某个你以为很小的模块占了几十 KB,说明有意外链接;三是Removing Unused input sections,这里列出了被链接器丢弃的段,如果你发现某个你明明用到的函数出现在这里,说明调用关系没建立起来。

4.4 一份能直接改的 Makefile 骨架

把上面三步固化成 Makefile,日常构建就只需要敲一个make:

# ARMCC 5.06 命令行构建骨架 TOOLCHAIN = C:/ARM/ARMCC506/bin CC = $(TOOLCHAIN)/armcc AS = $(TOOLCHAIN)/armasm LD = $(TOOLCHAIN)/armlink ELF = $(TOOLCHAIN)/fromelf CPU = Cortex-M3 CFLAGS = --c99 -c --cpu $(CPU) -O1 -g -I ./inc -D USE_STDPERIPH_DRIVER LDFLAGS = --cpu $(CPU) --scatter ./mcu.sct --map --list ./out/demo.map SRC_C = ./src/main.c ./src/driver.c OBJ = $(patsubst ./src/%.c, ./obj/%.o, $(SRC_C)) OBJ += ./obj/startup.o all: ./out/demo.bin ./obj/%.o: ./src/%.c $(CC) $(CFLAGS) -o $@ $< ./obj/startup.o: ./src/startup_stm32f10x_md.s $(AS) --cpu $(CPU) -g -o $@ $< ./out/demo.axf: $(OBJ) $(LD) $(LDFLAGS) -o $@ $(OBJ) ./out/demo.bin: ./out/demo.axf $(ELF) --bin --output $@ $< clean: rm -f ./obj/*.o ./out/*.axf ./out/*.bin ./out/*.map

这个骨架刻意写得朴素,目的是让你一眼能看懂每个变量对应哪段命令。实际使用时,SRC_C通常用wildcard自动收集,CFLAGS里按工程需要补宏定义和包含路径。fromelf --bin这条规则最后生成 bin,注意它依赖 axf 里存在正确的加载域信息,如果.sct写得有问题,这一步会直接报错,反而是一个不错的早期预警。

clean规则不要嫌麻烦,切换编译器版本之后第一件事永远是make clean,这个习惯能帮你省掉大量莫名其妙的链接错误。

5. 避坑与排查:ARMCC 5.06 在新系统上的五类翻车现场

这一章是我在不同机器上反复遇到的真实问题,按“现象 → 原因 → 解决”整理。每条都能单独拿出来当检查项用。

5.1 装完目录是空的,或者 uVision 里根本看不到 5.06

现象:MSI 走完进度条提示完成,去安装目录一看,bin里只有零星几个文件;或者文件都在,但 Keil 的编译器下拉框里就是没有这一项。

原因有两个层次。目录为空多半是安装时权限不够,MSI 往受保护目录写文件被拦了下来,而安装程序没有把失败状态正确暴露出来。下拉框看不到则是注册问题:uVision 只认它在TOOLS.INI和自身扫描路径里记录的编译器位置,你用独立目录安装,它自然不会自动发现。

解决:右键以管理员身份运行 MSI,路径改成非系统盘的自定义目录。装完先手工确认bin\armcc.exe在,再用前文 3.2 的方法在Folders/Extensions里注册。注册后如果还是看不到,关掉 uVision 检查TOOLS.INI,把里面指向旧路径的 ARMCC 段落清理掉或改正,保存重开。

5.2 命令行一跑就弹窗,提示缺少运行库

现象:双击armcc.exe或者在 cmd 里调用,弹出“无法启动此程序,因为计算机中丢失 MSVCR100.dll”之类的系统对话框;而在 uVision 里编译却一切正常。

原因:这套 AC5 工具链编译于较早的年代,依赖 VC++ 2010 时代的 32 位运行库。新装的 Windows 系统默认不带,而 uVision 安装时可能顺带装了兼容组件,所以 IDE 里能用、命令行不能用。

解决:装一个 Microsoft Visual C++ 2010 Redistributable 的 x86 版本。注意一定要 x86,即使你的系统是 64 位——armcc.exe本身是 32 位程序,装 x64 版本解决不了问题。装完重开 cmd 窗口,PATH 才会重新生效。

5.3 C 代码里一个 for 循环就报错

现象:从别人那儿拷过来的代码,for (int i = 0; i < 10; i++)这一行报error: #268: declaration may not appear after executable statement in block。

原因:AC5 默认按 C90 处理,变量声明必须放在块的开头。这是编译器默认行为,不是代码有 bug。

解决:工程里勾上C99 Mode,或者在命令行加--c99。如果是 C++ 文件,注意要用--cpp而不是--c99,两个开关的作用对象不同。顺便提醒,开了 C99 之后,一些老的隐式函数声明会从警告升级为错误,老工程可能需要补上头文件引用。

5.4 把 AC5 和 AC6 的目标文件混在一起链接

现象:链接阶段报L6236E: No section matches selector或者提示某个.o不是有效镜像,明明每个文件单独编译都能过。

原因:两个编译器产出的目标文件在 ABI 标记、段命名、调试信息格式上都不一致,armlink无法把它们当成同一套东西处理。最常见的触发场景是:切换编译器版本后没有清空Objects目录,旧.o被复用了。

解决:切换编译器版本后强制全量重编。uVision 里做一次Rebuild,命令行构建就把obj目录整个删掉再make。如果工程里有预编译好的第三方.lib,要确认它是用同一版本编译器产出的,否则必须拿到源码重新编。

5.5 代码一超过某个大小就链接失败

现象:小工程编译正常,代码量涨上去之后链接阶段报L250之类的错误,提示代码尺寸超出限制。

原因:这是授权层面的限制,不是工具链故障。评估版或精简版授权对可链接的代码体积有上限,超过就直接拒绝链接,报错信息里通常带RESTRICTED VERSION字样。

解决:确认当前使用的授权类型,换成正式授权。这一条没什么技巧,但值得写出来,因为报错信息看起来很像内存布局问题,容易让人往 scatter 文件方向查半天。

提示:以上五条里,5.2 和 5.4 出现的频率最高。前者是环境问题,后者是操作习惯问题,都可以在开工前用五分钟规避掉。

6. 两个编译器共存:把版本校验写进构建流程

一个稍微上点规模的项目组,机器上同时存在 AC5 和 AC6 是常态:新项目用 AC6,老项目维护用 AC5,偶尔还要在同一台机器上给两个项目出包。这时候靠人脑记“这个工程该用哪个版本”迟早出事,我的做法是把版本校验写进构建入口。

具体来说,在 Makefile 或者构建脚本最前面加一段前置检查,读一个工程级的配置文件,比对当前armcc --version的输出:

# 构建前强制校验编译器 build 号 EXPECTED_BUILD=960 ACTUAL=$("$TOOLCHAIN/armcc" --version 2>&1 | grep -o 'build [0-9]*' | grep -o '[0-9]*') if [ "$ACTUAL" != "$EXPECTED_BUILD" ]; then echo "编译器版本不匹配: 期望 $EXPECTED_BUILD, 实际 $ACTUAL" exit 1 fi

这段脚本的价值在于把“隐性约定”变成“显性失败”。没有它的时候,版本不对的典型表现是编译能过、链接偶尔报错、烧进去行为诡异,排查成本极高。有了它,构建第一步就停下来,报错信息直接告诉你差异在哪。

如果你的工程需要在 AC5 和 AC6 之间来回切,我建议的目录约定是这样:

用途目录配置方式
AC5 工具链C:\ARM\ARMCC506独立注册,工程内指定
AC6 工具链C:\ARM\ARMCC6MDK 自带或独立注册
工程配置仓库内.buildrc记录期望 build 号与优化等级
产物输出仓库内out/按版本分子目录,如out/ac5/

产物按编译器版本分目录这一条尤其重要。我见过不止一次,两个版本编译出的 axf 混在同一个Objects目录里,测试同事拿错文件烧进板子,然后花两天时间怀疑是硬件问题。

还有一个验证手法值得养成习惯:出包之后用fromelf反汇编看一下入口附近几条指令。AC5 和 AC6 生成的启动代码风格差异明显,AC6 会多出一层__main到main之间的初始化调用链。这一步不用一分钟,但能立刻确认“这个固件到底是谁编出来的”。

从那以后我每次接手一个老工程,第一件事都是跑一遍armcc --version,把 build 号写进仓库根目录的 README,再开始看代码。这个动作花不到两分钟,省下的却往往是半天以上的排查时间。希望帮到你。

本文还有配套的精品资源,点击获取