☰
Ubuntu下STM32F4开发实战:从工具链搭建到点灯调试
2026/10/13 1:08:26 网站建设 项目流程

1. 为什么放着好好的MDK不用,非要在Ubuntu下折腾

1.1 “换个心情”背后的真实需求

先说个真实场景。某天我在Windows下开Keil MDK,界面还是那个老界面,编译一个F407的工程,等着进度条慢慢挪,突然想到一个问题:我电脑上已经有几十个工程文件了,每次点“Build”都要对着那个灰底蓝条看半天,代码补全时灵时不灵,注册授权偶尔还要闹脾气。就在那一刻,我决定把这块STM32F4开发板挪到Ubuntu下试试。

这不是矫情,是真的有需求。很多人以为嵌入式开发就必须在Windows上,其实不是。STM32F4这种Cortex-M4芯片,ARM官方和开源社区早就把工具链做得非常顺:交叉编译器用gcc-arm-none-eabi,构建系统用Makefile或CMake,下载调试用OpenOCD配合ST-Link,调试器用GDB,串口监控用minicom、tio或者pyserial。整套流程一点不比MDK差,甚至在某些方面更顺手。

这篇文章就是写给三类人看的:一是刚接触STM32F4、手头正好有Ubuntu或者想换成Ubuntu的学生和爱好者;二是日常用MDK但想试试命令行构建、自动化流程的开发者;三是对“嵌入式开发的另一种打开方式”好奇,但不知道从哪里下手的朋友。我会带着你从安装工具链开始,一直走到烧录一个程序让板子上的LED亮起来。

1.2 换环境之前,先想清楚这三个问题

我见过不少人头脑一热就把Windows换成Ubuntu,然后第二天就后悔了。所以开始之前,先掂量一下自己的情况。

第一个问题:你的板子和调试器,在Linux下有没有成熟的软件支持?绝大多数STM32F4开发板都能用ST-Link或J-Link调试器,而OpenOCD对这两类调试器的支持都非常成熟。只要不是特别冷门的调试器,基本没问题。

第二个问题:你常用的芯片型号,在STM32CubeMX里能不能选到?只要能在CubeMX里选到具体型号,比如STM32F407VET6,它就可以生成Makefile工程,然后直接在Linux下编译。

第三个问题:你愿不愿意接受“命令行的日常”?Ubuntu下当然也可以开VS Code写代码,但编译、烧录、调试这些环节,大概率要敲命令。如果你习惯了点按钮操作,初始几天会有点别扭。但只要熬过第一周,效率提升会非常明显。

我之前在企业里看到一个团队的做法:代码统一放在Git仓库里,每次提交后服务器自动拉代码、跑脚本、交叉编译、生成固件。这台服务器根本没有图形界面,跑的就是Ubuntu加Makefile加gcc-arm-none-eabi。Windows那边的同事想参与这套流程,还得装一堆环境去模仿Linux行为。所以“在Ubuntu下开发STM32F4”不是少数人的奇怪爱好,而是嵌入式开发往自动化、工程化方向发展的必然趋势。

2. Ubuntu下STM32F4开发的整体方案与工具选型

2.1 一套完整的Linux工具链长什么样

先别急着装软件,搞清楚整套流程里每个角色负责什么,后面遇到问题才不会懵。

一个STM32F4项目,从源码到跑在板子上,要经历四个阶段:编译、链接、烧录、调试。对应到工具上,就是交叉编译器、构建系统、调试服务器/烧录工具、GDB客户端。

工具链的核心是arm-none-eabi-gcc。这是一套专门用于ARM Cortex-M这类裸机系统的交叉编译器。注意“交叉”这个词,意思是它在x86的Ubuntu上运行,但生成的目标代码是ARM指令集,不能直接在电脑上执行,必须下载到STM32F4的Flash里才能跑。

编译完一个工程会得到ELF文件,里面包含了代码、数据、调试符号等信息。烧录工具要做的,就是把ELF里的代码和数据写到芯片的Flash里。这里有两个选择:一是OpenOCD加ST-Link,这是完整调试方案;二是st-flash,这是轻量烧录方案,只负责下载,不能调试,但胜在命令简单。

构建系统方面,CubeMX会帮我们生成一个Makefile,里面写好了源文件列表、头文件路径、编译选项、链接脚本等。你可以直接敲make命令完成编译,也可以基于这个Makefile改成CMake工程。新手阶段直接用make就够了,改CMake反而容易分心。

