从STM32无缝迁到GD32F103这事儿,圈里传了很久,有人说“直接烧,啥都不用改”,也有人说“坑多到想砸板子”。我自己的看法是:两者同属 Cortex-M3 内核,外设寄存器大部分兼容,整体迁移成本确实低,但“无缝”俩字得加个引号。真正的门槛不在代码,而在烧录和启动这一层——偏偏网上教程多半只讲“Keil 里选个芯片型号就完事”,把 ST-Link 在 GD32 上遇到的 BOOT0 相关怪问题一笔带过。这篇我就把从接线、驱动、MDK 配置到算法下载、Flash 锁死、BOOT0 各种坑的完整流程拆开讲,保证你照着走一遍就能把程序烧进去,顺带搞明白背后到底是怎么回事。
这套方案适合谁?手上有 STM32F103 老项目想平移到国产替代方案的工程师,实验室里买了 GD32F103 核心板但被烧录卡住的学生,以及纯粹想搞明白 BOOT0 到底是什么、为什么有时要拉高有时要拉低的好奇派。不绕弯子,直接上干货。
1. 迁移前的核心认知:为什么 GD32F103 能“无缝”接 STM32 的班
1.1 内核同源,指令集天然兼容
先放下烧录的事,说说本质。STM32F103 和 GD32F103 用的都是 ARM Cortex-M3 内核,这意味着两者执行 ARMv7-M 指令集,机器码层面完全互通。你为 STM32F103 编译出来的 .hex 或 .bin 文件,GD32F103 的 CPU 可以直接取指执行,不需要重新编译,也不需要做指令翻译。这是“无缝”二字的物理基础,也是整个迁移故事最硬核的底气。
但内核一样不代表整颗芯片一样。GD32F103 由国内厂商兆易创新设计,虽然 pin-to-pin 兼容 STM32F103(也就是引脚定义基本对齐),但内部外设(USART、SPI、I2C、定时器等)的寄存器布局并非 100% 复制,而是“高度相似 + 少量差异”。相似到什么程度呢?大多数情况下,ST 的标准外设库代码可以原样编译运行;差异到什么程度呢?某些外设的时钟分频系数、某些寄存器的默认值、某些标志位的清除方式,会有细微不同。
所以聪明人的做法是:先平行烧录验证硬件,再逐个外设做回归测试,而不是天真的以为“烧进去能跑就万事大吉”。烧录只是第一步,但第一步都走不顺,后面全是空中楼阁。
1.2 FLM 烧录算法:ST-Link 能烧 GD32 的关键黑盒
接下来的问题是:ST-Link 明明是意法半导体的调试器,凭什么能给 GD32 烧程序?答案藏在 Keil MDK 的“烧录算法”(Flash Loader Algorithm,后缀 .FLM)机制里。
Cortex-M 内核的芯片,内部 Flash 编程逻辑并不直接暴露给调试器。调试器只是通过 SWD(Serial Wire Debug)或 JTAG 接口,对一个叫“Flash 控制器”的外设寄存器做读写。而不同厂商的 Flash 控制器寄存器地址、操作时序、解锁密钥都不一样,所以调试器必须配合一个特定于芯片的 .FLM 文件,才知道“擦除整个扇区时该往哪个地址写什么值”“编程一页数据时要不要先发解锁命令”。
Keil MDK 的通用流程是:点击 Download 按钮后,MDK 先把 FLM 算法加载到芯片的 SRAM 中,然后通过调试器控制 CPU 执行这个算法,从而完成擦除、编程、校验。也就是说,只要某个 FLM 文件适配 GD32 的 Flash 控制器,ST-Link 就能烧 GD32。这就是整个流程跑得通的核心机制。
而 GD32F103 之所以能复用 STM32F103 的 FLM,是因为——至少在设计意图上——GD 的 Flash 控制器很大程度上参考了 ST 的实现。但这不等于 100% 保险,后面我会讲我在实践中遇到的一个 Flash 校验失败的坑,就是从这里来的。
1.3 迁移前的准备清单
动手之前,先把家伙备齐,避免烧到一半找东西。
| 物料 | 型号/规格 | 作用与说明 |
|---|---|---|
| 目标板 | GD32F103C8T6 最小系统板或自制板 | 建议先用核心板验证流程,别直接上产品板 |
| 调试器 | ST-Link V2 或 V2 Clone | 淘宝几十块的克隆版也能用,但驱动兼容性看人品 |
| 杜邦线 | 母对母 4 根以上 | 连接 SWDIO、SWCLK、GND、3.3V |
| 开发环境 | Keil MDK 5.x(建议 5.30+) | 项目编译和烧录主战场 |
| 驱动 | ST-Link 驱动(安装 MDK 时可选装) | 装不上驱动后面全卡住 |
| 辅助工具 | STM32 ST-LINK Utility / STM32CubeProgrammer | 读保护、整片擦除、查看选项字节时用得上 |
顺带提醒一句,如果你用 STM32CubeProgrammer(简称 STM32CubeProg)烧 GD32,它自带的 STM32 系列 FLM 列表里本来就有 STM32F10x 的算法,但能不能覆盖 GD32 的内部 Flash,取决于 GD32 的 Flash 控制器的兼容程度。实测下来,ST-Link + Keil MDK 的组合兼容性最好,这也是为什么我强烈推荐用这条路线,而不是 J-Flash 或者 CubeProgrammer 单独硬上。
2. 烧录环境搭建与 ST-Link 接线实操
2.1 接线方式:四条线的事,别搞复杂
STM32F103 和 GD32F103 都支持 SWD 和 JTAG 两种调试接口。JTAG 要占用 5 个引脚,而 SWD 只要 2 根信号线(SWDIO、SWCLK)加电源和地,省引脚、省线序,绝大多数场景用 SWD 就够了。
标准接法如下:
ST-Link V2 GD32F103 目标板 3.3V ---------- 3.3V (VDD) GND ---------- GND SWDIO ---------- PA13 (SWDIO) SWCLK ---------- PA14 (SWCLK)注意几个要点:
- 供电策略:如果你的目标板上有自己的稳压电路(比如 AMS1117 从 5V 转 3.3V),最好用目标板自供电,ST-Link 只接 GND、SWDIO、SWCLK 三根线。如果目标板是裸的最小系统,没有板载稳压器,可以让 ST-Link 的 3.3V 输出给它供电,但要确认板子总电流不大(ST-Link V2 的 3.3V 输出能力一般只有 50mA 到 100mA 左右,带个最小系统没问题,带带 LCD 背光、Wi-Fi 模块就吃力了)。
- 线长控制:SWD 的时钟频率通常跑 4MHz 到 8MHz,杜邦线超过 20cm 就容易出现信号反射,导致连接不稳定。我习惯把 SWD 线控制在 10cm 以内,这是吸取了当年在飞线上踩信号的教训。
- 勿热插拔:烧录最好在断电状态下完成接线,再上电。热插拔有可能导致 SWD 引脚电平冲突,轻则烧录失败,重则损坏调试器 IO。
2.2 驱动安装与设备管理器验证
ST-Link 插上电脑后,正常会弹出驱动安装提示。如果设备管理器里出现“STM32 STLink dongle”或者“STM32 ST-LINK”相关设备,说明驱动正常。如果出现黄色感叹号,手动装一下 Keil MDK 安装目录里的驱动即可。
顺带说一句,淘宝便宜 ST-Link V2 的驱动兼容性是个大坑。有些山寨版在 Win10/Win11 下会识别成“Unknown Device”,怎么装驱动都没用。我的建议:如果你要长期在 GD32 上调试,买正版 ST-Link/V2 或者国产的 DAP-Link(CMSIS-DAP 协议),后者在 Keil 里同样是免驱即用,而且便宜又稳,烧 GD32 也是一把好手。对,你没看错,DAP-Link 也能烧,只要 Keil 里能识别调试器,走的都是 FLM 算法烧录这条路。
2.3 Keil MDK 工程配置要点
打开你的 Keil MDK 工程,按下图思路逐项配置:
- Device 芯片型号选择:点魔术棒(Options for Target),在 Device 页签里选择 STMicroelectronics STM32F1 Series 下的 STM32F103C8 或你实际使用的型号。这里不需要选 GD32,原因后面会说。
- Debug 页签:右栏下拉框选择“ST-Link Debugger”,点击旁边的 Settings。弹出的窗口中确认 SW Device 能识别到 Cortex-M3 SW-DP,如果识别不到先别急着烧录,查线序和供电。
- Utilities 页签:勾选“Use Debug Driver”,因为我们要用 ST-Link 作为烧录器;下面 Flash Download 选项框里点击 Settings 查看当前烧录算法列表。
Flash Download 框里就是 FLM 算法的管理界面。如果你当前列表里没有 STM32F10x Flash 相关的算法,点 Add 添加:
STM32F10x Flash 512kB (或者根据容量选对应的)重点来了:这里选择 STM32F10x Flash 512kB 的算法,对应的 Flash 起始地址是 0x08000000,容量 512KB,扇区大小 1KB 到 4KB 不等。GD32F103C8T6 内部 Flash 只有 64KB,但算法选择 512KB 的也没问题,因为实际烧录时是根据代码大小来操作扇区的,只要不超过片上 Flash 物理容量就行。如果你的 GD32 是 C 系列(64KB)但程序超了 64KB,那别折腾烧录了,是选型问题。
2.4 Keil 中 ST-Link 的 SWD 速度设定
前面提到 SWD 线长会影响稳定性,那么具体怎么调?在 Debug 页签的 Settings 里,找到 Max Clock 下拉框,默认可能是 4MHz 或 1.8MHz。如果你的板子布线质量一般(飞线、面包板、长排针),把 SWD 时钟降到 1MHz 或 500kHz 再试,往往能解决一大半“连接不上”的报错。
这个经验我反复验证过:SWD 通信速度不是越快越好,它取决于信号完整性和目标板复位电路的质量。GD32 的复位电路和 STM32 参考设计几乎一致,但如果你板子上的复位电容取值偏大(比如 1uF),可能会拖慢 SWD 的复位时序,这时候降速就非常管用。
3. 核心烧录流程:从编译到固件落板
3.1 代码编译与输出文件设置
烧录之前先确认工程能编译通过。点击 Build(F7),生成目标文件。Keil 默认会生成 .axf 文件,同时根据配置会生成 .hex 文件。在魔术棒 Output 页签里勾选“Create HEX File”,否则烧录时会出现“No Flash Device”或者没有可烧录文件的情况。
验证输出文件的方式有两种:
- 编译信息窗口会显示 Program Size 的具体值,比如 Code、RO-data、RW-data、ZI-data 四项。
- 打开工程目录下的 Objects 或 Listings 文件夹,查看 .hex 是否存在。
烧录前心里要有数:你的程序占多大 Flash、多少 RAM,别等下载时报溢出才回头砍功能。GD32F103C8T6 的 64KB Flash 和 20KB SRAM 比我 10 年前用 STM32F103C8T6 时紧张多了,做 Bootloader + App 方案时这个容量预算必须提前算好。
3.2 点击 Download 前后发生了什么
按下 F8(Keil 的 Download 快捷键),整个烧录流程大致如下:
- 初始化调试器(ST-Link V2 复位、连接目标板)。
- 通过 SWD 读取目标板内核信息,确认是 Cortex-M3。
- 将 FLM 算法加载到 SRAM 中(通常加载到 0x20000000 起始的 RAM 区域)。
- 执行 FLM 算法的 Init 函数,初始化 Flash 控制器。
- 根据 MDK 的配置,执行 Erase(擦除)操作:通常选择“Erase Full Chip”或“Erase Sectors”。
- 逐页编程(Program),每页通常为 1KB 或 2KB。
- 编程完成后执行 Verify(校验),逐字节读回 Flash 内容与缓冲区比对。
- 复位目标板并运行用户程序。
每一步背后都有对应的寄存器操作和时序要求。如果中途任何一步失败,Keil 会弹窗报错。这就是为什么有些现象看起来“烧录失败但代码能跑”——有时候程序其实写进去了,只是 Verify 环节因为 Flash 控制器差异读回结果不对,而 CPU 已经能从 Flash 取指执行了,这种情况最迷惑人。
3.3 实测烧录演示与现象记录
拿我手上一块 GD32F103C8T6 蓝色核心板做示范。板子上电后,3.3V 供电正常,复位键按下可以复位。ST-Link V2 接好后,设备管理器识别正常。Keil 工程配置好 ST-Link Debugger,选 STM32F103C8 芯片,Flash 算法选 STM32F10x Flash 512kB。
点击 Download 后,输出窗口打印:
Load "C:\\project\\GD32_Test\\Objects\\GD32_Test.axf" Programming Done. Verify OK.三行输出,干净利落。这说明 GD32 的 Flash 控制器对于 ST-Link + 标准 FLM 算法是完全兼容的,在不碰 BOOT0 的情况下(BOOT0 默认拉低),程序正常烧录并运行。
但这里有个特别容易让人困惑的现象——你在 Keil 里选中了 STM32F103C8 芯片,__STMicroelectronics__宏定义的是 ST 的标准外设库,但你拿它编译出来的代码能烧进 GD32。这说明两个厂商的寄存器定义在绝大多数路径上是一致的,但别忽略我在 1.1 节说的“细微差异”。比如 GD32 的 USART0 时钟使能位在 RCC_APB2ENR 的位 14,和 STM32F103 相同;但 GD32 的 ADC 时钟分频在某些型号上默认值不同,这就可能导致 ADC 采样率偏差,需要通过配置代码显式修正。
3.4 代码层面的“无缝”迁移实践
既然烧录打通了,代码怎么迁?
我的习惯做法是:保持工程芯片型号不变,直接替换启动文件与系统初始化文件。具体来说:
- 从 GD32 官方固件库(GD32F10x Firmware Library)中复制
system_gd32f10x.c和gd32f10x.h等核心文件到工程中,替代 ST 的对应文件。 - 将启动文件
startup_stm32f10x_hd.s替换为startup_gd32f10x_hd.s(也可以不换,实测 ST 启动文件在 GD32 上也能跑,但换了更规范,因为 GD32 启动文件里向量表对应的中断处理函数名称是 GD32 库的命名)。 - 编译。大部分功能代码(GPIO、USART、I2C、SPI、定时器)因为寄存器兼容,无需改动。
- 逐个外设做验证,重点测时钟频率是否准确(用定时器测量 1ms 是否真 1ms),因为 GD32F103 的主频虽然也可以跑到 72MHz,但它的时钟树配置和 PLL 倍频系数需要按 GD 的库来初始化,用 ST 的 SystemInit 代码虽然也能跑到 72MHz,但某些外设时钟分频会出现偏差。
这套流程走下来,比直接硬怼 ST 库代码在 GD32 上跑要省心不少。因为 GD 的固件库更新维护是针对自家芯片的,寄存器差异都处理过了,你不需要自己去翻手册逐条排查。
4. BOOT0 深度避坑:为什么有时要拉高,有时要拉低
4.1 BOOT0 和 BOOT1 的作用机制
现在聊标题里点名的 BOOT0。很多新手烧录失败,最后发现是 BOOT0 的问题,但你要是问他 BOOT0 到底干嘛的,他说不清楚。
STM32/GD32 的启动模式由 BOOT0 和 BOOT1(注意:GD32 中通常叫 BOOT1,对应芯片引脚 PB2)两个引脚的组合决定。F103 系列常见配置:
| BOOT0 | BOOT1 | 启动模式 | 起始地址 |
|---|---|---|---|
| 0 | X(任意) | 从主 Flash 启动 | 0x08000000 |
| 1 | 0 | 从系统存储器启动(ISP 引导程序) | 0x1FFFF000 |
| 1 | 1 | 从内置 SRAM 启动 | 0x20000000 |
这句话翻译成大白话:BOOT0=0,芯片上电后从你烧程序的那块 Flash 开始执行;BOOT0=1、BOOT1=0,芯片上电后执行芯片出厂固化的一段引导程序(System Bootloader),它可以通过串口等接口接收数据并写入 Flash;BOOT0=1、BOOT1=1,芯片从 SRAM 执行,相当于临时跑一个“内存中的程序”。
这里有一个致命的理解误区:不少人以为 BOOT0 拉高了就能烧录,烧的时候把 BOOT0 接 3.3V,烧完跑程序发现没动静,然后陷入“为什么我烧录了不运行”的困惑。
真相是:通过 ST-Link/SWD 烧录,根本不需要动 BOOT0。SWD 调试接口直接控制 CPU,把 FLM 加载到 SRAM 后执行 Flash 编程,和 CPU 从哪启动没有半毛钱关系。也就是说,BOOT0 维持默认低电平(接 GND),ST-Link 照烧不误。
那 BOOT0=1 的串口 ISP 模式什么时候才用?当你没有 SWD 调试器、只能用串口烧录时,必须把 BOOT0 拉高、BOOT1 拉低,让芯片进入系统存储器引导模式,然后通过串口把固件发给内置 Bootloader。这就是很多“只有 BOOT0 的情况如何下载固件”这类问题的答案。
4.2 烧录时 BOOT0 到底该拉高还是拉低
让我把这个场景讲透。
场景一:你有 ST-Link 或 DAP-Link 这类 SWD 调试器。BOOT0 拉低(默认状态),不需要任何额外操作。ST-Link 烧完直接运行,程序从 Flash 启动。这是最推荐的实践。
场景二:你没有调试器,只有 USB-TTL 串口模块。BOOT0 拉高、BOOT1 拉低,上电进入系统 Bootloader。用串口工具(如 FlyMcu、MCUISP)选择对应串口,发送固件。完成后把 BOOT0 拉低,复位,程序开始运行。
场景三:你的固件是做 IAP(In-Application Programming)升级的,也就是要通过 App 自己升级自己。这时候 BOOT0 必须保持低(从主 Flash 启动),因为 IAP 的主机(一般是 Bootloader 程序)就写在 Flash 里。你如果上电拉了 BOOT0,系统跑的是厂家的串口 Bootloader,而不是你自己的 IAP 主机,升级流程就跑乱了。
经常有人在 GD32 论坛问“GD32F103 无法通过串口 ISP 下载”,排查一大圈后发现是串口 TX/RX 接反了,或者BOOT1 没接对。GD32 的 ISP 串口是 USART0(PA9 TX、PA10 RX),别接错。
4.3 BOOT0 拉高后出现的“程序不运行”怪象
有几次用户描述的问题几乎一模一样:烧录成功,但板子上电没反应。远程看不到硬件,我只能引导他测量 BOOT0 引脚电平,结果十有八九 BOOT0 被拉到了 3.3V。
原因通常是:他在面包板/核心板上飞线调试,BOOT0 默认有下拉电阻(或者核心板设计时已经固定拉低),但他在之前尝试串口 ISP 时把 BOOT0 接到了 VCC,烧录完成后忘了改回来。上电后 CPU 进入系统存储器启动模式,你的 App 在 Flash 里躺着,一个字节都没执行。
这里有个很隐蔽的小技巧:如果 BOOT0 在 3.3V 而你的芯片里有有效程序,上电后程序不会从 Flash 启动,但如果你在 Keil 里点调试(Debug),ST-Link 连接后默认行为是复位并从复位向量执行,我实测很多情况下程序是能跑起来的,这会让问题更加扑朔迷离。最终判断标准只有一个:检查 BOOT0 电平。
4.4 核心板与自制板的 BOOT0 设计差异
市售的 STM32F103C8T6 蓝色核心板,BOOT0 默认通过 10K 电阻下拉到 GND,板上自带一个跳线帽或按钮用于切换。GD32F103 的核心板(比如某些国产蓝色板)设计思路相同。
但自制板就未必了。如果你画的是自己的小板子,BOOT0 引脚的处理有两种常见方案:
- 方案 A(推荐):BOOT0 经 10K 电阻下拉到 GND,同时预留一个 2.54mm 跳线针,需要 ISP 时用跳线帽短接到 3.3V。这样默认从 Flash 启动,调试和 IAP 都顺畅。
- 方案 B:BOOT0 直接接 100nF 电容到 GND,再串联一个 0 欧电阻可调接到 3.3V。这种方式适合生产定型产品,出厂后不再进 ISP 模式的场景。
BOOT0 悬空是大忌。有些所谓“最小系统板”偷工减料,BOOT0 只挂了个电容,依靠内部弱下拉维持低电平。这种板子在实验室常温下没事,但环境一变或引脚受干扰,BOOT0 可能误触发高电平,表现就是“程序莫名其妙不跑了”。
5. 烧录失败、Flash 锁死与读保护:ST-Link 常见故障排查实录
5.1 “No Target Connected” 连接失败怎么查
这是 ST-Link 烧录最常见的报错,没有之一。Keil 提示 “Cannot connect to target”,或者 ST-Link Utility 提示 “No target connected”。排查顺序我列一下,按优先级从高到低:
- 检查接线:SWDIO 接 PA13,SWCLK 接 PA14,GND 必须共地。我见过一半以上的情况是杜邦线松了或者插错排针。
- 检查供电:目标板有没有上电?测 3.3V 引脚电压是否正常。ST-Link 的参考电压检测引脚(VTREF)在某些版本上是独立的,如果你的调试器要求 VTREF 接入目标板供电,只有 GND/SWDIO/SWCLK 三根线是不够的——ST-Link V2 的 3.3V 引脚就是 VTREF,所以三根线通常也能用,但克隆版不好说。
- 降速:把 SWD 时钟从 4MHz 降到 1MHz 以下试试。很多“线太长、板子太差”的连接问题,降速就解决了。
- 复位电路检查:目标板复位引脚是否被外部器件拉低?如果复位一直被拉低,CPU 始终处于复位状态,SWD 也无法正常工作。拔掉复位引脚上的外部电容/按键测试一下。
- 内核是否进入低功耗:如果你的程序已经运行起来并且进入了 STOP/STANDBY 模式,SWD 会失效。这时需要把 BOOT0 拉低并强制复位连接到调试器。
这里有个非常容易踩的坑:GD32F103 的 SWD 引脚在程序里被重映射或禁用了。比如你在代码里调用了GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);,这会把 PA15、PB3、PB4 释放为普通 IO,但 PA13/PA14(SWDIO/SWCLK)不受影响。如果你把整个 SWJ 都禁用(GPIO_Remap_SWJ_Disable),那 PA13/PA14 都变成普通 IO,ST-Link 就彻底连不上了。解决办法是用串口 ISP 模式整片擦除,或者按住复位键的同时点击连接(如果运气好 Flash 里跑的程序还没把引脚全占了)。“程序烧录一次成功,第二次就连接不上”的经典案情,十有八九是这个原因。
5.2 “Flash Download Failed - Device ID incorrect” 怎么破
另一个高频报错:Device ID incorrect(器件 ID 不正确)。出现这个错误,一般是调试器读到了目标芯片的 Device ID,但和你 Keil 工程里选的芯片型号不匹配。
比如你选了 STM32F103C8(Device ID 应为 0x410),而 GD32F103 某些批次返回的 Device ID 是 0x411 或 0x413。这种情况下 Keil 会先拉响警报。
解决办法很简单:在 Device 页签里选择“更大的兼容型号”。比如选 STM32F103RB 甚至 STM32F103ZE,让 Keil 对 Device ID 的校验范围更宽松。如果你用的是 STM32F103C8(64KB),选 STM32F103RB(128KB)算法和 Flash 范围都能覆盖,编译出来的代码也能烧进 GD32。注意 Flash 大小不能超过目标芯片物理容量,否则校验阶段可能出现地址越界。
如果你用 STM32CubeProgrammer 连接,它比 Keil 更严格,会在连接阶段就校验 Device ID 与型号是否匹配,所以我才说 Keil 路线更省事,它给了你“换个大型号蒙混过关”的操作空间。
还有一个衍生坑:如果你把 GD32 的读保护(RDP)打开了,ST-Link 连接时会被拒之门外。左侧选项字节里 RDP Level 设成了 1,芯片的调试接口基本瘫痪,任何调试器都无法直接读 Flash。解除方法是整片擦除(Mass Erase),让 RDP 回到 Level 0。注意,整片擦除会清掉所有 Flash 内容,包括你的 Bootloader。
5.3 “Programming failed at address 0x08000000” 的 Flash 校验失败
烧录到一半报 “Programming failed at address 0x08000000”,让人头大。
这个问题的根源我在 1.2 节埋了伏笔:GD32 的 Flash 控制器和 STM32 并非完全一致。标准 STM32F10x FLM 算法在擦除和编程时序上和 GD32 兼容度 99%,但遇到某些特定操作序列时,可能触发 GD32 Flash 控制器的状态机异常。
解决方法按优先级排序:
- 检查供电稳定性:Flash 编程时内部电荷泵需要较稳定的电压,3.3V 如果被拉低到 3.0V 以下,Flash 控制器很容易编程失败。用万用表量一下目标板 3.3V,不要在 USB 供电不稳的扩展坞上调试。
- 更换擦除方式:在 Flash Download 设置里,把 Erase Full Chip 改回 Erase Sectors,或者反过来试试。有些 GD32 批次对全片擦除命令的某些中间态处理异常,逐扇区擦除更稳。
- 启动文件与算法不符:如果你追加了其他厂商的 FLM 算法(比如顺手加了 GD32 的官方 FLM),建议删除,只保留 STM32F10x Flash 算法。叠加多个算法在 Keil 里基本没意义,还可能因算法选择逻辑错乱导致烧录失败。
- 降低 SWD 速度:上面说了,Flash 编程期间如果调试链路本身就不稳,校验回读数据出错,也会报 Programming failed。降到 1MHz 试试。
5.4 一个真实翻车案例:烧录“成功”但程序跑飞
分享一个我印象很深的案例。用户用 ST-Link 给 GD32F103C8T6 烧自己写好的点灯程序,Keil 提示 “Programming Done. Verify OK.”,但板子上的 LED 就是不闪。他把代码检查了无数遍,甚至怀疑芯片坏了。
我让他量 BOOT0 电压——3.3V。问题瞬间定位。
他是这样翻车的:这块板子是二手淘来的,上家为了便于串口下载把 BOOT0 通过跳线帽接到了 3.3V,没拆。他拿到手后在 Keil 里烧录,因为 ST-Link 烧录不需要关注 BOOT0,所以一路绿灯,但程序根本没从 Flash 启动。他花了两个多小时排查代码逻辑,完全没想到是启动模式的问题。
这个案例告诉我们两件事:
- 烧录成功 ≠ 程序运行。启动模式不对,一切白搭。
- 遇到“烧录正常、运行异常”,第一件事就是检查 BOOT0 电平。
5.5 常见问题速查表
| 故障现象 | 可能原因 | 快速解决办法 |
|---|---|---|
| No Target Connected | 接线错误、未供电、SWD 速度过高、复位拉死 | 检查 SWDIO/SWCLK/GND,降速到 1MHz,检查复位电路 |
| Device ID incorrect | 工程型号与芯片 ID 不匹配 | 选更大兼容型号,或直接用 STM32F103ZE |
| Flash Download Failed at 0x08000000 | 供电不稳、篇章擦除方式不对、FLM 冲突 | 改善供电,切换擦除模式,只保留单个 FLM |
| Programming Done 但程序不运行 | BOOT0 被拉高,程序未从 Flash 启动 | BOOT0 拉低,复位 |
| 下载一次后第二次无法连接 | SWJ 引脚被禁用/重映射、读保护开启 | 串口 ISP 整片擦除,或按住复位连接 |
| ST-Link 驱动感叹号 | 山寨调试器驱动不兼容 | 换原版 ST-Link 或 DAP-Link |
6. 进阶技巧:让迁移和烧录链路更稳的几个小习惯
流程走通了,我再分享几个自己在实际项目中沉淀下来的习惯,能让这一步“烧录”环节的稳定性提升一个台阶。
6.1 用脚本固化烧录流程,告别手点按钮
Keil 的 F8 下载好用,但生产或者团队协作场景下,它不够自动化。我的做法是写一个批处理脚本,调用 ST-Link 的命令行工具(比如 STM32 ST-LINK Utility 的 CLI 版本)或 Keil 的命令行编译接口,实现一键编译 + 烧录。
ST-Link CLI 的基本用法类似于:
STM32_Programmer_CLI.exe -c port=SWD mode=UR -e all -w firmware.hex -v -rst这段命令的含义是:连接 SWD 端口,模式为热插拔(UR,Under Reset),整片擦除,写入 firmware.hex,校验,复位运行。用这种命令跑批量烧录,效率高得多,也不容易手滑点错。
6.2 读保护选项字节的正规玩法
GD32F103 同样支持读保护(RDP)。如果你做产品不希望别人用 ST-Link 直接读走固件,可以把 RDP 级别设为 1。但务必在生产流程里测试清楚:RDP=1 后,再次烧录必须整片擦除才能解除,而且整片擦除的时序比普通擦除慢很多,产线烧录节拍会受影响。
还有一种折中是开启“写保护”(WRP),让特定 Flash 区域不可编程但可读。这比 RDP 温和,适合做 Bootloader 保护。
6.3 预留 SWD 接口的硬件设计建议
如果你的产品板是自己画的,SWD 接口建议用 4 针(3.3V、SWDIO、SWCLK、GND)或 5 针(加复位 NRST)的标准座,而不是只在 PCB 上留四个过孔。因为量产调试、产测、售后返修都要插调试器,没有标准座会非常痛苦。
另外,SWDIO 和 SWCLK 引脚建议串联 33Ω 到 100Ω 的匹配电阻,靠近 MCU 放置,能有效抑制振铃和过冲。这个细节在批量生产时能避免很多“某些板烧录失败”的诡异问题。
6.4 GD32F103 的 IAP 升级链路设计
最后说说标题热词里反复出现的 GD32F103 IAP 升级。如果你要在 GD32F103 上做 IAP(Bootloader + App 方案),需要注意:
- Bootloader 和 App 的 Flash 分区要提前规划好。比如 64KB Flash:Bootloader 占 16KB(0x08000000 - 0x08003FFF), App 从 0x08004000 开始。
- App 工程的起始地址要改(在 Target 页签的 IROM1 起始地址设成 0x08004000, Size 相应减小),同时要在代码里设置向量表偏移:在 SystemInit 之后、外设初始化之前,执行
SCB->VTOR = 0x08004000。 - App 内不要再初始化将用于 IAP 的通信外设和 Bootloader 重复,否则两者会打架,导致跳转后卡死。
- 跳转函数要用
__set_MSP()设置主堆栈指针,同时确认跳转前关闭所有中断、恢复外设默认状态。细节很多,建议单独写一篇,但前提是你的基础烧录链路是通的,否则 IAP 无从谈起。
7. 写在最后:一点实践体会
从 STM32F103 迁到 GD32F103,烧录这一关只要掌握了 ST-Link + Keil + FLM 这条链路,其实半天就能打通。真正花时间的不是工具,而是理解 BOOT0 的启动逻辑、Flash 读保护机制和 SWD 引脚被禁用这些“隐性陷阱”。我个人的习惯是,拿到一块新 GD32 板子,第一件事不是写代码,而是先确认三件事:BOOT0 接地没有、SWD 引脚有没有被占用、芯片能不能被 ST-Link 识别。这三件事确认完,后面全是坦途。
另外一个我没提到但你肯定会遇到的细节:ST 的库函数和 GD 的库函数在部分外设 API 名称上有差异,比如 ST 的GPIO_InitTypeDef在 GD 的旧版本库里被命名为gpio_parameter_struct。这是因为 GD32 后期推出了一套风格接近标准库但成员命名不同的固件库。所以如果你想偷懒不改代码,用一套“双平台兼容”的写法,建议自己封装一层硬件抽象层,而不是直接依赖厂商库。这也是为什么有人说“无缝迁移”,有人说“坑很多”——取决于你项目的代码风格和用的库版本。
最后再分享一个排查锦囊:如果哪天 ST-Link 怎么都连不上 GD32,而你检查了所有硬件都没问题,试着把 BOOT0 临时拉高再复位一下。虽然最终工作模式要求 BOOT0=0,但这个“歪招”有时候能骗过 Flash 里的异常程序让调试器抢先接管,等连接成功后再把 BOOT0 拉回来。这招不是官方文档里的标准流程,但我实测过几次,有效。技术这东西,有时候就是经验比说明书管用。