简介:ARMCompiler_506_Windows_x86_b960.msi 是 ARM 公司推出的 ARM Compiler 5.06(960) 编译器安装包,面向从事 ARM 架构嵌入式开发的工程师与学习者,尤其适合使用 Keil MDK 进行单片机、物联网终端等项目的开发者。该版本包含符合 ANSI C 标准的 armcc 编译器,支持代码大小与运行速度优化,兼容 Thumb、Thumb-2 等指令子集,并可与 Keil 工具链配合完成编辑、构建与调试。资源包共 3 个文件,以 msi 安装程序、html 版本说明和 exe 安装引导为主,压缩包约 80.52MB,其中版本说明文档可帮助读者了解该版本的更新、改进与已知问题,安装程序则用于在 Windows x86 平台完成部署。目前已有 3584 人学习下载,适合需要搭建 ARMCC 5.06 编译环境、排查旧版工具链兼容问题或研究编译器优化选项的开发者参考使用。
1. ARMCC 5.06 在 Windows 上到底还能不能打:一个被低估的嵌入式工具链
如果你手上有一批基于 ARM7、ARM9、Cortex-M0/M3 的老工程,Keil MDK 的编译日志里大概率出现过ARMCompiler 5的字样。ARMCC 5.06(内部版本号 b960)是 ARM 公司被收购前发布的最后一版 ARM Compiler 5 系列工具链,也是大量存量嵌入式项目至今仍在使用的编译器。它对应的安装包就是ARMCompiler_506_Windows_x86_b960.msi,一个不到 200MB 的 Windows 安装器。
问题在于,很多人第一次拿到这个 msi 文件时是懵的:双击装完,Keil 里找不到;或者装到一半报错退出;又或者装上了但编译老工程时链接脚本报一堆 warning。这篇笔记就是围绕这个安装包,把「它是什么、怎么装、参数怎么配、坑在哪」讲清楚。适合两类人:一是维护老项目的嵌入式工程师,二是需要在 CI 里复现 ARMCC 5.06 编译环境的构建同学。
2. ARMCC 5.06 的定位与安装包结构:为什么它和 ARMCLANG 不是一回事
2.1 ARMCC 5.06 与 ARM Compiler 6 的本质区别
ARM Compiler 5 基于 ARM 自研的编译器前端,后端用的是 Edison Design Group 的 C++ 前端加 ARM 自己的代码生成器。ARM Compiler 6 则直接切到了 LLVM/Clang 架构。这个切换带来的直接后果是:两者对 C 标准的默认支持、内联汇编语法、__attribute__扩展、链接脚本语法都有差异。
ARMCC 5.06 默认使用 C90 加部分 C99 扩展,对 GNU 扩展的兼容性有限。很多老工程里写的__asm块、#pragma arm、__irq关键字,在 ARMCLANG 下要么报错要么行为不一致。这就是为什么大量存量项目宁可锁死在 ARMCC 5.06 上,也不愿意迁移。
从版本号看,5.06 是 ARM Compiler 5 的最后一个功能版本,b960 是它的 build 号。这个版本修复了 5.05 中若干关于-O3优化下 volatile 变量重排的问题,也是 Keil MDK 5.36 之前默认捆绑的编译器版本。
2.2 msi 安装包里到底有什么
ARMCompiler_506_Windows_x86_b960.msi是一个标准的 Windows Installer 包。解包后主要包含以下目录结构:
| 目录/文件 | 内容 | 用途 |
|---|---|---|
bin/ | armcc.exe、armasm.exe、armlink.exe、armar.exe | 编译、汇编、链接、打包 |
include/ | 标准 C/C++ 头文件 | 编译时头文件搜索路径 |
lib/ | armlib、cpplib等运行时库 | 链接阶段使用 |
include/下的arm_* | CMSIS 相关头文件 | Cortex-M 系列寄存器定义 |
sw/ | 示例工程和模板 | 参考用 |
安装器默认会装到C:\Keil_v5\ARM\ARMCC或C:\Program Files\ARM\ARMCompiler5.06,具体路径取决于安装时的选择。这个路径后面在 Keil 里配置编译器时需要用到。
2.3 安装步骤与路径选择
安装本身不复杂,但有几个点需要注意。直接双击 msi 后,安装向导会问安装路径。我一般建议不要装在带空格或中文的路径下,因为后续在 Makefile 或 CMake 里引用armcc.exe时,路径带空格会引发一堆转义问题。
# 假设安装到 D:\Tools\ARMCC506 # 安装完成后验证编译器是否可用 D:\Tools\ARMCC506\bin\armcc.exe --version # 预期输出类似: # ARM Compiler 5.06 update 6 (build 960) # Toolchain: ARM Compiler 5.06如果--version能正常输出版本号,说明安装成功。如果提示缺少armcc.exe或某个 DLL,大概率是安装路径下bin/目录不完整,需要重新安装。
安装完成后,还需要把bin/目录加入系统 PATH,或者在 Keil 的Project → Manage → Project Items → Folders/Extensions里手动指定编译器路径。Keil MDK 5.36 之后的版本默认不再捆绑 ARMCC 5,需要手动指向这个安装目录。
注意:ARMCC 5.06 是 32 位程序,在 64 位 Windows 上通过 WOW64 运行。如果遇到
0xc000007b错误,通常是缺少 VC++ 32 位运行库,装一个vcredist_x86即可。
3. 在 Keil MDK 里挂载 ARMCC 5.06 并跑通第一个工程
3.1 Keil 中配置编译器路径的完整步骤
Keil MDK 从 5.37 版本开始彻底移除了 ARMCC 5 的捆绑,只保留 ARMCLANG。如果你用的是这个版本之后的 MDK,需要手动把 ARMCC 5.06 挂回去。
操作路径是:打开 Keil →Project→Manage→Project Items→Folders/Extensions标签页 → 在ARM Compiler下拉框旁边点击...按钮 → 选择D:\Tools\ARMCC506\bin目录 → 确定。
配置完成后,在Options for Target→Target标签页里,ARM Compiler下拉框应该能看到Use default compiler version 5或类似选项。选中它,然后重新编译工程。
// 一个最小的 Cortex-M3 测试工程 main.c #include <stdint.h> // 模拟寄存器定义,实际工程中由 CMSIS 提供 #define REG_BASE 0x40000000UL #define REG_CTRL (*(volatile uint32_t *)(REG_BASE + 0x00)) volatile uint32_t g_counter = 0; void delay(volatile uint32_t count) { while (count--) { __asm volatile ("nop"); } } int main(void) { REG_CTRL = 0x01; // 使能外设 while (1) { g_counter++; delay(1000); } return 0; }这段代码里用了__asm volatile ("nop"),这是 ARMCC 5 支持的内联汇编写法。在 ARMCLANG 下,同样的写法需要改成__asm volatile ("nop")或__NOP()内建函数,行为才一致。编译这个工程时,如果 Keil 的 Build Output 窗口显示compiling main.c...且没有报错,说明 ARMCC 5.06 已经正常工作。
3.2 命令行编译:脱离 Keil 的最小验证
很多 CI 环境没有 Keil 的图形界面,需要用命令行直接调armcc.exe。下面是一个最小可用的编译命令:
# 编译 D:\Tools\ARMCC506\bin\armcc.exe -c --cpu Cortex-M3 -O2 -I"D:\Tools\ARMCC506\include" main.c -o main.o # 链接 D:\Tools\ARMCC506\bin\armlink.exe --cpu Cortex-M3 main.o --scatter scatter.scat -o output.axf # 生成 bin D:\Tools\ARMCC506\bin\fromelf.exe --bin -o output.bin output.axf参数说明:--cpu Cortex-M3指定目标内核,必须和芯片实际内核一致,否则生成的指令集可能不兼容;-O2是优化等级,ARMCC 5 支持-O0到-O3以及-Os(空间优化);-I指定头文件搜索路径;--scatter指定分散加载文件,用于描述内存布局。
scatter.scat文件内容示例:
LR_IROM1 0x08000000 0x00010000 { ER_IROM1 0x08000000 0x00010000 { *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x00005000 { .ANY (+RW +ZI) } }这个分散加载文件描述了一个 64KB Flash 加 20KB RAM 的典型 Cortex-M3 布局。RESET段放在最前面,保证中断向量表在 Flash 起始地址。如果链接时报L6406E: No space in execution regions,说明某个段超出了指定区域大小,需要调整地址或优化代码体积。
3.3 验证编译产物是否正确
编译链接完成后,用fromelf查看产物信息:
D:\Tools\ARMCC506\bin\fromelf.exe --text -c -o output.dis output.axf这会生成反汇编文件。检查开头几行,应该能看到中断向量表的地址列表,第一个字是初始 MSP 值,第二个字是 Reset_Handler 地址。如果这两个值明显不对(比如是 0 或随机值),说明链接脚本配置有问题。
另一个验证方式是看output.axf的大小和output.bin的大小是否合理。一个简单的点灯程序,bin 文件通常在 1KB 到 3KB 之间。如果 bin 文件有几十 KB,可能是把不必要的库段链接进去了,需要在 scatter 文件里加*(.ARM.exidx)之类的排除规则。
4. ARMCC 5.06 的编译参数与优化选项:哪些该动,哪些别碰
4.1 优化等级的选择与副作用
ARMCC 5.06 支持-O0、-O1、-O2、-O3和-Os。-O0关闭优化,调试信息最完整,但代码体积和运行速度都最差。-O1做基本优化,适合大多数调试场景。-O2是发布版本的常用选择,在体积和速度之间取得平衡。-O3开启激进优化,包括循环展开和函数内联,但可能引入难以复现的时序问题。-Os优先优化代码体积,适合 Flash 紧张的芯片。
我一般建议:调试阶段用-O0或-O1,发布阶段用-O2。只有在 Flash 实在放不下时才考虑-Os,因为-Os有时会把一些循环优化掉,导致延时函数的实际延时变短。
# 不同优化等级的对比编译 armcc.exe -c --cpu Cortex-M3 -O0 main.c -o main_O0.o armcc.exe -c --cpu Cortex-M3 -O2 main.c -o main_O2.o armcc.exe -c --cpu Cortex-M3 -Os main.c -o main_Os.o # 查看各目标文件大小 dir main_O0.o main_O2.o main_Os.o实际对比下来,一个中等复杂度的工程,-O2相比-O0通常能减少 30% 到 50% 的代码体积,-Os再减少 10% 到 20%。但-O3相比-O2的体积减少往往不明显,反而可能因为循环展开导致体积增大。
4.2 关键警告选项与关闭策略
ARMCC 5.06 的警告控制通过--diag_warning和--diag_suppress实现。默认情况下,很多有用的警告是关闭的。我习惯在编译选项里加上--diag_warning=225来开启所有警告,然后针对确实无法修复的警告用--diag_suppress单独关闭。
# 开启所有警告,但抑制 188-D 和 550-D armcc.exe -c --cpu Cortex-M3 -O2 \ --diag_warning=225 \ --diag_suppress=188,550 \ main.c -o main.o188-D通常是「枚举类型与整数比较」的警告,老代码里很常见。550-D是「变量未使用」的警告,在自动生成的代码里经常出现。这两个警告如果全开,编译日志会被刷屏,反而掩盖了真正的问题。
注意:不要用
--diag_suppress=all关闭所有警告。这会让一些真正的内存越界或未初始化变量问题被隐藏,后期调试成本极高。
4.3 内联汇编与内建函数的兼容性写法
ARMCC 5.06 支持两种内联汇编:__asm块和__asm关键字。前者用于嵌入多条指令,后者用于单条指令。在 Cortex-M 系列上,常用的内建函数包括__NOP()、__WFI()、__WFE()、__SEV()、__ISB()、__DSB()、__DMB()。
// 使用内建函数实现精确延时和内存屏障 static inline void cpu_relax(void) { __NOP(); __NOP(); __NOP(); __NOP(); } void write_register(volatile uint32_t *reg, uint32_t value) { __DMB(); // 数据内存屏障,保证前面的写入先完成 *reg = value; __DSB(); // 数据同步屏障,保证写入真正到达外设 }这些内建函数在 ARMCC 5.06 和 ARMCLANG 下的行为基本一致,但__DMB()在 ARMCC 5 下默认生成DMB SY,而在 ARMCLANG 下可能需要显式指定__DMB(0xF)才等价。如果工程里有对时序要求严格的外设操作,迁移编译器时需要重新验证这部分代码。
5. 避坑与排查:ARMCC 5.06 在 Windows 上的五个血泪教训
5.1 安装后 Keil 找不到编译器
现象:msi 安装完成,但在 Keil 的Options for Target里ARM Compiler下拉框是空的,或者只有 ARMCLANG 选项。
原因:Keil MDK 5.37 之后的版本不再自动扫描 ARMCC 5 的安装路径,需要手动指定。另外,如果安装时选择的路径和 Keil 默认搜索路径不一致,也会导致找不到。
解决:在 Keil 的Project → Manage → Project Items → Folders/Extensions里手动添加D:\Tools\ARMCC506\bin目录。添加后重启 Keil,下拉框里应该出现ARM Compiler 5.06选项。
5.2 编译时报 “cannot open source input file ‘stdint.h’”
现象:编译任何包含<stdint.h>的文件都报找不到头文件。
原因:ARMCC 5.06 的头文件搜索路径没有正确配置。默认情况下,armcc.exe会从自身所在目录的../include查找头文件,但如果安装时目录结构被改动,或者环境变量ARMCC5INC指向了错误路径,就会找不到。
解决:在编译命令里显式加-I参数指向include目录,或者在 Keil 的Options for Target → C/C++ → Include Paths里添加。命令行编译时,确保-I路径是D:\Tools\ARMCC506\include而不是D:\Tools\ARMCC506\bin\..\include,后者在某些 shell 下解析会出问题。
5.3 链接时报 “L6406E: No space in execution regions”
现象:编译通过,链接阶段报某个执行区域空间不足。
原因:分散加载文件里定义的区域大小和实际代码/数据量不匹配。常见情况是RW_IRAM1区域设得太小,而工程里全局变量和堆栈加起来超过了这个值。
解决:先用fromelf --text -z查看各段大小,然后调整 scatter 文件里的区域大小。如果 RAM 确实不够,考虑把一些大数组改成const放到 Flash 里,或者用__attribute__((section("...")))把特定变量放到外部 RAM。
# 查看各段大小 fromelf.exe --text -z output.axf输出里会列出Code、RO-data、RW-data、ZI-data的大小。RW-data加ZI-data就是运行时需要的 RAM 总量,必须小于 scatter 文件里 RAM 区域的大小。
5.4 优化开启后延时函数失效
现象:-O0下正常的延时函数,切到-O2后延时明显变短,甚至完全消失。
原因:编译器认为延时循环里的变量没有被外部使用,把整个循环优化掉了。这是嵌入式开发里最经典的坑之一。
解决:把延时循环里的计数器声明为volatile,或者用__NOP()内建函数替代空循环。更可靠的做法是用硬件定时器做延时,而不是软件循环。
// 错误写法:-O2 下可能被优化掉 void delay_bad(uint32_t count) { while (count--) {} } // 正确写法:volatile 阻止优化 void delay_good(volatile uint32_t count) { while (count--) { __NOP(); } }5.5 在 64 位 Windows 上编译报 “0xc000007b”
现象:双击armcc.exe或命令行调用时报错,错误码0xc000007b。
原因:这是典型的 32 位程序在 64 位系统上缺少运行库的症状。ARMCC 5.06 是 32 位程序,依赖msvcr100.dll或msvcr120.dll等 VC++ 运行库。
解决:安装vcredist_x86(注意是 x86 版本,不是 x64)。如果已经装了 x64 版本,仍然需要单独装 x86 版本,因为 WOW64 子系统不会自动复用 64 位运行库。
6. 把 ARMCC 5.06 塞进 CI:命令行构建与版本锁定的具体技巧
6.1 用 Makefile 封装 ARMCC 5.06 的构建流程
在 CI 环境里,通常没有 Keil 的图形界面,需要用 Makefile 或 CMake 驱动armcc.exe。下面是一个最小可用的 Makefile 片段:
# ARMCC 5.06 工具链路径 ARMCC_DIR = D:/Tools/ARMCC506 CC = $(ARMCC_DIR)/bin/armcc.exe AS = $(ARMCC_DIR)/bin/armasm.exe LD = $(ARMCC_DIR)/bin/armlink.exe FROMELF = $(ARMCC_DIR)/bin/fromelf.exe # 编译选项 CPU = Cortex-M3 CFLAGS = -c --cpu $(CPU) -O2 --diag_warning=225 INCLUDES = -I$(ARMCC_DIR)/include -I./inc LDFLAGS = --cpu $(CPU) --scatter scatter.scat # 源文件 SRCS = main.c system.c driver.c OBJS = $(SRCS:.c=.o) # 目标 all: output.bin %.o: %.c $(CC) $(CFLAGS) $(INCLUDES) $< -o $@ output.axf: $(OBJS) $(LD) $(LDFLAGS) $(OBJS) -o $@ output.bin: output.axf $(FROMELF) --bin -o $@ $< clean: del /Q *.o *.axf *.bin这个 Makefile 的关键点在于:路径用正斜杠而不是反斜杠,避免 Windows 下转义问题;--diag_warning=225开启所有警告;--scatter指定分散加载文件。在 CI 里跑make all就能完成从源码到 bin 的完整构建。
6.2 版本锁定与可复现构建
ARMCC 5.06 的 b960 版本是固定的,但不同来源的 msi 安装包可能存在细微差异。为了保证 CI 构建的可复现性,建议把安装包和安装后的目录一起打包,放到内部制品库里。
# 计算安装目录的哈希,用于版本校验 certutil -hashfile D:\Tools\ARMCC506\bin\armcc.exe SHA256 # 输出示例: # SHA256 hash of D:\Tools\ARMCC506\bin\armcc.exe: # a1b2c3d4e5f6... (实际哈希值)在 CI 脚本里,先校验armcc.exe的哈希值,再执行构建。如果哈希不匹配,说明工具链被替换过,构建结果可能不可复现。
另一个技巧是把armcc.exe的版本号写入构建产物的元数据里。可以在 Makefile 里加一个version目标:
version: $(CC) --version > build_version.txt这样每次构建都会生成一个build_version.txt,记录当前使用的编译器版本。后期排查问题时,能快速确认是哪个版本编译出来的。
6.3 用 Docker 封装 Windows 构建环境
如果 CI 跑在 Linux 上,但需要 ARMCC 5.06 的 Windows 版本,可以用 Wine 来运行。不过 Wine 对 ARMCC 5.06 的兼容性并不完美,某些链接阶段的操作会报错。更稳妥的做法是用 Windows 容器。
# 基于 Windows Server Core 的构建镜像 FROM mcr.microsoft.com/windows/servercore:ltsc2019 # 安装 VC++ 运行库 ADD https://example.com/vcredist_x86.exe /tmp/vcredist_x86.exe RUN /tmp/vcredist_x86.exe /quiet /norestart # 复制 ARMCC 5.06 安装目录 COPY ARMCC506 /Tools/ARMCC506 # 设置环境变量 ENV PATH="C:\Tools\ARMCC506\bin;%PATH%" # 验证 RUN armcc.exe --version这个 Dockerfile 的关键是把 ARMCC 5.06 的安装目录直接复制进镜像,而不是在容器里跑 msi 安装。因为 msi 安装需要图形界面或静默参数,在容器里容易失败。直接复制目录更可靠,但需要确保复制的是完整安装后的目录。
注意:Windows 容器镜像体积较大,基础镜像就有几个 GB。如果 CI 环境对镜像拉取时间敏感,可以考虑用
nanoserver基础镜像,但需要额外验证 ARMCC 5.06 在 nanoserver 上的兼容性。
6.4 构建缓存与增量编译
ARMCC 5.06 本身不支持分布式编译,但可以通过 Makefile 的依赖规则实现增量编译。关键是把头文件依赖也纳入 Makefile 的依赖链。
# 生成头文件依赖 %.d: %.c $(CC) -c --cpu $(CPU) -M $< > $@ # 包含依赖文件 -include $(SRCS:.c=.d)-M选项让armcc.exe输出源文件依赖的所有头文件列表。这样当头文件改动时,依赖它的源文件会自动重新编译。在大型工程里,这个技巧能把增量编译时间从几分钟降到几秒。
我自己的习惯是:在 CI 里第一次构建用全量编译,后续构建用增量编译加缓存。缓存目录挂载到宿主机上,避免每次 CI 都从头编译。但要注意,ARMCC 5.06 的目标文件格式和 ARMCLANG 不兼容,缓存目录不能混用。
6.5 一个具体的验证方法:对比不同优化等级的产物
最后分享一个我常用的验证技巧:用同一个工程,分别用-O0和-O2编译,然后对比生成的 bin 文件大小和反汇编代码。如果-O2的 bin 文件比-O0还大,说明优化选项可能没生效,或者 scatter 文件配置有问题。
# 分别编译 make clean && make CFLAGS="-c --cpu Cortex-M3 -O0" all ren output.bin output_O0.bin make clean && make CFLAGS="-c --cpu Cortex-M3 -O2" all ren output.bin output_O2.bin # 对比大小 dir output_O0.bin output_O2.bin正常情况下,-O2的 bin 文件应该比-O0小 30% 以上。如果差距很小,检查 Makefile 里的CFLAGS是否真的传递到了armcc.exe。我遇到过好几次因为 Makefile 变量覆盖导致优化选项丢失的情况,编译日志里看命令行参数才能发现。
希望帮到你。
本文还有配套的精品资源,点击获取