最后是编写代码的“皮”:VS Code、Vim、CLion,甚至纯文本编辑器都行。因为编译不依赖IDE,Ubuntu下的编辑器只要做好代码高亮和语法检查,剩下的活全部交给命令行工具。

2.2 为什么用STM32CubeMX生成Makefile工程

很多人第一次接触CubeMX,是拿来配置时钟树、外设引脚和中间件库,然后生成一个MDK工程。到了Ubuntu下,这个习惯要改一点:CubeMX照用,但工程类型要选Makefile。

CubeMX本质上是帮你做了两件麻烦事。第一件是芯片初始化代码生成。它会根据你在图形界面里勾选的引脚配置,生成SystemClock_Config、GPIO_Init等一堆代码,避免你对着参考手册一行行写寄存器。第二件是工程骨架生成。它会把HAL库、CMSIS、启动文件、链接脚本全部整理好,按固定的目录结构摆放,并且生成对应的Makefile。

为什么不让CubeMX生成MDK工程再想办法转到Linux编译?因为这个过程非常别扭。MDK工程文件里记录的是绝对路径、Windows文件路径和MDK特有的项目配置,拿到Linux下会被make工具搞晕。与其在转换上浪费时间,不如一开始就生成Makefile工程。

还有一个“为什么”值得解释:CubeMX是在Java虚拟机上运行的程序,跨平台支持做得还不错。你在Windows下怎么用,在Ubuntu下就怎么用,需要做的只是给CubeMX安装包添加可执行权限,或者通过命令行启动。

2.3 为什么选择OpenOCD作为烧录调试主力

烧录STM32F4有两种常见思路。第一种是用ST官方提供的STM32CubeProgrammer命令行版本,它功能全面,但讲的是ST自有的那一套生态。第二种是开源社区方向的OpenOCD。

OpenOCD全称是Open On-Chip Debugger,它的核心能力是通过调试器硬件的API,访问芯片内部的调试接口,比如SWD或JTAG。它支持很多调试器硬件,包括ST-Link、J-Link、CMSIS-DAP等,也支持几乎所有ARM Cortex-M芯片。你可以把它理解成一个“翻译官”:你能通过GDB命令告诉它“帮我在0x08000000处写一段数据”,它就通过ST-Link和SWD接口,和芯片内部的调试逻辑沟通,最终把数据写进Flash。

我选择OpenOCD还有一个实际原因:它的调试能力是免费的,而且非常稳定。配合GDB,你可以像在MDK里一样打断点、单步执行、读取寄存器、查看内存,唯一区别是敲命令而不是点按钮。等习惯之后,你会觉得这种“裸奔式”调试对程序运行过程的掌控感,反而更细致。

除了OpenOCD,st-flash这个工具也值得提一嘴。它的目标是做一个极简单的STM32烧录工具,一条命令就能烧录。如果只是为了快速下载固件、不做调试,st-flash更省事。反过来,如果你要排查一个诡异的硬件问题,需要单步调试,OpenOCD加GDB几乎是绕不开的组合。

3. 动手实操:从零到“点灯”的完整路径

3.1 安装工具链:apt能省事,但也要知道备选方案

Ubuntu下装软件的第一反应是apt。这一套工具链里大部分软件都能直接通过apt安装,只是不同Ubuntu版本的软件包名略有差异。推荐你执行下面这一条命令:

sudo apt update sudo apt install -y gcc-arm-none-eabi binutils make openocd gdb-multiarch stlink-tools

如果你用的是较新版本的Ubuntu,可能安装的是gcc-arm-none-eabi这个包;老版本里它叫arm-none-eabi-gcc。不用纠结包名,装完确认版本即可:

arm-none-eabi-gcc --version make --version openocd --version gdb-multiarch --version

如果第一条命令能正常输出版本号,就说明编译器已经就绪。如果提示“command not found”,去下面两个方向排查:一是apt源里没有这个包,二是安装的版本太旧。

有些场景下apt里的工具链版本偏老,比如你想用更新版本的GCC来获得更好的编译优化,或者想用特定版本号保证跟公司服务器一致。这时不建议强行和apt纠缠,直接去ARM官方工具链下载页拿最新版GNU Arm Embedded Toolchain,下载tar.xz包,解压后把bin目录加入PATH即可:

wget <下载地址> tar -xf arm-gnu-toolchain-*.tar.xz sudo mv arm-gnu-toolchain-* /opt/ export PATH=/opt/arm-gnu-toolchain-*/bin:$PATH

