1. 从一次AC5缺失报错说起:问题到底出在哪
1.1 报错现场全景复盘
大概每隔一两个月,我所在的嵌入式交流群里就会出现一次“求帮忙看看编译错误”的求助,截图往往是这样的:
*** Target 'Target 1' uses ARM-Compiler and there is no ARM compiler installed ***或者直接来一句:
error: C9555E: Failed to check out license第一次遇到的人基本都会懵:明明工程文件都在,器件包也装好了,头文件路径也没问题,怎么突然跟编译器较上劲了?更奇怪的是,同一个工程在同事电脑上编译得好好的,换到自己刚装好的MDK上就报错。如果你也是这种情况,基本可以断定:你的KEIL MDK里缺了ARM Compiler 5,也就是我们常说的AC5。
这里先帮大家把问题定位清楚:AC5缺失不是代码问题,不是仿真器问题,也不是许可证问题,纯粹是安装阶段少了一个组件。只要你用的是5.36之后的新版MDK,并且安装时没有特意去勾选旧编译器支持,那么MDK只带ARM Compiler 6(AC6),AC5并不会自动出现。而很多时间比较久的工程,尤其是用了STM32标准库、老版CMSIS,或者一些第三方SDK的工程,编译链默认还是AC5,于是就有了上面那一幕。
1.2 新版本MDK的编译器“分家”逻辑
要弄明白为什么AC5会被“抛弃”,得先理解KEIL MDK从5.36版本开始的一个大变化:官方把默认编译器从ARM Compiler 5换成了ARM Compiler 6。AC6是基于LLVM的armclang,编译速度快、对C11/C++11支持好,代码体积和优化都有优势,Arm官方也一直在推动生态迁移。
但问题是,老工程并不买账。AC6和AC5在语法层面有不少差异,比如:
- AC5允许使用
__irq、__asm这类关键字,AC6需要用__attribute__((interrupt("IRQ")))或__asm volatile替代。 - AC5的GNU风格内联汇编和AC6的armclang内联汇编规则不同。
- 某些老版本库内部用的编译器特殊选项,在AC6下面根本编不过去。
所以Arm官方也意识到,不能一刀切。于是从MDK 5.36开始,安装包里默认只装AC6,但官方依然保留了AC5的独立下载通道。也就是说,AC5并不是“没了”,只是不再默认安装。你完全可以在MDK 5.36、5.37、5.38,甚至5.39上手动把AC5补回去,新旧编译器共存,互不干扰。
1.3 为什么很多老工程离不开AC5
我知道有人会说:早晚都要迁移到AC6,何必抱着AC5不放?现实是,很多商业项目、科研代码、杂牌芯片SDK,并没有跟上新编译器的节奏。我见过一个量产产品的工程,用了好几年的STM32标准库,里面有不少__asm内联汇编和__irq中断函数,拿到AC6下面一编译就是几十个error。客户不可能为了换个编译器去重写整个底层,所以最保守的做法就是在新版MDK上继续用AC5。
另外,AC5和AC6生成的代码行为在某些细节上有差异。比如浮点ABI、结构体对齐规则、volatile访问的优化策略,可能导致老程序在新编译器下出现诡异的时序问题。对于稳定运行的产品,升级工具链的收益远远小于风险。这也是我在自己的电脑上始终保留AC5的原因——不是守旧,而是实用主义。
2. 手工添加AC5之前,先弄清楚版本来源
2.1 AC5的官方获取路径
既然要手动添加,第一步当然是拿到AC5的安装文件。这里严肃提醒一句:千万别去网上搜什么“5.36网盘版”“AC5绿色版”之类的东西。一方面安全性无法保证,编译器被植入恶意代码,你的固件、源码、甚至电脑都会有风险;另一方面,MDK本身是商业软件,官方提供了正版许可和评估模式,没必要冒这个险。
官方获取AC5的路径并不难找,我走一遍下来是这样的:
- 打开Arm Keil官网(developer.arm.com/downloads)。
- 找到“MDK-ARM”或者“MDK”产品下载页面。
- 在下载列表里寻找一个叫
ARM Compiler 5.06 Update 7或Legacy ARM Compiler 5的独立包,完整文件名类似ARMCompiler_5.06u7_Windows.zip。
这个包的大小一般在一百多兆,里面就是AC5完整的编译工具链。需要注意,我的经验是官网偶尔会调整下载页面结构,如果你一时找不到,可以直接在站内搜索“ARM Compiler 5.06”,或者在MDK的Pack Installer里看看有没有“Legacy Support”包可以勾选。但最直接的方法还是下载独立包,自己手动放置。
2.2 如何确认你需要的AC5版本号
AC5的最后一个正式版本就是5.06 Update 7,发布于多年前。所以对于绝大多数用户来说,不用纠结到底下载5.05还是5.06,直接用5.06u7就是最稳的选择。它修复了旧版本的一些已知问题,支持绝大多数ARM Cortex-M/R/A内核,是我推荐的首选。
不过也要注意,少数芯片厂商的老SDK文档里会特别指定要使用某个具体版本,比如“ARM Compiler 5.04”或“5.05”。这种情况多见于非常古老的ST/ NXP早期代码包。你可以先按厂商要求下载对应版本,如果找不到历史版本,再退回5.06u7试试。从我的实际经验看,5.06u7向下兼容性很好,绝大多数标称老版本编译器的工程都能正常编过,顶多有一些语法警告。
2.3 下载后文件结构长什么样
解压下载好的ARMCompiler_5.06u7_Windows.zip,你会看到一个名为ARMCC的文件夹。这个文件夹里面的结构大概是这样:
ARMCC/ ├── bin/ ├── data/ ├── doc/ ├── examples/ ├── include/ ├── lib/ └── sw/bin目录下能看到armcc.exe、armlink.exe、fromelf.exe等一系列可执行文件,这就是AC5的核心。include和lib分别是头文件和运行库,data里则有芯片相关的描述文件。
搞清楚结构很重要,因为后面你要把这个文件夹放到MDK的特定位置,MDK才能识别。如果放错层级,MDK在扫描编译器时就会找不到ARMCC/bin/armcc.exe,结果还是提示AC5缺失。
3. 手动安装AC5的完整操作流程
3.1 把编译器文件放到指定目录
这一步的核心就是让MDK能按约定路径找到AC5。MDK安装完成后,根目录下会有一个ARM文件夹,例如C:\Keil_v5\ARM。正常情况下,这个ARM目录下会存在ARMCC、ARMCLANG、CMSIS、Packs等子目录。其中ARMCLANG就是AC6的安装位置,ARMCC则是在你手动添加AC5后才会出现。
操作方法非常简单:把解压出来的ARMCC文件夹整个复制到C:\Keil_v5\ARM\目录下。最终路径应该是这样:
C:\Keil_v5\ARM\ARMCC\bin\armcc.exe复制完成后,建议顺手验证一下路径里不要有中文、不要带空格。如果你的MDK装在D:\Program Files\Keil_v5这种带空格的路径,可能会引发后面Make工具找不到编译器的问题。最省心的方案是:安装MDK时就直接使用C:\Keil_v5或D:\Keil_v5这样简洁的路径。
3.2 在MDK里注册编译器,让软件“看到”AC5
放好ARMCC文件夹后,打开你的MDK工程,接下来有两个地方可以验证或添加编译器,我的习惯是先去“项目管理器”里处理。
点击菜单栏的Project->Manage->Project Items,在弹出的窗口底部会有一个Folders/Extensions按钮,点进去,你会发现一个编译器列表。这里通常会显示MDK自动扫描到的已安装编译器,例如ARM Compiler 6.16。如果你把ARMCC文件夹放对了路径,MDK启动时会自动扫描到新增的AC5,并显示在列表里。
如果列表里仍然只有AC6,不用慌,可以手动添加。回到Project Items窗口,看Folders/Extensions里的内容,在编译器选择区域有类似Add another ARM Compiler Version的按钮,点击后浏览到C:\Keil_v5\ARM\ARMCC目录,确认即可。添加后,列表里会出现ARM Compiler 5.06 Update 7这样的选项。
这里有个小细节:有些MDK版本在添加编译器时,会要求你选择到包含armcc.exe的bin目录,而有些版本选择到ARMCC根目录就行。你可以先选根目录,如果MDK提示找不到,再尝试选择到bin目录。我实际测试过的MDK 5.36和5.37都是选择ARMCC根目录就能识别。
3.3 工程配置里切换为AC5并处理兼容项
编译器注册完成后,还需要在具体工程里把编译器切换到AC5。选中工程目标,按Alt+F7打开Options for Target,切换到Target选项卡,在Code Generation区域能看到ARM Compiler下拉框。点开下拉框,正常情况下会列出可用的编译器选项,比如“Use default compiler version 6”和“ARM Compiler 5.06 Update 7”。直接选择AC5的版本即可。
这里我建议同时检查一下C/C++选项卡里的“Language / Code Generation”设置。因为老工程可能用了一些旧编译器特有的选项,比如AC5时代习惯于在“C/C++”的Misc Controls里写--cpu=Cortex-M4,或者使用--gnu、--c99等标志。切换到AC5后,这些选项通常能继续生效。但如果开了太高优化等级,可以先用-O2甚至-O0跑通编译,再逐步优化,避免一开始被混淆。
全部设置好后,点击OK,然后Rebuild。编译日志里如果出现类似armcc.exe的调用,并且一长串路径指向C:\Keil_v5\ARM\ARMCC\bin,就说明AC5已经成功接管了编译。
4. 编译过程中最常踩的五个坑
4.1 AC5路径识别不了、仍提示缺失
第一种情况是明明把ARMCC放好了,MDK的编译器列表里也显示了AC5,但编译时还是报“there is no ARM compiler installed”。这种事我见过不少,很大概率是工程配置里选错了编译器。注意Options for Target里的ARM Compiler下拉框,有时候你选的是“Use default compiler version 5”,但系统实际没有找到默认AC5。尝试直接选中具体的“ARM Compiler 5.06 Update 7”选项,而不是用default。
还有一种情况是MDK的TOOLS.INI文件残留了旧的编译器路径信息。如果你曾经装过老版本MDK,后来又换到了新目录,TOOLS.INI里可能错误地指向了旧的ARMCC路径。可以打开MDK安装目录下的TOOLS.INI,在[ARM]段中检查是否存在ARMCC相关路径,确保它指向你当前实际的ARMCC目录。如果你不确定怎么改,最稳妥的办法是卸载重装MDK,或者把整个MDK和ARMCC统一放到同一个根目录。
4.2 老工程里编译选项失效
用AC5编译老工程,最常见的是优化等级、C标准、字节序等选项不兼容。例如有的老工程在“Misc Controls”里写了--split_section或--reduce_paths,这些开关在AC5下确实有效,但如果之前你尝试过AC6并在这些配置里写了-fno-short-enums之类的armclang选项,再切回AC5时就会报“unknown option”错误。
解决办法是:在切换编译器后,把C/C++选项卡里的“Misc Controls”内容清空,然后再逐步根据编译提示加回必要的选项。不要贪图省事从AC6的配置里直接复制选项过来,两个编译器的参数体系完全不同。我给一个参考方案:先清空所有额外编译选项,只保留优化等级-O2和调试信息--debug,能编过之后再一点点加东西。
4.3 头文件路径兼容问题
AC5的头文件搜索规则和AC6并没有本质区别,但老工程的相对路径在手动移动编译器后容易出问题。我遇到过一种情况:工程里引用了#include "stm32f10x.h",而标准库的头文件路径原本是相对于C:\Keil_v5\ARM\INC\ST\STM32F10x之类的目录,但新版MDK的INC目录结构已经变了,导致找不到头文件。
这种问题很好排查:看编译日志里第一个Error: #5: cannot open source input file的错误,再去工程设置里的Include Paths检查对应目录是否存在。正常情况下,只要你的工程路径是完整的,头文件路径都存放在工程本身的配置里,和MDK安装目录关系不大。但如果你之前引入了一些“绝对路径”的头文件目录,挪动编译器后就要同步更新。
4.4 多版本MDK共存时的选择器问题
有时候一台机器上装了多个版本的MDK,比如5.36做主力,5.34做老项目备份。这种情况下,AC5放在哪个MDK的AR目录下,就只会被哪个MDK识别。我见过有人把AC5放到了旧版MDK的目录里,然后打开新版MDK编译,却发现找不到AC5,急得一头汗。
建议是多版本共存的场景下,只在主力MDK里放AC5,或者每个MDK都单独放一份ARMCC。反正是绿色文件夹,复制一份不占多少空间,但能避免很多路径混乱。另外,双击.uvprojx文件默认打开的MDK版本要和你预期的工程一致,右键工程文件选择“Open with”可以指定MDK版本,免得打开错了编译器环境。
4.5 License验证偶发失败
添加AC5后,还有一个可能出现的幺蛾子:error: C9555E: Failed to check out license。这通常不是AC5文件本身的问题,而是MDK的许可证里没有包含AC5的使用权限。如果你使用的是在线浮点许可证,需要确保你的Keil账号已经登记了AC5的license(部分评估版不包含AC5,需要获取合适的license)。如果是单机锁,试着重新激活一次许可证。
这种时候可以先退出MDK,打开License Management,检查许可支持的特性里有没有“ARM Compiler 5”的字样。如果没有,可以联系代理商或Arm官方申请添加。如果你借用的是公司团队license,也要确认服务器端是否放开了AC5的授权。总之,这属于许可证配置问题,不是AC5安装问题。
5. 我自己长期维护多版本工程的几个心得
5.1 为什么我建议保留AC5但不要过度依赖
说句实在话,AC5终究是个“过气”编译器。我把AC5留在电脑里,是因为手头有历史项目要维护,客户并不会因为编译器不流行就允许我贸然改代码。但如果你是刚开始接触嵌入式的新人,或者准备启动全新的项目,我真心建议直接用AC6。理由很简单:AC6才是未来,Linaro、GCC、LLVM的新特性都会往AC6靠拢;而且现在新出的芯片官方SDK,很多已经默认只支持AC6了。
我之前有个项目因为要用一颗新的传感器芯片,SDK是用armclang写的,里面用了一些AC6特有的关键字和内置函数。老工程如果强行用AC5去编译那个SDK,反而会出现大量误差。所以正确姿势是:老代码用AC5,新模块用AC6,通过分目录、分库的方式混编。MDK支持在同一个工程里按汇编、C/C++源文件分别指定编译器?实际上,MDK工程粒度是Target级别的,不能直接每个文件独立选AC5/AC6。所以更实际的做法是,把新模块编译成静态库,丢给AC5的老工程链接。
5.2 如何把现有工程平滑迁移到AC6
如果你终于决定摆脱AC5,我给一个风险较小的迁移路径:先在MDK里复制一个Target,把编译器切换到AC6,修改编译选项,然后逐个解决错误。常见的迁移点包括:
- 内联汇编改写:AC5的
__asm{}变成AC6的__asm volatile("..."),尽量用C语言替代。 - 中断关键字:
__irq改成__attribute__((interrupt))。 $Sub$$和$Super$$补丁机制:AC6改用__attribute__((patchable))或使用函数指针进行挂钩,老办法不再适用。- 结构体对齐、位域的默认行为差异:编译后可能产生不同的内存布局,需要逐个结构体确认。
迁移过程中,建议先开-Wall,把可见警告清理一遍。不要试图一次搞定,可以先让代码编译出来,再烧到板子上验证功能,重点检查时序中断、DMA配置这类对编译器敏感的部分。
5.3 备份、记录与团队协作中的注意事项
最后分享一点干活的时候踩过的坑。如果你和我一样需要维护大量老工程,建议把AC5的安装包单独存到一个内部共享盘上,而不是只放在本机。因为新来的同事安装MDK后,大概率也会面对AC5缺失的问题,如果没有离线包,他们还要到处找下载链接。把ARMCompiler_5.06u7_Windows.zip放在团队公共资源目录里,可以省去很多沟通成本。
另一个关键是编译器和工程版本的“约定”。团队里最好有一份文档,明确写清楚:哪个工程必须用哪个版本的MDK、AC5还是AC6、Pack版本是多少。我见过最惨的事故是,有人用MDK 5.38打开了一个MDK 5.36创建的工程,MDK自动升级了工程文件格式,导致其他同事用老版本MDK打不开,只能回滚到SVN上一个版本重改。从那以后,我对所有工程都要求:在提交代码时明确标注“请使用MDK 5.36 + AC5.06u7”这样的标签,避免版本混乱。
还有一个小技巧:在工程文件夹里放一个README.md或build_env.txt,写清楚编译环境和依赖。虽然看起来麻烦,但在半年后你重新拾起一个项目时,会发现这份小小的记录能救你的命。我自己现在就习惯在每个项目根目录放一个文本文件,内容就是一句话:“编译器: AC5.06u7, MDK目录: C:\Keil_v5, 注意不要用AC6打开此工程”。
说实话,给MDK手动加AC5这件事本身并不复杂,整个过程下来也就几分钟。真正让人头疼的不是“不会装”,而是装完之后工程依然编译不过的挫败感。希望通过这篇文章,你能把AC5缺失这个根因解决掉,同时绕过我在实操中踩过的那些坑。下次再有人问“为什么我装了新版MDK还是编译失败”,你可以直接把这篇分享转给他,帮他少走点弯路。