STM32F103 Bootloader编译与烧录全攻略:从源码到3D打印机主板实战

发布时间:2026/7/28 19:24:22
STM32F103 Bootloader编译与烧录全攻略:从源码到3D打印机主板实战 1. 项目缘起为什么我们需要自定义 Bootloader如果你正在玩基于 Klipper 的 3D 打印机尤其是像 Voron 这类 DIY 机型那么对 STM32F103 这颗“神片”一定不陌生。它成本低廉、性能足够是许多主板如 SKR Mini E3、BTT 的诸多型号和独立 MCU 板如 Klipper 的专用“主机伴侣”的核心。然而当你拿到一块全新的、或者被刷成“砖”的 STM32F103 板子时第一件事往往不是刷固件而是先搞定 Bootloader。Bootloader中文常叫“启动程序”或“引导加载程序”是芯片上电后运行的第一段代码。它的核心任务很简单决定接下来要跳转到哪里去执行用户程序。对于 Klipper 来说我们通常通过串口UART来给主板刷写固件这就需要 Bootloader 提供一个“等待接收新固件”的窗口期。原厂芯片出厂时要么没有 Bootloader需要通过 SWD 接口直接烧录要么自带的是通过 USB DFU 或系统存储器启动的 Bootloader这对于我们常用的 3D 打印机主板串口刷机场景并不友好。这就是为什么 Klipper 官方和社区推荐使用像stm32f103 bootloader这样的自定义 Bootloader。它体积小巧通常 8KB专为通过 UART 接收 Klipper 固件而设计刷写成功后主板一上电就会在串口等待几秒钟如果检测到有效的刷机指令流就进入刷机模式否则就直接跳转到已有的 Klipper 固件运行。整个过程无需额外的硬件如 USB 转 TTL 之外的工具非常方便。但问题来了网上的教程往往告诉你“用这个 hex 文件”或者“运行这个make flash命令”。一旦遇到不常见的板子比如引脚定义不同或者 Bootloader 本身需要更新很多人就懵了。知其然更要知其所以然。今天我们就抛开现成的二进制文件从零开始手把手带你理解并编译一个属于你自己的 STM32F103 Bootloader。这不仅能让你在主板“变砖”时从容救砖更能让你深入理解 Klipper 固件加载的底层机制。2. 环境搭建编译工具链与源码获取编译 STM32 的程序你需要一套 ARM 架构的交叉编译工具链。对于 Bootloader 这种相对底层的程序我们通常使用 GNU Arm Embedded Toolchain。2.1 安装 ARM GCC 工具链在 Linux 系统比如运行 Klipper 的树莓派上安装最为方便。打开终端执行以下命令sudo apt update sudo apt install gcc-arm-none-eabi安装完成后可以通过arm-none-eabi-gcc --version来验证。Windows 用户可以去 ARM 官网或 GNU MCU Eclipse 网站下载预编译的工具链并设置好系统环境变量。注意确保安装的版本不要太旧。Bootloader 代码通常用 C 语言编写对编译器版本有一定兼容性要求太旧的编译器可能无法识别某些优化标志或语法。2.2 获取 Bootloader 源代码Klipper 社区维护了一个最常用的 STM32F103 Bootloader 项目它源自 Marlin 的 Bootloader并针对 Klipper 进行了优化。我们将以这个仓库为例。打开终端找一个合适的目录克隆代码仓库git clone https://github.com/kevinOConnor/stm32f103_bootloader.git cd stm32f103_bootloader这个仓库结构很清晰Makefile编译的核心控制文件。src/存放所有 C 源文件和头文件。lib/可能包含一些底层库文件。stm32f103.ld链接器脚本定义了程序在芯片内存中的布局这是最关键的文件之一。进入目录后先别急着编译。我们首先要理解一个核心概念Bootloader 在芯片内存中的位置是固定的它必须避开用户程序即 Klipper 固件的空间。对于 STM32F103常见的配置是将 Bootloader 放在 Flash 的起始位置0x08000000大小通常为 8KB0x2000。这意味着用户程序的起始地址需要偏移 0x2000即从 0x08002000 开始。这个偏移量必须在 Bootloader 和 Klipper 固件的编译配置中保持一致。3. 核心配置详解链接脚本与芯片选项Bootloader 的编译行为几乎完全由Makefile和链接脚本stm32f103.ld控制。理解它们你就能应对各种定制需求。3.1 剖析链接脚本stm32f103.ld用文本编辑器打开stm32f103.ld你会看到类似下面的内容不同版本可能有细微差别MEMORY { rom (rx) : ORIGIN 0x08000000, LENGTH 8K ram (rwx) : ORIGIN 0x20000000, LENGTH 20K } SECTIONS { .text : { *(.vectors) *(.text) *(.rodata) _etext .; } rom .data : { _sdata .; *(.data) _edata .; } ram AT rom .bss : { _sbss .; *(.bss) _ebss .; } ram }我们来拆解一下MEMORY部分定义了芯片的物理内存布局。rom指 Flash 存储器起始地址ORIGIN 0x08000000长度LENGTH 8K。这明确规定了本 Bootloader 最大只能占用 8KB 空间。如果你想增大 Bootloader 功能比如增加网络支持可能需要修改这个值但务必确保不会侵占后续 Klipper 固件的空间。ram指 SRAM起始地址0x20000000长度20K。STM32F103C8T6 只有 20KB RAM这里定义准确。SECTIONS部分定义了如何将程序的不同部分放入上述内存。.text包含代码.text、只读数据.rodata和中断向量表.vectors。它被放入romFlash中。_etext是一个符号标记了代码段的结束地址。.data已初始化的全局变量和静态变量。这里有个关键点 ram AT rom。意思是.data段在运行时位于 RAM ram但其初始值存储在 Flash 中AT rom。Bootloader 启动时需要自己将这部分数据从 Flash 拷贝到 RAM。_sdata和_edata标记了其在 RAM 中的起止地址。.bss未初始化的全局变量和静态变量运行时在 RAM 中启动时需要将其清零。_sbss和_ebss标记了其起止地址。这个脚本是 Bootloader 能在芯片上正确运行的“地图”。编译时链接器会根据这个地图将代码和数据安排到指定位置。3.2 Makefile 中的关键参数打开Makefile重点关注以下几个变量MCU cortex-m3 CFLAGS -Os -g -Wall -Wstrict-prototypes -I. -mthumb -mcpu$(MCU) LDFLAGS -nostartfiles -Tstm32f103.ld -Wl,-Mapout/debug.map -Wl,--crefMCU cortex-m3指定了芯片内核为 Cortex-M3STM32F103 正是此内核。CFLAGS编译选项。-Os优化代码尺寸这对空间紧张的 Bootloader 至关重要。-mthumb -mcpu$(MCU)指定生成 Thumb 指令集的代码针对 Cortex-M3 优化。LDFLAGS链接选项。-nostartfiles不使用标准库的启动文件因为我们有自己的启动代码通常在src/下的startup.c或类似文件中。-Tstm32f103.ld指定使用我们刚才分析的链接脚本。有时你可能需要为不同的硬件修改编译选项。例如如果你的板子使用外部高速晶振HSE可能需要定义宏HSE_VALUE来指定晶振频率通常是 8000000 或 12000000这通常在Makefile的CFLAGS中添加-DHSE_VALUE8000000。4. 编译流程与产物分析环境配置和代码理解清楚后编译就是一行命令的事。在项目根目录下执行make如果一切顺利你会在out/目录下看到几个关键文件out/bootloader.bin二进制文件。这是我们最终要烧录到芯片 Flash 起始地址的文件。它是纯二进制数据不包含地址信息需要通过烧录工具指定烧录地址0x08000000。out/bootloader.hexIntel HEX 格式文件。同样包含程序数据但格式中自带了地址信息一些烧录工具如 STM32CubeProgrammer使用它会更方便。out/bootloader.elfELF 格式文件。包含完整的调试信息、符号表等用于调试文件体积最大。out/debug.map链接映射文件。这个文件非常有用它详细列出了每个函数、变量被链接到了哪个地址占用了多少空间。当你怀疑 Bootloader 太大导致溢出时查看这个文件就能定位是哪个模块占用了过多空间。编译成功后建议先用arm-none-eabi-size out/bootloader.elf命令查看一下生成程序的大小text data bss dec hex filename 3024 20 1572 4616 1208 out/bootloader.elftext代码段大小3024 字节约 3KB。data已初始化数据段大小20 字节。bss未初始化数据段大小1572 字节。dec总计约 4.6KB。这远小于我们为 Bootloader 预留的 8KB Flash说明空间绰绰有余。这个检查习惯能帮你提前避免“程序太大烧不进去”的问题。5. 烧录 Bootloader 的多种方法与实践编译出.bin或.hex文件后下一步就是将其烧录到芯片的 Flash 中。根据你的主板状态有以下几种常见场景和方法。5.1 场景一全新或已“变砖”的芯片无 Bootloader这种情况下芯片的 Flash 是空的或者原有的程序无法运行无法通过串口进行通信。你必须使用SWDSerial Wire Debug接口进行烧录。这是最底层、最可靠的烧录方式。所需工具SWD 调试器/编程器最常见的是 ST-Link V2便宜且兼容性好或者 J-Link功能更强大。连接线需要连接调试器的 SWDIO、SWCLK、GND有时还需要连接NRST复位和3.3V供电线。具体连接方式取决于你的主板。烧录软件OpenOCD开源命令行工具功能强大在 Linux 上尤其方便。STM32CubeProgrammerST 官方图形化工具跨平台。pyOCD基于 Python 的烧录工具。以 OpenOCD 命令行烧录为例首先确保你的 ST-Link 已连接主板和电脑。然后编写一个简单的 OpenOCD 配置文件比如stlink.cfgsource [find interface/stlink.cfg] transport select hla_swd source [find target/stm32f1x.cfg]接着在终端中执行openocd -f stlink.cfg -c program out/bootloader.bin 0x08000000 verify reset exit这条命令会连接 ST-Link 和 STM32F1x 目标。将bootloader.bin文件烧录到地址0x08000000。进行校验verify。复位芯片reset并退出exit。烧录成功后芯片复位新的 Bootloader 就开始运行了。此时你可以尝试通过串口连接看看是否能在上电后的几秒内收到 Bootloader 的提示信息比如CCC字符。5.2 场景二芯片已有可用的 Bootloader如 USB DFU有些主板出厂时可能预刷了通过 USB 进入 DFUDevice Firmware Upgrade模式的 Bootloader。你可以利用这个现有的 Bootloader 来更新成我们编译的 UART Bootloader。操作方法将主板通过 USB 连接到电脑。让主板进入 DFU 模式。方法因板而异可能是按住某个按钮再上电也可能是通过发送特定命令。使用dfu-utilLinux/macOS或 STM32CubeProgrammer 的 DFU 模式将bootloader.bin烧录到0x08000000地址。使用 dfu-util 的命令示例sudo dfu-util -a 0 -s 0x08000000:leave -D out/bootloader.bin参数解释-a 0指定 DFU 交替设置alternate setting通常为 0。-s 0x08000000:leave指定烧录起始地址为0x08000000:leave表示烧录完成后让设备退出 DFU 模式并执行程序。-D指定要下载的文件。5.3 场景三芯片已有同类型 UART Bootloader升级 Bootloader这是最理想的情况。如果你的主板已经运行着一个可以接收串口命令的 Bootloader比如旧版本的stm32f103 bootloader你可以直接通过串口工具向其发送命令让它自己擦除并写入新的 Bootloader 数据。Klipper 的scripts/flash_usb.py脚本或其底层工具bossac针对 SAMD或stm32flash针对 STM32就具备这个能力。但对于 Bootloader 自身的升级需要格外小心因为一旦过程中断电或出错芯片就会“变砖”必须回到场景一用 SWD 救砖。重要警告通过 Bootloader 自身升级 Bootloader 是高风险操作除非有明确文档支持且你清楚每一步在做什么否则不建议新手尝试。更安全的做法是将 Bootloader 视为固件基础设施的一部分刷好一次后就不再变动。需要升级时宁愿使用 SWD 重新烧录。6. 与 Klipper 固件的协同工作与地址对齐Bootloader 刷好了不代表万事大吉。最关键的一步是确保 Klipper 固件知道 Bootloader 的存在并把自己安装到正确的位置。6.1 配置 Klipper 编译时的 Flash 偏移当你为带有 8KB Bootloader 的主板编译 Klipper 固件时必须在make menuconfig中指定正确的偏移量。在 Klipper 源码目录中执行make menuconfig进入微控制器架构选择STMicroelectronics STM32然后选择具体的芯片型号如STM32F103。在接下来的配置页面中找到类似“Bootloader offset”的选项。对于 8KB 的 Bootloader这个值应该设置为8KiB或者0x2000十六进制。这个配置会做两件事修改 Klipper 固件自身的链接脚本使其代码从0x08002000开始存放完美避开 Bootloader 的0x08000000到0x08001FFF区域。在生成的固件二进制文件头部可能包含一个指向0x08002000的跳转向量确保芯片复位后如果 Bootloader 决定跳转能准确地跳到用户程序的入口。6.2 上电启动流程详解让我们梳理一下完整的启动时间线上电/复位芯片硬件强制从0x08000000Flash 起始地址开始执行指令。这里存放的是 Bootloader 的中断向量表其中第一个字是初始栈指针SP第二个字是复位向量Reset_Handler 函数的地址。芯片跳转到Reset_Handler。Bootloader 初始化Bootloader 代码开始执行。它会初始化最基本的系统时钟可能使用内部 RC 振荡器 HSI 以加快启动、GPIO特别是串口引脚和 UART。等待窗口期Bootloader 通过串口发送一个特定字符例如CCC并开始计时进入一个持续数秒通常是 1-3 秒的等待状态监听是否有来自主机的刷机命令例如 Klipper 的make flash命令通过串口发送的特殊指令流。决策与跳转如果收到有效刷机命令Bootloader 进入刷机模式通过串口接收新的用户程序数据将其写入 Flash 中从0x08002000开始的区域。完成后通常会执行一次软复位重新从步骤1开始。如果超时未收到命令Bootloader 认为用户不想刷机于是执行跳转。它从0x08002000地址读取用户程序的中断向量表找到用户程序的复位向量然后使用汇编指令如bx跳转到那个地址。此后控制权完全交给 Klipper 固件。Klipper 运行Klipper 固件开始执行初始化所有外设步进电机驱动、温度传感器、加热棒等并等待来自 Klipper 主机树莓派的指令。这个过程清晰说明了 Bootloader 和应用程序如何“无缝”衔接。地址对齐是这一切能正常工作的基石。7. 常见问题排查与实战心得即使按照教程操作你也可能会遇到一些问题。这里分享一些我踩过的坑和排查思路。7.1 问题编译成功但烧录后串口无任何输出排查步骤检查硬件连接这是最容易被忽略的。确认 USB 转 TTL 工具的 TX、RX 线与主板是否正确交叉连接工具的 TX 接主板的 RX工具的 RX 接主板的 TXGND 是否共地。确认波特率是否匹配Bootloader 常用 115200 或 250000。检查 Bootloader 串口引脚配置打开src/目录下的源码如serial.c或config.h查看USART1或USART2具体映射到哪两个 GPIO 引脚例如 PA9/PA10 或 PA2/PA3。你的主板设计可能使用了不同的引脚。这是自定义 Bootloader 最常见的原因。你需要根据原理图修改源码中的引脚宏定义并重新编译。检查时钟源配置Bootloader 为了快速启动通常使用内部高速时钟HSI8MHz。但有些主板设计依赖外部晶振HSE。如果 Bootloader 代码默认使用 HSI而你的主板外部晶振电路有问题或者 Bootloader 配置错误地尝试使用 HSE 但失败了会导致系统时钟无法启动串口自然无法工作。检查system_stm32f1xx.c或类似时钟初始化文件中的相关宏定义。使用调试器验证如果条件允许用 ST-Link 通过 SWD 接口连接芯片用 GDB 进行单步调试。看程序是否能运行到串口初始化的位置。这是最直接的排查手段。7.2 问题能进入 Bootloader 模式但刷写 Klipper 固件失败排查步骤确认 Flash 偏移量再次核对 Klipper 固件编译时设置的Bootloader offset是否与 Bootloader 实际占用的大小一致8KB Bootloader 对应 0x2000 偏移。不一致会导致 Bootloader 试图将固件写到错误的位置可能覆盖自身或写到了未定义的 Flash 区域。检查 Flash 擦写函数Bootloader 中的 Flash 编程和擦除函数是针对 STM32F103 特定 Flash 页大小通常 1KB 或 2KB编写的。如果函数有 bug或者解锁 FlashFLASH_Unlock和加锁FLASH_Lock的流程不对会导致写入失败。可以尝试在 Bootloader 代码中增加一些调试输出打印 Flash 操作的状态寄存器FLASH-SR值。电源稳定性在刷写过程中尤其是通过 USB 供电时电压波动可能导致 Flash 写入错误。尝试使用更稳定的电源为板子供电。降低波特率尝试在刷机时使用更低的波特率如 115200 而不是 250000高波特率在长线或干扰环境下更容易出错。7.3 实战心得如何为一块新板子定制 Bootloader假设你拿到一块全新的 STM32F103 板子需要为其适配 Bootloader可以遵循以下流程获取原理图找到板子的原理图 PDF这是所有工作的基础。确定串口引脚找到计划用于刷机的 UART 引脚通常是 USART1。记下其对应的 GPIO例如PA9(TX) 和PA10(RX)。修改源码在 Bootloader 源码的配置头文件如config.h中找到UARTx_TX_PIN和UARTx_RX_PIN的定义将其修改为你的 GPIO 编号。同时检查对应的时钟使能宏如__HAL_RCC_USART1_CLK_ENABLE和__HAL_RCC_GPIOA_CLK_ENABLE是否正确。确定启动模式引脚STM32 的启动模式由BOOT0和BOOT1引脚决定。大多数 3D 打印机主板将其硬件拉低设置为从主 Flash 启动即运行我们的 Bootloader。确保你的原理图中这两个引脚状态正确通常BOOT0通过电阻接地。编译与测试修改后编译通过 SWD 烧录到板子上。用串口工具监听看上电瞬间是否有输出。如果没有返回步骤 2 和 3 仔细检查并用调试器辅助。验证跳转功能先烧录一个已知正确的、设置了正确偏移量的 Klipper 固件。然后给板子上电不进行任何串口操作看 Bootloader 超时后是否能成功跳转并启动 Klipper表现为加热头开始回温、电机上电等。这个过程需要耐心和细致的调试但成功一次后你对整个系统的理解会深刻得多。自定义 Bootloader 不仅仅是刷一个文件更是你完全掌控硬件的第一步。当你能够根据原理图修改并编译出适配特定硬件的 Bootloader 时就意味着你不再受限于预编译的二进制文件真正具备了修复和改造底层固件的能力。这种能力在调试复杂的硬件问题或进行深度定制时是无价的。