把export那行写进~/.bashrc,以后每次打开终端就不用重复设置了。

提示:新手最好先用apt安装。apt会帮你处理大部分库依赖,比如libncurses等。手动解压版虽然新,但遇到缺库的可能性也大一点,排查起来需要多一步功夫。

3.2 CubeMX生成Makefile工程的关键设置

在Ubuntu下运行CubeMX,先把安装包下载下来,解压后进入目录,输入CubeMX或者通过桌面图标启动。启动时如果提示Java缺失,装一个OpenJDK就行了,不同Ubuntu版本对应的openjdk-8、openjdk-11、openjdk-17都行。

新建工程,选择你的芯片型号,比如STM32F407VET6。在“Project Manager”标签页里,有三个地方要特别注意:

第一,“Project Name”填工程名,比如stm32f4_demo。第二,“Toolchain / IDE”这里选择“Makefile”。这一步最关键,它决定了CubeMX最终生成什么类型的工程。第三,“Root Scheme”建议选“Structured”或“Flat”,新手选Flat更简单,生成的目录结构清晰,所有源码都集中在Core和Drivers两个主目录下。

接下来正常配置引脚和时钟。比如让PC13引脚的LED闪烁,就把PC13配置为GPIO_Output,时钟树里把HSE选成外部晶振、SYSCLK设为168MHz。配置完成后点击“Generate Code”,在一个文件夹下就生成了带Makefile的完整工程。

生成的目录结构大致是这样:

stm32f4_demo/ ├── Core/ │ ├── Inc/ │ └── Src/ ├── Drivers/ │ ├── CMSIS/ │ └── STM32F4xx_HAL_Driver/ ├── Makefile └── stm32f4_demo.ioc

CubeMX生成完,打开Makefile看一眼,里面有C_SOURCES、C_INCLUDES、LDSCRIPT这些变量。编译器变量PREFIX默认是arm-none-eabi-,和apt装的工具链前缀完美对齐,也就是说你可以直接编译了。

3.3 用make编译:从第一条命令到ELF文件

进入工程目录:

cd ~/stm32f4_demo make -j4

第一次编译会输出很多信息,大致包括每个.c文件被编译成.o文件的过程,最后一步是链接操作。如果一切顺利,你会看到一个提示,并且目录下多了build文件夹,里面放着stm32f4_demo.elf这样的文件。

如果你看到一个很长的报错,别慌。最常见的原因是头文件路径不对。这时候改Makefile里的C_INCLUDES变量,加上对应路径即可。比如HAL库头文件在Drivers/STM32F4xx_HAL_Driver/Inc,必须在C_INCLUDES里写着-I这个路径和-I这个路径的父目录。

编译时可以加-j4或者-j8开并行编译。我实测过,一个中等规模的F4工程用-singlethread和-j8相比,编译时间能差到3倍以上。特别是你每天改一行代码就想重新编译烧录的场景,并行编译带来的体验提升是实打实的。

整个编译过程,本质上可以拆解成三条逻辑链:

  • 预处理和编译:每个.c文件先被编译器处理成汇编,再汇编成.o目标文件;
  • 链接:链接器把所有的.o文件、启动文件、HAL库库函数、链接脚本等信息合并,生成最终的.elf;
  • 格式转换:烧录工具通常能直接吃.elf,如果后续要做量产固件,可能需要用arm-none-eabi-objcopy转换成.bin或.hex。

3.4 烧录调试:OpenOCD加ST-Link的完整实施细节

实物操作之前,确认好三件事:板子供电正常,ST-Link和电脑的USB连接正常,ST-Link和板子之间的SWD四根线接对。SWDIO、SWCLK、GND是必接的,3.3V供电要不要接取决于你的板子是否单独供电。新手经常犯的错是只接了三根线,但SWDIO和SWCLK碰巧又被杜邦线接反了。我建议焊上去之前,先拿万用表量一下对应引脚的电平。

接线确认无误后,启动OpenOCD。最简单的烧录方式是用一条命令直接完成程序下载:

openocd -f /usr/share/openocd/scripts/board/stm32f4discovery.cfg \ -c "program build/stm32f4_demo.elf verify reset exit"

如果你的开发板不是Discovery板,而是自己画的板子或者通用最小系统板,推荐直接用更通用的配置组合:

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \ -c "program build/stm32f4_demo.elf verify reset exit"

