升级完Keil MDK 5.37,打开老工程一编译,满屏红色报错,第一反应是代码出问题了,结果折腾半天发现根子根本不在代码上——是5.37默认把编译器换成了ARM Compiler 6(AC6),而你手里的老项目还是按ARMCC V5(AC5)的语法写的。这个坑我踩过,身边不少同事也踩过,所以在网上找了半天资料、试了好几遍,总算把AC5完整地加回了Keil里。这篇就把整个来龙去脉、操作步骤、以及切换后的隐藏坑一起讲清楚,给正被编译报错折磨的人一份可以照着做的方案。
先说明一点,这篇针对的是已经装了MDK 5.37、又必须继续用ARMCC V5编译老工程的情况。如果你是新项目,没历史包袱,我其实更建议直接用AC6,没必要折腾,但老项目迁移成本高、第三方库还依赖AC5的时候,手动加回V5就是唯一省事的路。
1. 升级5.37后第一波报错:罪魁祸首是编译器被“调包”了
1.1 报错表面现象和真实原因
最典型的报错场景是:打开之前的工程,点Build,然后出现一批让人摸不着头脑的红色信息。常见的几种表现:
- 编译过程一闪而过,然后报
*** Target Build Failed,但看不出具体是哪个文件出错。 - 大量语法层面的报错,比如
expected ';'、unknown type name、use of undeclared identifier,而且报错位置全是以前根本不会出错的老代码。 - 链接阶段报
undefined symbol,但明明函数定义了,声明、包含关系都对。 - 更迷惑的是,有时候会报命令找不到,比如标题热词里出现的
*** error: createprocess failed, command: 'c:\keil_v5\arm\armcc\bin\fromelf...——这是编译器路径根本无法执行。
第一种、第二种和第四种看着像代码问题、路径问题,真实根源却是同一个:MDK 5.37把默认编译器切到了AC6,而你工程里的编译选项、语法细节还是AC5时代的。AC6的前端是Clang,对代码的解析规则、关键字支持、内联汇编语法,和AC5差别非常大。老代码在AC5下“合法”的写法,拿到AC6下可能直接就成了编译错误。
1.2 为什么5.37开始默认不带AC5了
ARM从MDK 5.36开始就把AC5从默认安装里拿掉了,5.37更是彻底。原因有两个层面。
一是ARM的战略重心早就转向了AC6。AC6基于LLVM架构,由ARM和LLVM社区共同推进,新架构支持、新特性、性能优化都往AC6上倾斜,而AC5处于只维护不更新的状态。ARM官方的支持周期早就进入倒计时,在5.37里,AC5不再是“预装组件”,而是变成了“可选/遗留组件”。
二是Keil的组件管理器(Pack Installer)里虽然还能看到ARM Compiler 5的条目,但它不再随MDK主程序一起安装。很多从老版本升级上来的用户没意识到这点,装完5.37直接打开工程就编译,自然报错。这属于典型的“升级容易降级难”问题。
1.3 怎么快速确认当前用的是AC5还是AC6
不用等报错再去猜,动手编译前可以先看一眼编译器设置。
在Keil里打开目标工程,进入Options for Target(快捷键 Alt+F7),切到Target标签页。看到Arm Compiler这一栏,如果下拉框显示的是Use default compiler version 6之类,说明当前用的是AC6;如果显示V5.06 update 7 (build 960),说明是AC5。另外在编译输出窗口第一行也会打出一段信息,类似*** Using Compiler 'V5.06 update 7'或*** Using Compiler 'V6.xx',这个最直接。
我遇到的情况是,5.37装完后Arm Compiler下拉列表里根本没有V5可选,只有V6。换句话说,不是切换的问题,是AC5压根没装进系统里。这就引出了下一步:怎么把AC5弄回来。
2. ARMCC V5与V6的差异到底差在哪,为什么老项目离不开AC5
2.1 编译器本质差异:armcc和armclang
AC5的编译器命令是armcc,在Keil安装目录下对应ARM/ARMCC/bin;AC6的编译命令是armclang,对应ARM/ARMCLANG/bin。这俩不仅是名字不同,内部架构完全不同。
armcc是ARM传统的闭源编译器,几十年沉淀,针对ARM芯片做了极端优化,很多老工程、老芯片的启动代码、寄存器定义、内联汇编都围绕它编写。armclang则是基于LLVM的Clang前端,和GCC、Clang生态同源,语法解析更加标准化,但也有自己的脾气,对未定义行为、隐式声明等问题的容忍度低很多。
这里打一个不是特别精确但容易理解的比方:AC5像是一位熟悉老厂房的老师傅,你用以前约定俗成的暗号指挥他,他心领神会;AC6像是一位只认国家标准的年轻工程师,你不按规范跟他说话,他就“拒绝执行”。代码注释、宏定义、#pragma、内联汇编这些,在老代码里经常带着“暗号”色彩,AC6不吃这套。
2.2 最常见的编译不兼容点
具体到代码层面,AC5迁移AC6的典型报错集中在这么几类:
- 关键字差异。AC5支持
__irq、__asm、__forceinline、__packed、__align这类编译器专属关键字,AC6不一定完整兼容,或者需要改成__attribute__((interrupt("IRQ")))、__attribute__((always_inline))这类GNU风格写法。 - 内联汇编。AC5使用
__asm配合ARM/Thumb汇编语法,AC6使用__asm volatile配合GNU汇编语法。寄存器约束、操作数写法几乎完全不同,老代码里大段内联汇编基本等于重写。 #pragma处理。AC5里常用的#pragma pack(push, 1)、#pragma anon_unions,AC6有些要改成#pragma pack(push, 1)(这个支持),有些则直接不支持,比如anon_unions就得移除或改写法。- C标准支持程度。AC5默认偏向C90/自定义扩展,AC6默认更靠近C11/ GNU扩展。以前“不报错就算了”的隐式函数声明、旧式函数定义,AC6可能直接报warning甚至error。
- 编译选项和优化选项。AC5的
--cpu、--apcs、-Ospace、-Otime和AC6的-mcpu、-mfloat-abi、-Oz、-Os完全是两套命名。工程文件里保存的编译器选项无法直接映射,切换编译器后Keil会丢弃或忽略一部分选项,间接引发不可预期的问题。
2.3 为什么老项目短期“离不开”AC5
既然AC6这么“正规”,那大家都应该迁移过去才对,为什么非要折腾AC5?真实情况是:
- 第三方的闭源库、老版本协议栈可能直接用AC5编译方式分发,没有源码,只能继续用AC5才能链接。
- 项目启动代码、
startup_xxx.s汇编文件可能是AC5风格的,AC6环境下汇编器(armasm vs armclang内置汇编器)语法兼容不完整。 - 老工程师多年的代码习惯、底层驱动都揉在AC5语法里,迁移成本不是一两天能消化的。
- 芯片厂商的不少老例程(比如一些工业级MCU的原厂库)只验证过AC5编译,升级到AC6后出问题,厂商也没有精力更新。
- 项目的编译脚本、CI流程可能还直接调用
armcc,全部改造成AC6需要连带改流水线。
所以,“加回AC5”不是逆向操作,而是嵌入式开发里非常现实的兼容性诉求。这也是这篇文章存在的意义。
3. 手把手把ARMCC V5请回Keil:从下载到切换的完整路径
3.1 资源获取:官方和本地两种合规途径
先说资源来源。ARMCC V5现在不会自动出现在MDK 5.37里,但获取途径是合法、官方的,有两种常见方式。
方式一:从Keil官网的历史版本(Previous Versions)页面下载旧版MDK安装包。
Keil官网的Downloads页面往下翻,有一块“Previous Versions”或者叫“Legacy versions”的区域,可以下到更早的MDK安装文件,比如MDK 5.36、5.35、5.34等。这些旧版本的安装包是完整自包含的,里面带AC5编译器。下载安装后,在你的Keil安装目录(比如C:\Keil_v5\ARM\ARMCC)下就能找到完整的 ARM Compiler 5。
方式二:从同事/另一台机器的旧版MDK安装目录里直接拷贝ARMCC文件夹。
ARMCC V5是独立目录,直接拷贝到本机也能用。具体来说,源机器上C:\Keil_v5\ARM下的ARMCC整个文件夹,复制到本机C:\Keil_v5\ARM\ARMCC,理论上是可行的。我实际验证过,只要能保证目录完整、版本一致(推荐5.06 update 7),Keil能正常识别。
注意:不要在网上下载来路不明的所谓“绿色版ARMCC”,里面文件不完整、被第三方改动过,容易踩各种诡异报错。官方历史版本安装包或者可信同事的完整拷贝,才是可靠路径。
网上流传最广的ARMCC V5最终版本是5.06 update 7 (build 960),这也是AC5的最后一个更新版本。我用的是这个版本,稳。
3.2 目录结构和安装位置说明
拿到ARMCC文件夹后,要先理解Keil的目录结构,避免放错位置。
Keil MDK的默认安装路径一般是C:\Keil_v5。在ARM目录下,并列放着多个工具链目录。5.37安装后常见的有:
C:\Keil_v5\ARM\ARMCLANG\ # AC6编译器 C:\Keil_v5\ARM\PACK\ # 芯片支持包 C:\Keil_v5\ARM\CMSIS\ # CMSIS相关文件 C:\Keil_v5\ARM\Flash\ # 下载算法文件而AC5对应的目录是C:\Keil_v5\ARM\ARMCC。如果这个文件夹不存在,Keil的编译器下拉列表里自然没有V5选项。把完整的ARMCC文件夹放到这里,是让Keil识别到AC5的前提。
3.3 具体操作步骤
整理一份可以照着做的完整流程:
备份当前工程。改编译器之前,用Git或者复制一份工程文件夹,避免操作失误导致工程文件损坏。这个动作建议养成习惯,编译环境的调整比改代码更容易引入隐蔽问题。
获取ARMCC V5目录。按照3.1里的方式拿到
ARMCC文件夹,最好先确认里面有bin、include、lib这三个子目录,缺少任何一个都不能正常工作。bin里有armcc.exe、fromelf.exe、armasm.exe等核心程序。放置目录。把
ARMCC文件夹放到C:\Keil_v5\ARM\下。如果Keil装在其他盘,找到对应位置,比如D:\Keil_v5\ARM\ARMCC。重启Keil。关闭所有工程后重新打开MDK。Keil在启动时会扫描
ARM目录下的编译器,识别到ARMCC目录后,Options for Target -> Target -> Arm Compiler下拉框里就会出现V5.06 update 7 (build 960)之类的选项。切换编译器。打开工程,进入
Options for Target -> Target标签页,把Arm Compiler从V6下拉项切换成V5。如果你的工程里某些子目标配置不同,要逐个配置项检查,不能只改主目标。重新编译。先
Rebuild(重新编译全部),观察输出信息。如果顺利,会看到编译器信息变成*** Using Compiler 'V5.06 update 7',同时不再有AC6那批语法报错。
还有一种情况:下拉框里已经有V5,但默认是V6,你只需要切换即可,不需要重新放置目录。这种情况多出现在旧版MDK升级到5.37但保留下来的旧工程配置里。
3.4 切换后立即遇到 “createprocess failed” 怎么处理
这一步专门说一下createprocess failed。很多人把ARMCC目录放好后,切换V5,编译时还是报类似这样的错:
*** error: createprocess failed, command: 'c:\keil_v5\arm\armcc\bin\fromelf.exe --text -c --output=... '出现这个错有几个可能:
fromelf.exe确实不在ARMCC的bin目录下——检查目录完整性。- Keil UV4的路径配置还残留旧设置,导致调用了错误路径下的fromelf。可以进入
Options for Target -> User标签页,查看After Build/Rebuild里的用户命令,看是否手写了绝对路径,包含旧路径的就改掉。 - 文件路径里有中文或空格(尤其是路径中带有中文用户名),Keil对这类路径的解析不稳定。直接把Keil安装到纯英文路径下最省心。
- 杀毒软件拦截了
fromelf.exe执行。个别安全软件会把Keil的fromelf当可疑程序处理,在杀毒软件里把C:\Keil_v5\ARM\ARMCC\bin和C:\Keil_v5\ARM\ARMCLANG\bin加入白名单。
实际排查时可以先用命令行手动执行一遍报错里的命令,看是命令本身有问题、路径找不到,还是程序被杀毒拦截。手动能执行,再回Keil里编译,基本就能定位到是Keil侧还是系统侧的问题。
4. 编译器版本切换成功,不代表编译一定通过:旧项目常见兼容坑
4.1 成功切换后仍可能遇到的编译错误清单一览
切换回AC5后,AC6那批语法报错消失了,但有些工程还会遇到属于AC5时代特有问题的报错。我整理了几个高频场景。
场景一:工程文件里残留AC6选项
如果工程是在5.37下用AC6配置过,又切回AC5,Keil可能会把部分AC6专属的编译选项原样传给armcc,armcc不认,就会报类似:
Error: L3900U: Unrecognized option '--target=arm-arm-none-eabi'解决办法是清理工程选项。选中工程,在Options for Target -> C/C++ (AC5)标签页里,把Misc Controls文本框里AC6相关的参数删掉;同理在Asm标签页、Linker标签页也检查一遍。如果手动清理麻烦,可以新建一个空的AC5工程,把源码添加进去,重新配置,比在旧工程上修补更干净。
场景二:分散加载文件(sct)不兼容
AC6的链接器是armlink,AC5的链接器也是armlink,但分散加载文件的语法细节有差异,尤其是使用#! armcc注释头或依赖编译器预处理的写法。此外,老工程里如果写的是AC5风格的scatter文件,比如LR_IROM1 0x08000000 0x00100000 {},切换到AC5时反而会恢复正常;但如果是从AC6工程转回来的,注意AC6的armlink接受更严格的段描述语法,AC5可能无法处理某些IF、FOR预处理指令外的写法。
场景三:CMSIS/芯片头文件版本不匹配
Keil 5.37的Pack Manager会把CMSIS、Device相关文件更新到较新版本,新版本CMSIS头文件里的某些定义、宏可能针对AC6优化,放在AC5下反而编译不过。报错往往在core_cm4.h、system_stm32f10x.c这些官方文件里。
最有效的处理方式是:把工程使用的Device Pack降级到和工程时代匹配的版本。比如STM32F103工程,以前用Keil::STM32F1xx_DFP@2.3.0编译正常,装5.37后被更新到更新版本,就在Pack Installer里把版本降回去。具体操作是Pack Installer左侧找到Device,右侧Versions区域选择旧版本,点Install。
4.2 从AC6改回AC5后,建议顺手做的三件事
即使编译通过了,我也建议做下面三件事,能少走很多弯路。
第一件:关闭AC6新增的优化选项残留。
在Options for Target -> C/C++ (AC5)标签页的Optimization下拉框,确认是你要的等级。AC5的-O0、-O2和AC6的-O0、-O2语义并不完全相同,最好到工程里实际验证运行效果,尤其是涉及时间关键代码的时候。
第二件:统一团队成员的Keil版本。
如果你把工程交给同事,他用的Keil版本、Pack版本和你不一致,很可能在他那里编译报错。建议在工程根目录写一个README或者在版本管理里注明:Keil版本、ARMCC版本、Device Pack版本、是否启用了AC5。这看起来是小事,但团队协作里节省的时间非常多。
第三件:把工程用到的库文件路径确认一遍。
AC5的库文件搜索路径是ARMCC/lib,AC6是ARMCLANG/lib。如果你之前手动添加过某个.a、.lib路径,里面写的是ARMCLANG路径,切回AC5后链接器会报找不到库。检查Options for Target -> Linker -> Misc controls和Libraries里的内容,确保是ARMCC路径。
4.3 验证编译结果的几个关键点
编译通过不等于万事大吉,建议至少验证以下三点:
- 编译日志里确认编译器路径。打开Build Output窗口,最顶部的信息会写明用的是哪个编译器。确认是
V5.06 update 7而不是其他。 - 查看MAP文件里的段布局。链接成功后打开
.map文件,看一下ROM、RAM占用是否符合预期。AC5和AC6对代码段、数据段的默认对齐方式有差异,RAM占用如果比原来大或者小,都要留个心眼。 - 下载后跑一遍基础功能。别只烧进去看个现象,重点跑一遍中断、外设初始化、通信协议模块。编译器版本不同,代码行为可能有微妙变化,尤其是未初始化变量、隐含的字节对齐问题。
5. ARMCC V5和V6的选项对照,以及什么时候该认真考虑迁移
5.1 常用选项对照表
实际操作中,经常要在AC5和AC6之间转换选项,整理一张常用对照表,方便参考。
| 功能描述 | ARMCC V5 (AC5) | ARM Compiler 6 (AC6) |
|---|---|---|
| CPU类型指定 | --cpu=Cortex-M4 | -mcpu=cortex-m4 |
| 浮点ABI | --fpu=FPU5/--fpu=SoftVFP | -mfloat-abi=hard -mfpu=fpv5-sp-d16 |
| 优化等级(时间优先) | -Otime | -O2/-O3 |
| 优化等级(空间优先) | -Ospace | -Os/-Oz |
| 关闭优化 | -O0 | -O0 |
| 设置包含路径 | -I. | -I. |
| 定义宏 | -Dxxx | -Dxxx |
| 编译成只读代码 | --split_sections | -ffunction-sections -fdata-sections |
| 链接时去掉未用段 | 配合--remove | 配合--gc-sections |
| 生成依赖文件 | --depend=xxx.d | -MMD -MF xxx.d |
| 汇编器 | armasm | armclang内置汇编器 |
| 反汇编/格式转换工具 | fromelf | fromelf(路径不同) |
AC6里的-ffunction-sections -fdata-sections和链接器--gc-sections经常配套使用,作用是删除未使用的函数和数据,减小代码体积。AC5侧的对应方案是编译时--split_sections,链接时在分散加载文件或Linker选项里开remove段处理。新手常犯的错是两个编译器选项混用,导致Keil配置面板里出现“Unrecognized option”,所以这里单列出来。
5.2 什么时候“加回AC5”是过渡方案,什么时候是长期方案
如果只是为了让老工程在MDK 5.37下继续编译,加回AC5后就能一直用,很多商业项目在生产维护期就是这么跑的。AC5虽然停止更新,但它本身已经足够稳定,在经典Cortex-M芯片上不会有“不能用”的问题。对这类项目,加回AC5就是长期方案。
但如果你维护的是新项目、新芯片、要长期迭代,那加回AC5只能算过渡。原因也简单:新芯片的Device Pack、CMSIS版本对这些新芯片的支持越来越偏向AC6,部分新特性、新外设库只验证过AC6编译。在新芯片上硬用AC5,遇到问题时可能孤立无援。
我做的一个判断标准是:看项目的芯片型号和启动文件。如果是五年前甚至十年前的芯片、老启动文件、老外设库,AC5就是最佳选择;如果芯片是近两年新出的、厂商SDK也是新出的,乘早规划AC6迁移,别把AC5当作逃避迁移的借口。
5.3 如果迟早要迁移AC6,建议的渐进路线
给未来准备迁移AC6的读者一个渐进式建议,不需要一夜之间全部改完:
- 先让工程在AC5下恢复可用(就是本文讲的操作),保证当前工作不受影响。
- 建一个分支,复制工程为AC6版本,在分支里尝试迁移,记录所有报错和修复方法。
- 优先迁移语法层面的兼容,比如
__irq改写成__attribute__((interrupt)),内联汇编改写成GNU语法,#pragma anon_unions移除,这些是硬骨头,也是收益最大的一步。 - 逐个模块验证,每迁一个模块就编译运行一次,不要等全部改完再验证,否则部署问题时有可能会互相干扰。
- 最后更新CMSIS和Device Pack版本,确认AC6下的ARMCLANG工具链能正确编译官方文件。
迁移完成后,AC5也不是马上删除,保留在Keil的ARMCC目录里,遇到老工程维护随时切回。
6. 写在最后:关于ARMCC V5和MDK 5.37共存的一点个人经验
这篇的实操方法看着不长,实际上每一步都踩过坑。最后分享几条自己的体会:
第一,老工程升级MDK之前,先截图或者记下原工程的编译器版本、Device Pack版本、编译选项,这是你回滚和排查的第一手依据。我见过太多人升级前什么都没记录,报错后连“之前用哪个版本编译的”都说不清楚。
第二,ARMCC V5放在C:\Keil_v5\ARM\ARMCC后,最好不要动它的文件夹名字和内部结构。Keil识别编译器除了看目录,还会校验内部文件版本号,如果你改了名或者精简了文件,即便能识别也可能在某些场景下报错。
第三,遇到莫名其妙的编译问题,先把杀毒软件退掉或者加白名单,再怀疑代码。这个顺序反了会浪费大量时间。Keil本身是老牌工具,但它的编译子进程很多,杀毒软件对这类频繁调用外部程序的软件确实容易误判。
第四,如果真的要用AC5编译新工程,建议在工程配置里显式选择AC5,不要依赖“Use default compiler version”这种默认设置。显式选项能让工程在不同电脑上更可控,也方便别人接手时快速理解环境。
最后分享一个工程实践:我们团队现在维护的工业控制项目就卡在第三方协议栈上,那个协议栈只能AC5编译,所以我一直保留一台装MDK 5.36的构建机专门出正式版本,个人开发机则用5.37加本文这套AC5补全方案。两边环境隔离,长期稳定运行,新项目则逐步往AC6走。这是一个挺实用的折中策略,你可以评估一下是否适合自己团队。
希望这篇能帮你把Keil MDK 5.37加回ARMCC V5这件事一次搞定。如果你在操作过程中遇到这里没提到的报错,建议先按这个顺序排查:确认ARMCC目录完整、确认编译器下拉框已经显示V5、确认工程选项里没有残留AC6参数、确认杀毒软件没拦截,大部分问题到不了代码层面就解决了。