1. 为什么搞了这么多年开发,我还是要专门聊聊STM32烧录
很多人觉得烧录固件嘛,不就是Keil里点一下Download,或者拿个ST-Link插上去的事?说实话,这个想法在纯开发阶段没毛病,但真正做过项目交付、去过产线、帮人救过砖之后,你会发现烧录这件事的水比想象中深得多。我最早接触STM32时也觉得烧录是最无聊的一步,直到有次在客户现场,板子没有留SWD接口、电脑上也没装仿真器驱动,手头只有一个USB转串口模块,那一刻才意识到:串口ISP烧录,或者说CoFlash这类工具,才是真正能在关键时刻救命的硬功夫。
CoFlash是ST官方提供的Flash烧录工具,走的是STM32芯片出厂自带的System Bootloader,不需要额外的仿真器,只需要一个串口就能完成擦除、烧写、校验这一整套流程。它的典型应用场景包括:新板子第一次烧录、维修板子时芯片被锁死、产线批量烧录、以及在没有ST-Link/J-Link的环境下救急。我从实际使用中的体会是,很多朋友在开发板上用串口下载一次成功,就以为会了,但换到自己的板子、换个芯片型号、或者遇到读保护,立刻就抓瞎。这篇东西就是按我踩过的坑和验证过的路子,把这套流程完整拆开讲清楚,适合刚入门的朋友,也适合想搭产线烧录流程的工程师,甚至搞维修的同行都能找到有用的一节。
在正式开始之前,先摆一个我的核心观点:烧录不是点按钮,而是“连接、握手、擦除、写入、校验、复位”这一整条链路。哪一环都不能省,哪一环出了问题都有对应的排查思路。下面我会从准备阶段讲起,一直讲到产线提效和OTA的扩展思路,尽量做到每一步都有依据、有参数、有踩坑记录。
2. 烧录之前先把这几件小事搞定,不然后面全是眼泪
2.1 驱动安不对,后面全白费
CoFlash烧录走的是串口,所以第一件事不是打开软件,而是让电脑能正确识别到你的串口设备。常见的USB转串口芯片就那么几种:CH340、CP2102、FT232,还有STM32板载的ST-Link虚拟串口。我建议你装完驱动之后,打开设备管理器,展开“端口(COM和LPT)”这一项,确认能看到类似“USB-SERIAL CH340 (COM3)”这样的条目。如果这里压根没有设备,或者有个黄色感叹号,那CoFlash这边怎么折腾都白搭。
这里有一个很容易被忽略的细节:很多新电脑尤其是笔记本,USB口供电能力有限,或者你用的是那种只能充电不能传数据的USB线,会导致串口芯片枚举失败。我帮人排查过不少“软件打不开端口”的问题,最后发现是换了一根手机充电线就解决了。判断方法很简单:插上USB线后,设备管理器里有没有任何新设备跳动,如果没有,八成是线的问题或者接触不良,不是驱动的问题。
驱动装好之后,还要注意串口号。CoFlash和一些串口助手对COM口超过10的情况偶尔会有兼容问题,如果设备管理里显示COM11甚至更高,建议手动改小。在设备管理器里右键端口设备,进入“端口设置”的“高级”选项,把COM端口号改成COM1到COM8之间的值,改完重启一下软件,能避开很多莫名其妙的连接报错。
2.2 硬件接线:别以为TXD接TXD就能通
CoFlash烧录STM32,走的是芯片内置的Bootloader引导程序,默认的通信接口通常是USART1,也有部分型号支持USART2或USART3,具体要看对应型号的Reference Manual里的System Bootloader章节。以最常见的USART1为例,对应的引脚是PA9(TX)和PA10(RX),你需要用USB转串口模块和板子做交叉连接:模块的TX接板子的RX(PA10),模块的RX接板子的TX(PA9),然后GND一定要共地。这个“交叉连接”是新手最容易搞反的地方,很多人拿着模块上的TXD去接板子上的TXD,结果当然是信号各说各话,完全不通。
还有电平匹配的问题。老一些的USB转串口模块有5V和3.3V两种电平选项,STM32的IO是3.3V逻辑,虽然很多STM32引脚标注“5V容忍”,但那是针对输入场景,串口通信这种双向信号,稳妥的做法是把模块跳线帽拨到3.3V。我见过不止一次,因为模块输出5V电平,导致通信时好时坏、偶尔校验失败的案例。如果你用的是CH340G这类模块,通常板上有3.3V输出引脚,供电和电平都从那里取,最省心。
接线顺序建议:先接GND,再接TX和RX,最后再考虑要不要接VCC。如果目标板是独立供电的,可以不接模块的VCC;如果板子没有供电,可以从模块的3.3V引脚引一根线给板子供电,但前提是你的模块稳压能力足够,而且板子整体电流不大。有些带WiFi模块、屏幕的板子,一上电瞬间电流能飙到几百毫安,这时候用模块供电就不可靠了,建议单独供电,只共地。
2.3 BOOT引脚:串口ISP的灵魂开关
串口ISP能工作的前提是芯片进入了System Bootloader。这就要说到BOOT引脚的配置了。STM32大部分型号有BOOT0和BOOT1两个引脚(BOOT1在部分型号上复用为PH2/PD2等,要看具体芯片),两个引脚的组合决定了芯片复位后从哪里启动:
| BOOT0 | BOOT1 | 启动区域 |
|---|---|---|
| 0 | x | Main Flash Memory,正常运行用户程序 |
| 1 | 0 | System Memory,运行出厂Bootloader |
| 1 | 1 | SRAM,调试用,一般不用 |
所以烧录前,把BOOT0拉高到1,BOOT1拉低到0,然后给板子断电重新上电,或者按一下复位键,让芯片重新启动,这时候芯片跑的就是出厂Bootloader,CoFlash连上之后才能握手成功。这里有个实战技巧:很多开发板上BOOT0和BOOT1都有跳线帽或拨码开关,但如果你用的是自己画的板子,就要仔细看原理图确认引脚有没有上拉或下拉电阻,别让引脚悬空。悬空状态下电平不确定,可能时好时坏,排查起来相当折磨人。
另外一个关键点:进入Bootloader之后,芯片的USART1是处于等待接收状态,但它不会主动发任何数据。所以你在串口助手里是看不到任何提示的,必须在CoFlash里主动发连接命令,才能得到回应。很多新手误以为“没反应就是没进Bootloader”,其实不是,得靠软件去“叫醒”它。
3. CoFlash实操全流程,从打开软件到程序跑起来
3.1 建立连接:让芯片回应你
设备驱动装好、接线无误、BOOT引脚配置正确之后,就可以打开CoFlash了。不同版本的界面差异不小,但核心参数就那么几个:串口号、波特率、校验位、数据位、停止位。串口号选你设备管理器里看到的那个;波特率我建议先用115200,如果目标板用的外部晶振不是标准的8MHz,或者连接线比较长、干扰大,再往下降,比如57600甚至9600。
填好参数后点“连接”或者“Connect”,工具会发送一个特定的握手命令给芯片。正常的话,界面上会弹出一行芯片信息,包括Flash容量、芯片型号、系统Bootloader版本等。这一步非常关键,相当于你和芯片之间建立了“这是自己人”的确认。如果读取到的Flash容量和你芯片型号对不上,先不要继续烧录,检查一下是不是选错了芯片型号,或者买到的是打磨片、翻新片。我在实际工作中就遇到过打着“STM32F103C8T6”旗号、实际识别出来Flash只有32K的芯片,这种片子做项目就是定时炸弹。
提示:连接成功之后,不要急着拔线。CoFlash在连接状态下会占用串口,如果程序里也要用串口调试,记得先把CoFlash断开,否则端口被占用,你的调试助手会一直“打不开串口”。
3.2 选对固件格式:Hex和Bin的区别要拎清
连接成功后的下一步是加载固件。CoFlash通常支持.hex和.bin两种格式,这里我强烈建议优先用hex文件。原因很简单:hex文件里每一行都带了起始地址信息,工具可以精确知道这段数据要写到Flash的哪个位置,你不需要额外指定起始地址,出错概率低。bin文件就是纯粹的二进制数据,不包含地址信息,烧录的时候必须手动指定起始地址,STM32最常见的默认起始地址是0x08000000,也就是主Flash的起始位置。
如果你是打算做Bootloader+App的分区设计,bin文件的优势就出来了:你可以把App编译到0x08008000这样的偏移位置,然后用bin格式烧到对应地址。这时候如果还用hex,工具也能识别地址,但需要你确认链接脚本里的偏移设置和实际烧录地址一致,否则App跑起来就是HardFault。我的习惯是:常规全片烧录用hex,分区烧录用bin加显式地址。
3.3 擦除、烧写、校验,三步一步都不能省
加载完固件之后,界面上通常有三个动作:擦除(Erase)、烧写(Download/Program)、校验(Verify)。很多人图省事,选择“全自动”,也就是让工具先自动擦除再写入再校验,这没问题,但我建议你在手动模式下,至少把这三个步骤都走一遍,每一步都看返回结果,这样才能在出问题的时候知道卡在哪一环。
擦除这一步,工具一般会问你是“全片擦除(Full Chip Erase)”还是“扇区擦除(Sector Erase)”。全片擦除最彻底,整个Flash变成0xFF,耗时也最长,但可靠性最好。扇区擦除则可以只擦需要写的地方,速度更快。我的经验是:第一次烧录或者产品升级,用全片擦除最干净,避免旧代码残留导致一些“改了代码但现象没变”的灵异事件;量产阶段为了提高效率,可以评估用扇区擦除,但前提是你的固件在Flash里的布局是稳定的。
烧写过程中,工具会显示进度条和当前写入的地址范围。这个阶段不要碰USB线,不要给板子断电。我见过不少人在烧写到一半的时候觉得“卡住了”,其实只是大容量芯片写入速度慢,尤其是串口波特率低的时候,烧一个256K的固件可能要好几分钟。这时候的正确做法是耐心等待,或者干脆把波特率调到921600(如果芯片Bootloader和你的串口模块都支持)。注意,不是所有芯片的Bootloader都支持超过115200的波特率,建议先查一下芯片对应的AN文档,别盲目拉高。
校验是很多人会略过的一步,但恰恰是我最看重的一步。校验的原理说穿了很简单:工具把芯片里的数据读出来,和你要烧录的文件逐字节对比,完全一致才算通过。这一步能发现写入过程中因为干扰、供电波动导致的“静默错误”——表面上看烧录成功了,实际上Flash里有几个字节是错的,程序运行起来就是莫名其妙的死机或功能异常。在我经手的项目中,凡是大批量烧录的板子,校验环节一律不省,宁可在产线上多花两分钟,也不能让一批坏板子流到客户手里。
3.4 收尾动作:复位运行才是真正的终点
烧录和校验都通过之后,工作还没结束。你需要断开CoFlash的连接,把BOOT0跳线恢复到0(或者拨回低电平),然后给板子重新上电,或者按一下复位键,让芯片从Main Flash启动,这时候你的程序才算真正跑起来。这个“断开连接再复位”的顺序不要搞反,如果CoFlash还占着串口,而你的程序里也初始化了同一个串口,那么程序一启动就会遇到串口被占用的问题,结果可能是程序跑飞或者输出乱码。
复位之后,怎么确认程序真的在跑?最直接的方法是用串口助手看程序打印的启动日志,或者用示波器/万用表量一下某个IO口的电平变化。如果什么都没发生,不要慌,检查BOOT0是不是真的拉低了,芯片的供电和复位电路是不是正常,程序里有没有配置Clock和启动文件。很多第一次接触的板子,程序没跑起来完全是硬件问题,比如复位电容没焊、晶振不起振,这些和烧录本身没有关系,但往往被误认为是“烧录失败”。
我把整个正常流程整理成下面的对照表,方便你实操时逐项确认:
| 阶段 | 操作 | 成功标志 |
|---|---|---|
| 上电前 | BOOT0=1,BOOT1=0,共地,TX/RX交叉 | 无特别现象 |
| 连接 | 选对COM口和波特率,点Connect | 识别到芯片Flash容量 |
| 加载 | 选择hex文件 | 能显示程序占用地址范围 |
| 擦除 | 全片或扇区擦除 | 擦除完成后无报错 |
| 烧写 | 点击Program/Download | 进度条走完,无Error |
| 校验 | 点击Verify | 显示校验一致 |
| 复位 | BOOT0=0,重新上电 | 程序按预期运行 |
4. 踩坑现场:连接失败和烧录异常,我的完整排查链路
这一节我想讲几个真实遇到的故障场景。不是直接给结论,而是把排查思路走一遍,因为每个问题的背后可能不止一个原因。
4.1 “无法识别USB设备”与“端口打不开”的连锁反应
症状:插上USB转串口模块后,电脑弹出“无法识别的USB设备”,或者设备管理器里显示未知设备;CoFlash点连接直接报“端口打开失败”。
我的排查顺序是这样的:第一步先换USB口和USB线。很多台式机前面板USB口供电不稳,换到后面板或者换根带屏蔽的短线往往就好了。第二步看设备管理器里有没有新的COM口出现,如果没有,可能是USB转串口芯片虚焊或者烧了。第三步,如果设备管理器里出现了设备但CoFlash打不开,那多半是端口被其他程序占用了。这时候把所有串口工具、Keil的调试器、浏览器里开的串口终端全部关掉,再试一次。还有一种隐蔽情况:之前某个程序异常退出后,串口句柄没有释放,系统认为端口还被占用。
如果以上都排除了还不行,我会把串口助手单独打开,以同样的波特率手动给芯片发一串0x7F数据试试。因为STM32的Bootloader在连接阶段会检测特定的数据模式,如果串口助手能收到芯片的应答(哪怕是乱码),说明硬件链路是通的,问题出在CoFlash的软件参数上。这个“分离开硬件和软件问题”的思路,是我排查烧录问题最常用也最有效的方法。
4.2 握手超时,芯片不理我怎么办
症状:CoFlash点连接后,一直转圈,最后提示“Timeout”或“No response from target”,芯片信息读不出来。
先说一个多数人都会忽略的原因:BOOT0虽然拉高了,但芯片根本没有复位。Bootloader是芯片复位后从System Memory启动才进入的,如果你只是把BOOT0从0拨到1,没有重新上电或者没有按复位键,芯片还跑在原来的程序里,自然不会响应Bootloader的握手。所以出现连接超时的第一反应,应该是:我再按一次复位键,或者把板子完全断电再上电。
第二个常见原因是串口没有真正交叉。我遇到过一个朋友,拿着板子上的“USB转串口”接口,以为直接插线就行,实际上他那个板子虽然有板载串口芯片,但出厂固件已经把串口重映射到了USART2,而Bootloader默认监听的是USART1,自然对不上。这种时候要么用外部USB转串口模块接PA9/PA10,要么查对应的Bootloader引脚映射,改接正确的串口。
第三个原因比较隐蔽:芯片的Bootloader已经被擦除了。你没看错,有些芯片的System Memory区域虽然出厂就写好了Bootloader,但如果你之前用某些工具勾选了“擦除整个芯片包括System Memory”,或者芯片本身是二手翻新片,Bootloader可能已经不完整了。这种情况下,串口ISP是永远连不上的,只能改用SWD接口配合ST-Link/J-Link先恢复Bootloader或者直接烧录用户程序。这也是为什么我一直强调:手头要留一个ST-Link,哪怕你日常用的是CoFlash串口烧录。
4.3 连接成功但校验不过,大概率是硬件传输问题
症状:CoFlash能读到芯片信息,擦除也正常,烧写过程也没报错,但最后校验失败,或者烧到一半进度条卡住,偶尔还会随机地址报错。
这种问题在我这里几乎都是硬件传输质量问题,不是软件问题。最常见的元凶是USB转串口模块本身输出电平不稳定,或者连接线太长、太细、没有屏蔽。解决办法是按顺序尝试:第一,降低波特率,从115200降到57600甚至38400,很多时候校验问题就是高速传输下信号畸变导致的;第二,使用更短的杜邦线,最好小于20厘米,并且把GND线接得尽量短;第三,在USB转串口模块的VCC和GND之间并一个10uF左右的电容,给通信过程一个稳定的参考电压,这个土办法我试过很多次,确实能改善校验失败的情况。
另一种情况是芯片Flash本身有问题,尤其是拆机片、翻新片。我一再提醒大家,采购芯片一定要走正规渠道。某次项目里用了一批说是全新原装但价格便宜得离谱的STM32,结果烧录成功率只有九成左右,不是这里校验失败就是那里擦除超时。后来换成正规代理渠道的片子,同样程序、同样产线,一次通过率几乎100%。芯片水很深,烧录环节其实是检验片子真伪的一个有效手段。
4.4 芯片被读保护/写保护锁死,怎么解锁
症状:CoFlash连接成功,但每次尝试擦除或烧写都报“Protected”或者“Read Protection”相关错误。
STM32的Flash有读保护(RDP)和写保护(WRP)机制,目的是防止固件被恶意读取或篡改。但有时候开发者自己不小心开启了读保护,或者买到的板子出厂就带保护,就会导致无法用串口Bootloader擦写。CoFlash这类通过Bootloader操作的工具,遇到读保护基本是无能为力的,因为Bootloader本身在System Memory里,它能访问用户Flash的能力受到了保护级别的限制。
解决办法是用ST-Link连接到SWD接口,然后用STM32CubeProgrammer的“Option Bytes”/“Access level”功能把读保护级别从Level 1降到Level 0。这里要特别提醒:降低读保护级别时,STM32的机制是全片擦除用户Flash,也就是说你板子里的固件会没了,这是正常现象,不是工具把你的代码弄丢了。所以在解锁之前,务必确认自己手上有固件的备份。另外,把读保护级别从Level 2降回Level 0是不可能的,Level 2是永久保护,一旦设置就再也无法通过任何方式恢复调试和再烧录,这个选项在生产时千万别乱点。
如果你的板子没有引出SWD引脚,那芯片基本就废了,除非你有能力用飞线的方式去戳芯片底部的焊盘。这个教训我吃过大亏:曾经一批小板为了省面积,没引出SWD,结果产线上有个别板子程序跑飞,想重新烧录却发现连ST-Link都无处安放,最后只能报废。现在我做设计的原则是:即使主打串口ISP烧录,也一定会把SWD的四个引脚引出来,哪怕只是一个4pin的测试点。
4.5 烧录成功但程序不跑,别急着怀疑工具
症状:所有步骤都成功,校验也通过,但复位后程序就是没有任何反应。
这种问题我会先用“最小系统排除法”排查。第一步,确认BOOT0确实回到了0,并且芯片是从Main Flash启动。我见过跳线帽接触不良,看起来拨到了0,实际上引脚还是被拉高。第二步,用示波器或万用表量芯片供电引脚,确认3.3V正常,纹波不要太大。第三步,检查复位电路,NRST引脚有没有被异常拉低。第四步,如果板子有外部晶振,检查晶振是否起振,或者程序里配置的是内部RC还是外部晶振,如果程序里配置用外部高速晶振但板子上没焊接晶振,那程序会卡在时钟初始化里,表现就是“烧录成功但程序不跑”。
还有一个和烧录本身直接相关的原因:固件文件的起始地址不对。如果你加载的是bin文件且起始地址填错了,程序会被写到一个不是启动地址的位置,复位后CPU从0x08000000取到的全是0xFF,自然跑不起来。hex文件一般不担心这个问题,但如果你用第三方工具做过hex转bin,也要确认一下地址偏移是否保留。
最后,确认一下你的Keil工程里是否配置了“Reset and Run”之类的选项。有些工程烧录后没有自动复位,需要手动复位一下才能运行,这听起来像是小事,但在产线环境下,工人不一定懂这个区别,所以最好是把“烧录后自动复位”作为标准流程固定下来。
5. 从单板调试到产线提效,CoFlash的进阶玩法与边界
5.1 批量烧录,用CoFlash的命令行模式
单板调试时图形界面很直观,但到了产线,一次要烧几百上千片板子,再让人坐在电脑前点点点就太浪费了。CoFlash支持命令行模式,可以把连接、擦除、烧写、校验一气呵成,做到无人值守。命令行的大致形式是类似的:
CoFlash -c port=COM3 br=115200 -e all -w firmware.hex -v不同版本参数会有点差异,但核心逻辑一致:指定串口、波特率、擦除方式、写入文件、是否校验。实际使用中,我会把命令行封装成一个批处理脚本,工人只需要双击脚本,插上板子按一下回车,看到“Verify OK”就换下一块板子。脚本里还可以加一个简单的日志输出,把每次烧录的时间、结果、序列号记录到文件里,方便后面追溯。对品质管理来说,这个“可追溯性”比烧录本身还重要。
为了提高效率,产线上建议一次性把串口波特率提到芯片支持的上限,并且使用质量好的USB转串口模块。市面上一些十几块钱的模块,平时调试看不出问题,高波特率下丢包率会明显上升。批量场景下,我更倾向于用带隔离的工业级串口模块,成本高一点,但烧录成功率带来的停工损失远大于这个差价。
5.2 生成Bin文件的方法,Keil里一个命令搞定
CoFlash加载bin文件时需要指定起始地址,但很多人卡在了“怎么生成bin文件”这一步。Keil里其实很简单,在Options for Target的User标签页里,After Build/Rebuild一栏勾选Run #1,然后在输入框里填上:
fromelf --bin --output=.\Output\firmware.bin .\Output\firmware.axf注意路径要改成你自己工程的实际输出路径。这样每次编译完,Keil就会自动生成一个和你当前Link脚本地址一致的bin文件。如果你的工程做了Bootloader和App分区,App的Link地址设在0x08008000,这个bin文件也天然带有这个偏移,烧录时把起始地址填成0x08008000即可,不需要手动合并。
如果你用的是STM32CubeIDE,也可以在Post-build steps里加一行类似命令。自己搭Makefile的朋友更简单,arm-none-eabi-objcopy一把梭:
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin生成bin是基本功,尤其是做OTA升级时,升级包通常都是bin格式,因为它体积小、结构简单,适合拆包和传输。
5.3 和STM32CubeProgrammer比,CoFlash还有什么不足
官方现在主推的烧录/调试工具是STM32CubeProgrammer,它同时支持ST-Link、UART、USB DFU等接口,图形界面和命令行都很完善。CoFlash在串口ISP这个领域做得简单直给,但在图形化配置Option Bytes、读取保护级别设置、以及某些新型号芯片的支持上,逐渐跟不上官方工具的更新节奏了。
我的建议是:如果你只是偶尔烧个串口固件,CoFlash足够顺手;如果你要设置读保护、烧录Option Bytes、处理不同接口的切换,那就老老实实上STM32CubeProgrammer。产线批量场景,我反而觉得CoFlash的轻量命令行更合适,因为依赖少、启动快、不会因为界面更新导致工位上的操作习惯要跟着变。
工具是死的,思路是活的。我更建议你把整个烧录流程理解为:连接层(串口/ST-Link/USB)→ 协议层(Bootloader协议/SWD协议)→ 应用层(擦写校验)。无论换成哪个工具,这个三层结构是不会变的。
5.4 烧录只是起点,OTA才是产品化的那一关
说到底,串口ISP烧录是开发和产线的基础保障,但产品一旦到了用户手里,你不可能让用户拆机接串口来升级固件。所以现在很多项目都会在出厂固件里预置一个Bootloader,运行期通过WiFi、蓝牙、LoRa或者4G网络接收升级包,写入到App分区,实现OTA远程升级。
OTA的基本架构是:Bootloader区(负责校验和跳转)+ App区(真正的业务逻辑)+ 参数区(保存版本号、升级标志等)。这种架构下,串口ISP烧录的价值体现在两个阶段:一是第一次给空片烧Bootloader,二是产线预烧App或恢复出厂固件。我遇到过不少朋友把Bootloader和App写到同一个地址导致启动失败,归根结底就是对Flash地址规划不清晰。建议在工程文档里画一张Flash分区表和一张启动流程图,哪怕不拿给别人看,自己半年后回来看代码也会感谢当时的自己。
从我个人的经验看,用串口ISP把整个启动流程摸一遍,比直接用仿真器一步步跟更容易理解STM32的启动机制:你会亲眼看到BOOT引脚如何决定启动区域、Bootloader如何通过串口收数据、校验机制如何保证数据完整性。这些底层理解,会在你面对各种稀奇古怪的板子问题时派上大用场。
最后分享一个我自己一直在用的习惯:烧录完成之后,不要急着拔线,先用串口助手打开对应端口,观察板子上电后有没有预期的启动日志。这一步只花十秒钟,却能在板子还没离开你手的时候,就把“烧录OK但程序有问题”和“硬件有问题”区分开。很多时候,产品到了客户手上才发现问题,代价就完全不一样了。