
简介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.06build 960的 Windows x86 安装包作用很直接——把 armcc、armasm、armlink、fromelf 这套 AC5 工具链装回机器让 Keil 工程和命令行构建重新指回旧版本。它适合三类人还在维护 Cortex-M 老项目、需要复现客户现场编译环境、以及不想因为一次编译器升级就把整个 MDK 回退的人。2. 拆开这个 MSIAC5 工具链里到底装了什么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 PATHC:\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.libarmlink链接与地址分配.o.lib.sct.axfELFfromelf镜像转换与反汇编.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/LLVMAC5 是 ARM 自家那套老编译器。两者在同一份代码上的行为差异远不止“报错信息长得不一样”这么简单。先是语言特性开关。AC5 对 C99 的支持需要显式打开--c99否则for (int i 0; ...)这种写法直接报错AC6 默认就按较新的 C 标准走。C 方面 AC5 用--cpp而且对 C11 之后的特性支持有限。很多老工程为了适配 AC5代码里塞满了各种兼容写法换成 AC6 之后这些写法反而会触发新的警告甚至错误。然后是内联汇编。AC5 用的是__asm { ... }这种块状语法AC6 支持的是 GCC 风格的__asm volatile(...)。一个用了大量寄存器级操作的驱动文件换编译器基本等于重写。再往下是链接脚本和启动流程。AC5 的 scatter 文件语法、__main到main之间的库初始化流程、microlib的启用方式和 AC6 都有差异。尤其microlib这个精简 C 库AC5 下是一个勾选框AC6 下是--library_typemicrolib这类链接参数混用会导致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就能出固件。对需要同时维护多个分支、或者要给客户交付可复现构建的场景这一点很关键。先把环境变量理清楚。除了 PATHAC5 还认几个环境变量其中ARMCC5_TOOLS或类似的名字在不同版本里不一致最稳妥的做法是不依赖它们直接在脚本里写死工具链前缀# 工具链前缀脚本里所有调用都基于它 TOOLCHAINC:/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_BUILD960 ACTUAL$($TOOLCHAIN/armcc --version 21 | 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再开始看代码。这个动作花不到两分钟省下的却往往是半天以上的排查时间。希望帮到你。本文还有配套的精品资源点击获取