ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Keil MDK中ARMCC v5与v6双编译器共存:安装配置与切换实战

Keil MDK中ARMCC v5与v6双编译器共存:安装配置与切换实战 做嵌入式这几年但凡用Keil MDK开发STM32的基本都绕不开一个尴尬局面老工程的代码是ARMCC v5编译的新需求又希望用上AC6的优化和新特性更别提新装一台电脑、打开一个新版MDK结果发现工程里找不到v5编译器一堆老代码直接编译不过。这个问题我在项目里也踩过不少次这篇文章就把我实际验证过的“双编译器共存”方案完整写出来覆盖从安装、配置、切换再到代码适配和问题排查的完整链路保证是能在工程里直接落地的那种。1. 为什么会有双编译器这个需求AC5和AC6的前世今生1.1 ARMCC v5和v6到底差在哪里很多新手一直没搞明白Keil里的ARM Compiler 5和6到底改了什么。简单说ARMCC v5是ARM公司自己研发的经典编译工具链从MDK诞生一直用到大概5.37版本左右特点是稳定、保守、对老代码兼容性好特别适合那些量产多年、代码结构又极其敏感的老项目。它的命令行工具叫armcc编译出来的代码风格紧凑但优化能力和最新语言标准支持都比较弱。ARMCC v6则完全换了内核底层是Clang和LLVM架构对就是苹果生态里那套开源编译器路线。compiler本身变成armclang优化能力比v5强很多支持C11、C14这些新标准还引入了link-time optimization之类的现代特性。ST官方从某个版本开始新出的芯片包、HAL库示例默认都用AC6编译。但问题恰恰出在这AC6虽然好AC5并没有死。大量2015-2022年之间开发的STM32工程尤其是用标准外设库Standard Peripheral Library或者老一批HAL版本的项目代码里多多少少都用了AC5特有的语法和扩展属性原封不动切到AC6编译错误能刷满一屏。所以双编译器共存不是闲着没事是刚需。1.2 哪些场景真的需要双编译器共存以我接触过的需求来分主要有三类第一类老项目维护。公司量产产品用的固件是三年前的代码BSP层、驱动层全是AC5风格跑在新版MDK上如果不装v5根本没法产出固件而老板也不会批预算让你做代码全面迁移。这属于要“原地干活”你必须让新版MDK能编译AC5工程。第二类新老工程并行开发。同一个产品线老项目还在维护要发版新项目已经搭好AC6的HAL库工程两套工程经常要在同一个IDE里打开、对比、复用代码。装上双编译器后工程文件各自指定编译器版本互不干扰这是最省心的状态。第三类工程迁移过渡期。你计划把AC5工程迁到AC6但不可能一晚上改完几千行代码。迁移期间保存一个“AC5分支”作为基线另一个分支用AC6逐步修编译错误两个分支在同一个Keil环境打开谁也没必要装两套MDK。就我个人的建议不管你现在用哪个编译器先把双编译器环境配好因为芯片包的更新、别人发来的工程文件、网上下载的开源项目根本不会提前跟你商量要哪个版本。2. 双编译器安装与工程级切换全流程2.1 检查本机编译器现状别稀里糊涂装重复开始之前先花一分钟确认你机器上现在到底有哪些编译器组件。打开Keil MDK在任意一个已打开或新建的工程里点击菜单栏的Project - Options for Target也可以直接点魔术棒图标切换到Target选项卡里面有ARM Compiler下拉框。正常情况你会看到几种不同状态。如果只有“Use default compiler version 6”或者“V6.xx”说明当前MDK只装了AC6。如果你看到“V5.06 update 7 (build 960)”之类的选项那就说明AC5也能用了。这里有个重要的背景知识MDK 5.37之前安装包默认内置AC5和AC6两个编译器但从5.37开始ARM官方把AC5从默认安装里移除了变成了一个单独的组件包。所以如果你用的是5.37、5.38或者更新的MDK新装完多半只有v6。还有一个容易踩的盲区同一台机器可以共存多个MDK版本但每个版本之间不一定共享编译器路径。我见过有人C盘一个Keil_v5D盘一个Keil_v5结果在D盘那个打开工程说找不到AC5其实C盘那个装得好好的。所以检查的时候不要只看启动快捷方式要看当前工程用的是哪个安装目录。2.2 给新版MDK补装ARMCC v5的三种途径装AC5其实不复杂关键是要找对渠道。我按推荐顺序列一下。第一种去MDK官网下载ARM Compiler 5的插件包。这个包官方名称一般是“MDK v5 Legacy Support”或者类似的Legacy组件里面包含了AC5编译器本体直接双击安装安装程序会自动识别你电脑上的Keil安装路径。装完之后再打开工程Target选项卡的编译器下拉框里就会出现V5.06 update 7这个选项。这个是官方途径稳定可靠推荐优先使用。第二种如果你手边有旧版MDK安装包5.36及以下的安装包就更方便因为自带AC5那可以直接安装旧版MDK到任意目录然后把里面ARM/ARMCC这个文件夹整个拷贝到新版MDK的ARM目录下。注意拷贝完之后不是马上生效你需要在Keil里手动把编译器路径指过去具体操作是进入Project - Manage - Project Items在Folders/Extensions选项卡里设置。第三种也是我自己常用的方式直接在管理器里用Pack Installer安装“ARM Compiler 5.06 update 7”这个组件。打开Pack Installer找到ARM - ARM Compiler 5.06 update 7点击Install。装完之后它会自动注册到当前MDK里。这种方式最无脑但有个前提你的MDK版本不要太老Pack Installer本身要能正常访问网络和Pack服务器。注意AC5的最终版本是5.06 update 7build 9606.0以上就不叫v5了。你能找到的任何“ARMCC 5.07”之类都不存在网上那种挂着5.07名字的下载链接基本都是坑。2.3 工程里切换编译器的具体操作装好双编译器后切换其实很快。打开工程进入Options for Target - Target在ARM Compiler下拉框里选择需要的版本。如果工程原本是AC5编译的你选到V5.06 update 7然后点OK先从Rebuild开始把整个工程重编一遍确认能顺利生成固件。但这里有个特别容易误导新手的细节切换编译器之后Keil不会自动帮你清理旧的中间文件。如果你在AC5编译出的输出目录上直接切到AC6中间文件.o、.d、.crf这些还是旧编译器生成的Rebuild时偶尔会出现一些离奇的链接错误比如提示符号找不到、类型不匹配其实是因为新旧目标文件混在一起。所以切换编译器后一定先点到Output选项卡把Select Folder for Objects里的路径重设一下或者直接用Clear Target输出按钮然后Rebuild。还有如果工程里同时有汇编文件和C文件切换编译器后汇编器路径也会变个别老芯片比如STM32F1系列早期的启动文件用AC6编译时启动文件里的语法可能不兼容后面我在代码适配部分会专门说。2.4 芯片包和C51共存安装的注意点很多人在同一个Keil环境里既搞STM32又搞51单片机这本身没问题MDK版本虽然是ARM专用的但通过安装C51芯片包可以让同一套IDE同时支持8051和ARM项目。不过C51和ARM使用的编译链完全不同C51用的是CX51编译器ARM下才是ARMCC两者共存时要注意别把工程类型搞混。双编译器的场景下更常见的问题是芯片包Device Family Pack的缺失或版本冲突。你打开别人的STM32工程如果提示找不到设备通常是因为Pack没有装或者Pack版本不符合工程要求的范围。建议在Pack Installer里给对应芯片装最新版比如STM32F1就装Keil.STM32F1xx_DFP最好勾选安装全部版本避免老工程用新版Pack编译时出现兼容性差异。C51共存和ARMCC唯一真正相关的坑是我遇到过的一个情况装C51芯片包时安装器会改变IDE的默认工具链路径导致某些ARM工程打开后编译器下拉框里看不到已经装好的AC5。解决办法也很简单在Project Items - Folders/Extensions里把ARMCC的路径重新指对路径一般是C:\Keil_v5\ARM\ARMCC\bin。3. 同一个STM32工程在AC5/AC6下来回横跳的实战配置3.1 一套能兼容双编译器的工程模板组织结构先给结论如果你打算让同一个STM32工程既能在AC5编译又能切到AC6编译那么工程结构的规划从一开始就要干净。我这里说的“同一个工程”是指共用一个UVprojx工程文件通过编译器选项切换来出两种不同固件而不是维护两份工程文件。我自己的模板分为三层。应用层只放业务逻辑不依赖任何编译器特性中间层放驱动和BSP代码里会少量用到编译器相关的条件编译底层是启动文件、链接脚本和系统配置。启动文件这块我强烈建议直接使用ST官方在芯片包Sample里带的最新版启动文件这些文件一般是.s后缀支持AC5和AC6双编译器内部用宏控制指令集语法。如果你用的是老工程里那些手工改过的启动文件切AC6编译会大概率报“unknown instruction”之类的错那就不是配置能解决的必须换文件。链接脚本上AC5用分散加载文件.sctAC6其实也可以用但更推荐换成通用的GCC风格链接脚本.ld。不过这个完全看你工程需求如果你确实要在两个编译器下都保持完全一致的内存布局那建议在AC6下用--scatter参数指定同一个.sct文件这样两个编译器产出的固件内存布局能保持大体一致方便对比或平滑切换。3.2 从AC5切到AC6必改的几个地方代码层面AC5和AC6最大的差异有两个关键字识别和变量布局规则。AC5里广泛使用的__CC_ARM这个编译器预定义宏在AC6下不存在。很多老代码靠这个宏来写条件编译比如选择向量表的实现方式、选择内联汇编是ARM指令还是Thumb指令甚至有些是从官方例程复制过来的代码里面用#ifdef __CC_ARM来判断当前是不是Keil环境。切到AC6后全工程最保险的做法是直接在C/C选项卡的Define里加上__CC_ARM把这当作“Keil环境”的兼容标志能让你少改几十处地方。当然更严谨的写法是把判断改为#if defined(__CC_ARM) || defined(__clang__)内联汇编这块是重灾区。AC5支持__asm关键字写内联汇编AC6兼容了一部分但语法要求更严格而且推荐使用__ASM加__attribute__((naked))的新风格。不过我实测下来如果你只是为了读取寄存器值、开关中断这种简单操作根本不用碰内联汇编直接用__get_PRIMASK()、__enable_irq()这类CMSIS内置函数就好这些函数在两个编译器下的头文件实现里已经做好了兼容。真正要改的是那些自己写的复杂内联汇编这种建议整体抽出到一个单独函数里用naked函数或者直接改成普通的C操作。还有一个很多人会踩的坑就是位域bit-field的布局差异。AC5默认对结构体位域的处理方式更接近ARM古老的AAPCS标准AC6则完全遵循现代Clang的规则两者分配位段顺序可能存在差异。如果你的代码里用结构体位域去映射外设寄存器比如自定义一个联合体来按位访问某个寄存器的不同字段切换编译器后这些位的实际位置可能会变化导致寄存器读写行为完全错误。我的建议是凡是映射寄存器寄存器的位定义一律用宏定义加位运算不要用结构体位域。3.3 优化等级和预处理宏的差异适配同一个工程在AC5和AC6下的优化等级选项位置一样但实际行为差距挺大的。AC5的-O2和AC6的-O2背后的逻辑完全不同同样一段代码两个编译器产出的固件大小和运行性能可能差很多。这就带来一个实际项目里的问题如果用AC5的品牌代码通过了所有验证测试迁到AC6后单纯因为编译器优化改变导致运行逻辑出现细微时序差异这在电机控制、ADC采样这类对时延敏感的场景里很容易出bug。所以双编译器共存时除非你确认代码完全不需要时序敏感否则不同编译器建议单独调优。我自己在工程里会用预处理宏做区分#if defined(__clang__) #define SYSTICK_DELAY_LOOP 666666 #else #define SYSTICK_DELAY_LOOP 555555 #endif另外说一个最常被问的问题就是delay延时函数卡死。很多人在AC6优化级别较高的情况下发现delay_ms直接跳不出来这是因为循环变量没有加volatile修饰编译器优化直接把循环体优化没了。这不是AC6独有的在AC5开高优化时也会出现只是AC6的优化更激进概率更大。根治方法只有一个延时循环的计数器变量请一律用volatile修饰或者干脆使用DWT-CYCCNT解决问题。3.4 实测对比两个编译器编译同一份工程的表现我这边用一个STM32F103C8T6的工程做过实测工程包含标准HAL库、FreeRTOS、一个OLED驱动全部代码量大概在8000行左右。同一份代码AC5 5.06 update 7和AC6 6.21分别编译结果如下对比项ARMCC v5 (5.06u7)ARMCC v6 (6.21)Flash占用-O248.2 KB43.6 KBRAM占用10.8 KB10.5 KB编译时间Rebuild约28秒约22秒警告数量8个31个延时函数偏差基本正常高优化下需volatile处理从这个结果能明显看到AC6在代码体积和编译时间上都有优势但告警明显更严格不是所有警告都能直接无视有些是类型隐式转换、未初始化变量的隐患建议逐个过一遍。切换回来AC5时最大的体感就是告警少很多老代码有一种“回家的感觉”。但说实话越是这种“平静”越危险AC5很多潜在问题都不报比如隐式函数声明这种能引起RAM错乱的错误AC5经常只是给个WarningAC6会直接Error。这就是为什么我建议即使不迁到AC6也要偶尔在AC6下编译一次当一把类似Klocwork的静态检查工具用来发现老代码的隐藏雷。4. 高频报错与排查实录4.1 CreateProcess failed / fromelf 找不到文件的处理这个报错算是所有Keil报错里的“顶流”。我自己就遇到过好多次典型提示类似*** error: CreateProcess failed, Command: C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o ...原因绝大多数情况下不是fromelf真的丢了而是编译器路径选错或者当前工程指定的编译器版本压根不存在。比如工程文件里记录的是V5.06 update 7但你机器上只装了v6Keil执行fromelf时直接去找AC5目录下的fromelf.exe找不到就爆这个错。排查步骤很固定搜一下你机器上有没有ARMCC这个目录如果有看里面有没有fromelf.exe如果没有说明AC5组件确实没装回到2.2节补装。如果目录存在但Keil还是报错大概率是MDK内部记录的工具链路径被改坏了最简单的办法是关闭工程后把项目的*.uvprojx文件里的编译器路径配置检查一遍或者干脆新建一个Target再重新配置编译选项。还有一种情况是环境变量PATH太长导致的CreateProcess失败Windows对命令行长度有限制而MDK调用fromelf时如果工程路径过长、输出目录过深也会触发这个错误。对策是把工程放到路径短一点的目录比如D:\prj\xxx不要放在桌面那种超长路径下面。4.2 仿真时cannot access memory“cannot access memory”这个报错在仿真和烧录时都出现过含义也很直白调试器访问目标芯片的某个内存地址失败。但触发原因五花八门。最常见的原因是芯片型号选错了。比如你手头是STM32F103C8T6只有64KB Flash但工程里选了STM32F103VET6仿真器在载入时访问到超出64KB地址的内存范围就会卡在这个报错上。解决办法很简单到Device选项卡确认芯片型号与实际芯片一致。第二种情况是仿真器与目标板连接不稳定多见于使用ST-Link或者J-Link时接触不良。这种我建议先把调试器速度降下来调试设置里的Max Clock从4MHz改成1MHz甚至更低很多稀奇古怪的访问失败都能解决。第三种跟编译器和内存布局有关。如果你在AC6下用了新的链接方式把某些段放到了不存在的RAM空间也会在启动阶段触发cannot access memory。用调试器窗口打开内存视图检查一下当前PC值卡在哪一条语句再对照链接脚本里的地址范围基本都能定位到。4.3 烧录失败和调试器识别问题Keil烧录STM32时最常见的失败提示是“Error: Flash Download failed - Target DLL has been cancelled”或者干脆找不到调试器。遇到这种先打开Options for Target的Debug选项卡检查右上角调试器型号是否正确ST-Link就选ST-Link DebuggerJ-Link就选J-Link/J-Trace。然后点击Settings查看能否正确识别到设备序列号和芯片ID。ST-Link识别不到芯片有80%的情况是驱动问题。Windows更新或USB口切换后ST-Link驱动丢了设备管理器里会显示一个带感叹号的未知设备。需要重新安装ST-Link驱动或者干脆插到主板原生USB 2.0口试试。这里提一句不要以为芯片包装了就能烧录Pack是编译器侧的烧录烧不进去纯属调试器链路问题。如果你用的是J-Link还可能是固件版本和MDK版本不匹配。新版MDK对J-Link固件有最低版本要求老固件直接会被Keil拒绝连接去SEGGER官网升级J-Link固件即可。4.4 Xtal变灰与芯片包缺失问题不少人在Options for Target的Target选项卡里发现XtalMHz这一项是灰色的改不了。这个现象不是硬件问题而是当前选择的Device不存在或者Pack数据有问题导致IDE没有从芯片配置文件拿到时钟信息于是Xtal输入框自动锁定。解决方法是先回Device选项卡确认你选的芯片型号是否已安装。如果型号列表里能看到但选不中或者选了之后Target页整体异常你重新把对应的DFP包卸载再装一次。特别提醒STM32F1系列的DFP包和STM32F4系列的DFP包是独立安装的不要只装一个F4的包就想去编辑F1的工程。另外如果你用的是STM32CubeMX生成的工程Xtal设置其实在CubeMX里定义好并通过RCC配置生成的MDK里的Xtal只是给仿真器做时间基准用的灰不灰不影响最终固件运行所以不用太慌。4.5 双编译器场景下特别的几个坑前面说得比较分散这里集中提几个双编译器下特有的问题。第一个是编译器的中间产物混用。这个前文提到过这里再强调一次AC5和AC6的Object文件格式不完全兼容如果你在同一个输出目录里切换编译不清理目标文件就Rebuild偶尔能过偶尔会报一些让人抓狂的错误比如“no definition for main”这种。切换编译器后第一件事就是Rebuild。第二个是浮点打印格式不匹配问题。AC5下如果你用Keil自带的微库MicroLIB处理printf浮点格式化功能默认是精简的AC6下微库的实现也有细微差异。在老工程切AC6时printf输出浮点出现0.00这类现象往往是编译器库差异导致可以检查Use MicroLIB是否勾选并且把重定向fputc的代码确认一遍。第三个是汇编文件加密或混淆问题。以前国内芯片原厂分发驱动库时很喜欢用带加密的汇编或者.a库。AC5编译出的静态库文件在AC6下不一定能用因为库文件同样存在ABI兼容性问题。如果你链接的是闭源静态库那切换编译器前务必向库厂家确认是否支持AC6不支持就直接被锁死只能继续用AC5。5. 最后分享几个实在的实践技巧这章节本来可以叫“总结”但我确实不想说那些“通过本文你掌握了...”之类的废话。就说几个我实际做项目时反复用到的技巧你下次切编译器或搭双环境时能直接用上。第一每次切换完编译器后不要急着拿正常功能去试先做三个基础检查编译能不能过、能不能下载、延时和系统定时器准不准。这三个全过了再继续跑应用逻辑能帮你把大问题先筛掉。第二我习惯在工程Uvprojx文件里用文本方式打开里面每个Target都会有一段ArmCompiler或者OCode这样的配置项记录当前Target使用的编译器。如果你用Git管理代码这个文件一定要纳入版本管理否则别人拉下来后编译器版本都不一致编译出来行为千奇百怪。第三如果你经常需要在AC5和AC6之间切换但工程里某一部分代码始终只在AC6下编译建议把这一部分单独拆成一个静态库工程用AC6编译后以.lib文件形式集成到主工程主工程继续用AC5。这样既保留AC5的兼容性又能局部享受AC6的优化是我在大项目里经常用的组合拳。第四善用条件编译和Keil的“multi-target”设计。在同一个Uvprojx文件里建两个Target一个叫“Release_AC5”一个叫“Release_AC6”各自指定不同的编译器版本和优化选项。这样切换只用在Target下拉框里选一下不用每次进Options改而且同一个文件里两套编译配置可以随时对比编译结果。双编译器共存这件事说到底不是技术壁垒问题而是工具链生态演化过程中的必然阶段。STM32的代码量越大、历史包袱越重AC5和AC6共存的时间就越长。把这一套环境配置好对你未来的工程开发和老项目维护都是稳赚不赔的投入。
返回列表