说实话,升级到 Keil MDK 5.37 之后,我第一次编译老工程就卡在了一个看起来极其诡异的地方。弹出的错误是Default Compiler Version 5 not available,紧接着一堆红色报错刷屏,连 main 函数都没编译到。我一开始以为是工程配置坏了,后来折腾了一整个中午才搞明白:这不是你的操作有问题,而是从 MDK 5.37 开始,Keil 官方默认不再捆绑安装 ARMCC(AC5)编译器了。
ARMCC 是好多老嵌入式工程师的心头好,稳定、可控,不少老工程的启动文件、汇编代码、第三方库都是基于 AC5 编译链写的。升级 Keil 后,这些工程在 5.37 里直接编译不过,很多人卡在这一步就放弃了。这篇博文专门说清楚这个问题怎么解决,包括 AC5 编译器怎么装、路径怎么配、哪些细节不能踩,以及编译过程中最常见的几个报错怎么排查。无论你是刚升级完发现工程崩了,还是正打算升级但心里没底,这篇都值得看完。
1. 问题根源:“Default Compiler Version 5 not available”到底在说什么
1.1 Keil 5.37 为什么不再自带 ARMCC
先说清楚 ARMCC 和 AC6 的关系。Keil MDK 早期默认带的编译器就是 ARMCC,也就是 AC5,命令行工具是armcc,对应的编译标准、内联汇编风格、优化选项都是老一派嵌入式工程师最熟悉的那套。而 AC6 是后来基于 LLVM/clang 架构的新工具链,命令行工具是armclang。从 MDK 5.37 开始,Keil 官方安装包默认只集成 AC6,AC5 变成了一个需要手动安装的可选组件。
为什么这么做?一个是 ARM 官方已经把 AC5 打入维护状态,不再做功能更新,所有新特性都往 AC6 上堆;另一个是 AC6 在代码体积、性能、C99/C11 支持上确实更有优势。但从使用者的角度来说,这就意味着,你从 5.36 升级到 5.37,老工程只要用到了 AC5 专属的库、汇编语法或者编译选项,就会被Default Compiler Version 5 not available卡住。这个报错说白了就是:Keil 找不到工程指定的编译器版本,但工程还在硬着头皮要找 AC5。
1.2 报错的真正含义与影响范围
这个报错并不是说你的代码有问题,而是 Keil 在工程配置里写明了“用默认编译器版本 5”,结果系统里根本没装 AC5,于是编译器状态直接变成了不可用。你打开Options for Target -> Target,在ARM Compiler下拉框里可能看到的是“Use default compiler version 5”,但下拉列表里只有Version 6.x,根本选不到 Version 5。
影响范围比你想象的大,不只是老工程:
- 早期芯片厂商提供的固件库、DSP 库、RTOS 移植包,有很多只发布 AC5 编译好的
.lib文件,你拿 AC6 去链接直接报一堆符号找不到。 - 一些老启动文件、分散加载文件写死了 AC5 的语法,比如
__main、__use_no_semihosting,AC6 下行为和报错都不一样。 - 工程里大量使用
__asm老式内联汇编、__forceinline、__packed这类 AC5 专属关键字,AC6 走的是 clang 风格,迁移成本很高。
所以我的判断很明确:如果手里全是老代码,没时间做 AC6 迁移,老老实实把 AC5 装回来比硬刚 AC6 划算得多。
2. 快速解决:5 分钟装好 ARMCC 编译器
2.1 方案 A:手动安装 ARMCC 编译器包(推荐)
AC5 编译器现在不是 Keil 安装包里直接勾选就有了,而是需要单独下载编译器包。ARM 官方提供的 AC5 编译器包一般叫ARM Compiler 5.06 update 6或5.06 update 7,文件名常见的是ARMCompiler5.06u7之类。我当时是从 Keil 官方旧版本下载页找到的,很多芯片厂商的 SDK 里也会附带。
安装过程很简单,但有几个细节要注意:
- 下载好的编译器包是一个
.exe可执行文件,双击运行,安装路径强烈建议选到 Keil 安装目录下,比如C:\Keil_v5\ARM\ARMCC。 - 安装完成后,打开 Keil,进入
Options for Target -> Target,在ARM Compiler下拉框里选择具体的Version 5.06 update 6或者通过右侧的文件夹图标手动指定编译器所在目录。 - 如果下拉框里没有 Version 5,可以先点下拉框边上的三个点按钮,手动定位到
C:\Keil_v5\ARM\ARMCC\bin,Keil 会自动识别这个目录下的armcc.exe。
安装路径千万别带空格和中文,这一点极其重要。我之前顺手装到了D:\Program Files (x86)\ARM_Compiler_5.06,结果 Keil 识别不干净不说,后续还频繁报createprocess failed,后来改成D:\Keil_ARMCC_5.06这种纯英文路径才消停。
2.2 方案 B:从老版本 Keil 里提取 ARMCC
如果你手头有老版本的 Keil MDK 5.36 甚至更早的安装包,可以直接从里面提取 ARMCC 文件,不用再折腾下载。
操作方式是这样的:安装老版本 Keil 到一个临时目录,安装完成之后,把Keil_v5\ARM\ARMCC整个文件夹复制出来,粘贴到新版 Keil 的C:\Keil_v5\ARM\ARMCC目录下。这样新版 Keil 就能识别到 AC5 编译器了。
这里有个容易踩的坑:光是复制文件还不行,Keil 有时候识别不到编译器版本,是因为 TOOLS.INI 里没有注册信息。如果一个 Keil 安装目录识别不到,直接在Options for Target里手动指定编译器路径即可。我自己的经验是,从 5.36 拷出来的 ARMCC 在 5.37 里用完全没问题,但从更老的 5.25 或者 5.14 里拷出来的,偶尔会出现编译器版本识别不准确的情况,编译出来的代码也可能有细微差异。
所以,能用官方独立安装包就优先用官方安装包,提取老版本只是备用手段。
2.3 工程配置:选择正确的编译器版本
编译器装好,接下来就是工程配置。打开Options for Target -> Target,把ARM Compiler从Use default compiler version 5改成具体的版本号,比如Version 5.06 update 7。
这一点纯粹是经验之谈,不要为了省事保留Use default compiler version这个选项。默认编译器在多个 Keil 版本之间切换时非常不可控,尤其你同时装了 AC5 和 AC6,一旦 Keil 检测不到 AC5,它会悄悄用 AC6 去编译 AC5 的工程,然后给你报一堆语法错误,你根本反应不过来是编译器版本不对。
选好编译器后,最好点一下编译按钮,看输出窗口里显示的编译命令是否带有armcc字样。如果是ArmClang,说明还是没切对。
正确设置了之后,编译输出会看到类似这样的命令行片段:
C:\Keil_v5\ARM\ARMCC\bin\armcc.exe --cpu Cortex-M4 --apcs=interwork ...看到这里,基本上 AC5 已经生效了。
3. 配置过程中的四个关键细节
3.1 “Use default compiler version” 别乱勾
刚才提到了别用Use default compiler version,这里再深挖一下原因。
Keil 的编译器版本管理机制和其他 IDE 不太一样,它不是每次编译都重新检测系统里装了什么编译器,而是优先读取工程文件里的记录。如果工程文件里写的是Use default compiler version 5,Keil 会先去找系统注册的 AC5 版本,找不到就报错。如果写的是具体版本号,比如Version 5.06 update 7,Keil 就直接调用这个版本的 armcc。
所以在工程配置里,一定要把编译器版本写死。尤其团队协作的时候,有人装了 5.06 update 6,有人装了 5.06 update 7,如果都写Use default,不同电脑编出来的结果可能完全不同。最稳妥的做法是大家都固定到一个版本,同时在工程目录下放一个 README 文件,写清楚编译器版本和安装路径。
另外,在Project -> Manage -> Project Items -> Folders/Extensions里,可以看到当前工程使用的编译器路径。这个路径必须是绝对路径,不要用相对路径,否则在换电脑或者同步代码时会出现找不到编译器的情况。
3.2 头文件路径要手动加上
AC5 编译器的头文件路径和 AC6 不一样,这个很多人会忽略。装了 AC5 之后,Keil 不会自动把所有头文件路径加到工程里,你需要手动在Options for Target -> C/C++ -> Include Paths里检查一下。
AC5 的头文件通常在C:\Keil_v5\ARM\ARMCC\include这个目录下,里面包含标准 C 库头文件、内建函数定义、CMSIS 相关头文件等。如果这个路径没加对,编译时会出现找不到stdio.h、string.h这类基础头文件的报错。
还有一个容易忽略的点:老工程里经常用到芯片厂商提供的标准外设库,这些库的头文件路径里可能会包含相对路径,比如..\..\Libraries\CMSIS\Include。当工程文件在新旧编译器之间切换时,这些相对路径如果没有根据编译器版本适配,编译也会出问题。我在实际项目中遇到过很多次,编译错误提示头文件找不到,实际查下来就是 Include Paths 里少了一个路径或者路径分隔符写错了。
建议在配置完编译器之后,第一时间编译一次,如果有头文件报错,直接去 Include Paths 里把所有缺失路径补齐。千万别相信工程文件里已有的路径是完美的,AC5 和 AC6 对路径的解析规则存在细微差别。
3.3 C99 模式与 ARMCC 的老特性
AC5 默认的 C 标准是 C90/C99 混用模式,和 AC6 默认的 C11 差别很大。如果你的代码用到了 C99 的特性,比如for循环内声明变量、//注释、指定初始化器等,在 AC5 下需要手动开启 C99 模式。
在Options for Target -> C/C++里,把C99 Mode勾选上。如果不勾选,很多现代代码风格在 AC5 下会报错,比如:
error: #77-D: this declaration has no storage class or type specifier这个报错很多时候不是代码写错了,而是编译器标准没选对。
另外,AC5 支持一些老式关键字,像是__asm、__forceinline、__packed、__align,这些在 AC6 里有不同的写法。如果代码里大量使用这些关键字,建议先做一个全局搜索,看看有多少地方用了,如果数量不大,可以手动替换;如果数量很大,那说明你的代码深度依赖 AC5,更要坚定地安装 AC5 而不是迁到 AC6。
还有个细节,AC5 的__asm直接内联汇编和 AC6 的差异非常大。比如在 AC5 里写__asm { NOP }是可以的,AC6 下面标准写法是__asm("NOP")或者__NOP()。这种差异在初级工程师看来是小问题,但实际改动量会很大,尤其启动代码和底层驱动里,内联汇编到处都是。
3.4 编译优化选项不要一步拉满
AC5 的优化选项和 AC6 不一样,最典型的是 AC5 里有-Ospace(优化代码体积)和-Otime(优化执行速度),而 AC6 里用的是-Os、-Oz、-O3这类 clang 风格选项。在Options for Target -> C/C++ -> Optimization里,AC5 显示的是-O0、-O1、-O2、-O3、-Ospace、-Otime,AC6 则是-O0到-Oz。
如果你从 AC6 切回 AC5,最好把优化等级先调到-O0或者-O1,编译通过之后再慢慢往高调。不要一上来就开-Otime或者-O3,AC5 在高优化等级下会产生一些看似莫名其妙的问题,比如变量被优化掉、函数被内联、时序不对,这些问题在排查时非常耗费时间。
我还遇到过一种情况:AC5 的-Otime会把某个内联函数展开,导致代码段体积异常增大,超出链接脚本的RO_SIZE限制,直接链接失败。这种错如果不开高优化就根本不会出现,很容易让人怀疑是编译器安装出了问题,实际上只是优化选项太激进。
4. 编译过程中最常见的报错与排查
4.1 fromelf 报错:createprocess failed
这个报错在装了 AC5 之后反而更容易出现,原因也很简单:Keil 在编译完成之后会调用fromelf.exe生成 bin 文件、反汇编文件或者烧录文件,而fromelf.exe所在的路径如果不对,就会出现下面这类报错:
*** error: createprocess failed, command: 'c:\keil_v5\arm\armcc\bin\fromelf.exe --bin -o ...这背后通常有三个原因:
- 路径带空格或中文,导致命令行解析出错。
- Keil 安装目录所在的磁盘没有写入权限,
fromelf生成文件时被拒绝。 - 杀毒软件拦截了
fromelf.exe的启动。
我的排查步骤是:首先看工程路径和 Keil 安装路径是否包含空格、中文,把工程移动到纯英文目录下,比如D:\Projects\Demo01。其次,以管理员身份运行 Keil,这个操作能解决绝大多数写入权限问题。最后,如果还是不行,关掉杀毒软件对 Keil 目录的实时监控,或者把C:\Keil_v5加入白名单。
注意:
fromelf报错不是 AC5 独有的,AC6 也会遇到。但 AC5 用户更多一些,因为老的批量烧录脚本、生成 bin 文件的命令基本都是基于 fromelf 写的,一旦路径不对就会集体报错。
4.2 编译报错:未包含 main 类型或者找不到 main
有些项目在切换编译器之后会莫名其妙报未包含main类型或者链接时提示找不到main,但实际上代码里明明写了 main 函数。这个问题的根源通常是启动文件没有正确包含到工程里。
以 STM32 为例,老工程使用的启动文件往往是startup_stm32f10x_hd.s这种基于 ARMCC 汇编语法的文件,如果你的工程里没有正确添加启动文件,或者启动文件的路径没有被 Keil 识别,链接器就不知道入口函数在哪里,自然找不到 main。
解决办法分两步看:
- 在工程视图里展开启动文件,确认它存在于工程中,且没有被 exclude 掉。
- 如果启动文件存在但编译不过,看一下是不是汇编语法不符合 AC5 规范。AC5 使用老式
PRESERVE8、THUMB、AREA语法,AC6 也支持,但某些老的启动文件在 AC5 下反而更稳。
还有一个容易忽略的:如果你用的芯片支持包(DFP)版本太老,里面的启动文件可能没有跟上 Keil 5.37 的支持情况。这时候可以在 Pack Installer 里看看有没有更新的 DFP 版本,升级之后启动文件会自动更新。
4.3 ARMCC 与 AC6 的代码兼容性问题
这个不是报错,但是是很多人踩坑的起点。同一个工程,在 AC6 下编译通过,切回 AC5 之后报一堆错,最常见的是:
__attribute__((always_inline))写法在 AC5 下报错,AC5 对应的关键字是__forceinline。__attribute__((packed))在 AC5 下不识别,AC5 要使用__packed。- 内联汇编的写法差异,AC6 是
__asm("nop"),AC5 是__asm { nop }或者__asm nop。 typedef enum在 AC5 下的默认类型是int,在 AC6 下有更严格的类型检测,切回 AC5 反而更容易过。
如果你是在 AC6 工程上临时切回 AC5,编译报错之后不要急着改代码,先看报错是语法层面还是链接层面。语法层面的报错,可以用#ifdef __CC_ARM做条件编译隔离,这样一套代码两个编译器都能编译。
4.4 常见问题速查表
我在折腾 AC5 的过程中,整理了下面这个速查表,遇到问题直接对照排查,节省大量时间。
| 报错现象 | 可能原因 | 解决办法 |
|---|---|---|
| Default Compiler Version 5 not available | 系统未安装 AC5 编译器 | 安装 ARM Compiler 5.06u6/u7,并在 Target 页指定路径 |
| createprocess failed with fromelf | 路径含空格/中文、权限不足、杀毒拦截 | 工程和 Keil 安装路径改纯英文,管理员运行,关闭监控 |
| 找不到 main 或未包含 main 类型 | 启动文件缺失或未被工程包含 | 检查启动文件是否在工程中,更新 DFP 版本 |
| __use_no_semihosting 未定义 | 标准库链接选项与 AC5 不符 | 勾选 MicroLIB,或检查 C/C++ 配置的链接选项 |
| 内核头文件找不到 | Include Paths 未添加 AC5 头文件目录 | 添加C:\Keil_v5\ARM\ARMCC\include |
| armcc.exe 无法启动 | 安装路径带中文/空格 | 重装到纯英文路径 |
| 某些库符号找不到 | 第三方库为 AC6 编译或版本不匹配 | 重新获取 AC5 版本库文件 |
这张表其实覆盖了 80% 的问题。剩下 20% 基本是芯片相关、库相关或者是工程配置本身的问题,需要具体问题具体分析。
5. 编译器版本管理:一次配置,一劳永逸
5.1 固定工程使用的编译器版本
很多工程师装完 AC5,编译通了就完事了,根本不记录工程用什么编译器版本。等到过两个月,工程换到另一台电脑,或者是自己升级了 Keil,又出现Default Compiler Version 5 not available,再重新折腾一遍。
我的习惯是,每个工程根目录下放一个README.md,里面第一行就写:
Toolchain: Keil MDK 5.37 + ARM Compiler 5.06 update 7 (ARMCC)这样不管谁来接手这个工程,或者多久之后再打开,都不会犯迷糊。团队协作时,这个做法尤其重要。否则你根本不知道同事用的 AC5 是哪个小版本,编译行为差异化问题排查起来极其痛苦。
5.2 把编译器包和工程一起归档
这个方法是我后来养成的习惯:把 ARMCC 编译器的安装包直接放到公司内部的软件仓库,或者放到项目服务器的工具链目录下。这样新同事入职、换电脑、重装系统,都能快速找到同一个版本的编译器,不用去网上重新搜索下载。因为官网的下载链接偶尔会变动,网上找的第三方链接又怕带后门,统一归档是最安全、最省事的方式。
对于个人开发者,也建议把下载好的ARMCompiler5.06u7.exe找个网盘或者移动硬盘存一份。这个编译器包现在虽然还能下载到,但谁也说不准官方哪天就不再提供了。等到真的找不到了,你手里的老工程就只能硬着头皮改 AC6,那工作量就大了。
5.3 AC5 还能用多久?我的实际建议
AC5 已经进入维护期,ARM 官方不会再给它加新功能,新的 ARM Cortex-M85、Cortex-M55 这些内核,AC5 支持得也不够全面。所以,如果你做的是全新项目,直接用 AC6 是更明智的选择,没必要为了情怀用老编译器。
但如果你手里有大量基于 AC5 的老工程、第三方库,或是暂时没时间做代码迁移,那么固定好 AC5 版本、做好归档、不要随便升级 Keil,这套组合拳能让你再安稳用好几年。我的经验是,老工程只要编译器版本固定,Keil 大版本不瞎升级,产线那边根本不会出什么问题。
我现在手上维护的一个量产项目,用的就是 Keil 5.37 + ARMCC 5.06 update 7,编译稳定,烧录稳定,半年多没出过编译器相关的幺蛾子。就是当初迁移的时候折腾了两天,把路径、头文件、启动文件、优化选项全部捋了一遍,之后就再也没碰过。如果你正在被这个报错折腾,按我上面的步骤操作,大概率半小时内能解决。最后分享一个小技巧:装完 AC5 后,在命令行手动执行一次armcc.exe --version,确认编译器能正常输出版本信息,再打开 Keil 配置,省得在 IDE 里反复试错。