这两条命令的含义是:先用interface/stlink.cfg告诉OpenOCD,我们手里是ST-Link调试器;再用target/stm32f4x.cfg告诉它,目标是STM32F4系列的芯片;最后让它把编译好的ELF文件写入Flash,校验一下,复位芯片,然后退出。

如果你调试的过程中不想退出OpenOCD,可以换一种启动方式:不写program和exit,只写:

openocd -f interface/stlink.cfg -f target/stm32f4x.cfg

这时OpenOCD会驻留在一个终端窗口里,通过3333端口对外提供GDB调试服务。等它稳定输出“target state: halted”或“Info : Listening on port 3333”后,另开一个终端窗口,用GDB连接上去:

gdb-multiarch build/stm32f4_demo.elf (gdb) target remote localhost:3333 (gdb) monitor reset halt (gdb) load (gdb) continue

target remote这行让GDB连接上OpenOCD代理的调试通道。load把程序写进Flash。continue让芯片跑起来。之后你就能用GDB的break、step、info registers等命令做完整的源码级调试。

如果不想用GDB,就想快速下载,可以用st-flash,命令更短:

st-flash --reset write build/stm32f4_demo.bin 0x08000000

这条命令需要你先把ELF转成bin:

arm-none-eabi-objcopy -O binary build/stm32f4_demo.elf build/stm32f4_demo.bin

3.5 顺手配置VS Code和串口工具,让日常开发更顺畅

代码编辑我不建议用CubeMX自带的编辑器,太难受。装VS Code,再装C/C++扩展和Cortex-Debug扩展,就可以直接在VS Code里完成代码编辑和调试操作,但后端仍然调用arm-none-eabi-gcc和OpenOCD。

VS Code里的C/C++扩展要配置一下c_cpp_properties.json,把编译器路径指到arm-none-eabi-gcc,同时把编译器的宏定义、include路径加进去,不然你会看到满屏红色波浪线。其实不配置也能编译,只是编辑体验差。

串口调试方面,安装minicom或者tio:

sudo apt install -y minicom tio

查看设备节点:

ls /dev/ttyUSB* /dev/ttyACM*

出现ttyUSB0或者ttyACM0就是你的串口设备。用tio打开更简单:

sudo tio /dev/ttyUSB0 -b 115200

为什么需要sudo?因为Ubuntu默认对串口设备权限限制很严格,普通用户没有读写权限。你也可以把自己加入dialout组来解决:

sudo usermod -aG dialout $USER

重新登录后就不用每次加sudo了,这对后面脚本化自动化很有帮助。

4. 踩坑实录与排查手册

4.1 编译阶段的高频问题

整个移植过程里,我遇到过最多的问题集中在编译阶段,大概有三类。

第一类:找不到arm-none-eabi-gcc。明明装好了,终端却提示command not found。排查顺序是这样的:先运行ls /usr/bin/arm-none-eabi-gcc,如果文件存在但命令找不到,说明PATH有问题;如果文件不存在,说明上次安装其实没成功。用dpkg确认一下:

dpkg -l | grep gcc-arm-none-eabi

第二类:头文件找不到,最常见的是stm32f4xx_hal_conf.h: No such file or directory。这个文件通常位于Drivers/STM32F4xx_HAL_Driver/Inc目录下,Makefile里的C_INCLUDES变量要指向这个目录,同时指向它的父目录,因为C代码里会以“stm32f4xx_hal_conf.h”这种相对形式去include。另一种常见情况是在你自己的目录里无意中写了include路径,导致编译器去错误地方找头文件。

第三类:链接阶段报错,比如undefined reference to_exit、undefined reference toSystemInit。这类错误几乎都和启动文件或链接脚本缺失有关。STM32F4的启动文件是startup_stm32f40xx.s,在CubeMX生成工程时会被自动加入Makefile。如果你是自己手动建工程又漏了它,就会在链接时报出一长串系统底层符号未定义。解决办法就是把对应型号的启动文件路径加到Makefile的ASM_SOURCES变量里,同时把LDSCRIPT变量指向正确的.ld链接脚本文件。

编译报错最忌讳“从头到尾再看一遍代码”,因为问题经常不在你的代码逻辑,而在工程配置。

4.2 烧录与调试阶段的坑,比编译更隐蔽

烧录时最常见的提示是权限不足:

Error: open failed

这个“Permission denied”很像系统级别的问题,其实只是当前用户对USB调试器没有访问权限。OpenOCD需要直接访问USB设备节点,但普通用户被Linux的USB权限体系挡在外面。最简单的解决办法是给OpenOCD加sudo:

