1. 从“写完代码”到“芯片跑起来”,中间到底发生了什么
很多刚接触STM32的朋友,手里拿着一块开发板,照着视频敲完了点亮LED的代码,点了一下编译,看到“0 Error”就以为万事大吉。结果插上ST-Link点下载,蹦出来一个Error: Flash Download failed - "Cortex-M3",当场愣住。这还算是好的,更常见的报错是No STM32 target found!,然后就开始怀疑接线、怀疑驱动、怀疑芯片烧了,甚至怀疑人生。
我当年也在这条路上卡了整整一个下午。后来把整条链路——从源码到烧录——彻底捋清楚之后才明白,问题根本不在于某一步做错了,而是我对整个流程缺少一个整体认知。所谓“从源码到烧录”,本质上是五件事:编写源码、编译链接、生成目标文件、通过调试器连接芯片、把程序写进Flash。这五件事环环相扣,每一环都有它的坑。
这篇文章就沿着这条链路,把每一步的原理、工具、操作和典型问题都摊开讲。适合三类人看:刚入手STM32但是在烧录这一步被卡住的新手、已经能跑程序但是对底层机制一知半解的进阶者、以及想从Keil迁移到其他工具链的折腾型选手。文章里的内容我都实际验证过,开发板是STM32F103C8T6最小系统板,调试器是ST-Link V2,编译环境以Keil MDK为主,部分内容会提到VSCode + GCC工具链的配置思路。
2. 环境搭建里最容易被忽略的三个坑
2.1 Keil MDK装好了,但Device列表里找不到芯片
在开始谈编译和烧录之前,先把环境说清楚。绝大多数人用的都是Keil MDK,装完之后顺手打开,新建工程,结果在Device型号列表里翻不到自己的芯片。比如你想选STM32F103C8T6,列表里空空如也。这个时候不要急着重装Keil,你缺的不是Keil本身,而是STM32的设备支持包——也就是CMSIS Pack。
正确的做法是先到ST官网或者Keil的Pack Installer里安装对应的Device Family Pack。Keil 5之后的版本把设备支持从安装包里拆出来了,变成了独立安装的Pack包。具体路径是:打开Keil,点击Pack Installer按钮,在搜索框输入STM32F1,找到Keil::STM32F1xx_DFP这个包,点击Install。装完以后重新打开Keil,Device列表里就能看到完整的STM32F1系列了。
这个坑很基础,但确实很多人遇到。如果你板子上的芯片是STM32F407、STM32F427之类,装的是Keil::STM32F4xx_DFP;如果是STM32H7,就装Keil::STM32H7xx_DFP。简单说就是芯片属于哪个系列,装哪个Pack。
提示:如果你用的是STM32CubeMX生成工程,工具链选项里选
MDK-ARM,生成的项目第一次打开会提示安装Pack,确认安装就行。这个流程能帮你自动完成设备支持包的匹配。
2.2 ST-Link驱动装了,但设备管理器里还是黄色感叹号
这是第二个高频翻车点。ST-Link插到USB口之后,Windows设备管理器里出现一个带黄色感叹号的未知设备,双击看属性,提示“无法验证此设备驱动的发布者”或者干脆“驱动未安装”。
网上很多人上来就让你关数字签名强制,实际上大部分情况只是驱动版本不对。新版的ST-Link驱动和老版本签名机制有差异,64位Windows下装驱动要先右键以管理员身份运行安装程序。具体可以到ST官网搜STSW-LINK009,这是ST-Link的USB驱动,下载解压后右键dpinst_amd64.exe以管理员身份运行。
装完之后重新插拔ST-Link,如果设备管理器里出现STMicroelectronics ST-Link dongle或者ST-Link Debug,说明驱动正常了。如果还是感叹号,再考虑是不是USB口供电不足的问题。尤其用笔记本电脑的时候,建议直接用机身USB口而不是HUB,ST-Link V2这玩意对供电挺敏感的。
2.3 核心板VCC接3.3,调试器却死了
接线问题我放到烧录章节详细讲,这里先提一个最容易被忽略的:ST-Link V2的供电选择。市面上常见的ST-Link V2山寨版有一个3.3V和5V的切换跳线或者引脚。如果你的STM32核心板是3.3V供电,而ST-Link输出的是5V,接到板子的3.3V引脚上,板子上的稳压芯片和芯片本身轻则发热,重则直接烧掉。
我自己的习惯是:用万用表先量一下ST-Link的输出电压,确认是3.3V再接板子。如果是那种带跳线帽的ST-Link,先确认跳线帽插在3.3V一侧。这个动作看起来多余,但烧过两块核心板之后你就知道它有多重要了。
3. 编译链接到底做了什么——从源码到hex文件的完整旅程
3.1 编译不是“点一下按钮”,而是四个阶段的接力
在Keil里点一下编译按钮,看起来是瞬间完成的事,实际内部执行了四个阶段:预处理、编译、汇编、链接。
预处理阶段会处理#include、#define、条件编译指令。你写的#include "stm32f1xx_hal.h"在这里会被展开成一大坨真正的代码。这个阶段不检查语法错误,只做文本层面的替换和展开。有时候你发现某个宏没定义导致编译报错,但明明在头文件里定义了,可能就是预处理时的头文件包含顺序问题,当头文件A引用B,而B又在A之后才被包含时就会出现这类问题。
编译阶段把预处理后的C代码转换成汇编代码,这个阶段才是真正的语法检查环节。Keil用的是ARMCC编译器(AC5)或者ARMClang(AC6,Keil 5.15之后支持)。编译时报的error: #20: identifier "xxx" is undefined,就是在这个阶段发现的。
汇编阶段把汇编代码转换成机器码,生成目标文件.o。每个.c文件都会生成一个.o文件。注意,到了这一步,各个.o文件之间的函数调用关系还没有被连接起来。你在main.c里调用了一个在uart.c里定义的函数,编译阶段是不知道这个函数具体地址在哪里的。
链接阶段把所有的.o文件和库函数打包在一起,分配地址,生成最终的.axf文件。Keil里如果你勾选了Create HEX File选项,链接器还会额外生成一个.hex文件,这就是我们要烧录到芯片里的东西。
3.2 启动文件为什么那么重要
新手理解编译流程时最容易忽略的,是启动文件startup_stm32f103xb.s的作用。这个汇编文件是程序的入口,干的事情包括:初始化堆栈指针、设置PC指针指向复位向量、拷贝.data段数据到RAM、清零.bss段、调用SystemInit()、最后调用main()。
如果你新建工程的时候漏掉了启动文件,编译大概率不报错,但烧录进去之后程序跑不起来。比如芯片上电后PC指针指到了一个非法地址,直接进HardFault。还有一个容易被忽略的细节:启动文件里定义的堆栈大小。STM32F103C8T6只有20KB RAM,如果你的局部变量很大,或者递归调用很深,堆栈溢出不会报错,但程序运行到某个时刻突然疯掉。很多莫名其妙的运行故障,追根溯源其实是栈溢出。
3.3 hex和bin的区别
烧录的时候顺便把生成的文件格式也搞清楚。Keil默认生成的是.hex,这是一个文本格式的文件,每一行包含了地址信息和数据,烧录器会根据这些地址把数据写到对应的Flash位置。.bin文件是纯二进制格式,没有地址信息,烧录的时候必须指定起始地址。
所以用STM32CubeProgrammer烧录.hex时可以不管地址,它自己会解析。烧录.bin就必须填地址,比如STM32F103的Flash起始地址是0x08000000。有些人在烧.bin的时候忘了填地址,烧录器直接报错,就是因为这个原因。
3.4 代码段、数据段和RAM的分配逻辑
关于链接阶段还有一个常见的困惑:Flash和RAM分别装了什么。简单说,程序编译完之后会分为几个段:.text放代码和常量,烧录到Flash里;.data放已初始化的全局变量,它的初始值存在Flash里,但运行时要拷贝到RAM中;.bss放未初始化或零初始化的全局变量,只占RAM空间。
.data和.bss的分段逻辑解释了为什么单片机程序编译后flash占用和RAM占用是两回事。你在Keil编译输出的信息里能看到Program Size: Code=, RO-data=, RW-data=, ZI-data=,其中RW-data就是.data,ZI-data就是.bss。烧录时看Flash大小要看Code + RO-data + RW-data,而RAM使用量要看RW-data + ZI-data。搞清楚这个对应关系,对挑选芯片型号、估算资源占用很有帮助。
4. 编译配置里直接影响烧录成败的几个开关
4.1 Flash Download选项中Address范围的学问
很多人编译成功但烧录失败,问题出在Keil的调试器设置里。具体位置:Options for Target->Debug-> 右上角的Settings,然后切到Flash Download选项卡。这里有一个Programming Algorithm列表,里面写的是烧录算法。如果你的芯片是STM32F103C8T6,Flash是64KB,那么列表里应该选STM32F10x Med-density Flash 64K。
如果你选成了STM32F10x High-density Flash 128K,芯片容量不够,烧录时报Erase Failed!或者写到一半卡住。如果选成Low-density,容量只有16K,写64K的程序直接报Address out of range。这个匹配关系和芯片型号后缀的对应表如下:
| 芯片型号后缀 | Flash容量 | Keil编程算法 |
|---|---|---|
| STM32F103C4/C6 | 16K/32K | STM32F10x Low-density Flash |
| STM32F103C8 | 64K | STM32F10x Med-density Flash 64K |
| STM32F103RBT6 | 128K | STM32F10x High-density Flash 128K |
| STM32F103ZET6 | 512K | STM32F10x High-density Flash 512K |
另一个容易踩的坑是Erase Full Chip和Erase Sectors的选项。如果选了Erase Full Chip,每次烧录都全片擦除,烧录时间会长一些,但对Flash寿命有一定消耗。全片擦除的另一个隐藏影响是:芯片里的其他数据也会被清掉,比如你之前存进去的校准参数。我做工程的习惯是选Erase Sectors,配合Program和Verify,只擦除需要写入的扇区,速度快而且相对温和。
4.2 Reset and Run到底该不该勾
Flash Download选项卡下方有一个Reset and Run复选框,它的作用是烧录完成后自动复位并运行程序。我见过很多人在这一步纠结:勾了之后程序烧录完确实跑了,但跑了一次之后第二次就不行;不勾的话每次烧完都要手动按一下开发板的复位键。
这里有一个被很多教程忽略的细节:Reset and Run的复位动作依赖于调试器的RST引脚与芯片的NRST引脚正确连接。如果你只接了SWDIO、SWCLK、GND、3V3这四根线,没有接RST,勾选Reset and Run后芯片并不会自动复位运行,有些调试器甚至会因此报错。解决的办法有两个:要么把ST-Link的RST引脚接到板子的NRST,要么就不勾这个选项,烧录完成后手动按复位键。
手动复位也就一秒钟的事,但很多人不知道问题出在哪,反而去怀疑程序写得不对。这块小经验写在这里,希望你能避开。
4.3 优化等级改了,程序烧进去跑飞了
再讲一个编译优化与烧录结果之间的隐性关联。Keil里Options for Target->C/C++选项卡里有一个Optimization下拉框,默认是-O0(不优化)。有人为了提高运行速度把优化等级调到-O3,编译通过了,烧录进去发现程序运行逻辑出错,比如某个变量明明赋了值但读出来是乱的。
这其实不是烧录的问题,而是编译器优化导致代码执行顺序和源码书写顺序不一致。尤其是在没有加volatile修饰的全局变量上,-O3下编译器可能直接把它放到寄存器里而不写回内存,中断里修改这个变量,主循环里读到的还是旧值。
如果在网上看到有人说“把优化等级调到-O2程序就跑飞了”,九成是代码里存在未定义行为或者遗漏了volatile。我的建议是:学习阶段老老实实用-O0,调试方便。项目阶段了解自己代码的健壮性之后,再逐步尝试-O1、-O2,-O3对于单片机应用来说收益有限,风险却不小。
5. 烧录方式全景:ST-Link、J-Link、串口ISP各自的玩法与局限
5.1 SWD还是JTAG——接口选择背后的逻辑
STM32支持多种烧录方式,最常用的是通过调试器走SWD或者JTAG接口。SWD只需要两根线(SWDIO和SWCLK)加上电源和地,占用引脚少,是目前的主流。JTAG需要5根线(TMS、TCK、TDI、TDO、NRST),速度快,但占用引脚多,在小封装芯片上不实用。
如果你的板子上没有板载调试器,只是一个最小系统板,那就要自己接线。ST-Link V2和STM32核心板的标准SWD接线如下:
| ST-Link V2引脚 | STM32核心板引脚 |
|---|---|
| SWDIO | PA13 |
| SWCLK | PA14 |
| GND | GND |
| 3.3V | 3.3V |
| RST(可选) | NRST |
接好线之后在Keil里选择调试器:Options for Target->Debug,右侧下拉框选ST-Link Debugger,然后点Settings,如果能识别到设备ID,说明连接成功。
5.2 用STM32CubeProgrammer串口烧录——没有调试器也能玩
手头没有ST-Link或J-Link的时候,用串口ISP模式烧录也是一个非常实用的备选方案。STM32出厂时固化了一段Bootloader在系统存储区,可以通过USART1接收数据并写入Flash。要进入ISP模式,需要把BOOT0引脚拉高,BOOT1拉低,然后复位芯片。
连接方式很粗暴:USB转TTL模块的TX接芯片的RX(PA10),RX接芯片的TX(PA9),GND共地。然后用STM32CubeProgrammer选择UART模式,选择正确的串口号,波特率一般用115200,点击Connect之后就能识别到芯片,然后加载.hex或.bin文件烧录。
串口烧录最大的问题是速度慢,一个几十KB的程序要等几十秒。但它有一个不可替代的优势:不需要额外硬件,一根USB转TTL线就能搞定。而且如果哪天你不小心把SWD引脚复用成普通GPIO了——这是新手最常见的操作——调试器连不上芯片,用串口ISP烧一个把SWD引脚恢复的固件就能救回来。
5.3 J-Link与ST-Link的兼容性
很多人手头是J-Link仿真器,但在Keil里选了ST-Link Debugger,自然连接不上。J-Link要用它自己的驱动和J-Link Debugger选项。不过注意,STM32的SWD接口是通用的,J-Link也能用SWD模式连接,只是在Keil里要选对应选项。如果你用的是正版J-Link,直接选J-Link / J-Trace Cortex就行。如果是山寨J-Link,驱动可能被最新版Keil识别为盗版而拒绝工作,这时候要么换ST-Link,要么想办法用回老版本驱动,总之问题在工具本身。
5.4 Boot0引脚的三种模式
Boot0引脚的状态决定了芯片上电后从哪里启动。这里值得花点时间说清楚,因为很多人烧录失败其实不是烧录本身的问题,而是启动模式搞错了。
STM32F103有三种启动方式:BOOT0拉低(默认),从主Flash启动,正常运行用户程序;BOOT0拉高、BOOT1拉低,从系统存储区启动,执行出厂Bootloader,可以串口烧录;BOOT0拉高、BOOT1拉高,从内置SRAM启动,一般用于调试。
你需要记住的就一句话:正常烧录完程序之后,BOOT0一定要回到低电平,否则上电后芯片不会运行你烧进去的程序。很多人的板子烧录时一切正常,断电再上电程序却不跑,检查一下BOOT0跳线帽是不是还插在高电平的位置。
6. 烧录失败排查实录——那些“No target found”背后的真凶
6.1 最经典的报错:No STM32 target found
这个报错是搜索热词榜的常客,几乎每个玩STM32的人都会遇到。报错全文通常是这样的:
Error: No STM32 target found! If your product embeds Debug Authentication, please perform a recovery sequence with Debug Authentication第一次见到这个弹窗的人基本都是慌的。但其实排查思路非常固定。
先检查硬件连接。SWDIO、SWCLK一定要接对,很多人把PA13和PA14接反了,自然检测不到。然后是供电问题,这里有个很容易忽略的细节:如果用ST-Link给核心板供电,要确认ST-Link的3.3V和GND真的接到了核心板的3.3V和GND上。我用万用表实测过一块板子,看起来ST-Link灯亮,但芯片VCC引脚那里只有1.8V——实际上是板子上的某个焊点虚焊了。所以排查硬件问题时,不要只盯连接线,还要用万用表确认芯片电源引脚处的实际电压。
软件层面的排查也不难。在Keil里进入Options for Target->Debug->Settings,如果SW Device列表里什么都没有,说明调试器压根没连上芯片。如果这里能显示一个ARM核心的ID,说明连接正常,问题可能在Flash Download的配置上。
还有一个非常隐蔽的杀手:芯片被读保护了。当你在STM32CubeProgrammer里不小心勾选了Read Out Protection(RDP)并设为Level 1,之后再用Keil烧录就会报No target found。恢复的办法是用STM32CubeProgrammer切换到Option Bytes选项卡,把RDP等级改为AA(即Level 0),执行一次全片擦除。这也可以解释为什么有些人前一天烧录正常,第二天突然连不上芯片——多半是折腾选项字节时打开了读保护。
6.2 连线全对还是找不到目标——可能你把SWD引脚给复用了
这是我在实际项目中踩过最惨的坑。当时做一块控制板,为了省引脚,把PA13(SWDIO)配置成了普通GPIO输出,直接驱动LED。编译烧录毫无问题,程序也跑得欢。第二天改需求,想重新烧录固件,Keil直接报错找不到目标。当时第一反应是芯片坏了,折腾了半个小时,后来才反应过来——SWD引脚被程序本身占用了。
解决思路有两个。一个是通过串口ISP模式把引脚复位程序烧进去,释放SWD引脚。另一个更简单粗暴但非常实用:把BOOT0拉高,在系统存储区的Bootloader模式下,SWD引脚默认是调试功能,此时用串口或直接连ST-Link把新固件烧进去,烧完再把BOOT0拉低。
这里也建议初学者在项目早期,但凡引脚不紧张,都不要动PA13和PA14。不是不能复用,而是每次烧录都要走一遍救砖流程,太折腾了。
6.3 Keil能识别芯片但烧录时Flash Download failed
No target found解决了,又来一个新问题:Keil能识别到芯片,但一点下载就报Flash Download failed。常见的原因有三个。
第一个是编程算法不匹配,这个前面讲过,不赘述。
第二个是芯片Flash已被读保护或者写保护。写保护跟读保护不是一回事,写保护是针对特定Flash扇区的,官方库里有FLASH_Erase和FLASH_Program接口可能被配置过,这种需要先用官方工具解除保护。
第三个,也是很多人忽略的:复位电路的电容问题。STM32的NRST引脚上一般会接一个100nF的电容到地,这个电容是正常的。但个别设计会在NRST上加大电容(比如1uF),导致调试器复位时序不满足要求。这种情况烧录时报错Cannot access Memory或者下载超时。解决办法是在调试器设置里把Reset方式从Halt after Reset改成Connect under Reset,在复位期间建立连接,绕过复位时序问题。
6.4 烧录成功但程序不运行
烧录成功不等于万事大吉,程序不运行的情况太常见了。第一步确认BOOT0引脚状态,前面说过。第二步按一下复位键看能不能跑。第三步检查供电,用万用表量芯片VCC对GND电压是否是3.3V。第四步检查时钟,如果外部晶振没起振,而程序里配置的是外部高速时钟(HSE),系统会一直卡在HAL_RCC_ClockConfig的等待超时里。
最后一步,也是最容易忽略的:用调试器在线仿真看PC指针停在哪里。Keil里进入调试模式,全速运行,然后暂停,看反汇编窗口里PC指针的位置。如果停在某个HardFault_Handler,说明程序执行过程中发生了硬件异常,这时候要配合Call Stack窗口看调用栈。这个操作流程比盲猜有效得多,建议所有初学者都掌握。
7. 从Keil走向开源工具链——VSCode + GCC烧录实践
7.1 为什么要换工具链
Keil很好用,但它的编辑器体验、代码补全、版本管理协作体验,和现代化IDE相比确实有差距。当你开始接触更大的项目、需要和同事协作、或者想用Git做版本控制的时候,Keil的劣势就很明显了。另外Keil的AC5编译器对C99/C11的支持并不完整,一些第三方库编译会报语法错误。
开源的替代方案是:VSCode + ARM GCC工具链 + OpenOCD + ST-Link。这套组合完全免费,跨平台,配置好了之后编译烧录用命令行一条龙搞定。
7.2 环境搭建与工程配置
先用STM32CubeMX生成一个Makefile工程,这个操作和生成MDK工程类似,只是工具链选Makefile。生成后,需要安装几样东西:ARM GCC编译器、OpenOCD、VSCode的C/C++扩展和Cortex-Debug扩展。
编译过程在VSCode的终端里执行make,生成.elf和.hex文件。烧录用OpenOCD,命令长这样:
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg -c "program build/stm32_f103.elf verify reset exit"解释一下这条命令:-f interface/stlink.cfg指定调试器接口配置,-f target/stm32f1x.cfg指定目标芯片配置,program命令会把.elf文件烧录进Flash,verify在烧录之后做校验,reset让芯片复位运行,exit退出OpenOCD。
如果不想记命令,可以配置VSCode的tasks.json,把编译和烧录做成两个任务,按Ctrl+Shift+B一键编译,再按Ctrl+Shift+P执行烧录任务。
7.3 烧录Vector表偏移这个隐藏坑
用开源工具链烧录时有一个Keil下很少遇见的坑:如果程序里使用了Bootloader,APP程序的起始地址不是0x08000000而是0x08008000这类偏移地址,必须在代码里设置向量表偏移。Keil下这个配置在Options for Target->Target的IROM1起始地址里改,而GCC工具链下要在链接脚本.ld文件里修改FLASH的ORIGIN,并且在SystemInit函数里调用:
SCB->VTOR = APP_ADDRESS;这个操作不下于几十次有人问为什么烧录成功但程序跑飞,最后查下来都是向量表没有重定位。不过如果你是烧录整个固件到Flash起始地址,不存在这个问题。
8. 源码层面的烧录前自检清单——把这些做到位,烧录失败率降低九成
8.1 编译信息里隐藏的健康指标
每次编译完成后,Keil的Build Output窗口会输出一行类似这样的信息:
Program Size: Code=8720 RO-data=512 RW-data=280 ZI-data=1272把这些数值记下来,每次改动代码后对比一下。如果ZI-data突然增长了几个KB,说明你无意中声明了一个很大的全局数组。如果Code大幅增长而功能没增加多少,检查是否无意中引用了某个庞大的库函数。学会看这行输出,等于给自己的程序做了一个简单的“健康体检”。
8.2 硬件连接自检的固定动作
我现在的烧录操作流程已经固化成这样了,供你参考:
- 用万用表蜂鸣档检查ST-Link与目标板的SWDIO、SWCLK、GND、3.3V四根线是否全部连通,防止杜邦线内部断裂。
- 测量目标板VCC对GND电压,确认芯片供电正常。
- 确认BOOT0处于低电平。
- 插上ST-Link,在设备管理器确认识别正常。
- 打开Keil,确认调试器Settings里能识别到目标芯片。
- 编译,确认
0 Error。 - 下载,观察下载日志是否出现
Verify OK。
这七步全部做完再写代码调逻辑,能省掉大量无意义的排查时间。如果你总是烧录失败,不妨把这套流程作为默认动作——尤其是第2步,我一直觉得供电问题占烧录失败原因的三成以上。
8.3 救砖思路:SWD被复用、读保护开启、代码进HardFault
最后把救砖三板斧列在这里,遇到对应情况直接用。
| 故障现象 | 首要排查 | 第一梯队方案 |
|---|---|---|
| 调试器完全找不到芯片 | 供电、SWD接线、BOOT0 | 拉高BOOT0,串口ISP烧录恢复固件 |
| 能识别芯片但下载失败 | 编程算法、读保护 | STM32CubeProgrammer解除RDP,恢复选项字节 |
| 烧录成功但运行异常 | BOOT0状态、外部晶振、堆栈溢出 | 在线仿真看PC指针位置与调用栈 |
其中第一行和第二行的方案本质上都是“绕过问题”:不依赖原有SWD连接方式,改从系统存储区启动。STM32出厂自带的Bootloader在这个时候是真的能救命。
9. 一条贯穿始终的经验:烧录问题九成出在物理层,而不是软件层
从源码到烧录,这条链路说长不长,说短不短。但绝大多数问题,最后追查到的根源都不是代码逻辑,而是物理层:电压不对、地线没共、引脚接反、连接线断路。这不是玄学,而是烧录属于芯片调试接口的底层操作,它在软件栈的最底部,任何硬件层面的不稳定都会先在这里暴露出来。
如果你也想培养自己的排查能力,我建议不要只依赖Keil的图形界面,试着在STM32CubeProgrammer的命令行模式下敲一次烧录命令,再通过OpenOCD的命令行方式烧录一次。当你能在命令行下控制整个过程,对芯片启动流程和调试接口协议的理解会有质的提升。工具永远是手段,对这个过程的底层理解才是真正属于自己的东西。
以后遇到烧录失败不要慌,从供电开始,一路查下来,大多数问题都能解决。折腾过几个来回之后,你会发现自己对STM32的理解比只写业务代码的人深得多。