ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

新MDK安装ARM Compiler 5:遗留工程兼容AC5的完整解决方案

新MDK安装ARM Compiler 5:遗留工程兼容AC5的完整解决方案 项目还是五年前的工程芯片是几年前的片子接手的人换了两拨——这就是我一打开同事发来的Keil工程时脑子里冒出来的话。原工程是用ARM Compiler 5也就是ARMCC 5编译的但新电脑上装的却是新版MDK里面默认只有AC6armclang。一编译满屏的__asm、__packed、__weak直接不认当年能跑得飞起的代码在新工具链面前就像一门外语。如果你正在被这种遗留代码折腾这篇文章就是为你准备的。它不会教你重写整个工程也不会劝你立刻迁移到AC6而是告诉你一个更现实的方案如何把ARM Compiler 5独立安装到新Keil环境里并让旧工程在全新的生态下继续编译、烧录、调试。内容包括独立安装包从哪下、怎么装、怎么让MDK认出AC5、老代码跨编译器的小规模适配、命令行自动化以及C51和AC6共存的避坑经验。无论你是刚接手老项目的嵌入式新人还是被历史包袱缠住的团队负责人这篇都能帮你省下两三天排查时间。1. 为什么2025年了还要回头装AC5遗留代码的现实困境在动手安装之前得先把一个根本问题聊透AC5到底还有没有价值为什么那么多老工程离不开它1.1 ARMCC 5和armclang 6之间到底差了什么从工具链架构上看AC5和AC6不是同一个爹。AC5的前身是ARM自家的armcc历史可以追溯到ADS/RVCT时代语法和优化策略非常“老派”。AC6则是从LLVM/Clang衍生出来的armclang代码生成后端、编译器基础设施、C标准支持都更现代。两者都叫“ARM编译器”但从命令行参数、内联汇编语法到堆栈对齐规则差别非常大。对比项ARM Compiler 5AC5ARM Compiler 6AC6底层引擎ARMCC自家Clang/LLVM常用命令armcc / armasmarmclang内联汇编__asm关键字__asm或asm volatile数据对齐__packed/__align__attribute__((packed))/__attribute__((aligned))函数修饰__weak/__forceinline__attribute__((weak))/__attribute__((always_inline))版本宏__CC_ARM__ARMCC_VERSION、__clang__C标准支持C90/C99为主C11/C17编译速度一般更快并行优化好对老工程师来说AC5最大的价值不是性能而是“稳定”。当年用AC5写出来的代码很多都踩过它的特性边界什么__irq中断函数、__value_in_regs结构体返回、分散加载文件里的特殊段名这些都是AC5时代留下来的写法。AC6虽然也能处理其中大部分但语法不兼容的报错足以让维护成本陡增。1.2 哪些老工程必须用AC5哪些其实可以换不是所有老工程都非AC5不可。做这个判断能省下大量无用功。必须用AC5的场景我大致划了这几类启动文件、链接脚本或外设库中大量使用ARMCC专有语法且无人力和测试资源去改产品已经量产固件行为必须保持“零变化”任何编译器升级导致的重排指令、优化差异都不可接受老的CMSIS版本或芯片PACK只对AC5做了完整支持换了AC6后虽然能编过但某些外设库函数行为会有细微差异项目里有第三方闭源静态库库是用AC5编译的而AC6的ABI特别是结构体返回、对齐规则和AC5不完全一致直接链接会出问题。可以换到AC6的工程通常满足代码量不大、没有重度内联汇编、依赖的库和PACK都较新、有足够的回归测试时间。如果你满足这些条件还是建议尽早迁到AC6毕竟ARM官方对AC5的维护周期已经拉得非常长了。1.3 新MDK不再默认带AC5这才是根源Keil MDK从5.37版本开始默认安装包里就不再包含ARM Compiler 5。这是被很多人忽略的坑你以为装了最新的MDK就万事大吉结果新建工程时发现编译器下拉列表里只有“Use default compiler version 6”。网上很多老教程还停留在“装完MDK自然有AC5”的认知所以照着教程折腾半天也无济于事。解决办法不是退回老版本MDK而是单独下载ARM Compiler 5的独立安装包再手动把路径登记到新版MDK中。这样做的好处是你既能享受新版MDK在调试器、芯片PACK、IDE体验上的更新又能保留AC5这条老工具链两头的好处都拿到。接下来就讲具体怎么装。2. 独立安装包下载与安装和“集成安装”说再见ARM官方从5.37开始有意把AC5从MDK安装器中剥离给出的理由很简单AC5已经进入维护末期新功能新优化都不可能再往它身上加了。但剥离不等于消失它提供了一个独立安装包专门给有兼容需求的用户用。2.1 版本怎么选认准5.06 update 7ARM Compiler 5的最终版本号是5.06 update 7如果你在工程或者旧电脑上看到的版本是5.06 update 6那也大概率能正常用但建议统一装到update 7避免不同电脑上编译器小版本不一致导致结果有细微差别。下载入口在ARM官方的“Product Downloads”区域搜索ARM Compiler 5或Keil MDK相关页面可以看到Historical Software / Previous Versions目录ARM Compiler 5独立安装包就在那里。注意下载通常需要登录ARM账号并且页面只保留当前和历史的主要版本不会包含所有测试版。提示ARM Compiler 5独立安装包体积不大几十MB级别和动辄几个GB的MDK安装包比起来轻量得多。下载时最好核对一下文件版本号别把AC6当AC5下了。2.2 安装路径选择装哪都不如装进Keil目录拿到安装包后官方推荐、也是我最推荐的做法是把它安装到Keil MDK的安装目录下的ARM/ARMCC子目录。例如你的MDK装在C:\Keil_v5那就把AC5装到C:\Keil_v5\ARM\ARMCC。为什么一定要认这个目录因为MDK在识别外部编译器时会按固定的相对路径查找bin/armcc.exe。你把AC5装进这个标准位置MDK大概率能自动识别不需要额外配置。安装步骤非常简单以管理员身份运行安装程序安装目录前几步会要求你选择MDK根路径直接选择C:\Keil_v5安装时它会自动往ARM\ARMCC里释放文件装完可以在C:\Keil_v5\ARM\ARMCC\bin下看到armcc.exe打开MDK稍等片刻它会自动刷新工具链列表。如果你不想装进Keil目录也可以自定义路径比如D:\ARM_Compiler_5.06u7但之后必须手动在MDK里登记步骤在第3节会讲。装在外部路径的优势是重装Keil时不会被覆盖但对大部分人来说还是装在标准位置省心。2.3 验证安装是否成功看版本号别偷懒装完以后打开命令行进入C:\Keil_v5\ARM\ARMCC\bin目录执行armcc --version正常情况下会输出类似Arm Compiler 5.06 update 7 (build 960)的版本信息。同时可以看到版权、编译日期等元数据。这一步非常重要很多人在MDK里看不到AC5回头排查时才发现独立安装包其实根本没装成功或者装成了ARM Compiler 6。另外建议顺手验证一下汇编器armasm --versionARMCC 5的汇编器叫armasmAC6里已经改名或者改走了不同路径。如果armasm也能正常输出版本说明工具链文件是完整的。之后就能进入MDK配置环节了。3. 把AC5登记进Keil工具链绑定与工程切换独立安装只是解决了“文件在磁盘上”的问题MDK默认不会盯着每个目录看。要让IDE在工程里认出并用上AC5还得完成工具链登记的步骤。3.1 在Project Items的Folders/Extensions里添加编译器打开MDK后先别急着打开工程找到菜单栏的Project - Manage - Project Items...切到Folders/Extensions标签页。这个页面顶部有一个“ARM Compiler”区域的配置MDK会显示当前可用的编译器列表。如果AC5装在了标准路径这里通常会自动出现“ARM Compiler 5.06 update 7”选中它所有新建工程默认或旧工程打开后都能手动切换过去。如果列表里没有出现点旁边的“Add another ARM Compiler Version”或者类似按钮不同小版本按钮文字略有差异然后选择AC5的安装目录注意是ARMCC根目录不是bin子目录。MDK需要根据根目录结构去识别bin/armcc.exe、bin/armasm.exe等完整工具链。注意手动添加时目录选错是最常见的失败原因。很多人习惯性找到bin目录就点确定结果MDK提示“No valid ARM Compiler found”。根目录的标志是里面直接能看到bin、include、lib这三个文件夹。3.2 工程层面切换Options for Target里的Compiler选择工具链登记好之后还要在具体工程里切换。打开目标工程右键Target或点魔法棒图标进入Options for Target在Target标签页的“ARM Compiler”下拉框中把默认的Use default compiler version 6改成Use installed toolchain并选择“ARM Compiler 5.06 update 7”。这里有个小细节值得注意如果你是拿一个已经用AC6编译过的工程来切AC5切完之后不要立刻满怀期待地去点Rebuild。先把Project窗口里的文件列表过一遍尤其注意有没有使用新版CMSIS Pack提供的外设库文件。因为你的PACK如果已经升到较新版本它可能默认按AC6的语法编译切回AC5反而会报错。最稳妥的做法是老工程用老工程配套的PACK版本别混合。3.3 新旧工程并存管理一个IDE里养两套工具链实际项目中你往往会同时维护一个AC5老工程和一个AC6新工程。这种情况下两个工程在同一IDE里来回切换是完全没问题的MDK会把每个工程的编译器选择保存在各自的.uvprojx文件里。我有一次同时打开三个工程一个STM32F1老项目用AC5一个GD32新项目用AC6另一个STM32H7项目也是AC6。切换编译时MDK表现很老实每个工程各用各的工具链互不干扰。真正容易出错的反而是“人心”你需要时刻清楚当前开着的是哪个工程别改完老工程代码后顺手在新工程里也动了两笔回头又不记得了。建议给工程文件名加后缀标识比如motor_ctrl_AC5.uvprojx一目了然。4. 老工程在新Keil里编译通过兼容性改造与常见报错当你完成工具链切换并按下Rebuild迎面而来的大概率是一批报错。不要慌这些报错大多有固定规律。这一节我按“编译-汇编-链接”三个环节把最容易踩到的坑和修法梳理一遍。4.1 编译阶段ARMCC专有关键字怎么兜底最常见的是内联汇编和关键字不兼容。虽然AC5本身就是ARMCC但如果你装了新PACKPACK提供的头文件可能已经按AC6风格做了适配甚至某些地方直接把AC5的写法给“优化”掉了。对于老工程自己的代码如果里面写了__asm、__forceinline、__packed在AC5下都是合法的不需要改。真正需要改的是那些“既想兼容AC5又想兼容AC6”的公共头文件。老工程里通常没有这种文件但团队未来要做双工具链支持的话建议加一段条件编译#if defined(__ARMCC_VERSION) (__ARMCC_VERSION 6000000) // AC5 legacy keywords #define ALIGN(n) __align(n) #define PACKED __packed #define WEAK __weak #define FORCEINLINE __forceinline #else // AC6 and GCC style #define ALIGN(n) __attribute__((aligned(n))) #define PACKED __attribute__((packed)) #define WEAK __attribute__((weak)) #define FORCEINLINE __attribute__((always_inline)) #endif这样写的好处是公共模块将来迁移AC6时只需换宏定义不用满工程找关键字替换。我在好几个项目里都是这样干的成本极低收益极高。4.2 汇编阶段启动文件和处理器的匹配汇编阶段的报错十有八九出在启动文件startup_xxx.s上。AC5的汇编器是armasm语法是经典的ARM汇编AC6的汇编器改用armclang --target很多老启动文件的写法它不认。但既然你已经切回了AC5启动文件反倒是省心的那个——用AC5的armasm编老启动文件基本是原配组合。唯一需要注意的是启动文件和芯片型号必须匹配。很多遗留工程在新建时从其他项目复制了启动文件芯片换了但启动文件没换这在AC5时代就可能埋雷只是当时没炸。切工具链时MDK会重新生成工程目标文件的依赖关系这时不匹配的启动文件更容易露出马脚。解决方法是直接打开.s文件查看文件头注释里的芯片型号和Target页面选择的芯片比对不一致就从正确的PACK里重新拷贝一份。4.3 链接阶段分散加载文件与地址段保持一致AC5工程通常使用.sct分散加载文件里面写着ROM/RAM布局、各个段的摆放位置。只要芯片没换.sct文件一般不需要动。但有一种情况要注意工程里新加了一个大数组或者新模块.sct里如果用了ANY链接器还能自动分配如果老工程里有人偷懒写了FIRST或者在固定地址放某个段新代码体积一变就可能“放置不下”。链接器报L6220E、L6221E这类错误时不要急着改代码先去看.sct。我见过一个工程老代码把一个大缓存固定放在了内部SRAM的末尾后来调试功能加了一堆日志缓冲区直接顶到边界链接器开始报错。切AC5后报错信息变得更显眼才让人想起来去查分散加载文件。这种事情靠编译器换版本不解决问题必须老老实实调整段布局。4.4 优化等级、字节对齐和未定义行为的一个提醒AC5的优化等级分为-O0到-O3以及-Os。老工程当年为了调试方便很多人都选-O0稳定运行后也懒得改回去。如果你从事的是量产项目建议在AC5下至少试一次-O2甚至-Os并做完整的功能回归。AC5的优化器比AC6保守对代码行为的影响相对小但这不代表你可以在-O2下继续依赖未定义行为。特别要提一下结构体对齐。老代码如果直接访问寄存器映射结构体且结构体字段没有加__packed在AC5下可能一切正常因为编译器按默认对齐规则布局而寄存器地址恰好满足对齐但如果照搬到AC6编译器可能把结构体布局优化得更紧凑或更宽松导致字段偏移变化。项目里如果有这种结构体哪怕已经在AC5下编译也建议抽时间明确PACKED属性免得换工具链时埋雷。5. 把AC5用成命令行工具独立安装带来的自动化便利细心的读者可能已经发现AC5独立安装包本质上就是一套独立的编译工具链它不依赖MDK IDE。这就引出一个容易被忽略的价值命令行自动化构建。遗留代码往往没有CI但如果你要接手、改造、持续交付给AC5配上命令行构建脚本是非常划算的投资。5.1 armcc命令行基础一次编译一个文件AC5的armcc是标准的C/C编译器前端用法和其他编译器类似。一个典型的编译命令armcc --cpu Cortex-M4 --c99 --apcsinterwork -I./inc -DUSE_STDPERIPH_DRIVER -DSTM32F10X_MD -c ./src/main.c -o ./build/main.o这里解释几个关键参数--cpu Cortex-M4指定目标CPU架构老工程务必和芯片匹配Cortex-M0、M3、M4的指令集不一样--c99启用C99标准老代码很多用到for循环内声明变量这在C90下会报错--apcsinterwork启用ARM/Thumb指令集间切换Cortex-M系列基本都要求这个-I、-D、-c、-o和GCC语义一致。如果你不知道老工程原来用的什么参数最简单的参考是MDK工程里的Options for Target - C/C页面它会把命令行拼好显示在下面直接复制过来就能用。5.2 fromelf从ELF里抠出hex/bin编译链接之后AC5生成的是ELF文件.axf要烧录还得转成hex或bin。这个任务由fromelf完成fromelf --bin --output./build/app.bin ./build/app.axf fromelf --i32 --output./build/app.hex ./build/app.axf老工程师喜欢把这一步放到MDK的User选项卡里在编译完成后自动执行。命令行场景下这行命令就是烧录脚本的前置步骤。产线烧录、测试固件打包都可以靠这条命令自动化。5.3 写一个最小可用的Makefile一个简单的Makefile思路是这样的把所有源文件列出来用armcc编译成.o再用armlink链接生成.axf最后用fromelf转成可烧录的.hex/.bin。考虑到老工程依赖的宏和头文件路径五花八门我建议第一版先把编译命令写死能跑通整体流程后再考虑用通配符优化。ARMCC C:/Keil_v5/ARM/ARMCC/bin/armcc.exe ARMLINK C:/Keil_v5/ARM/ARMCC/bin/armlink.exe FROMELF C:/Keil_v5/ARM/ARMCC/bin/fromelf.exe CFLAGS --cpu Cortex-M4 --c99 --apcsinterwork CFLAGS -I./inc -I./Drivers/CMSIS/Include DEFS -DUSE_STDPERIPH_DRIVER -DSTM32F10X_MD SRCS src/main.c src/stm32f10x_it.c src/system_stm32f10x.c OBJS $(SRCS:.c.o) all: app.hex app.bin %.o: %.c $(ARMCC) $(CFLAGS) $(DEFS) -c $ -o $ app.axf: $(OBJS) startup_stm32f10x_md.o $(ARMLINK) --scatterstm32f10x_flash.sct --entryReset_Handler $^ -o $ app.hex: app.axf $(FROMELF) --i32 --output$ $ app.bin: app.axf $(FROMELF) --bin --output$ $ clean: rm -rf *.o *.axf *.hex *.bin实际项目中你还需要处理汇编启动文件的编译规则、链接库路径、预编译宏等。但先把这条骨架跑通后面的一切都好说。我的经验是给遗留代码写CI的最大难点从来不是脚本语法而是搞清楚老工程那些隐藏的头文件路径和宏定义。建议直接从MDK生成的.uvoptx或编译器命令行窗口里抄作业别自己猜。5.4 自动化构建的价值不只是省时间有人可能觉得接手老项目已经够累了还要搭自动化不是没事找事吗但我的体会恰恰相反。遗留代码最恐怖的地方是“不确定”——不确定改动一行会不会影响另一个模块不确定换台电脑编译结果是否一致。有了命令行脚本你至少能保证每次构建用的是同一套参数、同一个工具链版本、同一份源文件这才是对遗留代码最基本的尊重。而且AC5独立安装包的存在让这整套自动化和普通程序的调用方式没区别完全可以把编译动作交接到Jenkins、GitLab CI或者简单的Windows批处理脚本里。哪怕团队只有你一个人维护老产品这个投入也值得。6. 多套工具链共存与授权管理少踩几个坑最后一节聊几个和“共存”相关的细节。这东西看着不起眼但真踩进去会让人很烦躁。6.1 Keil C51和MDK-ARM能装在一起吗能但要注意方式。热搜里“keil c51和arm 能装在一起吗”是个非常经典的问题。答案是可以装在同一个目录比如都选C:\Keil_v5安装时第二项会让你选择“Install support for C51”或者MDK核心装完后IDE会在同一个UV4窗口中同时支持8051和ARM芯片新建工程时根据芯片类型自动选用C51工具链或ARM工具链。但有几个坑必须知道安装顺序没有严格限制但建议先装MDK-ARM后装C51或者反过来都行关键是两者的安装目录要一致不要用一个版本的C51去配另一个版本特别旧的MDK尽量保持大版本一致如果你后续升级MDK切记C51插件可能会被覆盖或丢失升级后要把C51再装一遍保证版本匹配合适。这个和AC5有什么关系关系在于当你为了兼容老代码单独装了AC5后IDE里实际存在三套编译器——AC5、AC6默认ARMCC 6、C51。三套工具链同时在文件系统里共存是正常的MDK能处理好。唯一的风险是装AC5时误把安装目录指到了C51目录下导致工具链文件混在一起。安装时看到“ARMCC”字样就一定是指向ARM目录别选到C51目录。6.2 AC5和AC6工程混用如何避免“编译结果秒变”我在4.1提到过条件编译宏那是从代码层面兼容两套工具链。但还有一种更隐蔽的坑同一个工程文件在不同人手里被切换工具链。比如团队里A同事用AC5维护老代码B同事习惯用AC6。两人在同一份工程文件上操作一旦B把工程保存为“Use default compiler version 6”下次A打开后一编译直接收获一堆AC6风格的报错。稳妥的做法是把编译器选择固定下来通过版本管理忽略本地的.uvoptx等用户配置文件或者在工程文件里明确选中AC5并提交到版本库。如果你手里有多个适配工具链的方案那就更有必要在工程路径里或README里写明“此工程只能使用ARM Compiler 5.06 update 7”减少沟通成本。6.3 关于授权和激活给自己留条干净路顺着热搜词往下翻能看到“注册机”“破解”之类的词。这里我明确提一句MDK和AC5都属于商业授权软件破解激活既不安全也可能给公司带来合规风险。如果公司有正版授权直接在License Management里输入授权码即可如果只是个人学习评估版也能满足相当一部分场景。我之前帮朋友排查过一个诡异问题他电脑上的MDK编译老工程时报“No License”代码和工具链切换都没问题最后发现是新装的AC5需要单独在License Management里激活和IDE主授权不是一回事。正确的激活路径是打开Project - Manage - License Management查看“Single-User License”列表里是否包含ARM Compiler 5对应的节点如果显示Feature: MDK-ARM Plus或类似授权项不足就联系管理员加授权。别去信那些来路不明的破解工具你在量产项目里用一个破解环境最后出了问题谁都兜不住。6.4 多版本共存时的维护清单基于我自己的实际经历整理一套检查清单供你维护多版本工具链时参考每次升级MDK主程序前先确认AC5独立安装包还能不能从ARM官网下载到备份安装包在版本管理里提交一份toolchain.env或README记录MDK版本、AC5版本、所需PACK版本开发机之间保持工具链一致避免“在我电脑上能编译”的经典问题给每个遗留工程打上明确的编译器和PACK版本的标签方便后续交接。最后一点个人体会前面几节看似在讲安装和配置实际上核心是“让旧代码继续安全地活着”。独立安装AC5的最大价值不在于让某个工具链“复活”而在于它让你保留了一个完整的历史环境做横向对比时能更清楚地区分“代码问题”和“工具链差异”。如果你还有余力我建议把这套环境折腾完之后顺手给老工程写一份README把MDK版本、AC5更新包版本、芯片PACK版本、编译注意事项全部记录下来。几个月后再有人打开这份工程时就不用再经历一遍你今天查资料、翻论坛、试错的过程了。这种事做起来不显眼但对一个长期维护的嵌入式项目来说价值不亚于代码本身的优化。
返回列表