1. 别急着写代码,先把GD32的环境搞顺
做嵌入式开发这么多年,我见过太多人在GD32F103上栽跟头,十有八九不是芯片的问题,而是开发环境没搭好。GD32F103是国内厂商兆易创新推出的Cortex-M3内核通用MCU,从引脚定义到外设寄存器,基本和STM32F103是兼容的,但坑就藏在“基本”这两个字里。它的内核、启动流程、时钟树、烧写协议都有自己的差异,如果你拿一套STM32的习惯直接套,轻则编译告警满天飞,重则芯片跑起来完全不是你预期的那样。
这套入门实践文章,我会从零开始带着你完整走一遍GD32F103的开发流程。第一篇先把地基打牢:配置开发环境、搭建一个干净可复用的工程模板、把程序烧进板子并确认跑起来了。这篇做完,你手里会有一套属于自己的GD32F103工程模板,后面写流水灯、串口通信、驱动LCD12864、移植RTOS都可以基于它直接扩展,不需要每次重新搭积木。
这篇文章适合谁看?没用过GD32、但熟悉其他Cortex-M芯片的人;以及用过STM32F103、想切换到GD32F103做项目选型对比的同学。我会尽量把每个步骤背后的原因解释清楚,不光告诉你“怎么做”,也告诉你“为什么这样做”。
2. 硬件准备和开发环境选型
2.1 硬件清单与选型思路
先把手里要用的东西理清楚。开发GD32F103,最少需要几样东西:
- GD32F103系列开发板或者自己画的板子。最常见的是GD32F103C8T6(小容量,64脚)和GD32F103RCT6(中等容量,64脚),很多入门板子用C8T6,因为便宜、引脚够用、资料也多。
- 调试/烧写器。官方有GD-Link,和ST-Link用法类似,但注意GD-Link的驱动和固件和ST-Link并不是完全通用的。我实际测试下来,J-Link配合GD32F103用也比较稳,尤其是用J-Link的SWD接口烧写,速度和平稳性都让人放心。如果你手头暂时没有GD-Link,用J-Link或者ST-Link(部分版本)也能顶一阵子,但不同调试器的兼容性表现不一样,下文我会专门说一下这个问题。
- USB转TTL模块。这个不是必须的,但建议备一个。后面调试串口通信、看打印日志都离不开它,入门阶段提前准备好没坏处。
- 若干杜邦线、面包板、LED、电阻、按键之类的基础元件。这篇只烧一个点灯程序,不需要太多外围,但后面做外设实验肯定用得上。
选型上的个人建议是:第一块板子选带板载GD-Link或者板载调试器的开发板,这样烧写这部分直接插USB就能搞定,能少踩很多环境适配的坑。如果你手头已经有一块空板,那就用J-Link的SWD方式连接,四根线(SWDIO、SWCLK、GND、3.3V)接上就能用。
2.2 开发工具:为什么选Keil MDK
GD32F103的开发工具链,主流选择有三个,我列一个对比:
| 开发工具 | 上手难度 | 调试体验 | 兼容性 | 适合场景 |
|---|---|---|---|---|
| Keil MDK | 低 | 好 | 对GD32官方库支持完善 | 绝大多数入门者和公司项目 |
| IAR EWARM | 中等 | 好 | 也支持GD32,但工程配置稍繁琐 | 老手、已有IAR项目基础的人 |
| GCC + VSCode/CLion | 偏高 | 一般 | 需要自己配链接脚本和编译链 | 喜欢折腾、或者做开源项目的人 |
我的建议非常直接:入门阶段,老老实实用Keil MDK。原因很简单——GD32官方提供的标准外设库、固件库示例、应用笔记,默认都是基于Keil工程组织的;你网上搜到的资料、踩坑帖子也大多以Keil为例。用最主流的工具,意味着当你卡住的时候,能找到最多人帮你。等后面你对整个编译链接流程很熟了,再切到GCC或者折腾VSCode里的C/C++环境也不迟。
Keil MDK在官网下就行,安装的时候需要选对芯片支持包。安装完主程序后,要单独装一个“GD32F10x AddOn”设备支持包,否则在芯片选择列表里根本看不到GD32F103。这个支持包可以到Keil官网的Device Pack页面搜索GD32F10x直接下载,也可以从兆易创新的官网下载集成包。装好之后,新建工程的Device弹窗里选GigaDevice目录下的GD32F103系列具体型号,整个环境才算配好。
2.3 烧写工具:GD-Link、J-Link与串口ISP
程序写完编译出hex文件之后,要进到芯片里跑,这一步叫烧写。GD32F103支持的烧写方式主要有三种:
第一种,基于SWD/JTAG接口的调试器烧写。这是日常开发最常用的方式。GD-Link是官方推荐的,ST-Link也能被Keil识别,J-Link就更不用说了。我实测下来,GD32F103对J-Link的SWD响应速度比较快,下载32KB左右的程序基本是秒级完成。GD-Link在兼容性上虽然不是问题,但很多山寨GD-Link的固件版本参差不齐,偶尔会出现识别不到芯片的怪问题。
第二种,串口ISP烧写。GD32F103出厂自带Bootloader,可以通过串口把程序烧进去。这种方式的优点是不需要额外调试器,一个USB转TTL模块就够了,缺点是速度慢、不能在线调试、每次烧写要手动设置启动模式。具体做法是把BOOT0引脚拉高、BOOT1拉低,复位后芯片进入Bootloader模式,然后用官方上位机或者第三方工具通过串口发送固件。这个方法我在量产或者没有调试器的场景下用过几次,当作备用手段是够的,但日常开发不建议用这个。
第三种,J-Flash等独立烧写工具。如果只是需要把编译好的hex文件烧进芯片,不想打开Keil,可以用J-Flash单独操作。这个在产线批量烧写的场景比较常见。开发阶段一般直接在Keil里按F8下载,两步到位。
不管用哪种方式,有一个前提必须满足:芯片的供电要正常、复位电路要可靠、SWD引脚没有被复用或锁死。很多新手烧不进程序,最后查来查去,发现是板子上电时序有问题或者SWDIO引脚被代码里设置成了普通IO口。这个问题我后面单独说。
3. 搭建第一个GD32F103工程模板
3.1 GD32标准外设库和“最小工程”的思路
GD32F103的裸机开发,官方给了一套标准外设库(配合Firmware Library,也有第三方像RT-Thread等社区整理的精简版GD32库),这套外设库的代码组织结构非常有规律。对一个新项目来说,关键不是把整个库的全部文件扔进工程,而是搭一个“最小可编译、最小可跑”的骨架,后续再按需添加外设驱动。这个思想很重要——很多人一上来把整个库都加进去,编译一大堆用不到的文件,看着很厉害,实际上出了问题很难定位。
一个真正能达到“最小可跑”标准的GD32F103工程,通常由这几部分组成:
- 启动文件:负责初始化堆栈、调用SystemInit、跳转到main函数。这是芯片上电后执行的第一段代码。
- 系统时钟配置:一般在system_gd32f10x.c里,用来配置系统时钟,比如把外部晶振倍频到108MHz(GD32F103最高主频是108MHz,注意不是STM32的72MHz,这个差别经常有人忽略)。
- 标准外设库的核心文件:包括寄存器的地址定义、中断号定义、GPIO和RCC等基础外设的驱动文件。
- 链接脚本(分散加载文件):对于Keil来说,就是.sct后缀的文件,决定代码、数据、堆栈放在Flash和RAM的哪些区域。Keil通常会自动生成一份,自己建的裸工程里也可以手动指定。
- main.c和gd32f10x_it.c:一个是主逻辑,一个是中断服务函数集合。
这个结构说白了就是:芯片上电后,启动文件先做“暖场”,把必要环境初始化好,然后交出控制权给main();时钟配置保证CPU按正确频率运行;外设库让你不用手抠寄存器就能操作GPIO、串口等外设;链接脚本保证编译出来的程序和内存布局是正确的。四者分工明确,缺一不可。
3.2 从官网下载固件库并理解目录结构
先去兆易创新官网找到GD32F10x的固件库(Firmware Library),下载后解压。你会发现目录结构大概是这样的:
GD32F10x_Firmware_Library ├── Board // 板级支持包,比如LED、按键初始化 ├── CMSIS // ARM内核相关文件 │ ├── CMSIS_Include │ └── GD32F10x_Include // 设备头文件、系统配置源文件 ├── Firmware // 外设库源文件 │ ├── GD32F10x_standard_peripheral │ │ ├── inc // 外设头文件,比如gd32f10x_gpio.h │ │ └── src // 外设源文件,比如gd32f10x_gpio.c │ └── GD32F10x_usbfs_library // USB相关,入门先用不到 ├── Template // 官方给的工程模板 ├── Utilities // 辅助调试函数 └── Projects // 官方示例,按照开发板型号分目录不要被这个目录吓住,你真正需要的是几个核心文件的组合。我的习惯是把我需要的文件复制到一个自己的工程目录里,而不是直接改官方目录。这样做的好处是工程整洁、可迁移性强、不会被官方库里没用的文件干扰。具体复制哪些文件,我在下一节直接给你一张清单。
3.3 手动创建工程目录结构与Keil新建步骤
我每做一个新项目,工程目录都遵循同一个规范,强烈建议你从一开始养成这个习惯。以这个模板工程为例,目录是这样:
GD32F103_Project ├── User │ ├── main.c // 主函数 │ ├── gd32f10x_it.c // 中断服务函数 │ ├── gd32f10x_it.h │ └── systick.c // 可选,系统节拍定时 ├── Core │ ├── startup_gd32f10x_hd.s // 启动文件 │ └── system_gd32f10x.c/h ├── Periph │ └── gd32f10x_xxx.c/h // 用到哪个外设就加哪个 ├── FWLib │ └── 外设库的inc和src ├── Output // Keil生成的中间文件和hex └── Doc // 放数据手册和笔记在Keil里创建工程的操作步骤如下:
- 打开Keil,菜单Project → New uVision Project,选择你的工程根目录,输入工程名。
- 在弹出的Device选择界面,找到GigaDevice目录,展开后选择你用的具体型号(比如GD32F103C8T6)。
- 接下来Keil会弹一个“是否添加启动文件”的对话框,建议选“否”,我们手动手动添加官方针对GD32的启动文件。因为Keil自动添加的可能还是ST的启动文件,搞错了编译能过,跑起来必出问题。
- 然后在工程里创建对应的分组(比如User、Core、Periph、FWLib),把我们准备的文件加进去。
- 最后配置Output、C/C++编译选项、Debug选项,具体参数看3.5节。
这里要重点提醒:芯片型号如果是C8T6这种小容量芯片,启动文件要用startup_gd32f10x_ld.s;如果是RCT6这种,用hd版本。选错了也可能编译通过,但内存布局完全不对,程序初始化和向量表都会偏移,轻则中断进不去,重则上电直接跑飞。
3.4 启动文件、系统时钟和外设库之间的关系
这节我从运行原理的角度讲一下它们之间的关系,这部分搞懂了,后面Debug查问题会轻松很多。
GD32F103上电后,硬件复位释放,CPU的第一条指令是取“向量表”的第一个word作为栈顶地址(MSP),第二个word是复位中断处理函数的地址。这个向量表就放在Flash的起始位置,启动文件里就定义了整个向量表和复位处理函数的骨架。
复位中断函数(Reset_Handler)做三件事:
- 调用SystemInit(),配置时钟树,把系统时钟设置到目标频率。
- 把数据段的初始值从Flash复制到RAM,把BSS段清零。
- 调用__main,最终跳转到用户的main()。
看到没有?如果SystemInit有问题,时钟配置不对,即使main()里的代码逻辑完全正确,整体行为仍然是错的。典型的表现是程序烧进去不跑、跑得极慢、串口波特率不对。所以我建议初学者在调试阶段,先确认自己板子的时钟是正常的,最简单的方法是用LED点灯然后对比闪烁频率是否符合预期。
GD32F103的系统时钟配置,和STM32F103有个关键区别需要注意:STM32F103的系统时钟最高一般是72MHz,而GD32F103文档里标称的是108MHz(内部PLL倍频之后)。但很多网上流传的代码模板和教程都还是按72MHz来配,这不是不能跑,只是没有发挥出这个芯片的全部性能。官方固件库里system_gd32f10x.c默认配置了三种时钟源,你可以按自己的外部晶振频率去修改PLL倍频系数。如果板子上用的是8MHz外部晶振,配置到108MHz的PLL系数和配置到72MHz是不一样的,具体数值在官方头文件里有注释,我建议入门时先用官方的默认配置,确认能跑通后再研究超到108MHz的事。
3.5 Keil工程的关键配置项
新建工程之后,有几个编译和调试的配置项必须设置对,否则后面会出现莫名其妙的问题。我按重要程度从上往下排:
第一,Output选项卡。勾选Create HEX File,这样编译后会生成hex二进制文件,方便用独立工具烧录。如果想Debug的时候查看变量和寄存器,把Browse Information也打开。
第二,C/C++选项卡。这里最核心的是Define预定义宏。使用GD32标准外设库的工程,通常需要定义“GD32F10X_HD”或者“GD32F10X_MD”,这个宏明确告诉外设库你用的是哪种容量的芯片,配合头文件里的条件编译来选择寄存器定义。其次,要确认Include Paths把所有头文件目录都加进去了,否则编译会报找不到头文件的错误。
第三,Debug选项卡。选择你用的调试器。如果选J-Link,然后在右侧的Settings里确认能识别到芯片ID;如果选ST-Link,也同理。这里有一个很常见的坑——Keil的调试器下拉列表里默认是ULINK,很多人忘记改成自己的调试器,第一次按下载按钮就会报错“Cannot load Flash Device Description”。
第四,Utilities选项卡。这里要勾选“Use Debug Driver”或者“Use Flash Download Tool”,并且在Flash Download里选对编程算法(Flash Algorithm)。GD32F103C8T6是64KB Flash,需要对应容量的算法文件。如果算法不对,烧写时会提示“No Algorithm found”或者“Erase Failed”。
一个额外的注意点:Keil工程文件(.uvprojx)一旦在某台电脑上配置好了,换一台电脑打开,不要急着编译,先全选一遍上面的配置。因为我遇到过很多次,工程在A电脑编译烧写正常,到B电脑就报各种诡异错误,最后都是因为编译器版本不一样或者调试器配置没改,跟代码本身没关系。
3.6 编写一个最小点灯程序来验证模板
主体框架搭好之后,我们写点实际代码,让它编译、烧录、跑起来。我在模板里放一个最简单的GPIO点灯程序,验证整个工程从编译到烧写的通路是否正常。
GD32F103的RCU(Reset and Clock Unit)相当于STM32的RCC,也就是复位和时钟控制器。操作任何外设之前,第一步永远是开启该外设对应的时钟。以PB2引脚接LED为例,完整代码如下:
#include "gd32f10x.h" void led_config(void) { rcu_periph_clock_enable(RCU_GPIOB); gpio_init(GPIOB, GPIO_MODE_OUT_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2); gpio_bit_reset(GPIOB, GPIO_PIN_2); } int main(void) { led_config(); while(1) { gpio_bit_set(GPIOB, GPIO_PIN_2); delay_1ms(500); gpio_bit_reset(GPIOB, GPIO_PIN_2); delay_1ms(500); } }注意,这里用到的delay_1ms需要自己实现,一种简单方式是使用SysTick。SysTick是Cortex-M内核自带的24位递减计数器,用它做时间基准既省资源又不占用额外定时器。我这里给出一个非常精简的实现:
static volatile uint32_t delay_count = 0; void delay_1ms(uint32_t ms) { delay_count = ms; while(delay_count != 0); } void SysTick_Handler(void) { if(delay_count != 0) { delay_count--; } }然后在main之前或者SystemInit之后,调用SysTick_Config(SystemCoreClock / 1000),这样SysTick中断频率就是1kHz,每毫秒触发一次中断。
编译前再确认一遍:system_gd32f10x.c里根据外部晶振配置了正确的系统时钟,Keil的Preprocessor Symbols里定义了正确的芯片容量宏,Debug设置里选了正确的调试器并且能识别芯片。都确认无误之后,按F7编译,看看0 Error 0 Warning,然后按F8下载。如果一切顺利,LED会以500ms间隔闪烁,你的GD32F103开发环境就算正式打通了。
4. 烧写环节的完整流程与掉坑指南
4.1 Keil内置下载的使用步骤
Keil内集成了Flash下载功能,正常情况下一键就能烧进芯片。但你得先保证三个前提:调试器连接正确、调试器驱动正常、芯片供电正常。具体流程是这样的:
- 给开发板上电,确认电源指示灯亮。
- 用SWD方式连接调试器,如果是板载调试器,直接插USB即可。
- 在Keil的Debug选项卡里选择对应调试器,打开Settings窗口。正常情况下,这里能看到芯片的IDCODE以及SW Device列表里出现目标芯片信息。如果这里就是空白的,先别急着下载,检查连线。
- 确认Utilities里的Flash Download配置正确,勾选了对应的编程算法。
- 按F8下载。下载过程中Keil会先擦除Flash,然后写入,最后自动执行复位并运行程序。
我观测到的实际效果是:用J-Link下载一个10KB左右的程序到GD32F103,耗时在2到3秒左右;如果是ST-Link,稍微慢一点,但也在可接受范围。调试模式的进入和退出偶尔会有小概率失败,最常见的原因是调试器连接线太长或者接触不良,SWD频率太高导致时序不稳定。遇到这种情况,先换短线,或者在Debug设置里把SWD时钟频率调低重试。
4.2 用J-Flash独立烧录的替代方案
某些场景下你不会打开Keil去烧程序,比如产线批量烧录、替客户烧录、现场升级固件。这时候可以用SEGGER的J-Flash工具,配合J-Link硬件来操作。它的逻辑很简单:新建工程,选择芯片型号(GD32F103C8T6或者RCT6,取决于你的具体型号),然后打开hex文件,按F7编程。
这里有一个容易碰到的坑:J-Flash的芯片型号列表里,GD32相关型号不是按兆易创新的名字排在前面,而是在器件选择窗口里搜索“GD32F103”后选择。选错型号或者选成ST的同型号,在有些情况下也能擦写成功,但读回来的芯片ID、Flash容量信息会异常,不建议混用。
生产环境中我还用过一种方式——先用J-Flash烧录一个Bootloader,然后通过串口或者CAN、USB等方式升级应用程序。这种方式对批量生产特别有用,因为板子出厂后不需要每个都接调试器,产线工人只要接上电源和通信线,用上位机软件就能完成固件更新。后面如果涉及批量交付,非常建议实现一个简单的IAP方案,把Bootloader和App分开管理。
4.3 串口ISP烧写方法详解
说完调试器烧录,再说一下不依赖调试器的串口ISP烧录。这个方法完全利用芯片出厂固化的Bootloader,好处是硬件成本最低,一个USB转TTL模块就能干活。
操作步骤:
- 把BOOT0和BOOT1引脚按一定电平配置。对于GD32F103,通常是BOOT0=1、BOOT1=0,将芯片设置为系统存储器启动模式。
- 连接USB转TTL模块的TX、RX到芯片的USART0(或USART1,看具体型号)对应引脚,同时共地。
- 给板子重新上电或者按复位键。
- 打开官方上位机软件(可以到兆易创新官网找),选择串口号和固件文件(hex或者bin),点击下载。
- 烧写完成后,把BOOT0跳线恢复为0,重新复位,程序就开始运行了。
这个方式的缺点是不能在线调试,每次烧完都要手动改跳线,对开发阶段来说效率太低。而且在芯片的main函数里如果已经配置了复用串口引脚,使用同一个串口做ISP,需要确认没有冲突。我一般是把它当作没有调试器时的应急方案,不推荐作为主要烧写方式。
4.4 烧写报错“No Target Connected”的排查思路
烧写报错是极其常见的一类问题,我不能不提。我见过很多初学者第一次尝试下载,弹出的不是绿灯,而是一堆红色错误,最常见的就是“No Target Connected”或者“Cannot connect to target”。遇到这个,别慌张,按下面顺序排查:
先从最简单的开始。看开发板的电源指示灯亮不亮,亮说明供电正常。然后看SWD四根线是不是连对了:SWDIO连接SWDIO、SWCLK连接SWCLK、GND必须有,3.3V在某些情况下可以不用连,因为目标板自己有电源,但不连的话逻辑电平参考会出问题,所以最好还是接上。
如果连线都对,还是找不到芯片,把调试器的SWD速率调低再试。默认的4MHz或5MHz在部分连线上会因为信号质量不好而失败,降到1MHz以下往往就好了。我曾经遇到过一块板子,怎么连都不识别,最后发现调试器下载线质量太差,换成10厘米的短杜邦线瞬间正常。
再往下排查,检查芯片本身是否处于异常状态。比如代码里把SWDIO引脚复用成普通GPIO了、或者芯片进入了休眠模式、或者看门狗在疯狂复位。这种系统级问题通过普通的复位连接很难救回来。解决办法之一是在尝试连接时按住复位键,在Keil点击下载后、连接到目标的瞬间立刻松开复位键——这是嵌入式老江湖惯用的“裸奔连芯片”技巧。另一个办法是用串口ISP方式擦除整个Flash,恢复正常启动后再用SWD调试。
有一种情况特别坑:如果你之前烧过一段代码,把PB3、PB4这些默认的JTAG引脚复用成普通IO了,那么J-Link/ST-Link通过JTAG协议连接自然会失败。不过GD32F103默认是SWD和JTAG同时启用的,一般用SWD两线接口不受影响,除非你的代码把PA13、PA14也一起改了,那才是真正的“自杀式操作”。遇到这种情况,别无他法,只能靠串口ISP擦除救命。
4.5 关于烧写算法和Flash容量的坑
GD32F103C8T6标称Flash 64KB,GD32F103RCT6是256KB。Keil在下载时需要一份“烧写算法”,描述怎么擦除和写入这颗芯片的Flash。如果在Flash Download配置里选的算法不对,下载会中途报错。
我在实际项目里遇到过一种情况:从网上下载的某个“通用GD32工程模板”,它的烧写算法是给512KB的型号配的,我换成C8T6之后忘记改,每次下载到一半就失败。排查了很久,最后在Utilities里把Flash Algorithm改成GD32F103C8T6对应的算法,问题立刻消失。
所以建议是:每个新工程第一次下载前,打开Utilities → Settings → Flash Download,看一眼里面的Programming Algorithm列表。如果芯片型号是C8T6,就确保是64KB的算法;如果是RCT6,则是256KB。另外,勾上“Reset and Run”,下载完成后芯片自动复位运行,省去手动按复位键的步骤。
5. 常见问题速查与排错技巧
5.1 我把干过的坏事都记下来了——典型问题清单
做教程这么多年,我积累了一份“新手必踩坑”清单,下面用表格列出来,方便你排查:
| 现象 | 根本原因 | 解决方法 |
|---|---|---|
| 编译报错No such file or directory | Include路径没加全 | 把所有包含头文件的目录都加到C/C++ Include Paths |
| 编译报错identifier "xxx" is undefined | 芯片容量宏没定义 | Preprocessor Symbols里定义GD32F10X_HD或GD32F10X_MD |
| 下载时报错No Target Connected | SWD接线错误或速率过高 | 检查四根线,降速率到1MHz以下再尝试 |
| 下载时报错Cannot Load Flash Device Description | Flash算法缺失或型号不对 | Utilities里添加正确型号的Flash Algorithm |
| 板子能识别但下载失败Erase Failed | Flash算法和芯片实际容量不匹配 | 换对应容量的烧写算法文件 |
| 程序烧进去不跑 | 启动文件用错,或者时钟配置异常 | 检查启动文件是否为GD32特定版本,SystemInit配置是否正确 |
| 程序能跑但LED闪烁频率不对 | 时钟配置和实际晶振不匹配 | 对照板子外部晶振频率确认PLL系数 |
| 用一个工程烧两个不同型号,第二个报错 | 芯片型号选择没更新 | 确认Options for Target里的Device型号和目标是同一个 |
这个表几乎覆盖了我自己能想到的入门阶段80%以上的报错场景。如果你遇到的问题不在列表里,还有一个通用策略:编译和下载过程本身就是最好的诊断工具。先看编译警告,很多问题在编译阶段就暴露了;再看下载阶段的日志输出,Keil的Build Output窗口会告诉你卡在哪一步。
5.2 识别芯片ID,判断调试器是否正常
排错过程中,有一个基础操作特别有用:读取芯片ID。在Keil的Debug设置里,打开Settings窗口,如果一切连接正常,你会看到SW Device列表里有你的芯片信息,包括设备ID、内核类型等。不同厂家的Cortex-M芯片,其设备ID不完全一样。
如果SW Device列表里出现了一长串问号或者错误代码,通常是下面几种情况:
- SWDIO和SWCLK接反了。赶紧换过来。
- 复位引脚被拉低,芯片一直处于复位状态,调试器无法建立连接。
- 芯片功耗异常,电流不足导致内核无法稳定工作。
- 调试器本身驱动有问题,重装驱动或者换个USB口试试。
我遇到过最哭笑不得的一次:芯片板子完全正常,就是我忘记给调试器插USB了。所以顺手也提醒一下——先看物理连接,再怀疑代码,这是嵌入式Debug的第一法则。
5.3 从串口ISP模式中恢复一只“死芯片”
大部分“芯片死了”的情况,并不是芯片真的损坏,而是Flash里跑了一段不正常的程序,或者调试端口被占用了。这时候串口ISP模式是最后的救命稻草。
把BOOT0拉高、BOOT1拉低,复位芯片,芯片进入系统存储器中的Bootloader。这时候通过串口连接,用官方工具和芯片建立通信,执行全片擦除,然后再把BOOT0拉低、复位,芯片就恢复了出厂时的空白状态,SWD调试接口也重新可用。这个方法我已经救回过好几块“砖”,可靠度极高。
还有一点:如果串口ISP模式也连不上,检查USB转TTL模块的驱动是否正常,以及TX、RX是否接反。接反这种情况太常见了,芯片的TX接模块的RX,芯片的RX接模块的TX,别搞成同名相连。
5.4 初始化代码执行不到的排查思路
还有一个问题很隐蔽:程序明明能编译、能烧写,但执行效果不对。比如LED不亮、串口没输出、某个外设没反应。这种情况的排查思路,我的经验是按“时钟→引脚→外设配置→逻辑”四层递进排查。
先确认时钟有没有开。GD32F103的所有外设都需要先通过RCU打开对应时钟,不开时钟操作寄存器是无效的。再看引脚复用配置是否正确。比如串口的TX/RX引脚,不仅要配置为复用功能,还要选对对应的AFIO映射。再看外设初始化是否正确,比如波特率、引脚模式、中断优先级等参数。最后才怀疑逻辑代码本身。
我之前带过一个人,他写了个串口发送程序,串口一点反应都没有。他检查了好几遍逻辑代码,最后发现是RCC里没使能USART的时钟。这种错,光是看代码逻辑根本看不出来,必须一层层往上追。
6. 模板工程还能怎么扩展
第一篇的内容到这里,其实已经完成了“配环境、建模板、烧程序”三个目标。但你手里这个刚搭好的模板,不应该只用来点个灯。我把这个模板后续的扩展方向简单列一下,下一篇或者你自己实践的时候可以直接接上:
加串口驱动。在Periph目录里添加USART的驱动文件,实现printf重定向,用串口输出调试日志。这是后续所有调试的必备基础。
加Systick优先级管理。如果后面频繁使用延时函数,建议把Systick的优先级配置好,避免在中断里调用延时导致系统卡死。
加按键扫描。外部中断或者轮询都行,配一个状态机处理按键消抖、长短按识别,这些都是工程实践中特别常用的基础模块。
移植RTOS。目前很流行在GD32F103上移植RT-Thread或者FreeRTOS,这个模板的时钟配置和启动文件如果规范,移植RTOS会非常顺利。有些朋友在宿舍搞GD32F103移植RTOS,卡住的地方往往是systick的优先级问题和内存大小分配问题,基础工程打好了,这两块就能少踩很多坑。
我个人的体会是,一个干净、可复用的模板工程,价值远远超过它看起来的那几KB代码。因为你在后面所有项目中积累的工具函数、驱动模块,都是围绕着这个基础模板不断叠加的。模板本身设计得好,后面每加一个功能都会很顺手;模板乱糟糟的,后面越改越难受,直到某一天不得不推倒重来。
这篇的实操内容就分享到这里。大家在实际操作中如果遇到“环境都对但不工作”的怪问题,欢迎在评论区贴出你的具体报错信息和硬件连接方式,一起分析解决。下一篇我会基于这个模板,带大家一起实现串口通信和调试日志输出,那个阶段能做的事就多起来了。