sudo openocd -f interface/stlink.cfg -f target/stm32f4x.cfg

但这治标不治本,每次都要sudo非常影响体验。更好的办法是配置udev规则,让系统在接入ST-Link时自动赋予当前用户访问权限。新建一个规则文件,内容填写ST-Link的vendor ID和product ID,然后执行sudo udevadm control --reload-rules,重新插拔USB设备后,OpenOCD无需sudo也能正常工作。

还有一种情况是OpenOCD能启动,但一直提示target not halted或Error: JTAG-DP STICKY error。这种多半是接线问题,SWDIO和SWCLK交叉接反,或者GND没接,或者板子供电不足导致调试接口电压不稳定。别急着换配置,先量线、换线、重插USB,八成能解决。

调试过程中如果GDB提示localhost:3333: Connection refused,说明OpenOCD还没起来,或者已经异常退出。确认OpenOCD窗口里有没有稳定输出监听端口信息。有时候两个OpenOCD进程会同时占用3333端口,后启动的自然连不上。该挂的进程先杀掉:

sudo pkill openocd

4.3 工程管理的两个实用习惯

当你从Windows切换过来之后,我强烈建议改两个习惯。

第一个习惯是:不要直接改CubeMX生成的Makefile来添加自己的源文件。每当你重新生成代码,CubeMX会重写Makefile,你手工加的那些源文件列表有可能被覆盖。更稳妥的做法是把自己的代码放在CubeMX不会覆盖的独立目录里,然后在Makefile的C_SOURCES变量里追加。即使覆盖,也只是一行歧义,改起来很容易。

第二个习惯是:把整个工程纳入Git管理。CubeMX生成的文件、配置文件.ioc、Makefile、Core目录,建议全提交进去。唯一不要提交的是build目录,因为它是编译产物,每次可能产生几百个文件且在Git里没有意义。在.gitignore里写上build/,你会少很多不必要的版本冲突。

4.4 常见问题速查表

现象可能原因解决思路
arm-none-eabi-gcc: command not found未安装或PATH未配置安装工具链或检查~/.bashrc
找不到stm32f4xx_hal_conf.hMakefile缺少include路径修改C_INCLUDES变量,加上Driver路径
undefined reference to_exit启动文件缺失或链接脚本错误检查Makefile的ASM_SOURCES和LDSCRIPT
OpenOCD open failedUSB权限不足配置udev规则或加sudo
target not halted接线或供电异常重查SWD四线,确认调试器供电稳定
GDB连接refusedOpenOCD未启动或端口被占用确认OpenOCD进程,监听3333端口
烧录成功但没现象时钟配置不正确,或下载地址不对检查CubeMX时钟树,确认烧录地址0x08000000

5. 切换到Ubuntu后,工作流到底变成了什么样

最后聊一点个人体会。以前用MDK的时候,我的开发流程是:打开工程,写代码,点编译,下载,看串口。每一步都靠在图形界面里找按钮。换到Ubuntu之后,我的日常变成:打开终端,写代码,敲make,执行烧录脚本,看串口日志。听起来只是习惯变了,但久而久之,整个人的思维方式也会变。

因为命令行天然适合写脚本,我开始把自己的烧录动作写成一个shell脚本,每次编译完成后自动执行openocd下载,再开启tio查看日志。后来又在脚本里加了编译前自动从Git拉取最新代码、编译失败自动停止、编译成功自动上传固件的逻辑。几行命令就完成了一套本地CI流程,这在Windows下想要实现,得折腾好几个工具才能拼起来。

在Ubuntu下开发STM32F4,最大的收获不是省了某个IDE的授权费,也不是编译速度快了多少,而是让我意识到:嵌入式开发和普通的软件开发并没有那么大的隔阂。交叉编译、调试协议、构建系统这些概念,放到任何一个平台都是相通的。平台只是工具,真正值钱的是你对整个构建和调试链路理解了多少。

如果你手头正好有一块吃灰的STM32F4板子,又有一个装好Ubuntu的电脑,找一个周末,按这个流程走一遍。给自己一天时间折腾工具链,一天时间重新编译点亮第一个LED。踩了坑就查手册,查不到就看看日志,心里烦了就出去透口气。等你第一次在Linux终端里敲下make,看到自己的程序运行在板子上,那种感觉,和当年在Windows下第一次下载成功后是完全不一样的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询