简介:ARM Compiler v5.05 Build 169 是面向嵌入式开发者的编译器更新包,适用于使用 Keil MDK 等 ARM Compiler 5 工具链的工程师,用于升级现有编译环境、修复旧版本问题并提升代码编译稳定性。该更新需配合 ARM Compiler 5 产品的有效授权使用,且不适用于 ARM DS-5 中的编译器更新,同一功能版本的多份更新包也不建议共存于同一台机器。资源包共 3 个文件,包含 1 个 exe 安装引导程序、1 个 msi 主安装包以及 1 个 html 格式的版本说明文档,压缩包整体约 79.71MB,体积适中便于下载与本地部署。目前已有 2395 人学习下载,说明该版本在嵌入式社区中具有一定关注度。对于需要维护旧项目、复现历史编译环境或排查工具链兼容性问题的开发者而言,这份更新包能提供明确的版本信息与安装入口,配合说明文档可快速确认适用条件,减少因版本错配导致的编译异常。
1. 老 Keil 工程迁移时,为什么我死磕 ARM Compiler v5.05
上周帮一个做工业控制的朋友处理遗留代码,Keil MDK 打开一个 2014 年的电机控制工程,编译直接报cannot open source input file "stdint.h",换了几台机器都一样。问题不在代码,在编译器版本——那个工程是用 ARM Compiler 5 时代的--c99和--gnu混合模式写的,换成 AC6 之后内联汇编语法、__align关键字、分散加载文件全对不上。折腾半天,最后还是回到 ARM Compiler v5.05, Build 169 for Windows 这个版本才把工程救活。
这就是今天要聊的资源:ARM Compiler v5.05(内部版本号 Build 169)的 Windows 安装包。它属于 ARM Compiler 5 系列,是 Keil MDK 4.x 到 MDK 5 早期版本默认集成的工具链,核心组件包括armcc、armasm、armlink、fromelf和armar。对现在还在维护 STM32F1/F4、LPC17xx、nRF51 这类老平台的工程师来说,这不是"怀旧",是刚需——很多量产项目的库文件、启动代码、链接脚本就是按 AC5 的规则写的,换编译器等于重做底层适配。适合谁?维护 legacy 工程的人、需要复现老版本编译结果的人、以及被 AC6 迁移报错卡住的嵌入式开发者。
2. ARM Compiler 5 与 AC6 的差异:先搞清楚为什么不能随便换
在动手装之前,得先明白 AC5 和 AC6 到底差在哪。很多人以为编译器就是"把 C 翻译成机器码",版本高低只影响优化效果,实际上 AC5 到 AC6 是一次底层架构切换——AC6 基于 LLVM/Clang,AC5 是 ARM 自研的闭源前端。这个差异导致的不兼容不是"改几行代码"能解决的。
2.1 编译器前端与语言标准的根本分歧
AC5 默认走的是--c90加 GNU 扩展的路子,对 C99 的支持是"部分实现",很多写法在 AC5 下能过,到 AC6 就报错。最典型的是内联汇编:AC5 用__asm { ... }块,AC6 强制要求__asm volatile加 GNU 扩展语法,寄存器约束符也完全不同。还有__align(8)这种 AC5 特有的对齐关键字,AC6 里得换成__attribute__((aligned(8)))。
另一个高频翻车点是#pragma指令。AC5 的#pragma arm section、#pragma import在 AC6 里要么废弃要么语义变了。我见过一个工程,光是把#pragma import(__use_no_semihosting)改成 AC6 的写法就花了半天,因为链接行为跟着变了,半主机模式没关干净,跑起来直接进 HardFault。
2.2 链接器与分散加载文件的兼容边界
armlink是 AC5 的链接器,AC6 换成了armlink的 LLVM 版本(也叫armlink但行为不同)。分散加载文件(.sct)的语法在 AC5 下很宽松,比如RW_IRAM1 0x20000000 UNINIT 0x00010000 { }这种写法,AC6 对UNINIT段的对齐和初始化检查更严,稍有不符就报L6226E之类的错误。
还有库文件的 ABI 兼容问题。AC5 编译的.lib静态库,AC6 链接时可能因为__aeabi_系列函数的调用约定微调而失败。这不是"重新编译库"就能解决的——如果库是第三方提供的二进制,没有源码,那就只能回到 AC5。
2.3 什么时候必须用 AC5.05 而不是其他 AC5 版本
AC5 系列有多个 build,v5.05 Build 169 是其中一个比较稳定的节点。选它的理由很实际:Keil MDK 5.14 到 5.23 之间默认集成的就是这一版,网上能找到的工程模板、例程大多基于这个版本验证过。如果你手头的工程明确标注了"基于 MDK 5.20"或"AC5 Build 169 编译通过",那就别用 v5.06 去试,build 号不同,优化器的行为可能有细微差异,导致时序敏感的代码(比如软件延时、PWM 死区)跑飞。
提示:判断工程该用哪个 AC5 版本,最直接的方法是看工程目录下的
.uvprojx文件,搜索<pArmCC>标签里的版本号,或者看编译输出窗口第一行的Toolchain: ARM Compiler 5后面的 build 号。
3. 在 Keil MDK 里挂载 AC5.05:路径、环境变量与工程切换
装好编译器只是第一步,关键是让 Keil 能找到它,并且让工程用上它。这一章按操作顺序走,每一步都给出具体路径和验证方法。
3.1 安装包解压与目录结构确认
ARM Compiler v5.05 的 Windows 包通常是一个自解压 exe 或者 zip。解压后你会看到类似这样的结构:
ARM_Compiler_5.05_169/ ├── bin/ │ ├── armcc.exe │ ├── armasm.exe │ ├── armlink.exe │ ├── fromelf.exe │ └── armar.exe ├── include/ │ ├── stdint.h │ ├── stdlib.h │ └── ... ├── lib/ │ ├── armlib/ │ └── ... └── sw/ └── mdk/ └── ...关键目录是bin和include。bin里是可执行文件,include是标准头文件。有些打包版本会把sw/mdk下的文件单独放,那是给 Keil 用的配置文件,不影响编译器本身。
安装时建议放到一个没有空格和中文的路径,比如D:\Tools\ARM_Compiler_5.05_169。路径里有空格的话,Keil 调用armcc时可能因为引号处理问题报cannot execute。
3.2 在 Keil 中注册编译器路径
打开 Keil MDK,点Project→Manage→Project Items,切到Folders/Extensions标签页。在ARM Compiler下拉框里选Use Custom,然后浏览到你的bin目录。
但这里有个坑:Keil 只认bin目录下的armcc.exe,它不会自动去include找头文件。你还需要在工程的Options for Target→C/C++标签页里,把Include Paths加上D:\Tools\ARM_Compiler_5.05_169\include。否则编译时会报找不到stdint.h、stddef.h这些基础头文件。
配置完成后,点Rebuild,看编译输出窗口第一行。如果显示Toolchain: ARM Compiler 5.05 (build 169),说明挂载成功。如果还是显示 AC6 的版本号,检查是不是在Options for Target→Target标签页里,ARM Compiler下拉框没选对——这里有两个地方要设,一个是全局的Manage里,一个是工程级的Target里,后者优先级更高。
3.3 环境变量与命令行编译的配置
如果你需要在命令行下用armcc编译,或者写批处理脚本,那就得配环境变量。在系统环境变量里加:
# Windows 命令行下设置(临时) set ARMCC5_HOME=D:\Tools\ARM_Compiler_5.05_169 set PATH=%ARMCC5_HOME%\bin;%PATH% set ARMCC5_INC=%ARMCC5_HOME%\include然后验证:
armcc --version正常输出应该是:
ARM Compiler 5.05 Toolchain: ARM Compiler 5.05 (build 169)如果报'armcc' 不是内部或外部命令,检查 PATH 是否拼错,或者bin目录下是不是真的有个armcc.exe。有些打包版本里armcc.exe是个快捷方式,指向armcc_real.exe,这种在命令行下会出问题,得把真实文件复制出来。
命令行编译一个简单文件:
armcc -c -O2 --cpu Cortex-M3 -I%ARMCC5_INC% main.c -o main.o参数说明:-c只编译不链接,-O2优化等级,--cpu Cortex-M3指定目标内核,-I加头文件路径。这一步能过,说明编译器本身没问题,剩下的就是工程配置的事了。
4. 用 AC5.05 编译一个 STM32 工程:从报错到跑通的完整记录
光说配置不够,得拿一个真实场景走一遍。下面用一个虚构的 STM32F103 电机控制工程(就叫它MotorCtrl_Legacy)做例子,记录从 AC6 切回 AC5.05 的完整过程。
4.1 工程切换后的第一轮报错与修复
把工程从 AC6 切到 AC5.05 后,第一次编译通常会炸出一堆错。我遇到的典型报错有这几类:
报错一:error: #20: identifier "xxx" is undefined
原因:AC6 默认包含了一些 AC5 不自动包含的头文件,或者反过来。比如stdbool.h在 AC5 下需要显式#include,而 AC6 可能通过--c99自动带进来。
解决:在main.c或公共头文件里补上#include <stdbool.h>、#include <stdint.h>。
报错二:error: #268: declaration may not appear after executable statement in block
原因:AC5 默认走 C90 标准,变量声明必须放在块的开头。AC6 默认 C99,允许随时声明。
解决:要么把变量声明挪到块首,要么在Options for Target→C/C++里勾上C99 Mode。但注意,勾了 C99 之后,某些 AC5 的 GNU 扩展可能行为变化,得逐个验证。
报错三:error: L6218E: Undefined symbol __use_no_semihosting (referred from xxx.o)
原因:AC5 的半主机模式处理方式和 AC6 不同。AC6 里__use_no_semihosting是个弱符号,AC5 里需要配合#pragma import使用。
解决:在代码里加:
#pragma import(__use_no_semihosting) void _sys_exit(int x) { x = x; } void _ttywrch(int ch) { ch = ch; } FILE __stdout;这段代码的作用是告诉链接器不要链接半主机相关的库函数,避免在无操作系统环境下调用printf时卡死。
4.2 分散加载文件与启动代码的适配
AC5 的分散加载文件(.sct)语法和 AC6 有细微差别。一个典型的 STM32F103 的.sct文件长这样:
LR_IROM1 0x08000000 0x00010000 { ER_IROM1 0x08000000 0x00010000 { *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x00005000 { .ANY (+RW +ZI) } }在 AC5 下,*(InRoot$$Sections)这个写法是必须的,它把启动代码里的__main和__scatterload相关段放到最前面。AC6 里这个语法变了,如果直接拿 AC6 的.sct给 AC5 用,会报L6226E: Missing section之类的错。
启动代码startup_stm32f10x_hd.s也要注意。AC5 的armasm对汇编文件的语法要求比 AC6 的armclang宽松,但有些伪指令不一样。比如AREA STACK, NOINIT, READWRITE, ALIGN=3在 AC5 下没问题,AC6 里ALIGN=3得写成ALIGN(8)或者用ALIGN指令单独处理。
4.3 编译通过后的验证:从 hex 到实际运行
编译链接通过后,用fromelf生成 hex 和 bin:
fromelf --i32combined --output=MotorCtrl.hex MotorCtrl.axf fromelf --bin --output=MotorCtrl.bin MotorCtrl.axf参数说明:--i32combined生成 Intel HEX 格式,--bin生成纯二进制。MotorCtrl.axf是链接输出的可执行文件。
烧录前,用fromelf -c反汇编看一下关键函数的地址:
fromelf -c --output=disasm.txt MotorCtrl.axf在disasm.txt里搜main,确认入口地址和预期一致。如果地址对不上,可能是分散加载文件里的RESET段没放对位置。
实际跑起来之后,重点验证三件事:中断向量表是否在0x08000000正确映射、SystemInit里的时钟配置是否生效(用示波器看 MCO 输出或者翻转 GPIO)、以及printf重定向是否正常(如果用了半主机模式,确认__use_no_semihosting生效)。
5. 避坑与排查:AC5.05 在 Win10/Win11 上的五个血泪经验
这一章全是踩过的坑,每条按"现象 → 原因 → 解决"写,不绕弯子。
5.1 编译时报 "cannot execute armcc"
现象:Keil 里点编译,输出窗口一闪而过,报cannot execute 'D:\Tools\ARM_Compiler_5.05_169\bin\armcc'。
原因:路径里有空格或中文,或者armcc.exe被杀毒软件隔离了。Win10 自带的 Defender 对老版本编译器的某些行为敏感,可能静默删除。
解决:把编译器移到纯英文无空格路径,比如D:\AC5_05_169。然后在 Defender 里把该目录加入排除项。如果文件已经被删,重新解压一份。
5.2 链接时报 "L6226E: Missing section"
现象:编译单个.c文件没问题,链接时报L6226E: Missing section .ANY (+RO)或类似。
原因:分散加载文件语法和 AC5 不匹配,通常是InRoot$$Sections没写对,或者RESET段没加+First。
解决:对照 AC5 的官方模板改.sct文件。关键点是*.o (RESET, +First)必须在ER_IROM1的第一行,*(InRoot$$Sections)紧随其后。
5.3 程序跑起来就进 HardFault
现象:烧录后复位,程序不跑,调试器显示停在HardFault_Handler。
原因:多半是启动代码里的堆栈配置和 AC5 的默认库不匹配。AC5 的__initial_sp符号和 AC6 的__initial_sp语义不同,如果启动文件是从 AC6 工程拷过来的,堆栈指针可能没初始化对。
解决:用 AC5 配套的启动文件,或者手动检查startup_xxx.s里的Stack_Size和Heap_Size定义。AC5 下Stack_Size至少0x00000400,Heap_Size如果不用malloc可以设0x00000000。
5.4 优化等级 -O2 下代码行为异常
现象:-O0编译跑得好好的,切到-O2后某个循环延时不准了,或者某个volatile变量没按预期更新。
原因:AC5 的-O2优化比 AC6 更激进,对volatile的处理在某些边界情况下有差异。特别是软件延时循环,如果循环变量没加volatile,会被优化掉。
解决:延时函数的循环变量加volatile,或者用__nop()内联汇编替代空循环。如果问题依旧,降到-O1试试,AC5 的-O1和-O2差距比 AC6 大。
5.5 命令行编译时找不到标准库
现象:命令行下armcc -c main.c报cannot open source input file "stdio.h"。
原因:armcc默认不会去include目录找头文件,需要显式指定-I或者设置ARMCC5_INC环境变量并在命令里引用。
解决:在命令行里加-I%ARMCC5_INC%,或者把include目录加到系统INCLUDE环境变量里。但注意,系统INCLUDE可能被其他编译器污染,建议只在当前会话临时设置。
6. 进阶:用批处理脚本固化 AC5.05 编译流程
手动在 Keil 里点来点去,适合调试阶段。一旦工程稳定了,我习惯写个批处理脚本,把编译、链接、生成 hex、反汇编一条龙跑完。这样换机器或者交给 CI 的时候,不用重新配 Keil 环境。
6.1 批处理脚本模板与参数说明
下面是一个可复用的build.bat模板:
@echo off setlocal REM 编译器路径,按实际修改 set AC5_HOME=D:\AC5_05_169 set PATH=%AC5_HOME%\bin;%PATH% set INC=%AC5_HOME%\include REM 工程参数 set CPU=Cortex-M3 set OPT=-O2 set DEFS=-DUSE_STDPERIPH_DRIVER -DSTM32F10X_HD REM 源文件列表 set SRCS=main.c stm32f10x_it.c system_stm32f10x.c motor_ctrl.c REM 编译 for %%f in (%SRCS%) do ( echo Compiling %%f ... armcc -c %OPT% --cpu %CPU% %DEFS% -I%INC% -I.\inc %%f -o %%~nf.o if errorlevel 1 goto :error ) REM 链接 echo Linking ... armlink --cpu %CPU% --scatter=.\MotorCtrl.sct ^ --info totals ^ -o MotorCtrl.axf *.o if errorlevel 1 goto :error REM 生成 hex 和 bin fromelf --i32combined --output=MotorCtrl.hex MotorCtrl.axf fromelf --bin --output=MotorCtrl.bin MotorCtrl.axf echo Build OK. goto :eof :error echo Build FAILED. exit /b 1参数说明:AC5_HOME指向编译器根目录,CPU指定内核,OPT是优化等级,DEFS是宏定义,SRCS是源文件列表。--scatter指定分散加载文件,--info totals让链接器输出各段大小,方便检查 RAM/Flash 占用。
6.2 验证编译产物的一致性
脚本跑通后,怎么确认生成的 hex 和 Keil 里编译的一致?我的做法是对比.axf文件的 MD5。在 Keil 里编译一次,把Objects\MotorCtrl.axf拷出来,和脚本生成的MotorCtrl.axf做certutil -hashfile MotorCtrl.axf MD5。如果 MD5 一样,说明编译参数完全一致;不一样的话,检查优化等级、宏定义、头文件路径是不是有差异。
还有一个技巧:用fromelf --text -c生成反汇编,对比关键函数的指令条数。如果条数差很多,说明优化行为不同,得回去查参数。
6.3 从 AC5 迁移到 AC6 的评估方法
如果你手头的工程迟早要迁到 AC6,别等到被迫迁移时才动手。我的习惯是:先用 AC5.05 编译出一个"黄金版本",记录 Flash 和 RAM 占用、关键函数的执行周期(用 DWT 计数器测)。然后切到 AC6,同样测一遍,对比差异。如果 Flash 占用增加超过 10%,或者某个中断服务函数的执行时间变长超过 20%,那就得逐项排查——通常是内联汇编或者#pragma指令的兼容问题。
迁移评估表可以按这个格式记:
| 指标 | AC5.05 结果 | AC6 结果 | 差异 | 是否可接受 |
|---|---|---|---|---|
| Flash 占用 | 28,432 B | 31,208 B | +9.8% | 需优化 |
| RAM 占用 | 6,128 B | 6,240 B | +1.8% | 可接受 |
| 中断响应时间 | 1.2 us | 1.5 us | +25% | 需排查 |
| 编译时间 | 12 s | 8 s | -33% | 可接受 |
这张表填完,迁不迁、怎么迁,心里就有数了。
从那以后我每次拿到 legacy 工程,第一件事就是确认编译器版本,第二件事是备份一份能编译通过的原始环境。别嫌麻烦,等哪天原始环境找不到了,再想复现就是大海捞针。希望帮到你。
本文还有配套的精品资源,点击获取