Skip to content

04 - 编译与烧录

本章目标:掌握 STM32F103C8T6 项目的编译流程和固件烧录方法,能够独立完成从源代码到硬件运行的完整过程。


📋 目录


🔨 构建系统介绍

什么是 CMake?

CMake 是一个跨平台的构建系统生成器。它不直接编译代码,而是生成平台特定的构建文件(如 Makefile),然后由构建工具(如 make)执行实际的编译过程。

为什么选择 CMake?

优势说明
跨平台支持 Linux、Windows、macOS
可扩展易于添加新源文件和库
依赖管理自动处理文件依赖关系
增量编译只重新编译修改过的文件
广泛支持主流 IDE 和 CI/CD 系统都支持

项目构建配置

本项目的 CMakeLists.txt 配置概览:

cmake
# 最低版本要求
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:进入项目目录

bash
# 进入模板项目目录
cd /path/to/ST-Forge/project/0_template

步骤 2:创建构建目录

bash
# 创建 build 目录(用于存放编译产物)
mkdir -p build

# 进入 build 目录
cd build

💡 提示:使用独立的 build 目录可以保持源代码目录整洁,便于清理编译产物。

步骤 3:运行 CMake 配置

bash
# 配置项目(生成 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:执行编译

bash
# 编译项目
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:验证编译结果

bash
# 检查生成的文件
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

完整编译流程(一键执行)

bash
# 进入项目目录
cd /path/to/ST-Forge/project/0_template

# 创建并进入 build 目录,配置并编译
mkdir -p build && cd build && cmake .. && make

📦 输出文件说明

编译完成后,build 目录中会生成以下文件:

文件类型详解

文件格式大小用途
STM32F1.elfELF较大调试符号、反汇编、GDB 调试
STM32F1.binBinary较小直接烧录到 Flash 的二进制文件
STM32F1.map文本中等内存映射、符号地址、段分布

ELF 文件 (.elf)

ELF (Executable and Linkable Format) 是包含调试信息的可执行文件格式。

特点

  • 包含完整的符号表和调试信息
  • 包含段信息(代码段、数据段、BSS 段等)
  • 用于 GDB 调试和反汇编分析

查看 ELF 信息

bash
# 查看文件头信息
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.txt

Binary 文件 (.bin)

Binary 文件 是纯二进制格式,直接对应 Flash 中的原始数据。

特点

  • 不包含任何元数据或调试信息
  • 文件大小等于实际占用的 Flash 空间
  • 直接用于烧录到芯片

生成方式(由 CMakeLists.txt 自动执行):

bash
# 从 ELF 生成 BIN
arm-none-eabi-objcopy -O binary STM32F1.elf STM32F1.bin

Map 文件 (.map)

Map 文件 记录了链接过程中的内存布局信息。

内容包含

  • 所有符号的内存地址
  • 各段(section)的大小和位置
  • 函数和变量的地址映射
  • 内存使用统计

查看 Map 文件

bash
# 查看内存使用摘要
grep -A 20 "Memory Configuration" STM32F1.map

# 查看特定符号地址
grep "main" STM32F1.map

代码大小分析

bash
# 查看各段大小
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:

cmake
# 烧录配置
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烧录后复位并退出
0x08000000Flash 起始地址

烧录步骤

步骤 1:确认硬件连接

bash
# 检查 ST-Link 是否被识别
lsusb | grep -i ST-LINK

# 预期输出:
# Bus 001 Device 005: ID 0483:3748 STMicroelectronics ST-LINK/V2

步骤 2:设置 USB 权限(如需要)

Linux 原生系统

bash
# 如果已配置 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 用户

bash
# 使用项目提供的脚本
cd /path/to/ST-Forge/project/0_template
./chmod_usb.sh

步骤 3:执行烧录

bash
# 在 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:

bash
# 在 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)

如果需要手动执行烧录命令:

bash
# 手动烧录
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"

🔧 常见问题排查

症状

Error: libusb_open() failed with LIBUSB_ERROR_ACCESS
Error: no device found

原因分析

  • USB 权限不足
  • ST-Link 驱动未安装
  • 硬件连接问题

解决方案

bash
# 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 用户额外步骤

bash
# 确保 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

解决方案

bash
# 检查当前用户组
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

解决方案

bash
# 检查编译器是否安装
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 函数
  • 链接脚本配置错误
  • 库文件缺失

解决方案

bash
# 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 子模块未初始化
  • 头文件路径配置错误

解决方案

bash
# 初始化并更新子模块
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 引脚被占用
  • 芯片损坏

解决方案

bash
# 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.

解决方案

bash
# 检查当前 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 配置

解决方案

bash
# 确保在 build 目录中
cd /path/to/ST-Forge/project/0_template/build

# 如果 build 目录不存在,创建并配置
mkdir -p build && cd build && cmake ..

# 然后编译
make

🚀 进阶技巧

增量编译

CMake 支持增量编译,只重新编译修改过的文件:

bash
# 修改源文件后,只需执行
make

# CMake 会自动检测修改并只编译必要的文件

清理编译产物

bash
# 清理所有编译产物
make clean

# 完全清理(包括 CMake 缓存)
rm -rf build

查看详细编译信息

bash
# 显示完整编译命令
make VERBOSE=1

并行编译

bash
# 使用多核并行编译(加速编译)
make -j$(nproc)

查看编译数据库

bash
# CMake 生成的 compile_commands.json 可用于 IDE 和静态分析工具
cat build/compile_commands.json

调试构建

bash
# 查看 CMake 配置信息
cmake --system-information

# 查看编译器标志
make VERBOSE=1 | grep "arm-none-eabi-gcc"

自定义编译选项

如需修改优化级别或添加自定义标志,编辑 CMakeLists.txt:

cmake
# 修改优化级别
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/

🎉 下一步

恭喜您完成编译与烧录的学习!接下来建议:

  1. 实践调试:继续阅读 05_debugging 学习 GDB 调试
  2. 深入理解:阅读 03_code_walkthrough 理解代码逻辑
  3. 修改代码:尝试修改 main.c 并重新编译烧录
  4. 添加功能:尝试添加新的源文件并更新 CMakeLists.txt

📖 参考资料


上一章代码详解 | 下一章调试方法

Built with VitePress