资讯详情

ARM Compiler 5.06(960)安装配置与Keil老工程编译避坑

📅 2026/11/6 7:19:10 | 华诺云谱 👁 阅读
ARM Compiler 5.06(960)安装配置与Keil老工程编译避坑
简介面向ARM架构嵌入式开发的ARM Compiler 5.06(960)编译器安装包主要服务于使用Keil MDK进行单片机与嵌入式系统开发的工程师、高校学生以及需要维护旧版ARMCC工具链的项目团队。它提供符合ANSI C标准的armcc编译能力支持代码大小与运行速度优化、Thumb/Thumb-2等指令子集并可与Keil调试工具链配合完成固件构建与问题定位适合老项目复现、课程实验和工具链补装等场景。压缩包共3个文件包括Windows x86平台的msi安装包、安装引导程序以及版本更新说明网页整体约80.52MB便于在本地一次取齐编译器与版本信息。已有3609人浏览学习说明该版本在嵌入式社区仍有稳定需求。读者可据此获得可安装的ARMCC 5.06(960)工具链并通过更新说明了解该版本的改进、修复与已知问题为旧工程迁移、编译环境搭建和日常开发提供参考。1. 老项目一打开就报错缺的多半就是这份 AC5接手一份两年前的嵌入式硬件工程MDK 一打开选项里翻遍编译器下拉框也找不到 ARMCC或者代码里直接顶出一行#error This project must be built with ARM Compiler 5——十有八九不是代码出了问题是这台机器上根本没装 ARM Compiler 5。ARMCompiler_506_Windows_x86_b960.msi 就是补上这个缺口的安装包ARMCC 5.06 update 6build 960的 Windows x86 版安装完成后会得到 armcc、armasm、armlink、fromelf、armar 一整套 arm 编译器命令行工具Keil 只是给它套了个图形外壳。它服务于 Cortex-M0/M3/M4/M7 这一代以及更老的 ARM7/ARM9 工程尤其是那些靠内联汇编、自定义 .sct 分散加载文件、C99 老写法堆起来的存量代码。下面按「装在哪、怎么挂进工程、参数怎么给、哪一步最容易出问题」顺下来。2. 拆开这个 MSIarmcc、armasm、armlink、fromelf 各管一段很多人对 ARMCC 的印象停留在 Keil 里那个下拉框实际上 5.06 是一套独立的交叉工具链Keil 只是调用了其中的几个可执行文件。先把这套工具的分工理清楚后面在命令行里单独复现一次编译时才知道每一步该找谁。2.1 安装包里落地的五个可执行文件一个 build 能不能把 C 源码变成烧进 Flash 的 .bin是这五个程序接力完成的。它们的输入输出关系很清楚出问题时也基本能靠「哪一步的中间产物不对」快速定位。工具干的事典型输入典型输出armccC/C 编译前端预处理、优化、生成汇编或目标文件.c/.cpp/.h.o/.sarmasm汇编器处理启动文件与手写汇编.s/.asm.oarmar静态库打包一堆.o.aarmlink链接器按分散加载文件布局段做 unused section 剔除.o.a.sct.axf/.elf.mapfromelf镜像后处理反汇编、导 bin、看尺寸.axf.bin/.dis/ 尺寸报告新手最容易忽略的是 armlink 那一步。代码「编译过了但跑不起来」「中断向量表跳飞了」八成不是 armcc 的锅而是 .sct 里加载域和执行域写得跟芯片实际 Flash/SRAM 对不上。所以排查顺序一般是从 map 文件往回看而不是从 C 代码往前翻。2.2 AC5 和 AC6 之间那道分界线ARM Compiler 6 换的是基于 LLVM/Clang 的前端AC5 用的是另一套前端两者不是「版本高低」的关系而是两套编译器。这个差别决定了哪些工程能直接升级哪些只能继续钉在 5.06 上。以下这些情况我一般判定为「别折腾留在 AC5」工程里大量内联汇编用的是 AC5 的__asm { ... }块语法。AC6 走 GNU 风格的__asm volatile语法不兼容一改就是几十处。老版本芯片支持包生成的启动文件、系统文件里有 AC5 专有的__asm和#pragma且没有可替换的新版。依赖第三方.a静态库供应商只给了 AC5 编的版本。混用会出现后面第 5 章讲的那个链接属性冲突。分散加载文件用了 AC5 的旧语法细节比如* (RO)后面跟老式的{ }嵌套armen 版本的链接器能过新版会报语法错。反过来如果工程是纯 C、没有内联汇编、第三方库都有源码那升级到 AC6 通常能换来更好的 -O2 优化效果和 C11 支持。但那是另一个话题这次只解决「怎么把 5.06(960) 正确装起来并用对」。2.3 安装后的目录结构与三条关键路径不管是默认安装还是手动指定装完的目录长得基本是这样ARMCC/ ├── bin/ armcc.exe armlink.exe fromelf.exe armar.exe armasm.exe ├── include/ 标准头文件 stdio.h stdlib.h string.h ... ├── lib/ armlib 的 .a 与 microlib 的 .a ├── sw/ 示例、帮助文档 └── license/真正影响构建的只有三条路径认得它们比记十个参数有用ARMCC/bin所有可执行文件。命令行复现、写脚本、做 CI都往这里加 PATH。ARMCC/include交叉编译用的标准头文件。如果你的工程里混进了一份宿主机比如本机装的某个 gcc的 include会出现「结构体大小跟目标机不一样」这类很难发现的问题所以-I的顺序里AC5 的 include 要放在系统头之前或干脆不引入其它。ARMCC/lib链接时找 armlib/microlib。armlink 一般能自己定位到但用脚本调的时候--userlibpath或-L指到这儿会更稳。提示安装完成后的第一件事是跑armcc --vsn确认输出里同时有5.06、update 6、960。版本号对不上后面所有排查都是在错的基线上做。3. 把 5.06(960) 挂进 Keil从 MSI 到第一行编译通过装完不等于能用。Keil 从某个版本开始默认自带的是 AC6AC5 需要单独安装再手动登记否则你在 Options for Target 里根本看不到这个选项。这一章把「装、挂、验」三件事走完。3.1 安装路径共用一份还是各放一处两种做法我都用过差别在后期维护成本。第一种是装进 Keil 自己的目录树例如C:\Keil_v5\ARM\ARMCC。好处是 Keil 和命令行调的是同一份二进制永远不会有「IDE 里能编过、脚本里编不过」这种莫名其妙的状态坏处是升级 Keil 或者重装 Keil 时这份目录有一定概率被覆盖或清掉得提前备份。第二种是独立目录比如D:\tools\armcc506_960然后靠登记让 Keil 去引用。好处是版本隔离干净一台机器上可以同时存在 5.06 和另一个 build互相不干扰坏处是路径一旦挪动所有工程都要重设。静默或基本界面安装可以用 msiexec 把路径压进去# /qb 走基本界面能看到进度/qn 全静默适合批量装机器 msiexec /i ARMCompiler_506_Windows_x86_b960.msi /qb INSTALL_ROOTC:\Keil_v5\ARM\ARMCC这里有个坑不同打包方式给安装路径用的属性名不一定是INSTALL_ROOT也可能是INSTALLDIR或TARGETDIR。硬猜属性名的结果是装完发现东西还是落在默认目录。稳妥做法是用 Orca 打开这个 msi 的 Property 表看一眼或者先跑一遍带界面的安装记住它默认给的路径再决定要不要改。命令行参数只是省事不是必须。3.2 在 MDK 里把编译器切成 AC5装完之后打开 Keil找到 Options for Target 的 Target 页第一行就是 ARM Compiler 下拉框。如果里面只有 v6 系列说明这个版本的 MDK 没有自动扫描到 AC5需要手动登记。步骤如下打开 Project → Manage → Project Items切到 Folders/Extensions 页。找到 ARM Compiler 那一栏点后面的...在弹出的对话框里把 AC5 的安装根目录含 bin、include、lib 的那一层加进去。关掉对话框回到 Options for Target下拉框里应该出现对应条目选中它。顺手把Use MicroLIB勾选状态确认一遍。老工程往往是勾着的换编译器后如果状态被重置printf 的行为会突然变掉这个后面第 5 章细说。登记完成后的验证方式不是「能编过就行」而是看编译输出窗口里 Keil 回显的完整命令行里面会带--cpu、--c99、-I等参数。把那一行复制出来就是第 3.3 节要用的命令行原型。3.3 脱离 IDE 用 armcc 编译一个 .o能脱离 IDE 单独编出一个 .o才说明这套工具链是真的可用而不是 Keil 内部某个缓存骗了你。CI 环境、批量验证、给同事复现问题时都靠这一步。CCC:/Keil_v5/ARM/ARMCC/bin/armcc.exe # 先确认版本输出里应该能看到 5.06 update 6 (build 960) $CC --vsn # 编一个单文件C99、Cortex-M4F、-O2、带调试信息 $CC --c99 \ --cpu Cortex-M4.fp \ --fpufpv4-sp-d16 \ --apcs/hardfp \ -O2 --split_sections -g \ -I./Inc \ -IC:/Keil_v5/ARM/ARMCC/include \ -DUSE_HAL_DRIVER -DSTM32F407xx \ -c ./Src/main.c \ -o ./build/main.o逐项说一下为什么这么给--c99是显式打开 C99。AC5 默认是 C90 语义代码里在语句中间声明变量、用for(int i...)都会报错。老工程里那种「明明在别的机器上能编」的情况多半就是别人工程里加了--c99而你没加。--cpu Cortex-M4.fp里的.fp后缀表示这个核带 FPU。只写Cortex-M4也不会报错但生成不了浮点指令所有 float 运算退化成软浮点性能差一个数量级而且不会有任何警告。--fpufpv4-sp-d16指定单精度 FPU。跟上一项是配套的缺一个都不算真的开了硬浮点。--apcs/hardfp才是切换调用约定的开关。只给--fpu而不给这句函数参数传递仍走软浮点混着编就会出现「同一个函数在不同文件里签名不一致」的怪事。--split_sections让每个函数单独成段链接阶段才能把没被引用的函数剔掉。Flash 紧张的工程必开。3.4 链接与产物验证map、反汇编、bin编译只是前半程。链接这一步的输入里除了 .o还有分散加载文件它决定了代码最终落在哪段地址上。LDC:/Keil_v5/ARM/ARMCC/bin/armlink.exe FEC:/Keil_v5/ARM/ARMCC/bin/fromelf.exe $LD --cpu Cortex-M4.fp \ --fpufpv4-sp-d16 \ --scatter ./MDK-ARM/app.sct \ ./build/*.o \ --library_typemicrolib \ --infosizes \ --map --list./build/app.map \ -o ./build/app.axf # 导烧录用的 bin $FE --bin --output ./build/app.bin ./build/app.axf # 反汇编确认关键函数没有被优化掉 $FE --text -c -o ./build/app.dis ./build/app.axf # 看各段尺寸 $FE --text -z ./build/app.axf--scatter指向的就是 Keil 工程里那份 .sct别自己另写一份否则 IDE 和脚本两条路径的布局会不一致。--library_typemicrolib要配合编译阶段的-D__MICROLIB一起用只给链接器加是无效的。验证是否真的用对了编译器我一般看三处map 文件头部的编译器版本行、.dis反汇编里有没有出现VLDR/VSTM这类 FPU 指令、以及fromelf -z打出来的 Code 尺寸是否跟 IDE 里一致。三处都对上才敢说这次构建是可信的。4. 参数怎么设-O2、--c99、FPU 与 microlib 的组合边界参数不是越多越好AC5 的参数里有一半是「加了没坏处」另一半是「加错了很难查」。挑真正影响结果的几个讲清楚。参数作用什么情况必须给常见误用--cpu Cortex-M4.fp指令集与 FPU 可用性换芯片后只改设备型号不改这里-O0/-O1/-O2/-O3优化等级调试 -O0发布 -O2用 -O3 单步调 bug--split_sections函数独立成段Flash 紧张、库多开了但链接没剔除--c99C99 语义代码里有 C99 写法以为等价于--gnu--gnuGNU 扩展老代码用了typeof等在 C99 下重复叠加--fpufpv4-sp-d16允许生成 FPU 指令M4F/M7以为这就是硬浮点--apcs/hardfp硬浮点调用约定全工程统一硬浮点只给部分文件加--library_typemicrolib换精简库Flash 极紧、无 OS用了还想 malloc 大块-g调试信息需要在线调试发布版还带着镜像虚胖4.1 优化等级与 --split_sections 的实际取舍-O0下每个变量都在栈上有实体单步调试能看到所有中间值代价是代码体积和速度都难看。-O2是绝大多数量产固件的默认选择-O3会在循环展开和函数内联上更激进代码体积可能反而变大而且断点会跳来跳去。我的习惯是调试阶段-O0 -g回归测试和发布用-O2两者都跑一遍对比 map 里的段尺寸。如果-O2之后某个功能失效、-O0正常九成是缺volatile或者依赖了未定义行为别急着改优化等级去掩盖。--split_sections配合 armlink 的默认行为能把没被引用的函数剔除掉。这个特性在引入一个大而全的 HAL 库时特别有用你不用的外设驱动不会占 Flash。但要注意被中断向量表引用的函数、被链接脚本KEEP的段不会被剔掉所以看 map 时发现某个函数还在先确认它是不是被向量表抓着了。4.2 C99、GNU 扩展与老代码的兼容开关AC5 的默认语言模式是 C90。--c99打开 C99--gnu打开 GNU 扩展两者可以叠加使用。判断标准很简单代码里有//注释之外的 C99 特性混合声明、变长数组、指定初始化器、stdint.h的完整形态就加--c99代码里出现typeof、语句表达式、__attribute__的奇怪用法就再加--gnu。有个细节容易忽略--c99会改变某些头文件的行为比如stdio.h里对可变参数宏的处理。老工程切成 C99 之后如果 printf 相关代码报错先看是不是这个引起的而不是去怀疑 armcc 坏了。4.3 浮点--fpu 不等于 hardfp这是 AC5 上最容易出问题的一处值得单列。--fpufpv4-sp-d16只是告诉编译器「目标机有这个 FPU你可以生成 FPU 指令」。但函数之间传递 float 参数时究竟是用通用寄存器还是 FPU 寄存器是由调用约定APCS决定的。AC5 默认走软浮点约定所以必须再显式加--apcs/hardfp才会真正走硬件浮点调用。更麻烦的是这个开关必须全工程一致。如果只有部分 .c 加了/hardfp链接时会报属性不兼容即便侥幸链过去了运行期也会因为参数位置对不上而算出离谱的结果。所以在 Options for Target 的 C/C 页里这个参数要么全给要么全不给。4.4 microlib 与标准库省下来的体积从哪来microlib 是 ARM 提供的一套精简 C 库牺牲的是完整性和标准符合度换来的是体积。它不支持操作系统、不支持某些 locale 相关特性、不可重入malloc的行为也比标准库简单得多。省下来的主要是这几块完整的格式化输出解析、文件 I/O 相关的桩函数、以及标准库里的可重入保护。代价是printf的格式支持会缩水比如浮点格式化需要额外配置而且在中断里调printf更容易出问题。判断要不要用很简单Flash 还剩 20KB 以上、工程里没有 RTOS 的多任务打印需求就别开 microlib标准库更省心Flash 卡着最后几 KB且只用printf打打整数和字符串那就开但记得把fputc重定向写好。注意开了 microlib 之后标准库的头文件仍然可以 include但链接进去的是精简版实现某些函数的行为会有差异。切换时建议全量重编别做增量。5. 避坑与排查五条实际操作中踩过的坑这一节记录的都是「报错信息看起来跟真实原因没关系」的情况。每条按现象、原因、解决三步写遇到同类问题可以直接对照。5.1 装了 5.06Options 里还是选不到现象安装程序跑完提示成功bin目录里 armcc.exe 也在但 Keil 的 ARM Compiler 下拉框里就是没有 AC5 选项。原因新版 MDK 不会自动扫描已安装的编译器必须手动登记安装根目录而且登记的层级要对指到bin那一层是无效的得指到包含bin、include、lib的那一层。解决走 Project → Manage → Project Items → Folders/Extensions把 ARM Compiler 一栏指向正确层级重启 Keil。还不行就把 Keil 的配置文件里残留的旧路径清掉再登记一次。5.2 L6915Eprintf 一调就链接失败现象链接阶段报L6915E: Library reports error: __use_no_semihosting was requested, but _sys_open was referenced或类似信息涉及_ttywrch、_sys_exit这些符号。原因代码里用#pragma import(__use_no_semihosting)关掉了半主机但标准库的 printf 实现仍然依赖那几个底层桩函数。关掉半主机等于承诺「这些函数我自己实现」可你没实现。解决把最小的一整套桩函数补上。/* 告诉库不要半主机相关符号我自己提供 */ #pragma import(__use_no_semihosting) struct __FILE { int handle; }; FILE __stdout; /* 退出与字符输出桩必须存在否则链接报 L6915E */ void _sys_exit(int x) { (void)x; while (1) { } } void _ttywrch(int ch) { (void)ch; } /* printf 最终会走到这里重定向到串口 */ int fputc(int ch, FILE *f) { (void)f; while ((USART1-SR 0x80) 0) { } /* 等发送寄存器空寄存器按实际芯片改 */ USART1-DR (uint8_t)ch; return ch; }这段代码的要点在于_sys_exit和_ttywrch是链接器要的符号函数体可以是空的但不能没有fputc才是真正决定输出去哪的。用了 microlib 的话不会触发半主机但fputc依然要写否则 printf 什么都不输出还找不到原因。5.3 浮点计算结果不对或者一进中断就 HardFault现象-O0下浮点运算正常-O2下结果偏差极大或者一进带浮点运算的中断就进 HardFault。原因三种可能。一是--apcs/hardfp只给部分文件加了链接期属性不一致二是启动文件里没使能 FPUCPACR 寄存器的 CP10/CP11 位第一次执行 FPU 指令就异常三是中断里用了浮点而中断栈没有按 8 字节对齐异常压栈时出错。解决先统一编译参数并全量重编再检查启动文件里的 FPU 使能代码是否被注释掉最后确认中断服务函数里避免浮点或者在进入中断前把栈对齐处理好。这三步按顺序排查基本能覆盖。5.4 L6242EAC5 和 AC6 的目标文件混链现象链接阶段报L6242E: Cannot link object xxx.o as its attributes are incompatible with the image attributes指向某个第三方库。原因工程主体用 AC5 编译但某个.a或.o是 AC6 编出来的。两套编译器的对象文件格式虽然都能被 armlink 读但属性标记尤其是浮点约定、C ABI对不上。解决不要试图靠改链接参数糊过去。要么让供应商提供 AC5 版本的库要么拿到源码自己用 AC5 重编。同理工程里如果混进了别的架构编出来的库也会报这个错先确认库的编译目标是不是同一颗核。5.5 fromelf 导出的 bin 体积异常现象fromelf --bin出来的文件是 0 字节或者比 map 里统计的 ROM 尺寸大出一截。原因--bin要求镜像只有一个连续的加载域。如果 .sct 里写了多个不连续的加载域比如把配置参数单独放在 Flash 尾部fromelf 无法用一个 bin 表达就出问题。体积虚大则通常是段之间有空洞bin 会把空洞一起填成 0xFF。解决先看 map 里的加载域分布确认是否是单一连续区域。多加载域的场景改用--bincombined或者在链接阶段就把段合并。体积虚大的情况不用太紧张烧录工具通常会按实际内容裁剪但如果做 OTA 差分就得把这个空洞问题处理干净。6. 把 5.06 固定进构建链版本校验与尺寸回归多人协作时最容易出现的问题是「我这儿能编过」。同事机器上装的可能是另一个 build或者干脆是 AC6 加了个别名。靠人自觉没用得让构建脚本自己拦。我一般在构建入口加一段版本断言五行代码能省掉后面几小时的排查#!/usr/bin/env bash set -euo pipefail ARMCC_BIN${ARMCC_BIN:-C:/Keil_v5/ARM/ARMCC/bin} # --vsn 的首行可能是授权信息抓真正带编译器版本的那一行 VER$($ARMCC_BIN/armcc.exe --vsn 21 | tr -d \r | grep -m1 -E ARM Compiler [0-9]) case $VER in *5.06*update 6*960*) echo [ok] $VER ;; *) echo [fail] 期望 5.06 update 6 (build 960)实际: $VER; exit 1 ;; esacARMCC_BIN留成环境变量是为了让不同机器能指向各自的安装位置而版本要求写死在脚本里谁改了都得先过这一关。这一段放在整个构建脚本的最前面比放在编译失败之后去猜有效得多。第二个习惯是把 map 里的段尺寸存成基线。armlink 的 map 文件里会有这样的统计行# 每次构建后把三行尺寸抓出来存档跟上一版 diff grep -E Total (RO|RW|ROM) ?Size ./build/app.map | tee ./build/size.txt把size.txt一起提交进仓库改完代码 diff 一下就知道这次提交是谁把 Flash 吃掉了几 KB。这个做法比事后看fromelf -z更直接因为 map 里的数字跟你最终烧进去的镜像是一一对应的。我给一个工程定过规矩单次提交 ROM 增量超过 2KB 必须说明原因几次下来那种「随手 include 一个大头文件」的提交就绝迹了。第三个是目录约定。同一台机器上要维护多个老工程时我一般把编译器按armcc/5.06_960/这样的层级放工程里的路径配置写成相对这个根目录的形式而不是硬编码绝对路径。这样换机器、换盘符都不用改工程文件只需要改一个环境变量。说个自己的教训。早些年接手一个量产过的老工程图省事直接用了机器上现成的 AC5编出来的固件在实验室跑了一周都没事小批量试产时出现零星死机。查了三天最后发现那台机器上装的是另一个 build某个库函数的边界处理有差异恰好被工程里一处越界访问触发了。从那以后我每次接手老工程第一件事不是看代码而是先把--vsn跑一遍把版本号写进构建脚本再把 map 尺寸存成基线。这三步花不了十分钟但能挡掉后面几天的返工。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