ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32F103C8T6固件拷贝实战:从读保护到完整克隆

STM32F103C8T6固件拷贝实战:从读保护到完整克隆 前阵子有个朋友找到我说手里有一块STM32F103C8T6最小系统板也就是常见的Blue Pill里面烧了一段非常重要的固件但原来的工程文件已经找不到了。他手头还有几块一模一样的空板子想把这颗芯片里的固件原封不动复制过去问我有没有办法。这其实是一个非常典型的需求——固件备份、量产复制、或者设备维修时的芯片替换都需要把固件从一颗STM32完整拷贝到另一颗。这篇内容我就把这套操作从头到尾拆开讲清楚包括读保护的处理、工具选择和踩坑记录认真看完你也能自己搞定。先说结论STM32F103C8T6的固件拷贝完全可行核心工具就是STM32CubeProgrammer加一个ST-Link调试器通过SWD接口把Flash内容完整读出来保存为bin文件再写到目标芯片里。整个过程大概十分钟就能跑通但这里面有几个关键细节如果不注意很容易读出来一堆0xFF或者写完不运行我下面会逐一展开。1. 动手前先搞清楚固件到底存在哪拷贝的本质是什么1.1 STM32F103C8T6的Flash结构别只盯着一颗芯片看STM32F103C8T6是STM32F1系列里的经典型号Cortex-M3内核主频最高72MHzFlash容量标称64KBSRAM是20KB。它内部存储器的布局大概是这样主Flash区从地址0x08000000开始到0x0800FFFF结束正好64KB系统存储器在0x1FFFF000里面是出厂固化的Bootloader选项字节在0x1FFFF800存放读保护等级、硬件看门狗配置等信息最后还有一个96位的唯一ID区在0x1FFFF7E8附近。固件拷贝的核心操作就是把0x08000000到0x0800FFFF这一段主Flash里的内容完整读出来保存成文件再原样写进另一颗芯片的主Flash里。这个思路听起来简单但实际操作中要注意固件不是单纯的一堆二进制代码它里面可能包含中断向量表、程序代码、常量数据有时候还会有一小段用于保存运行时参数或者校准值的区域。如果你只拷贝代码区而漏掉了末尾的数据区设备跑起来可能功能正常但参数全部丢失这就很尴尬了。1.2 什么场景下需要拷贝固件什么场景下不建议硬来我总结了一下最常见的固件拷贝需求有三类。第一类是量产复制你做了个产品焊了几百块板子总不能每块都用IDE重新编译下载一遍直接把一台调好的样机固件读出来批量烧写效率高很多。第二类是固件备份客户现场设备偶发故障你想在返修之前把Flash里的当前版本完整扒下来留底防止后面升级出问题回不去。第三类是芯片替换某颗芯片烧了、锁了或者性能不良换一颗新芯片后要把旧固件移植过去。但有一种情况我要特别提醒如果你的固件里做了芯片唯一ID绑定校验直接从A芯片拷贝到B芯片是跑不起来的。STM32每颗芯片都有96位唯一ID很多商业固件会用这个ID做授权绑定拷贝过去的固件检测到ID不匹配就会主动罢工。这种情况只有在原厂允许的前提下找原厂重新授权或者想办法绕过校验这涉及逆向工程合规问题自己判断不是简单拷贝能解决的。1.3 为什么读保护是拷贝路上最大的拦路虎STM32F103系列从硬件层面就支持Flash读保护也就是RDPRead Protection。读保护可以设置三个等级Level 0是完全不保护调试器和程序都能正常读FlashLevel 1是禁止通过调试接口读写Flash但程序自身还能访问Level 2是最高等级调试接口完全锁死而且这个等级不可逆设置之后永远无法降级。很多商用设备出厂前会设置Level 1读保护防止别人用调试器一把梭把固件扒走。你拿ST-Link连上去会发现能连上内核但读Flash读出来的全是0xFF或者直接报错。这种情况下想拷贝固件唯一的办法就是执行Mass Erase全片擦除来解除读保护但代价是Flash里所有内容会被清空。换句话说加密的固件在物理层面就是没法原样拷贝的这个问题后面我会单独讲。2. 工具选型和硬件接线这一步决定了你后面是省心还是折腾2.1 软件就一个STM32CubeProgrammer其他都是辅助市面上能读STM32 Flash的工具不少常见的有STM32CubeProgrammer、STM32 ST-LINK Utility、OpenOCD还有各种第三方脱机烧录器。但如果只让我选一个我首推STM32CubeProgrammer。因为它免费、跨平台、官方持续维护读取和写入都做得非常直观而且对F1系列的支持很完整。STM32 ST-LINK Utility其实也够用读固件、存bin、烧写都能做界面比CubeProgrammer更简洁但官方已经把它整合进CubeProgrammer了新项目建议直接用前者。OpenOCD适合喜欢命令行的高手灵活性最高但对新手来说配置有点劝退。我这里的所有操作都以STM32CubeProgrammer为例你从ST官网下载安装就行版本新一点就好基本流程多年没变过。2.2 硬件接线四根线搞定但这里最容易翻车硬件需要一个ST-Link调试器V2或V3都行淘宝上几十块的国产ST-Link V2克隆版也能用然后接四根线SWDIO、SWCLK、GND、3.3V。连接关系如下ST-Link引脚STM32F103C8T6最小系统板引脚SWDIOPA13SWCLKPA14GNDGND3.3V3.3V板子供电引脚接线看着简单但我见过太多人在这里翻车。首先是供电问题如果你的目标板是独立供电的那ST-Link的3.3V可以不接只接SWDIO、SWCLK、GND三根线避免两个电源打架如果板子没通电那就让ST-Link给目标板供电但要注意ST-Link的3.3V输出能力通常只有几百毫安带个最小系统板绰绰有余带带电机、屏幕之类的就可能掉电压这种情况还是外接电源靠谱但GND必须共地。其次是SWDIO和SWCLK这两根线建议越短越好我一般控制在10厘米以内。线太长的话SWCLK频率高一点就可能出现通信不稳定读一半掉线非常折磨人。如果手头只有杜邦线把频率调低到4MHz、1.8MHz甚至800kHz基本都能稳。还有一点要注意STM32F103C8T6默认的调试端口就是SWD不需要额外配置所以新板子拿来就能连接。除非之前的固件里把SWD引脚重新映射成了GPIO这种属于特殊情况需要先用Bootloader方式恢复后面我会提一嘴。2.3 读固件之前先确认芯片状态别上来就乱点在打开读操作之前有一个小步骤建议花十秒钟确认一下先用CubeProgrammer连上目标芯片看看左下角读出来的UID、Flash容量、芯片型号是否正确。如果连芯片型号都读不对要么接线有问题要么芯片已经锁死只能走特殊流程这种状态下直接操作可能会白费半天功夫。读出来芯片型号显示STM32F103C8Tx、Flash容量64KB那就可以放心继续。如果显示的是错误型号或者Flash容量明显不对先不要着急读优先解决连接问题。我遇到过几次国产GD32F103C8T6兼容芯片它们的Flash容量有些标称是128KB实际用起来其实能当128KB用但CubeProgrammer会识别成64KB这个不影响我们拷贝64KB固件只是要按实际识别的容量来操作别读超了。3. 用STM32CubeProgrammer完整读取固件的实际操作流程3.1 连接芯片CubeProgrammer里这三个选项别忽视打开STM32CubeProgrammer界面右侧是模式选择区。在ST-LINK标签页下有几个关键选项频率Frequency、接口Interface、模式Mode。第一次使用建议这样设置接口选SWD频率根据你的线材质量来短线高质量杜邦线可以选4MHz长线或者质量差的线选1.8MHzMode保持Normal。设置好之后点击右上角的Connect按钮。连接成功的话界面左侧会显示芯片信息包括Device ID、Flash size、UID等。这时候你会发现左下角有Address: 0x08000000和Size: 0x10000这样的字段这就是后面读写操作要用的起始地址和长度。0x08000000是Flash的起始地址0x10000是64KB对应的十六进制长度刚好匹配。连接这步如果失败最常见的提示是Error: Connection error或者Target DLL has been cancelled大概率是SWDIO/SWCLK接反、GND没共地、或者目标板没供电。把这些检查一遍一般都能解决。3.2 执行读取一次完整的Read操作包含哪些动作连接成功后点击界面上方菜单栏的Read按钮。CubeProgrammer会按照你在左侧设置的起始地址和长度从0x08000000开始把64KB的数据全部读出来。这个速度很快通常几秒钟就完成整个过程就是把Flash里的每个字节通过SWD接口搬出来。读取完成后需要把数据保存成文件。注意这里有一个非常重要的选择文件类型选Binary.bin不要选Intel Hex.hex或者Motorola S-Record.srec。bin文件是纯二进制数据烧写的时候直接按地址写入最省事。hex文件虽然也可以烧但它带地址信息如果后面处理不当容易写错位置。保存bin文件时记得起始地址填0x08000000长度填0x10000这样保存下来的文件正好是64KB的完整Flash镜像。提示保存的bin文件可以顺便用十六进制编辑器打开看一眼开头应该是中断向量表也就是一堆看起来像地址的数据。如果文件开头全是0xFF或者全是0x00说明读取过程有问题或者芯片被读保护了不要进入下一步先排查。3.3 额外备份选项字节和系统信息备份要备份全套主Flash读出来只是拿到了最基本的固件但严谨的备份应该把选项字节也保存下来。选项字节位于0x1FFFF800总共16字节里面包含读保护等级、硬件看门狗、独立看门狗、启动引脚配置等信息。在CubeProgrammer里点击左侧Option Bytes标签页可以看到当前读保护等级、各类配置位的状态。如果你想把选项字节也备份成文件可以记下这些配置的值或者用Read功能把这个地址段也读出来单独存一份。不过要说明的是对于简单拷贝固件这个需求来说选项字节一般保持默认值就行了除非源芯片设置了特殊的看门狗或者保护配置你才需要把这份配置也搬到目标芯片上。大多数情况下目标芯片保持默认选项字节直接把主Flash内容烧进去就能正常跑。另外系统存储器0x1FFFF000里的Bootloader是出厂固化的不需要也不能拷贝任何情况下都不要尝试读整个地址空间然后全盘写入那样反而会出问题。4. 把固件写入目标芯片擦除、下载、校验三步走4.1 烧写前必须执行的擦除操作别跳过把读出来的bin文件烧到目标芯片之前有一个强制步骤擦除目标芯片的Flash。STM32的Flash特点是只能把1写成0不能把0写成1所以如果目标芯片里残留了旧数据直接写入会出现0写不进1的问题导致最终数据混乱。CubeProgrammer的下载功能一般会提示你是否先执行擦除但保险起见我建议手动操作。连接上目标芯片后点击菜单栏的Erase按钮选择Full chip erase全片擦除点击确定。这个过程会把整个主Flash区域全部变成0xFF。擦除速度也很快几秒钟就能完成。擦除完成后可以再用Read功能确认一下如果读出来全是0xFF说明擦除成功这个步骤虽然看起来多余但能避免后续烧写后出现各种诡异问题。4.2 烧写bin文件地址填对一切才成立擦除完成后点击菜单栏的Download按钮或者用快捷键CtrlD不同版本略有差异。在弹出的文件选择框里选中你之前保存的bin文件。这里关键的一步来了下载地址必须填0x08000000因为bin文件是纯数据没有地址信息软件不知道往哪里写你必须手动指定起始地址。选好文件、填好地址点击Start按钮。CubeProgrammer会把bin文件的内容逐段写入目标芯片的Flash同时进行校验。这个过程比读取要慢一些64KB的数据大概也就十几秒。写入完成后界面会显示编程成功和校验成功的提示。看到校验成功说明目标芯片的Flash内容已经和源芯片完全一致了。烧写完成后记得把ST-Link从SWD接口断开然后给目标板重新上电看程序是否正常运行。如果正常恭喜你固件拷贝已经完成。如果没反应先别急看看是不是时钟频率、晶振配置这类问题我下面会详细讲。4.3 校验不是可选项我建议每次烧完都Verify一次CubeProgrammer自带Verify功能它会把当前芯片Flash里的内容和本地文件逐字节对比。我在实际项目里养成了一个习惯不管时间多紧烧完之后一定会点一下Verify。因为STM32的烧写偶尔会因为通信干扰出现个别字节错误虽然概率很低但一旦发生设备运行起来就是莫名其妙的问题而且极难排查。如果你的板子已经断开连接了重新连上点击菜单栏的Verify或者Read后将读出的数据与源文件手动对比选择同样的bin文件和地址0x08000000软件会自动比较。如果提示Verification OK那就万无一失了。这个步骤多花不到十秒钟但能省掉后面几小时的调试时间。5. 遇到读保护怎么办三种情况的处理思路和代价5.1 判断芯片是否设置了读保护看这个标志位就行当你尝试连接一颗疑似被保护的芯片时CubeProgrammer的界面会有明确提示。连接后在Option Bytes标签页里可以看到RDP读保护等级如果是0xAALevel 0说明没有保护随便读如果是其他值Level 1说明开启了读保护如果是0xCCLevel 2那就是最高保护硬件上已经不可逆了。还有一种简单粗暴的判断方法连接成功后直接读Flash如果读出来的数据大量出现0xFF而且文件大小看起来不太对劲大概率是被Level 1保护了。被保护的芯片在连接时通常也能正常识别芯片型号和UID就是读不出Flash内容。5.2 Level 1读保护的解除方法擦除是唯一出路如果芯片只是Level 1读保护想恢复可以正常读写方法是把RDP等级改回Level 0。在CubeProgrammer的Option Bytes页面里把读保护等级选为Level 0AA然后点击Apply。这里要特别强调一个硬件原理任何从Level 1降级到Level 0的操作都会触发芯片的全片擦除Mass Erase。这是ST在设计芯片时故意做的安全策略防止攻击者靠临时降级保护来读取加密固件。所以芯片原来的内容会全部被清空你之前想备份的固件自然也没了。换句话说Level 1的芯片在物理上没办法不擦除就读取这是铁律。所以遇到Level 1芯片你要做一个选择如果固件内容对你至关重要宁可不读也要保住设备运行那就不动它如果能接受擦除重烧那就先降级然后重新通过程序烧录一份你手头已有的固件或者走正版授权流程。5.3 Level 2读保护基本可以放弃这台芯片了Level 2是终极保护特征是RDP值为0xCC或者你在试图降级时软件直接提示Protection level 2 cannot be removed。这个级别的保护一旦设置JTAG/SWD调试接口永久关闭任何调试工具都无法再连接芯片也无法改回Level 0或Level 1。遇到这种芯片想拷贝固件是彻底没戏的。芯片本身的Flash内容无法读取而且后续调试也无法进行它在功能上就是一个黑盒。如果设备还能正常运行那就继续用如果设备坏了需要维修只能换新的芯片重新烧录原厂固件前提是你有。这个等级多用于防抄板、防逆向的高商业价值设备从安全角度讲是好事但从维修和备份角度讲确实非常头疼。6. 固件拷贝成功不等于万事大吉烧完跑不起来的四个常见坑6.1 读出来全是0xFF或写入不完整先怀疑连接质量这个坑我在开头提过但值得再强调一次。如果用CubeProgrammer读出来的文件中从某个地址开始连续出现大段0xFF但前面一部分看起来是正常数据这通常不是芯片里真没数据而是读取过程中通信出了问题SWCLK频率太高或者线太长导致部分数据传输错误。解决办法是降低SWCLK频率从4MHz降到1.8MHz或者更低重新读取一次。如果还是有问题换一根更短的杜邦线或者焊接一小段飞线代替杜邦线往往能立竿见影。我实测过同样一颗芯片用20厘米的杜邦线4MHz读出来就是有随机错误换成10厘米以内的线1.8MHz一切正常。这种软故障比硬故障更难排查因为它不会直接报错而是给你一个看起来差不多的文件烧到另一颗芯片上可能跑几个月才暴露问题。6.2 写入成功但目标板跑不起来检查目标板供电和BOOT0引脚烧写成功、校验成功、但目标板就是没反应这是新手最容易懵的情况。先别怀疑固件有问题按这个顺序排查第一看目标板供电是否正常3.3V对地电压稳不稳电源指示灯亮不亮第二检查BOOT0引脚正常运行时BOOT0必须接GND低电平如果BOOT0悬空或者被拉高芯片会进入Bootloader模式而不是运行你烧好的程序表现就是没反应第三确认目标芯片的晶振是否焊接好、起振正常STM32F103C8T6虽然可以用内部RC振荡器跑起来但如果原固件配置的是外部高速晶振HSE目标板没焊晶振就必死无疑。我在实操中遇到过一种情况源板用的是8MHz外部晶振目标板焊的是12MHz晶振固件拷过去之后串口波特率全乱了部分外设的工作节奏也不对。这就是典型的外部时钟不匹配导致的问题STM32F1系列的时钟树里PLL倍频系数是软件写死的HSE频率不同最终系统时钟就天差地别。这种情况下要么把目标板的晶振换成和源板一致的要么你得改固件里的时钟配置需要源码没有别的办法。6.3 固件里绑定了唯一ID、外部Flash或其他外设功能异常这么查前面提到过STM32每颗芯片的96位唯一ID在0x1FFFF7E8附近这个地址不在主Flash里所以拷贝固件不会把ID也拷过去。如果原固件读取了UID做加密绑定、授权认证或者数据加密就会导致拷贝后的芯片功能异常。怎么判断是不是这个原因呢如果你的设备在拷贝后能启动、能亮灯、能响应按键但某些高级功能不可用或者在连接上位机时报授权错误那大概率就是UID校验的问题。另外还要检查原板子上是否还有其他非易失性存储比如外部SPI Flash、EEPROM等。如果固件的配置数据存放在外部存储里你只拷贝了主控芯片的Flash外部存储里的内容还得单独拷贝一次很多人忽略这一点导致固件一样、外设配置却对不上。6.4 源芯片没有启用的功能在拷贝后可能被意外触发拷贝固件是全量复制不只是复制代码还把Flash里的数据和选项字节配置一起复制了。如果源芯片之前在调试时改动过一些内部配置比如开启了看门狗、调整了选项字节里的某些标志这些状态也会被带到目标芯片。有些看起来莫名其妙的问题其实是源芯片的脏数据被带过去了。我的建议是如果只是需要干净固件最好在源芯片确认功能正常后先用Option Bytes页签把配置导出来看一眼。如果源芯片的选项字节是默认值那就放心烧。如果做了特殊配置要么在目标芯片上手工配置一遍要么确认这些配置是否真的需要。总之不要把拷贝理解为无脑复制要理解你复制的是什么内容有没有副作用。7. 进阶技巧备份固件时顺手做的三件小事关键时刻能救命7.1 把固件的版本号和校验值记录下来我每备份一颗芯片的固件都会顺手记一下版本信息。具体做法是读出的bin文件保存时文件名不要只写backup.bin而是写清日期、芯片型号、设备标识例如backup_20250115_F103C8T6_设备A_v1.2.bin。然后在笔记里记录这个文件的MD5或SHA256值。这样以后拿到另一个文件时可以用校验值对比是否一致快速判断固件版本和完整度。用命令行算校验值很简单Linux/macOS用md5sum或者sha256sumWindows用certutil -hashfile命令。养成这个习惯之后你会发现在做固件升级、回归对比时非常方便尤其是当你手里有多个版本固件的时候校验值能帮你快速确认哪个文件对应哪个版本。7.2 保存一份备份说明把板子硬件差异也写进备注里拷贝固件不是孤立操作它背后往往对应着一块具体的硬件。我强烈建议你在备份固件时把源板的硬件配置也记录下来主控型号、外部晶振频率、PCB版本号、外设型号、BOOT0/BOOT1跳线方式等等。这些信息看起来跟固件无关但在后续写入目标板、排查兼容性问题时它们往往是关键线索。比如我前面举的那个例子两个板子晶振不一致导致串口波特率异常如果备份固件时就在说明里写清了源板使用8MHz HSE那调试排查时会快很多。这种备注成本很低但价值很高尤其是当时间过去几个月甚至几年之后你翻出这个二进制文件时还能看懂它当初是怎么来的。7.3 如果想批量复制用脱机烧录器比每次接电脑快得多如果你不只是拷贝一两颗芯片而是要复制几十上百颗那么每次接个电脑用CubeProgrammer操作就太慢了。这种情况下推荐用脱机烧录器比如J-Link的脱机模式、ST-Link的脱机功能、或者专门的脱机编程器像广州致远的P800系列、正点原子的脱机烧录器。脱机烧录器的用法基本是先用配套软件把bin文件烧录到烧录器内部的存储空间然后烧录器用一根SWD线连接目标板按下按键自动完成擦除、写入、校验。这样不需要电脑在场工人操作也很傻瓜化。大批量生产用这种方式效率能提升好几倍而且出错的概率更低。如果你只是在实验室里拷贝一两颗芯片用CubeProgrammer就足够了不必在这上面额外投入。写在最后再分享一个我翻过车的小细节最后分享一个我自己在整机拷贝时踩过的坑。有一次我帮客户把一台仪器的固件从旧芯片拷到新芯片读取一切正常写入也校验通过但仪器上电后显示屏就是黑的串口也没有任何输出。我排查了很久最后发现是源芯片的BOOT0引脚硬件上被拉高芯片一直处于系统Bootloader模式压根没运行我的程序。我把源板的BOOT0接GND之后再读固件、再写入问题立刻消失。这件事给我的教训是读固件之前一定要确认源芯片处于正常运行模式BOOT0为低否则你读出来的可能根本不是应用固件而是Bootloader区或者其他莫名其妙的内容拷贝过去自然跑不起来。另外一个小习惯也值得养成每次写完目标芯片先不要急着断电用Verify再确认一遍然后看它运行起来的状态指示灯或者串口日志。拷贝固件这件事操作流程本身不难真正的难点全在细节里。希望这篇内容能帮你少踩几个坑顺利搞定STM32F103C8T6的固件克隆。
返回列表