ARTICLE DETAIL

资讯详情

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

ARMCC 5编译器为何仍被坚守?MDK中AC5的配置、坑与迁移实践

ARMCC 5编译器为何仍被坚守?MDK中AC5的配置、坑与迁移实践 简介MDK ARMCC 5编译器是ARM微控制器开发套件MDK中的关键编译工具链适合需要维护旧工程或应对特定代码兼容性需求的嵌入式开发者。这套资源以完整编译器组件形式打包共651个文件包含大量.b/.l库文件、.h头文件、.cc源文件以及.exe可执行工具涵盖标准C/C库、内建算法容器与运行时支持压缩包约58.54MB可满足独立安装或替换V6版本的需要。目前已有1981人学习下载经社区验证具备实用价值。资源中除编译器本体外还包含链接器映射文件.map、启动汇编与内存布局示例并支持C/C及汇编语法、多种优化级别与RTOS接口方便开发者核对编译流程、调整优化选项或排查链接错误。对于因历史项目依赖ARMCC 5而无法直接升级的团队此包可提供一套可回退的构建环境降低迁移成本。 做嵌入式开发的人大概率都跟 Keil MDK 打过交道。这几年 ARM 官方把编译器重心全面转向了 ARMClang 6简称 AC6MDK 从 5.25 版本开始也把 AC6 作为默认工具链。但奇怪的是到现在依然有大量项目、大量开发者坚持用老牌的 ARMCC 5简称 AC5甚至新开的项目也会刻意把编译器切回 V5。这篇博客我就结合自己的实际开发经历聊聊 MDK 里的 ARMCC 5 编译器到底是什么、为什么它还没被淘汰、怎么安装配置、以及实际使用中那些糟心的报错和解决思路。无论你是刚接触 STM32 的新手还是被 AC5/AC6 来回切换折腾过的老手相信都能从里面找到点有用的东西。ARMCC 5 最让人放心的就是它的稳定性。很多老工程师对 AC6 的编译告警“过敏”项目一用 AC6 就爆出来几百条 warning有些还是结构性改动带来的看着就头皮发麻。而 ARMCC 5 的告警少、行为可预测加上大量芯片厂商的老版本固件库比如标准外设库、老的 HAL 库都是基于 AC5 调试验证过的这就导致兼容性成了大家死守 AC5 的头号理由。1. ARMCC 5 到底是什么为什么今天还在用1.1 从编译器的定位说起ARMCC 5 是 ARM 公司推出的 C/C 编译工具链隶属于 MDK-ARM 工具链的一部分。它的全称是 ARM Compiler 5在 Keil MDK 的安装目录里对应ARM\ARMCC\bin核心可执行文件叫armcc.exeC 编译器和armcpp.exeC 编译器。当年 MDK 4.x 和 MDK 5 早期版本用的编译器就是它后来 ARM 推出了基于 LLVM 架构的 ARMClang 6也就是大家经常在 MDK 里看到的 “Compiler version 6”。编译器这东西说穿了就是把 C 语言转换成机器指令的“翻译官”。工程里你写的int a 1;最终变成MOVS r0, #1这个翻译过程由编译器负责。ARMCC 5 和 AC6 最大的区别在于底层架构完全不同AC5 是 ARM 自己维护的传统编译器AC6 则基于 LLVM/Clang 架构语法检查更严格、优化策略更激进但随之而来的是跟老的 C 语言写法、内联汇编、关键字扩展有更多不兼容的地方。1.2 为什么老项目离不开 AC5我用一个很实际的现象说明。很多还在量产维护的老产品用的是 STM32 标准外设库SPL这个库在 2017 年左右就不再更新了官方最后的版本就是用 AC5 验证的。如果你把编译器切到 AC6SPL 里的很多文件会报错比如assert_param(IS_GPIO_ALL_PERIPH(GPIOx))这种宏定义在 AC6 下可能会触发未使用变量的警告老代码里常见的#pragma指令比如#pragma pack在 AC6 下的语法有出入部分内联汇编的写法换到 AC6 需要改成__asm volatile的新格式。以上还不是最要命的更关键的是启动文件startup_stm32f10x_hd.s这种汇编文件在不同版本编译器下的指令集兼容性也有差异。很多项目从 MDK4 时代一路升级到 MDK5工程文件里的编译器配置默认就是 AC5一冲动点了 “Switch to Compiler 6”结果几十个编译错误扑面而来最后只能默默切回去。1.3 当前环境下 AC5 的定位现在 MDK 官网都很难直接下载到 ARMCC 5 了新装 MDK 5.37 及以后版本时V5 编译器往往需要手动安装。但你要知道AC5 并没有真正“死掉”如果你在 MDK 的包管理器Pack Installer里安装了对应芯片的器件支持包并且手动安装了 ARM Compiler 5 的 standalone 版本它依然能正常使用。还有一点需要澄清网上很多人说 “MDK 5.37 之后不支持 AC5”这其实是个误传。准确说法是MDK 5.37 开始V5 编译器不再随默认安装包一起自动安装需要单独从 Arm 官网下载并集成。而且如果你的芯片是 Cortex-M0/M3/M4/M7AC5 是完全可用的但如果是 Cortex-M23/M33/M55 等较新的架构AC5 就不支持了必须用 AC6。这个限制是硬性的跟编译器版本无关纯粹是 AC5 的“年龄”摆在那里。2. MDK 下安装与配置 AC5 编译器的完整过程2.1 先确认自己缺不缺 V5 编译器打开你的 Keil MDK在工程界面点魔术棒Options for Target切到 Target 选项卡在 ARM Compiler 下拉框里如果只有Use default compiler version 6这一个选项说明你的环境里没有安装 V5。如果你能看到类似Use default compiler version 5或者Use installed toolchain version 5.06那说明已经装好了。具体确认步骤在 MDK 菜单栏点Project-Manage-Project Items切到Folders/Extensions选项卡看ARM Compiler区域里有没有列出ARMCC (5.06 update 7)这类条目。我自己的机器上装的是 5.06 update 7这是 ARMCC 5 系列最后一个稳定版本build 960网上传的 “AC5 最后的版本” 指的就是它。绝大多数情况下用 5.06u7 就够了。2.2 手动安装 ARMCC 5 的步骤如果你装了最新版 MDK 但没有 V5可以按下面步骤来。先说重点不要随便从第三方网站下载所谓“绿色版 ARMCC”一是可能有病毒风险二是版本混乱容易出兼容问题。最靠谱的方式是到 Arm 官网的 Product Download Center找到MDK-ARM相关下载页面里面通常会有一个单独的Legacy ARM Compiler 5下载项下载后是一个ARMCompiler_5.06u7_xxxxx.exe之类的安装包。安装时有些注意事项安装路径不要去改动默认目录默认是C:\Keil_v5\ARM\ARMCC如果你自定义到别的盘MDK 虽然也能识别但后续各种环境变量、脚本调用容易出岔子。安装过程中如果提示“检测到 MDK 已安装”直接选“是”它会自动把编译器集成进去。安装完成后重新打开 MDK在魔术棒的 Target 选项卡里就能看到 V5 编译器选项了。2.3 在工程里切换编译器工程中切换到 AC5 的方法很简单但有几个隐含的坑。打开魔术棒 - Target 选项卡ARM Compiler下拉框选Use default compiler version 5点 OK。如果你安装了多个 V5 小版本可以通过Manage Toolchain Versions对话框去选择具体要用的版本。这里有个很多人忽略的关键点切换编译器后一定要做一次Project - Clean Targets然后重新 Build。因为编译器切换后目标文件.o、.axf、.hex的格式和调试信息不通用直接增量编译会出现 “out of date” 没生效的奇怪现象甚至偶尔碰到链接时符号找不到的错误。我自己遇到过好几次明明是干净的代码切一次编译器就报L6218E: Undefined symbol最后 clean 掉全部重新编译就好了。另外一个建议是在代码里如果有跟编译器相关的条件编译尽量用#if defined(__ARMCC_VERSION) (__ARMCC_VERSION 6000000)这种写法而不是靠#ifdef __CC_ARM。因为 AC5 的__ARMCC_VERSION值在 5xxxxx 范围AC6 在 6xxxxx 范围这个宏是 ARM 官方保证可用的比传统的__CC_ARM更通用。3. ARMCC 5 和 ARMClang 6 的工程级差异3.1 不单是编译命令不同很多新人对 AC5/AC6 的理解停留在“编译器名字不一样”但实际上它们的差异从上到下辐射了整个开发流程。对比维度ARMCC 5 (AC5)ARMClang 6 (AC6)底层架构ARM 自研编译框架LLVM/Clang 开源架构默认 C 标准C90/C99 混合C11/C17告警数量少多且严格优化级别-O0 到 -O3-O0 到 -Ofast还有 -Oz内联汇编语法传统__asm {}写法GNU/LLVM 风格更接近 GCC支持的 ARM 架构扩展最多到 Cortex-M7支持到最新的 M55/M85二进制兼容性无法与 AC6 的目标文件混链无法与 AC5 的目标文件混链生成编译依赖文件.crf.d或.crf格式不同那个“二进制不兼容”是挺麻烦的事。如果你很多年前用 AC5 编译了一个静态库.a 文件想在新工程里换 AC6 继续用基本上没戏。反过来也一样。因为两套编译器使用的 ABI应用程序二进制接口在结构体对齐、函数调用约定上存在差异直接混链会引发非常隐蔽的内存错位问题。这个一定要注意遇到链不上或者运行错乱先查是不是 AC5 和 AC6 生成的库混用了。3.2 优化行为的区别ARMCC 5 在 -O3 优化下会比较“保守”它对 C 语言别名规则的处理没有 AC6 那么激进。AC6 开启-O2甚至-O3后如果代码里有未定义行为比如有符号整数溢出、未初始化变量使用它会做一些“大胆”的假设导致程序跑起来行为异常但编译器不报错。我遇到一个典型的例子是代码里写了一个for(i0; ilen; i) buf[i] 0;len 初始化为 0结果数组只有 1 个元素的时候在 AC6 -O2 下居然“看起来正常”但换到 AC5 反而崩溃。排查到最后发现罪魁祸首是 len 的类型是uint8_tilen这个判断在某种边界条件下会让 i 溢出死循环。AC6 的优化器直接把整个循环优化没了所以看不出来问题。这类坑不是语言本身的错而是编译器优化策略不一样导致的“假象”。我的建议是老代码在 AC5 下跑得好好的千万别手痒去切 AC6 刷存在感新项目可以优先 AC6但要把编译告警当错误看。3.3 关键字和语法兼容性AC5 里有很多自己扩展的关键字比较常用的有__packed用于结构体紧凑排列替代语法是__attribute__((packed))__align(8)指定对齐方式替代语法是__attribute__((aligned(8)))__inline内联关键字AC6 支持标准inline或__inline;__irq中断函数声明AC6 下更推荐用__attribute__((interrupt))或芯片 SDK 提供的宏__forceinline强制内联AC6 下可用__attribute__((always_inline))。如果你要写跨 AC5/AC6 兼容的代码最省事的做法是定义一层宏。比如#if defined(__ARMCC_VERSION) (__ARMCC_VERSION 6000000) #define ALIGN_8 __align(8) #define PACKED __packed #else #define ALIGN_8 __attribute__((aligned(8))) #define PACKED __attribute__((packed)) #endif这样上层业务代码不直接依赖编译器关键字以后切到其他工具链比如 GCC也容易移植。4. 使用 ARMCC 5 经常踩的坑与排查实录4.1 经典报错*** error: createprocessfailed打开 Keil MDK 点编译立刻弹出一个错误框内容类似*** error: createprocessfailed, command: D:\Keil5\ARM\ARMCC\bin\armcc.exe ...这个报错出现的场景五花八门但本质上就一句话MDK 没能成功启动 armcc 编译器进程。我整理一下最常见的几种原因和自己实测有效的解决办法编译器路径带空格或中文比如D:\软件\Keil\ARM\ARMCC这样 MDK 调用命令时引号处理出错。解决办法是重装 MDK 到纯英文无空格的路径这一点是新手最容易忽略的。杀毒软件拦截。Windows Defender 或其他安全软件有时候会把 armcc.exe 误判为风险程序直接拦截了进程启动。去安全软件的隔离区看一下恢复并添加信任即可。环境变量 PATH 被改乱。有时候安装其他软件把系统的 PATH 改了MDK 找不到位置。试着在命令行手动执行D:\Keil5\ARM\ARMCC\bin\armcc.exe --version如果提示“不是有效的命令”说明 PATH 或者安装目录有问题。一个偏方是卸载重装 MDK把这堆问题一次性解决。编译器版本不匹配。这个比较隐蔽你工程选择的编译器版本是 5.06u7但 MDK 里实际安装的是 5.06u6这种情况下偶尔也会出现 createprocess 失败。去Project - Manage - Project Items - Folders/Extensions里把 Toolchain 路径重新指定一下或者卸载掉多余版本。这个错误的坑在于提示信息根本没给出具体原因所以排查效率很低。我个人的习惯是装了新 MDK 后第一时间先编一个最简单的空工程确认编译器能正常编出来再往里加业务代码。4.2 编译报错的排查思路以头文件stm32f10x.h为例排查思路很有代表性。说说“添加头文件 include stm32f10x.h 时报错”这个经典问题。常见错误形如..\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\stm32f10x.h(78): error: #5: cannot open source input file stm32f10x.h或者error: #256: invalid redeclaration of type name s32这类问题九成是三个原因头文件路径没有包含。在魔术棒 C/C 选项卡的 Include Paths 里没有添加包含stm32f10x.h所在目录的路径。注意 MDK 里路径分隔符用反斜杠\并且相对路径是相对于工程文件.uvprojx所在目录而言的最好用相对路径方便整个工程目录迁移。宏定义缺失。STM32 的头文件里大量使用条件编译比如#ifdef STM32F10X_HD如果你在 C/C 选项卡的 Define 里没写对应的芯片型号宏头文件会跳到错误的分支里导致一系列类型定义缺失。解决办法是查看启动文件对应的宏比如STM32F10X_HD, USE_STDPERIPH_DRIVER。C 标准选择问题。AC5 的默认模式相对宽松但如果你在工程里勾选了 “C99 Mode” 之外的奇怪选项有些头文件的写法会触发告警甚至错误。用 AC5 时一般保持默认即可。4.3 关于编译器堆空间不足的问题“编译器的堆空间不足”这个说法其实有点歧义。它分两种情况第一种是编译过程中编译器自身的内存不足通常在编译超大文件或开启了极高的优化等级时出现报错会有internal error: C3907U之类的关键字。解决办法是降低优化级别或者把一个大文件拆成多个小文件编译。这在 AC5 里不算罕见毕竟 armcc 是 32 位进程单个文件的编译处理能力有限。第二种是目标代码里的堆Heap配置不足这是运行时的概念。在启动文件startup_stm32f10x_hd.s里有类似Heap_Size EQU 0x00000200的配置这决定了 C 库函数malloc能用的内存块大小。如果你跑 RTOS 或大量使用动态内存可以把 Heap_Size 调大但要留意片内 RAM 总量别和栈空间打架。有意思的是AC5 移植到 AC6 后堆栈配置的方式基本一样但链接脚本从.sct文件变成.sct或.ld取决于你用的分散加载文件格式。AC5 默认用 ARM 自己的分散加载文件*.sctAC6 也可以用但更推荐用 GCC 风格的链接脚本。如果你不太熟悉这些底层细节建议先保持原来的.sct方式AC6 对它也有兼容支持。4.4 如何生成 .bin 文件很多人在做量产烧录或 OTA 升级时需要bin文件MDK 默认只生成.hex需要额外配置。用 AC5 时在魔术棒的 User 选项卡里After Build/Rebuild的Run #1里填一行命令fromelf.exe --bin -o $LL.bin #L$LL.bin的意思是输出文件名为当前输出目录下的、跟工程同名的 bin 文件#L表示链接器生成的 axf 文件。如果你觉得这个语法太抽象也可以直接写绝对路径比如C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o D:\out\app.bin D:\project\obj\app.axf注意AC6 的 fromelf 路径不一样在C:\Keil_v5\ARM\ARMCLANG\bin\fromelf.exe用法参数基本一致。这算是一个“切换编译器后以为自己会了结果命令不对”的经典案例。5. AC5 迁移到 AC6 的现实思考5.1 什么时候应该下定决心迁现在芯片厂商新出的 SDK 基本都是基于 AC6 来验证的新接触的生态也是这个方向。如果是全新项目有以下几个指标建议直接用 AC6目标芯片是 Cortex-M23/M33/M55/M85 或较新的 Cortex-A/R 系列用了较新版本的 HAL 库或 MCU 厂商 SDK需要用到 AC6 的链接时优化LTO、更高的编译告警等级打算长期维护代码未来要迁移到其他 LLVM/GCC 工具链。如果是老产品或者量产稳定项目没有特殊需求就别折腾。嵌入式行业有个铁律能正常跑量产的程序不要为了“技术先进”去动它。编译器一换可能触发一堆隐藏 bug出了问题还不是编译器背锅是写代码的人背锅。5.2 迁移时的常规操作清单如果决定迁按这个顺序来能少踩点坑备份工程包括所有源码、配置文件、链接脚本。用版本控制工具建一个分支专门做迁移实验。在魔术棒里切换到 AC6clean 后全量编译。优先解决报错启动文件、系统文件如system_stm32f10x.c、板级支持包里的汇编、#pragma、__packed等。把编译告警调到-Wall -Wextra逐条过一遍特别是“未定义行为”相关警告。在优化等级 O0 下验证功能再逐步提高优化等级每提升一级就做一次完整功能回归。特别注意检查栈空间使用情况。AC6 在优化后可能产生不同的函数调用深度原先的栈大小可能不够跑着跑着就进 HardFault。5.3 我个人的建议如果非要说一个我的倾向性那我会说新手直接学 AC6老工程继续用 AC5两套并存不冲突。MDK 允许同一个版本里同时装多个工具链你可以在工程级别随意选择所以不需要“二选一”的焦虑。唯一需要注意的是不要贪多机器上同时装一堆不同版本的 AC5 和 AC6反而容易给自己制造问题。6. 一些实用小技巧和心得最后分享几个我这些年用 ARMCC 5 攒下来的小技巧。用__attribute__((section()))做自定义段。AC5 也支持这个语法只不过写法上要带双下划线。比如uint8_t my_buffer[1024] __attribute__((section(NOINIT)));在分散加载文件里可以把这个段放到指定 RAM 区域这在做无初始化变量重启不清零时非常好用。善用__weak关键字。AC5 里函数定义前加__weak可以在链接阶段被同名的强定义覆盖。这在做回调函数、底层驱动默认实现时是神器。比如__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { /* 默认空实现 */ }应用层再定义一个同名函数链接时默认实现自动被替换。注意多个源文件里有相同__weak定义时链接器只保留其中一个具体保留哪个要看链接顺序逻辑上别依赖这个特性。排查未初始化变量。AC5 编译时会给未初始化全局变量默认放到 ZI 段如果你的程序跑起来某些变量“莫名其妙有值”先检查是不是 ZI 段被启动文件清零了还是硬件复位后 RAM 残留。这跟编译器关系不大但很多奇怪的 bug 其实就是没分清.bss和.data的初始化时机。最后再分享一个关于 AC5/AC6 的坑有时候你在 MDK 里编译时明明已经选了 AC5但在工程目录下却能搜到armclang.exe的调用记录。这不是巧合而是 MDK 的某些组件比如 AC6 语法检查器的索引程序一直在后台跑。不用太紧张真正参与编译的进程看 Output 窗口里的编译命令行就知道了。这种东西不搞清楚会让人虚惊一场。本文还有配套的精品资源点击获取
返回列表