ARTICLE DETAIL

资讯详情

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

Ubuntu下手动安装gcc-arm-none-eabi工具链及多版本管理实战

Ubuntu下手动安装gcc-arm-none-eabi工具链及多版本管理实战 1. 为什么我劝你别再只用 apt-get 装了1.1 apt 源里的版本可能老到你怀疑人生很多人问我Ubuntu 上搞 STM32 开发编译器是怎么装的。我看到的答案大多是一句“sudo apt-get install gcc-arm-none-eabi”完事。这个命令确实能装但如果你真的用过几个月就会发现版本旧、全局唯一、升级换版全靠赌。我最早也是从 apt-get 入门的后来被一个老项目和一个新项目同时压在桌上才老老实实学会手动安装 gcc-arm-none-eabi 并做版本管理。这篇就是把这条路走通的经验整理出来包括为什么要抛弃 apt、手动安装的完整步骤、多版本并存的玩法以及搭好编译器之后 STM32 工程怎么编译、烧录、排错。先看版本问题。Ubuntu 和 Debian 的软件源里确实有gcc-arm-none-eabi但版本往往停在几年前有的发行版源里只有 7.x、8.x 或 9.x而 ARM 官方早就发布了 10.3、11.3、12.3 甚至更新的版本。我记得很清楚有一次我在帮朋友调一块 STM32U5 的开发板官方示例工程的 Makefile 里默认用的是 10.3 以上工具链结果 apt 装出来的版本太老编译时直接报某些新特性不支持排查了半天才意识到是编译器版本问题。版本老不光是“功能少一点”那么简单它直接影响你能不能在新芯片上顺利编译。比如 Cortex-M23、Cortex-M33 这类新核心老编译器不是不能用但编译参数、链接脚本的处理方式都可能踩到莫名其妙的坑。再加上 ARM 官方每次发布工具链都会修一些针对 GCC 的 bug、完善对最新器件支持老版本编译出来的代码在极端情况下可能有微妙的运行差异。你当然可以说“老版本我也能跑”但如果两个工程师用的工具链版本不一样编译产物对不上、链接顺序对不上最后查出是工具链差异这个时间成本真的一点都不值。1.2 多版本共存时 apt 根本帮不上忙apt 工具链最大的问题不是不能装而是同时只能有一个版本。你用apt-get install gcc-arm-none-eabi装好之后系统里就这一套想换版本只能 remove 再 install或者去第三方 PPA 碰运气。实际开发里多版本共存的需求非常常见。我自己就被这个坑过一次。那时候手头有一个 2018 年的量产固件客户一直没升级过固件里某些校验逻辑对编译优化极其敏感只能用当时那套旧编译器编译出来的 bin 才能稳定过产线测试。另一个新项目又要求用比较新的工具链官方 SDK 的脚本、链接脚本都是按新版工具链写的旧编译器编译直接报错。两边都要交付我总不能天天卸了装、装了卸吧后来果断放弃 apt把两套工具链手动装到不同目录按项目切换这个问题才算彻底解决。这也是手动安装最大的价值工具链本质上是“一组文件放在某个目录里”只要你把不同版本放到不同目录想用哪个就指哪个天然就支持多版本共存。apt 的包管理器反而把这个简单的事情搞复杂了它强迫你每次只能面对一个全局状态。1.3 apt 装上后也不一定省心还有一个容易被忽略的问题apt 安装是全局的需要 sudo 权限而且工具链会跟着系统一起升级/依赖管理。听起来挺省事但一旦系统里其他包升级时把某个动态库版本动了工具链可能就莫名其妙抽风。我见过不少发行版源里的gcc-arm-none-eabi依赖特定的 libstdc 版本系统升级后工具链直接报GLIBCXX_3.4.xx not found非常难排查。另外apt 源里的工具链是发行版维护者自己打的包不是 ARM 官方 Release。虽然大多数情况功能上没区别但一旦遇到诡异链接问题你想去 ARM 官方论坛、社区文档里找对照会发现别人的编译版本号和你的对不上很多排错经验都没法直接套用。更直白一点某些精简版系统的源里甚至根本没有这个包或者需要你手动启用额外仓库折腾半天还不如直接去官网下载 tar 包来得快。1.4 什么时候我还是会选 apt把话说公道一点“别只用 apt-get”不是“禁止用 apt-get”。如果你满足下面任意一条apt 依然是个可以的选择只是临时编译一个小测试程序对工具链版本没有要求你用的发行版源里工具链版本恰好和项目要求一致你不想折腾任何环境变量能编译就行坏了就直接重装系统你是新手第一次接触 STM32只想要一个“能跑通”的环境。但如果你打算长期做 STM32 开发、会同时维护多个项目、或者要给团队搭标准构建环境我还是建议你把工具链手动装到一个自己能完全掌控的目录里。花十来分钟换来的是版本可控、多版本共存的自由这笔账怎么算都不亏。2. 手动安装之前先搞清楚这几件事2.1 先确认你的系统架构在下载工具链之前先在终端里敲一下uname -m绝大多数 PC 输出是x86_64那你就下载 ARM 官方 GNU 工具链的 x86_64 Linux 版本。如果你是在 ARM 开发板、树莓派或者某些国产 CPU 的 Linux 机器上做开发uname -m可能会输出aarch64那就得下载对应的 aarch64 版本。选错架构基本上没办法运行这一点别偷懒。这里多说一句不要因为名字里带“arm”就以为是给 ARM 开发板用的安装包。gcc-arm-none-eabi是“运行在你的电脑上、生成 ARM 机器码”的交叉编译器它本身跑在 x86_64 或者 aarch64 的 Linux 系统上。搞清楚这一点后面遇到“这个工具链怎么不能直接在我的 ARM 板子上跑”之类的新手问题你就会心一笑。2.2 看懂版本号命名规则ARM 官方工具链的压缩包名长这样gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2拆开看其实很清晰10.3是 GCC 的版本号2021.10是发布日期x86_64-linux是运行平台。同样的你可能还会看到11.3-2022.02、12.3-2022.11、13.2-rel1之类的版本号命名规则基本一致。选版本的时候不要总追最新。我的经验是优先看你的项目文档、教程、BSP 里建议用哪个版本跟着社区主流的版本走一般不会错。比如说很多 STM32 老教程和开源工程默认用 10.3你直接跟着用 10.3 就好跟教程截图、命令行都对得上。等你熟悉了这套流程再按需升级也不迟。2.3 下载渠道和校验工具链建议从 ARM 官方下载不要从第三方镜像站随便下。地址是 Arm GNU Toolchain 的官方下载页面打开后在 Linux 分类里找到对应架构的.tar.bz2文件复制直链用wget下载即可。官方页面有时候会改版直链很长最稳的办法是用浏览器“复制链接地址”再粘贴到终端里。下载完之后不放心的话顺手做个哈希校验sha256sum gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2把输出结果和官网给出的 SHA-256 值比对一下一致再继续解压。这一步在长期维护的场景里很有必要能避免因为下载文件损坏导致编译器行为诡异省得后面排查半天。3. 手把手手动安装 gcc-arm-none-eabi 完整步骤3.1 下载并解压到指定目录我习惯把工具链放在~/tools下面个人开发机嘛不需要 sudo也不会污染系统目录。如果你想放在/opt下面给多人共用操作也差不多区别只是要多加 sudo 和权限管理。先建目录、下载、解压mkdir -p ~/tools cd ~/tools wget 从官网复制的 gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 地址 tar xjf gcc-arm-none-eabi-10.3-2021.10-x86_64-linux.tar.bz2 -C ~/tools解压完之后~/tools下面会出现一个类似gcc-arm-none-eabi-10.3-2021.10的目录。为了后面好写路径可以改个短一点的名字mv ~/tools/gcc-arm-none-eabi-10.3-2021.10 ~/tools/gcc-arm-none-eabi-10.3这里解释一下为什么我不直接用 apt 安装到系统目录手动解压的本质是“把工具链当成普通文件集来管理”你不想要了直接rm -rf删目录就行不会残留一堆依赖关系。装到用户目录还有一个额外好处就是换电脑时把这个目录整个拷走export一下 PATH 就能接着用不需要重新走任何安装流程。3.2 配置环境变量PATH 到底怎么加才不踩坑工具链解压好之后bin 目录下的arm-none-eabi-gcc、arm-none-eabi-g、arm-none-eabi-gdb这些可执行文件已经可以用了但系统默认不知道它们在哪里所以要把 bin 目录加到 PATH 里。打开你的 shell 配置文件比如 bash 是~/.bashrczsh 是~/.zshrc然后在末尾追加一行export PATH$HOME/tools/gcc-arm-none-eabi-10.3/bin:$PATH注意两点。第一加的是bin目录不是工具链根目录也不是可执行文件本身。第二我习惯把工具链目录放在 PATH 的最前面也就是$PATH放后面这样如果系统里还残留了/usr/bin/arm-none-eabi-gcc手动装的版本会优先被找到。如果你很确定系统里没有旧包放前面放后面其实都行。改完之后让配置生效source ~/.bashrc然后验证which arm-none-eabi-gcc如果输出的是~/tools/gcc-arm-none-eabi-10.3/bin/arm-none-eabi-gcc说明 PATH 生效了。如果还是/usr/bin/arm-none-eabi-gcc说明你之前用 apt 装过旧版本需要检查/usr/bin下的软链或者优先级。3.3 验证安装并确认交叉编译正常光是--version能输出版本号还不够我一般会做一次真实的交叉编译测试确认工具链生成的确实是 ARM 机器码。先写一个最简 C 文件cat /tmp/test.c EOF int main(void) { return 0; } EOF用-mcpucortex-m4 -mthumb编译这两个参数是 STM32F4 系列最常用的~/tools/gcc-arm-none-eabi-10.3/bin/arm-none-eabi-gcc -mcpucortex-m4 -mthumb -c /tmp/test.c -o /tmp/test.o file /tmp/test.ofile命令输出里如果出现类似这样的内容就说明交叉编译工具链工作正常ELF 32-bit LSB relocatable, ARM, EABI5 version 1如果你看到的是x86-64或者没有 ARM/EABI 字样那大概率是你 PATH 里调到了系统自带的本地 gcc而不是arm-none-eabi-gcc回头检查一下which arm-none-eabi-gcc指向的位置。顺便把-v参数看一眼arm-none-eabi-gcc -v这个输出里能看到编译器内置的配置参数比如默认的架构、头文件搜索路径。以后遇到头文件找不到之类的问题这些信息排错很有用。3.4 确认配套工具都齐全一个完整的 GNU ARM 工具链条里不只是 gcc 一个命令后面编译、烧录、调试还要用到这些arm-none-eabi-gC 交叉编译器arm-none-eabi-as汇编器arm-none-eabi-ld链接器arm-none-eabi-objcopy生成 bin/hex 文件的核心工具arm-none-eabi-objdump反汇编、查看目标文件信息arm-none-eabi-size查看固件占用大小arm-none-eabi-gdb交叉调试器arm-none-eabi-nm、arm-none-eabi-ar等你可以ls ~/tools/gcc-arm-none-eabi-10.3/bin确认下这些文件都在。正常 ARM 官方工具链是全部都打包好了的但如果某个工具的软链不小心被碰坏了后面 make 的时候会出现 “No rule to make target” 或者命令找不到提前确认一下能省好多事。4. 版本管理技巧一个系统里养多套工具链4.1 用 update-alternatives 做系统级切换如果你只是偶尔在多个工具链版本之间切换不想每次手动改 PATH可以用 Debian/Ubuntu 自带的update-alternatives管理默认版本。它本质上就是维护一组软链接把/usr/bin/arm-none-eabi-gcc指向你指定的版本。注册版本的方法sudo update-alternatives --install /usr/bin/arm-none-eabi-gcc arm-none-eabi-gcc ~/tools/gcc-arm-none-eabi-10.3/bin/arm-none-eabi-gcc 100 sudo update-alternatives --install /usr/bin/arm-none-eabi-gcc arm-none-eabi-gcc ~/tools/gcc-arm-none-eabi-12.3/bin/arm-none-eabi-gcc 90后面的数字是优先级数字越大越优先。注册好之后切换版本sudo update-alternatives --config arm-none-eabi-gcc终端会列出当前已注册的所有版本输入对应编号就能切换。不过这里面有个坑update-alternatives 是按单个命令管理的一个项目编译时不只是用 gcc还有 g、objcopy、size、gdb 等一系列命令。你只注册 gcc 是远远不够的。所以如果要走这条路建议把工具链 bin 目录下所有需要用的命令都注册一遍或者写个简单的 for 循环脚本批量注册否则切了 gcc 版本objcopy 还是旧版本链接和生成 bin 文件时照样会出幺蛾子。4.2 项目级锁定把工具链版本写进项目配置update-alternatives 是全局操作换版本会改变整个系统的默认状态。但在真实项目里我更推荐的是把工具链版本“写死”在项目里这样不管谁拿到代码用哪台电脑构建出来的产物才是一致的。最简单的方式是在项目根目录放一个setenv.sh#!/usr/bin/env bash export PATH$HOME/tools/gcc-arm-none-eabi-10.3/bin:$PATH每次新开终端准备编译这个项目时先执行source setenv.sh这样只对这个终端窗口生效不影响系统其他项目的环境。如果嫌手动 source 太麻烦可以装一个direnv工具进入项目目录时自动加载环境变量退出目录自动卸载体验会好很多。另一种更硬核的方式是在 Makefile 里直接指定工具链完整路径完全不依赖 PATH。比如PREFIX $(HOME)/tools/gcc-arm-none-eabi-10.3/bin/arm-none-eabi- CC $(PREFIX)gcc OBJCOPY $(PREFIX)objcopy SIZE $(PREFIX)size这样不管 PATH 被改成什么样make 都会找固定路径下的工具链彻底消除环境问题。如果你在 CI 服务器上有多套工具链这种方式尤其好用即使并行编译不同项目也不会串。4.3 升级换代和设备拷贝的技巧手动安装还有一个隐藏福利升级工具链特别简单。比如你从 10.3 升到 12.3只需要新版本解压到一个新目录然后按项目逐步切换。老目录先不要删等项目完全验证通过再清理整个过程可以做到零风险回滚。设备拷贝也很方便。比如团队里两个工程师用的都是同一个手动安装路径~/tools/gcc-arm-none-eabi-10.3新同事入职时直接把整个目录用 U 盘或者内网传过去解压到相同路径source一下配置文件就能开工。要是用 apt 装的时候想在新电脑上复现完全一样的环境就得祈祷发行版源版本不变不然很容易出现“我这边能编、你那边编不过”的尴尬。4.4 更进一步容器化封装构建环境如果项目对工具链版本的要求苛刻到每一个小版本都要严格锁定又或者你觉得本机装太多工具链很乱可以试试用 Docker 把构建环境封装起来。思路很简单写一个 Dockerfile在里面用手动安装的方式装好指定版本的 gcc-arm-none-eabi然后挂载源代码到容器里执行编译命令。容器化最大的优势是“和宿主机完全隔离”不管你的笔记本是 Ubuntu 还是 Fedora只要装了 Docker构建结果就可以保持一致。团队协作时新人拉代码后只需要一句docker compose run build不用再折腾任何工具链非常省心。缺点是第一次配置 Dockerfile 成本有点高如果你的项目才刚开始、对版本要求也没那么严格直接用 4.2 的方法其实就够了。5. 配好编译器后怎么在 STM32 项目里真正用起来5.1 编译一个 STM32 工程需要的不只是编译器很多新手以为装好gcc-arm-none-eabi就万事大吉了实际上编译器只是“把源代码变成机器码”的那一环。一个能烧进 STM32 并跑起来的固件还需要三类东西配合启动文件startup_xxx.s负责初始化栈指针、调用 SystemInit、跳到 main 函数链接脚本xxx.ld规定 flash 和 RAM 的地址范围告诉链接器代码放在哪里CMSIS 设备头文件 / 系统初始化代码里面是寄存器定义和时钟初始化逻辑。这三样东西咋来两种主流的路径第一种是从零手写或者从老工程里拷适合学习原理第二种是用 STM32CubeMX 根据芯片型号生成一个 Makefile 工程这是现在大部分项目的默认做法省时省力。如果你用 STM32CubeMX 生成工程注意在 Project Manager 里把 Toolchain 选成Makefile或者CMake这样生成的工程目录里会自带 Makefile、链接脚本、启动文件。然后打开终端进入工程目录执行make如果工具链路径已经配好、环境变量没问题编译过程就会开始滚动。最终在 build 目录下会生成.elf、.bin、.hex文件这就是可以烧录的固件。5.2 一个顺手 Makefile 的关键参数这里我给一个非常简化的 Makefile 片段方便你理解工具链参数到底在配什么PREFIX $(HOME)/tools/gcc-arm-none-eabi-10.3/bin/arm-none-eabi- CC $(PREFIX)gcc OBJCOPY $(PREFIX)objcopy SIZE $(PREFIX)size CFLAGS -mcpucortex-m4 -mthumb CFLAGS -mfloat-abihard -mfpufpv4-sp-d16 CFLAGS -Os -g -Wall -fdata-sections -ffunction-sections LDFLAGS -T stm32f4xx.ld --specsnano.specs -Wl,--gc-sections # 目标规则省略...-mcpucortex-m4 -mthumb指定目标 CPU 类型和 Thumb 指令集STM32F4 系列基本就是这个组合。-mfloat-abihard -mfpufpv4-sp-d16是优化浮点运算的配置如果你用的是 F103 这种不带硬件浮点单元的芯片这两个参数要去掉。链接时--specsnano.specs表示使用精简版 C 库可以显著减少固件体积这是 STM32 项目里非常常用的一个参数。实际编译项目时如果报一些和 floating point ABI 有关的错误八成就是-mfpu参数和芯片不匹配。这时候不要乱猜翻一下芯片手册确认有没有 FPU、是单精度还是双精度再调整参数。5.3 烧录和调试的联动工具链配好了、固件也编译出来了下一步是把固件烧到芯片里。这里用到的是另外一套工具但和工具链一样也需要提前装好。常见的选择用 ST-Link 调试器 st-flash烧录st-flash write build/main.bin 0x08000000用 OpenOCD ST-Link 烧录并调试openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c program build/main.elf verify reset exit用 STM32CubeProgrammer 命令行烧录STM32_Programmer_CLI -c portSWD -w build/main.elf -v调试方面arm-none-eabi-gdb可以配合 OpenOCD 做源码级调试和你在 IDE 里点断点的体验差不多只是纯命令行操作需要一点学习成本。如果你更习惯图形界面VSCode 安装 Cortex-Debug 插件配置好 gdb 路径和 OpenOCD 路径也能获得接近 Keil/STM32CubeIDE 的调试体验。这也是很多工程师脱离 IDE、拥抱 VSCode 的原因——编译和调试命令都暴露出来可控性非常高。6. 常见问题与排查技巧实录6.1 我明明配了 PATH为什么还是找不到命令这种情况非常多见。排查第一步是which arm-none-eabi-gcc如果输出为空说明 PATH 配置没生效或者写错了。常见原因有你改了.bashrc但没有source直接开了个新终端应该能解决你想把 export 加在.profile里但当前 shell 是 zsh根本不读.profile你的 export 写在了某个 if 判断语句块里逻辑没走到那一行PATH 里的路径写错了比如少了/bin。如果which输出已经是手动安装的路径但 make 的时候还是报 not found那多半是 Makefile 在调用编译器的时没有继承当前 shell 的环境变量。因为 make 默认执行的 shell 是/bin/sh某些情况下不会读取你.bashrc里定义的 PATH。解决办法很简单在 Makefile 里使用完整路径或者用make PATH$PATH显式传过去。make PATH$HOME/tools/gcc-arm-none-eabi-10.3/bin:$PATH6.2 编译出的文件不是 ARM 机器码这种情况一般是在 Makefile 里漏了-mcpu和-mthumb或者 PATH 冲突导致实际调用的是系统本地 gcc 而不是交叉编译器。一个快速判断方法which arm-none-eabi-gcc如果输出是/usr/bin/arm-none-eabi-gcc那没问题这本来就是交叉编译器。但如果你在 Makefile 里把CC gcc了那就彻底完蛋了系统会拿本地 x86 编译器去编译 ARM 工程编译出来的是 x86 机器码烧进 STM32 肯定跑不起来。拿file命令检查一下生成的目标文件看到ARM, EABI5才放心。6.3 老版本工具链在新系统上报动态库缺失arm-none-eabi-gcc: error while loading shared libraries: libncurses.so.5: cannot open shared object file这类错误在比较老的工具链版本上比较常见。新系统默认只带新版 ncurses 库老工具链编译时依赖的是旧版本库找不到就会报错。解决办法要么去装兼容库要么干脆换新版工具链。我自己通常选择后者因为装一堆老兼容库只是为了一个编译器有点得不偿失而且新版工具链修了老版本不少问题编译速度也更快。6.4 多版本切换后每次结果都不一样如果你不是通过完整路径调用工具链而是靠 PATH 切换那么切换完记得验证一下当前生效的版本arm-none-eabi-gcc --version有时候 shell 会缓存命令路径你切换了 PATH 但 shell 还记着旧位置这时候可以执行hash -r清除一下命令缓存。如果项目里有比较严格的版本要求我还是强烈建议在 Makefile 里直接用绝对路径别把命运交给外部环境变量。经历过“同一份代码在不同电脑上编译结果不一致”的团队应该能懂这个建议的价值。6.5 OpenOCD 连接不上 ST-LinkOpenOCD 报No STLink found典型原因是 udev 规则没配置。Linux 默认不允许普通用户直接访问 USB 设备你需要给 ST-Link 设备写一条 udev 规则或者把当前用户加入plugdev组。如果你是用st-flash烧录也遇到权限问题处理思路是一样的。这个和 gcc-arm-none-eabi 工具链没有直接关系但它是 STM32 命令行开发环境里最常踩的坑之一顺手写在这里。6.6 排查问题速查表现象可能原因快速解决arm-none-eabi-gcc: not foundPATH 未配置或未生效source ~/.bashrc检查路径是否写错编译出的目标文件是 x86 格式调用了本地 gcc未用交叉编译器which确认路径Makefile 里用完整路径GLIBCXX_3.4.xx not found工具链版本和系统库不匹配换新版工具链或用容器封装构建环境No STLink foundudev 规则缺失或 USB 权限不足配置 udev 规则加入plugdev组编译时找不到 CMSIS 头文件头文件路径没加-I在 Makefile 的 CFLAGS 里补上头文件路径链接时大量undefined reference没链接对应的外设库或启动文件检查 Makefile 里的源码列表和链接脚本我个人的建议是遇到工具链问题先别急着重装按which - version - file - 编译参数 - 链接脚本的顺序排查大概率能定位到真正原因。重装只是把问题往后推迟而且容易把原本能用的环境也搞乱。最后再分享一个我的习惯每次新建 STM32 项目我会在项目根目录放一个README第一行就写明“本项目要求 gcc-arm-none-eabi 10.3推荐路径 ~/tools/gcc-arm-none-eabi-10.3”。这样过几个月之后自己或者同事回来维护项目不用靠回忆去猜工具链版本。手动安装工具链本身并不难难的是把“版本可控”这事落在项目日常里养成习惯之后你会发现换电脑、换系统、加新人这些以前折腾半天的事都变成了解压目录加source一下的流水线操作。
返回列表