04 - 编译与烧录
本章目标:掌握 STM32F103C8T6 项目的编译流程和固件烧录方法,能够独立完成从源代码到硬件运行的完整过程。
📋 目录
🔨 构建系统介绍
什么是 CMake?
CMake 是一个跨平台的构建系统生成器。它不直接编译代码,而是生成平台特定的构建文件(如 Makefile),然后由构建工具(如 make)执行实际的编译过程。
为什么选择 CMake?
| 优势 | 说明 |
|---|---|
| 跨平台 | 支持 Linux、Windows、macOS |
| 可扩展 | 易于添加新源文件和库 |
| 依赖管理 | 自动处理文件依赖关系 |
| 增量编译 | 只重新编译修改过的文件 |
| 广泛支持 | 主流 IDE 和 CI/CD 系统都支持 |
项目构建配置
本项目的 CMakeLists.txt 配置概览:
# 最低版本要求
cmake_minimum_required(VERSION 3.16)
# 设置交叉编译环境
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR ARM)
# 指定编译器
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)
# 项目定义
project(STM32F1 VERSION 0.0.1 LANGUAGES C ASM)
# 编译选项
add_compile_options(
-mcpu=cortex-m3 # 目标处理器
-mthumb # Thumb 指令集
-O2 # 优化级别
-Wall -Wextra # 警告选项
-ffunction-sections # 函数分段
-fdata-sections # 数据分段
-DUSE_HAL_DRIVER # 启用 HAL 库
-DSTM32F103xB # 芯片型号
)
# 链接选项
target_link_options(STM32F1.elf PRIVATE
-mcpu=cortex-m3
-mthumb
-flto # 链接时优化
-T...FLASH.ld # 链接脚本
-nostartfiles
-specs=nano.specs # 精简 C 库
-specs=nosys.specs # 无系统调用
-Wl,--gc-sections # 垃圾回收未使用段
-Wl,-Map=STM32F1.map # 生成映射文件
)构建目标
| 目标 | 命令 | 说明 |
|---|---|---|
| 默认目标 | make | 编译生成 .elf 和 .bin 文件 |
| 烧录 | make flash | 通过 OpenOCD 烧录固件 |
| 擦除 | make erase | 擦除芯片 Flash |
🏗️ 编译步骤详解
步骤概览
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 创建 build │ -> │ cmake 配置 │ -> │ make 编译 │ -> │ 输出文件 │
│ 目录 │ │ 项目 │ │ 项目 │ │ .elf/.bin │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘步骤 1:进入项目目录
# 进入模板项目目录
cd /path/to/ST-Forge/project/0_template步骤 2:创建构建目录
# 创建 build 目录(用于存放编译产物)
mkdir -p build
# 进入 build 目录
cd build💡 提示:使用独立的 build 目录可以保持源代码目录整洁,便于清理编译产物。
步骤 3:运行 CMake 配置
# 配置项目(生成 Makefile)
cmake ..预期输出:
-- The C compiler identification is GNU 10.3.1
-- The ASM compiler identification is GNU 10.3.1
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/arm-none-eabi-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting ASM compile features
-- Detecting ASM compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /path/to/ST-Forge/project/0_template/build配置过程说明:
| 阶段 | 说明 |
|---|---|
| 编译器检测 | CMake 检测 arm-none-eabi-gcc 编译器 |
| 特性探测 | 确定编译器支持的语言特性 |
| 配置生成 | 生成 Makefile 和其他构建文件 |
步骤 4:执行编译
# 编译项目
make预期输出:
[ 5%] Building C object CMakeFiles/STM32F1.elf.dir/main.c.obj
[ 11%] Building C object CMakeFiles/STM32F1.elf.dir/system.c.obj
[ 17%] Building C object CMakeFiles/STM32F1.elf.dir/__/third_party/STM32CubeF1/Drivers/CMSIS/Device/ST/STM32F1xx/Source/Templates/system_stm32f1xx.c.obj
[ 23%] Building ASM object CMakeFiles/STM32F1.elf.dir/__/third_party/STM32CubeF1/Drivers/CMSIS/Device/ST/STM32F1xx/Source/Templates/gcc/startup_stm32f103xb.s.obj
[ 29%] Building C object CMakeFiles/STM32F1.elf.dir/__/third_party/STM32CubeF1/Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal.c.obj
...
[ 88%] Building C object CMakeFiles/STM32F1.elf.dir/__/third_party/STM32CubeF1/Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_uart.c.obj
[ 94%] Linking C executable STM32F1.elf
Generating STM32F1.bin
text data bss dec hex filename
1234 108 1024 2366 93e STM32F1.elf
[100%] Built target STM32F1.elf步骤 5:验证编译结果
# 检查生成的文件
ls -lh STM32F1.*预期输出:
-rwxr-xr-x 1 user user 12K Mar 30 10:00 STM32F1.bin
-rwxr-xr-x 1 user user 45K Mar 30 10:00 STM32F1.elf
-rw-r--r-- 1 user user 23K Mar 30 10:00 STM32F1.map完整编译流程(一键执行)
# 进入项目目录
cd /path/to/ST-Forge/project/0_template
# 创建并进入 build 目录,配置并编译
mkdir -p build && cd build && cmake .. && make📦 输出文件说明
编译完成后,build 目录中会生成以下文件:
文件类型详解
| 文件 | 格式 | 大小 | 用途 |
|---|---|---|---|
| STM32F1.elf | ELF | 较大 | 调试符号、反汇编、GDB 调试 |
| STM32F1.bin | Binary | 较小 | 直接烧录到 Flash 的二进制文件 |
| STM32F1.map | 文本 | 中等 | 内存映射、符号地址、段分布 |
ELF 文件 (.elf)
ELF (Executable and Linkable Format) 是包含调试信息的可执行文件格式。
特点:
- 包含完整的符号表和调试信息
- 包含段信息(代码段、数据段、BSS 段等)
- 用于 GDB 调试和反汇编分析
查看 ELF 信息:
# 查看文件头信息
arm-none-eabi-readelf -h STM32F1.elf
# 查看段信息
arm-none-eabi-readelf -S STM32F1.elf
# 查看符号表
arm-none-eabi-nm STM32F1.elf
# 反汇编
arm-none-eabi-objdump -d STM32F1.elf > disassembly.txtBinary 文件 (.bin)
Binary 文件 是纯二进制格式,直接对应 Flash 中的原始数据。
特点:
- 不包含任何元数据或调试信息
- 文件大小等于实际占用的 Flash 空间
- 直接用于烧录到芯片
生成方式(由 CMakeLists.txt 自动执行):
# 从 ELF 生成 BIN
arm-none-eabi-objcopy -O binary STM32F1.elf STM32F1.binMap 文件 (.map)
Map 文件 记录了链接过程中的内存布局信息。
内容包含:
- 所有符号的内存地址
- 各段(section)的大小和位置
- 函数和变量的地址映射
- 内存使用统计
查看 Map 文件:
# 查看内存使用摘要
grep -A 20 "Memory Configuration" STM32F1.map
# 查看特定符号地址
grep "main" STM32F1.map代码大小分析
# 查看各段大小
arm-none-eabi-size --format=berkeley STM32F1.elf输出解读:
text data bss dec hex filename
1234 108 1024 2366 93e STM32F1.elf| 段 | 说明 | 存储位置 |
|---|---|---|
| text | 代码段(只读) | Flash |
| data | 已初始化数据段 | Flash(初始值)+ RAM(运行时) |
| bss | 未初始化数据段 | RAM |
| dec | 十进制总大小 | - |
Flash 占用 = text + data
RAM 占用 = data + bss
🔥 烧录步骤详解
硬件连接
在烧录之前,请确保 ST-Link 与 Blue Pill 正确连接:
ST-Link V2 Blue Pill (STM32F103C8T6)
┌─────────┐ ┌──────────────┐
│ SWDIO │ ─────── │ SWDIO (PA13) │
│ SWCLK │ ─────── │ SWCLK (PA14) │
│ GND │ ─────── │ GND │
│ 3.3V │ ─────── │ 3.3V (可选) │
└─────────┘ └──────────────┘⚠️ 注意:
- 3.3V 引脚仅在开发板未通过 USB 供电时连接
- 确保 Blue Pill 的 BOOT0 跳线设置为 0(正常启动模式)
OpenOCD 配置
本项目使用 OpenOCD 作为烧录工具,配置文件位于 CMakeLists.txt:
# 烧录配置
add_custom_target(flash
COMMAND openocd
-f interface/stlink.cfg # ST-Link 接口配置
-f target/stm32f1x.cfg # STM32F1 目标配置
-c "program STM32F1.bin verify reset exit 0x08000000"
DEPENDS STM32F1.elf
COMMENT "Flashing STM32F1.bin via OpenOCD"
)配置说明:
| 参数 | 说明 |
|---|---|
-f interface/stlink.cfg | 使用 ST-Link 调试器 |
-f target/stm32f1x.cfg | 目标芯片为 STM32F1 系列 |
program ... verify | 烧录并验证 |
reset exit | 烧录后复位并退出 |
0x08000000 | Flash 起始地址 |
烧录步骤
步骤 1:确认硬件连接
# 检查 ST-Link 是否被识别
lsusb | grep -i ST-LINK
# 预期输出:
# Bus 001 Device 005: ID 0483:3748 STMicroelectronics ST-LINK/V2步骤 2:设置 USB 权限(如需要)
Linux 原生系统:
# 如果已配置 udev 规则,跳过此步骤
# 否则临时设置权限:
sudo chmod 666 /dev/bus/usb/$(lsusb | grep -i "ST-LINK" | awk '{print $2}')/$(lsusb | grep -i "ST-LINK" | awk '{print $4}' | sed 's/://')WSL 用户:
# 使用项目提供的脚本
cd /path/to/ST-Forge/project/0_template
./chmod_usb.sh步骤 3:执行烧录
# 在 build 目录中执行
make flash预期输出:
Open On-Chip Debugger 0.10.0
Licensed under GNU GPL v2
...
Info : clock speed 1000 kHz
Info : STLINK V2J14S0 (API v2) VID:PID 0483:3748
Info : Target voltage: 3.2V
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32f1x.cpu on 3333
...
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x08000134
** Programming Started **
** Programming Finished **
** Verify Started **
** Verified OK **步骤 4:验证烧录结果
烧录成功后,开发板会自动复位运行程序。观察开发板上的 LED 或串口输出验证程序是否正常运行。
擦除芯片
如需完全擦除芯片 Flash:
# 在 build 目录中执行
make erase预期输出:
Open On-Chip Debugger 0.10.0
...
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
target halted due to debug-request
stm32f1x mass erase complete手动烧录(不使用 make)
如果需要手动执行烧录命令:
# 手动烧录
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \
-c "program STM32F1.bin verify reset exit 0x08000000"
# 手动擦除
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \
-c "init; halt; stm32f1x mass_erase 0; exit"🔧 常见问题排查
问题 1:ST-Link 未识别
症状:
Error: libusb_open() failed with LIBUSB_ERROR_ACCESS
Error: no device found原因分析:
- USB 权限不足
- ST-Link 驱动未安装
- 硬件连接问题
解决方案:
# 1. 检查设备是否连接
lsusb | grep -i ST-LINK
# 如果能看到设备但无法访问,配置权限:
# 方法一:配置 udev 规则(推荐,永久生效)
sudo tee /etc/udev/rules.d/49-stlinkv2.rules > /dev/null << 'EOF'
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="3748", MODE:="0666"
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", MODE:="0666"
EOF
sudo udevadm control --reload-rules
sudo udevadm trigger
# 方法二:临时设置权限(每次重新插拔后需重新执行)
sudo chmod 666 /dev/bus/usb/$(lsusb | grep -i "ST-LINK" | awk '{print $2}')/$(lsusb | grep -i "ST-LINK" | awk '{print $4}' | sed 's/://')
# 重新插拔设备后重试WSL 用户额外步骤:
# 确保 Windows 端已附加 USB 设备
# 在 Windows PowerShell(管理员)中执行:
usbipd wsl list
usbipd wsl attach --busid <BUSID>
# 在 WSL 中设置权限
./chmod_usb.sh问题 2:端口权限问题
症状:
Error: cannot open /dev/bus/usb/001/005: Permission denied解决方案:
# 检查当前用户组
groups
# 将用户添加到 plugdev 组(如果存在)
sudo usermod -aG plugdev $USER
# 注销并重新登录使更改生效
# 或者使用 udev 规则(推荐)
# 参见问题 1 的解决方案问题 3:编译器找不到
症状:
CMake Error: CMAKE_C_COMPILER not found
/usr/bin/arm-none-eabi-gcc: command not found解决方案:
# 检查编译器是否安装
which arm-none-eabi-gcc
# 如果未安装
sudo apt install -y gcc-arm-none-eabi
# 验证安装
arm-none-eabi-gcc --version问题 4:链接错误
症状:
undefined reference to `main'
cannot find -lc原因分析:
- 缺少 main 函数
- 链接脚本配置错误
- 库文件缺失
解决方案:
# 1. 确保 main.c 中有 main 函数
grep "int main" main.c
# 2. 检查链接脚本是否存在
ls -la STM32F103C8TX_FLASH.ld
# 3. 清理并重新编译
rm -rf build
mkdir build && cd build && cmake .. && make问题 5:找不到头文件
症状:
fatal error: stm32f1xx_hal.h: No such file or directory原因分析:
- third_party 子模块未初始化
- 头文件路径配置错误
解决方案:
# 初始化并更新子模块
cd /path/to/ST-Forge
git submodule update --init --recursive
# 检查 HAL 库是否存在
ls third_party/STM32CubeF1/Drivers/STM32F1xx_HAL_Driver/Inc/
# 重新配置并编译
cd project/0_template
rm -rf build
mkdir build && cd build && cmake .. && make问题 6:烧录失败 - 目标未响应
症状:
Error: target not halted
Error: flash programming failed原因分析:
- 芯片处于低功耗模式
- SWD 引脚被占用
- 芯片损坏
解决方案:
# 1. 尝试连接时复位
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \
-c "init; reset halt; flash write_image erase STM32F1.bin 0x08000000; verify_image STM32F1.bin 0x08000000; reset run; shutdown"
# 2. 检查 BOOT0 跳线位置
# 确保 BOOT0 = 0(连接到 GND)
# 3. 尝试降低 SWD 时钟速度
# 在 OpenOCD 配置中添加:
# adapter speed 1000问题 7:CMake 配置错误
症状:
CMake Error at CMakeLists.txt:1 (cmake_minimum_required):
CMake 3.16 or higher is required.解决方案:
# 检查当前 CMake 版本
cmake --version
# 如果版本过低,安装新版本
# Ubuntu 20.04+ 通常自带 3.16+
# 方法一:使用 Kitware 官方源
wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | gpg --dearmor - | sudo tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
sudo apt update
sudo apt install -y cmake
# 方法二:使用 pip
pip install cmake --upgrade
# 方法三:使用 snap
sudo snap install cmake --classic问题 8:make 命令失败
症状:
make: *** No targets specified and no makefile found. Stop.原因分析:
- 未在 build 目录中执行
- 未运行 cmake 配置
解决方案:
# 确保在 build 目录中
cd /path/to/ST-Forge/project/0_template/build
# 如果 build 目录不存在,创建并配置
mkdir -p build && cd build && cmake ..
# 然后编译
make🚀 进阶技巧
增量编译
CMake 支持增量编译,只重新编译修改过的文件:
# 修改源文件后,只需执行
make
# CMake 会自动检测修改并只编译必要的文件清理编译产物
# 清理所有编译产物
make clean
# 完全清理(包括 CMake 缓存)
rm -rf build查看详细编译信息
# 显示完整编译命令
make VERBOSE=1并行编译
# 使用多核并行编译(加速编译)
make -j$(nproc)查看编译数据库
# CMake 生成的 compile_commands.json 可用于 IDE 和静态分析工具
cat build/compile_commands.json调试构建
# 查看 CMake 配置信息
cmake --system-information
# 查看编译器标志
make VERBOSE=1 | grep "arm-none-eabi-gcc"自定义编译选项
如需修改优化级别或添加自定义标志,编辑 CMakeLists.txt:
# 修改优化级别
add_compile_options(-O0) # 无优化(调试用)
add_compile_options(-O1) # 基本优化
add_compile_options(-O2) # 标准优化(默认)
add_compile_options(-O3) # 最大优化
add_compile_options(-Os) # 优化大小
# 添加调试符号
add_compile_options(-g)
# 添加自定义定义
add_compile_options(-DDEBUG)
add_compile_options(-DMY_DEFINE=123)📊 编译流程总结
┌─────────────────────────────────────────────────────────────────────────┐
│ 编译流程完整图 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 源文件 (.c, .s) │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 预处理 │ -> │ 编译 │ -> │ 汇编 │ │
│ │ (cpp) │ │ (gcc) │ │ (as) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ 预处理文件 (.i) 汇编文件 (.s) 目标文件 (.o) │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 链接 │ │
│ │ (ld) │ │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ELF 文件 (.elf) │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 转换 │ │
│ │ (objcopy) │ │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ BIN 文件 (.bin) │
│ │
└─────────────────────────────────────────────────────────────────────────┘📝 快速参考
常用命令速查
| 操作 | 命令 |
|---|---|
| 配置项目 | cd build && cmake .. |
| 编译项目 | make |
| 清理编译产物 | make clean |
| 烧录固件 | make flash |
| 擦除芯片 | make erase |
| 查看文件大小 | arm-none-eabi-size STM32F1.elf |
| 查看符号表 | arm-none-eabi-nm STM32F1.elf |
| 反汇编 | arm-none-eabi-objdump -d STM32F1.elf |
文件位置
| 文件 | 位置 |
|---|---|
| 源代码 | project/0_template/*.c |
| 构建配置 | project/0_template/CMakeLists.txt |
| 链接脚本 | project/0_template/STM32F103C8TX_FLASH.ld |
| 编译产物 | project/0_template/build/ |
🎉 下一步
恭喜您完成编译与烧录的学习!接下来建议:
- 实践调试:继续阅读 05_debugging 学习 GDB 调试
- 深入理解:阅读 03_code_walkthrough 理解代码逻辑
- 修改代码:尝试修改 main.c 并重新编译烧录
- 添加功能:尝试添加新的源文件并更新 CMakeLists.txt