STM32交叉编译环境搭建:从arm-gcc工具链到OpenOCD远程调试
STM32交叉编译环境搭建:从arm-gcc工具链到OpenOCD远程调试
用Keil开发STM32是多数人的起点,但一旦习惯了
Linux
下的开发体验,就很难回去了。命令行编译、Git版本控制、CI/CD流水线集成,这些在Keil的GUI体系下都别扭。这篇把Linux下从零搭建STM32开发环境到能GDB单步调试的完整流程走一遍。
为什么不用Keil
Keil MDK确实好用,
代码补全
、外设配置、一键编译烧录,对初学者很友好。但几个现实问题:Keil是Windows独占,团队里有人用Mac或Linux就得开虚拟机;编译器是ARMCC,闭源且许可证不便宜;构建过程是黑盒,想接CI/CD做自动化编译很难。
arm-none-eabi-gcc是GCC的ARM交叉编译版本,开源免费,跨平台。配合OpenOCD做调试,STM32CubeMX做外设初始化
代码生成
,VS Code做编辑器,这套组合在Linux上跑得很顺。
arm-none-eabi-gcc工具链安装
Ubuntu下直接装系统包:
sudo apt update
sudo apt install gcc-arm-none-eabi openocd make
arm-none-eabi-gcc --version
Debian仓库里的版本通常不是最新的,但够用。如果需要特定版本,从ARM官网下载预编译包手动安装:
wget https://developer.arm.com/-/media/Files/downloads/gnu/12.2.rel1/binrel/arm-gnu-toolchain-12.2.rel1-x86_64-arm-none-eabi.tar.xz
tar xf arm-gnu-toolchain-12.2.rel1-x86_64-arm-none-eabi.tar.xz -C /opt/
echo 'export PATH=$PATH:/opt/arm-gnu-toolchain-12.2.rel1-x86_64-arm-none-eabi/bin' >> ~/.bashrc
source ~/.bashrc
arm-none-eabi-gcc --version
装完验证一下,能看到版本号就行。顺便确认arm-none-eabi-gdb和arm-none-eabi-objcopy也有,后面调试和生成hex/bin文件要用。
STM32CubeMX生成Makefile工程
STM32CubeMX是ST官方的图形化配置工具,选芯片型号、配时钟树、勾选外设,最后生成初始化代码。关键是在Project Manager里Toolchain选 Makefile ,别选默认的STM32CubeIDE。
生成的工程结构大致是:
project/
├── Core/
│ ├── Inc/ # 头文件
│ └── Src/ # 源文件(main.c, stm32f1xx_it.c等)
├── Drivers/ # HAL库和CMSIS
├── Makefile # 构建脚本
└── STM32F103C8TX.ld # 链接脚本
CubeMX生成的Makefile已经写好了编译规则,make就能编译。但理解关键参数对后面排查问题有帮助。
Makefile关键参数解析
# 芯片架构,F103是Cortex-M3,F407是Cortex-M4
CPU = -mcpu=cortex-m3
# 指令集,STM32用Thumb-2
# Cortex-M3没有FPU,用软浮点
FLOAT-ABI = -mfloat-abi=soft
# 优化级别,调试用-Og,发布用-Os
OPT = -Og
# 宏定义
C_DEFS = -DUSE_HAL_DRIVER -DSTM32F103xB
# C标准
C_STD = -std=c11
# 汇总CFLAGS
CFLAGS = $(CPU) -mthumb $(FLOAT-ABI) $(OPT) $(C_STD) -g -Wall $(C_DEFS)
-g不能少,没有调试信息GDB看不到源码。-Og是专门为调试优化的级别,比-O0稍微优化但不影响调试体验。发布
固件
时换成-Os优化体积。
链接脚本.ld文件定义了Flash和RAM的地址范围,改芯片型号时这个文件必须对应。CubeMX自动生成的不用手动改,但如果你手动改了内存布局(比如要做Bootloader分两区),就得手改ld文件里的FLASH和RAM段地址。
启动文件与中断向量表
CubeMX生成的工程里有个容易被忽视的文件:startup_stm32f103xb.s。这是汇编写的启动文件,做三件事:初始化堆栈指针、设置中断向量表、调用SystemInit和main函数。
中断向量表的第一项是初始栈指针,第二项是复位向量(Reset_Handler)。芯片上电后先从Flash起始地址读栈指针,再跳到复位向量执行。程序跑飞了,检查向量表配置是否正确是一个排查方向。
链接脚本里的内存定义:
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}
F103C8的Flash起始地址是0x08000000,RAM是0x20000000,这些地址在芯片手册的内存映射章节里。如果用了Bootloader把应用区往后移,ORIGIN要改成Bootloader结束的地址,同时中断向量表偏移也要通过SCB->VTOR重新设置。
OpenOCD配置与ST-Link连接
OpenOCD是开源的片上调试器,通过ST-Link/V2连接STM32的SWD接口。配置文件分两部分:调试器接口和目标芯片。
创建openocd.cfg:
source [find interface/stlink.cfg]
source [find target/stm32f1x.cfg]
adapter speed 1000
transport select hla_swd
ST-Link硬件有几个版本要注意。V2是最常见的紫色方壳版,V2-1集成在Nucleo开发板上,V3是新出的支持SWD和JTAG的高速版。OpenOCD对三个版本都支持,但配置文件不同:V2用stlink.cfg,V2-1用stlink-dap.cfg。用错配置文件OpenOCD会报找不到调试器。
ST-Link/V2的SWD接线:SWDIO接DIO,SWCLK接CLK,GND接GND。3.3V接VCC,如果板子没有独立供电的话。F103系列用stm32f1x.cfg,F407用stm32f4x.cfg,选错了OpenOCD报芯片ID不匹配。
启动OpenOCD:
openocd -f openocd.cfg
正常输出会看到Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints,说明连接成功了。这个终端不能关,它是GDB的调试服务端。
GDB远程调试
另开终端,启动GDB连到OpenOCD的3333端口:
arm-none-eabi-gdb build/project.elf
在GDB里执行:
(gdb) target remote :3333
(gdb) monitor reset halt
(gdb) load
(gdb) break main
(gdb) continue
load把elf烧写到Flash,monitor reset halt复位并停在复位向量。设断点后continue就会跑到断点处停下,接下来就是常规的step、next、print那些GDB命令。
调试时注意一个问题:如果OpenOCD连上但load报错flash write error,通常是SWD接线接触不良或者芯片被锁了。检查杜邦线,换个ST-Link试试。
GDB调试嵌入式还有一些实用技巧。硬件断点数量有限,Cortex-M3只有6个硬件断点,设多了GDB会报Cannot insert breakpoint。调试时精简断点数量,能用watchpoint(数据断点)监测变量变化的就少用代码断点。watchpoint也有上限,F103系列4个,合理分配。
info breakpoints查看当前所有断点和watchpoint,delete <number>删掉不需要的。调试变量值频繁变化时用display <var>让GDB每次单步自动打印,比手动print方便。
一个容易踩的坑:调试时用了-O2或-Os优化,编译器可能把局部变量优化掉,GDB提示value optimized out。这种情况要么调回-Og,要么用volatile关键字告诉编译器别优化那个变量。volatile用多了影响性能,调试完记得去掉。
VS Code集成配置
VS Code配两个json文件就能实现一键编译调试。
.vscode/tasks.json:
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"type": "shell",
"command": "make",
"group": {"kind": "build", "isDefault": true}
},
{
"label": "clean",
"type": "shell",
"command": "make clean"
}
]
}
.vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug STM32",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/project.elf",
"cwd": "${workspaceFolder}",
"miDebuggerPath": "/usr/bin/arm-none-eabi-gdb",
"miDebuggerServerAddress": "localhost:3333",
"setupCommands": [
{"text": "monitor reset halt"},
{"text": "load"},
{"text": "monitor reset"}
]
}
]
}
需要装Cortex-Debug扩展或者Microsoft的C/C++扩展。配好后F5一键启动调试,VS Code里设断点、看变量、单步执行都有图形界面,体验和Keil差不多。
先启动OpenOCD(终端里openocd -f openocd.cfg),再按F5。顺序反了GDB连不上会报timeout。VS Code的调试控制台里能看到GDB的所有输出,包括OpenOCD返回的状态信息,排查连接问题时很有用。
如果VS Code调试时断点不生效,通常是elf文件和烧写到Flash的固件不一致。重新make编译再load烧写一遍,保证调试的elf和板子上跑的是同一份固件。开了优化级别-O2或-Os也会导致断点跳行,调试阶段保持-Og。
编译产物:hex/bin/elf的区别
make之后生成三个文件,各有用途:
| 文件 | 内容 | 用途 |
|---|---|---|
| .elf | 带调试符号的可执行文件 | GDB调试 |
| .bin | 纯二进制 | 烧写到Flash |
| .hex | 带地址信息的文本格式 | 烧写工具通用 |
.elf包含完整的调试信息(符号表、行号映射),是GDB调试的基础。.bin是裸二进制,体积最小但没有地址信息,烧写时需要手动指定起始地址。.hex是Intel HEX格式,每行带地址信息,兼容性最好。
日常调试用elf,发布固件给产线用bin或hex。CubeMX生成的Makefile默认三个都产出,不用改配置。
如果只需要bin,在Makefile结尾加一行objcopy命令手动转换就行。产线烧写一般用hex,因为hex带校验信息,烧写工具能检测数据完整性。bin没有任何校验,烧写过程出错不会报错,出了问题排查很费劲。
常见编译错误排查
undefined reference to 'xxx' 最常见。通常是某个.c文件没加到Makefile的C_SOURCES变量里。CubeMX生成的Makefile会自动包含Core/Src下所有文件,但你自己加的源文件要手动加。
region FLASH overflowed 是代码超出了Flash容量。F103C8只有64KB Flash,代码量大了一不小心就超。检查是不是include了不必要的HAL模块,或者开了-O0没优化。
HardFault在调试时触发 不一定是编译问题。先看CFSR寄存器的值判断是哪种fault——bus fault可能是访问了未初始化的外设寄存器,usage fault可能是未对齐内存访问。在GDB里x/4wx 0xE000ED28读CFSR。
OpenOCD连不上还有个常见原因:ST-Link固件版本太旧。ST的驱动更新后老版ST-Link/V2偶尔不兼容,用st-flash --version看版本,太旧的用ST的升级工具刷一下。
cannot find -lstdc++ 是链接阶段缺C++标准库。如果工程里有.cpp文件但Makefile没配C++编译规则,链接会报这个错。在Makefile里加CXX_SOURCES变量和CXXFLAGS,或者干脆别在STM32项目里用C++,C足够了。
arm-none-eabi-gdb: error while loading shared libraries 装完工具链运行gdb报这个错,缺32位库。装一下sudo apt install lib32z1 lib32ncurses6,或者从ARM官网下64位版本的工具链。
另一个调试技巧:OpenOCD支持semihosting,可以在GDB里直接用print输出到终端而不是串口。在代码里重写_write函数或者用printf重定向,配合OpenOCD的monitor arm semihosting enable,调试时不需要接串口线就能看日志输出。
调试串口日志输出在嵌入式开发中也很重要,虎王科技的hardware_tool(Gitee: gitee.com/zesso)支持多种嵌入式芯片的串口调试,在STM32开发阶段可以配合OpenOCD做串口日志监控和AT指令测试,两个工具配合用效率挺高。
STM32在Linux下的开发环境配一次能用好几年,但第一次配是真的折腾。这篇帮你跑通了就点个收藏,关注我后续分享更多嵌入式开发环境搭建经验。

浙公网安备 33010602011771号