简介:基于STM32F103的程序加密保护实验例程源代码,面向嵌入式开发者、单片机初学者及对代码安全感兴趣的读者,提供一套可直接打开的Keil工程。包内共122个文件,以58个头文件与56个C源文件为主,配合uvprojx工程文件、hex固件、批处理脚本与图片说明,整体约395KB,目录结构清晰,便于按模块研读。内容涵盖软件加密与Bootloader定制思路,可了解如何在固件中集成AES/DES类算法、配置Flash保护权限;同时包含DS1302实时时钟、HS0038红外遥控、SysTick系统定时器等外设驱动示例,其中RTC可用于生成时间相关密钥,红外解码增强设备交互,SysTick则支撑精确延时与任务调度。已有181人学习,适合在开发板上对照源码练习程序加密保护设计,也可直接移植RTC、红外解析与延时相关代码到实际项目中,加深对STM32底层编程的理解。
1. STM32F103程序加密保护:为什么你抄了读保护还是被抄板
说一个每年都会遇到的场景:产品开好模,样板给方案公司打了一个月,对方用烧录器把你的固件读出来,改个开机logo就成了竞品。原因是STM32F103出厂时读保护是关闭的,SWD口一接,Flash内容可以整片读走。标题里“基于STM32f103单片机程序加密保护实验软件例程源代码”要解决的,就是这件事:程序加密保护不是放一个密码变量,而是把“固件能不能被读走、被搬到别的板子上还能不能用”做成一套可验证的实验。这套东西适合两类人:一类是刚把最小系统跑通、准备做产品的开发者;一类是被抄板抄到怕、想在量产前补上防拆方案的小团队。先给结论:开启读保护只是起点,不是终点;RDP、唯一ID、运行期校验缺一不可。
2. 先把加密对象摸清:STM32F103的Flash权限、调试接口与选项字节
2.1 RDP等级0/1/2到底防住了什么
在嵌入式里有个省流的误解,认为“加密保护”就是设置一个软件密码,或者把JTAG熔丝烧掉。STM32F103的实际情况比这复杂,但也没有那么玄学。芯片内部有一套读保护机制,缩写RDP,写在Flash的选项字节里。选项字节是一块单独的存储区,不跟你的代码共用地址,需要用专用的Flash接口去写。读保护等级直接决定了调试器、从RAM启动、从系统Flash启动时能不能拿到用户Flash里的内容。
| RDP等级 | 调试口读取Flash | 调试口写/擦除Flash | 从RAM/系统存储器访问用户Flash | 降级 |
|---|---|---|---|---|
| Level 0 | 可以 | 可以 | 可以 | 不需要降级 |
| Level 1 | 禁止 | 允许 | 禁止 | 可以,降级时全片擦除 |
| Level 2 | 永久禁止 | 视WRP配置 | 永久禁止 | 不可降级 |
这张表基本就是整个加密实验的底层逻辑。Level 0是出厂默认,也是大多数开发板的状态;你的固件对烧录器几乎是裸体,J-Link可以一页一页读出来。Level 1是最常用的量产状态:调试器连得上,能擦除、能重新烧录,但读不出Flash内容。Level 2是终极状态:调试口彻底关闭,连你自己的返修程序都访问不了,降级操作被芯片拒绝。STM32F103不像某些新系列那样每一款都保底支持Level 2,所以例程包一般把重点放在Level 1,先锁读,再配合别的办法锁芯片。
这里要强调一个容易被忽略的区别:Level 1仍然允许调试口写Flash。也就是说抄板的人虽然读不出HEX,但可以用自己的算法往你Flash里写数据,或者通过调试接口强制把程序跑起来,读RAM、读外设寄存器,然后把结果通过串口发出来。所以RDP只是第一道门,后面的唯一ID校验和运行期自校验,是为了堵住“通过调试口执行代码”这条路。
网上搜“stm32f103 读保护 失效”会看到不少抱怨,说开了Level 1还能被某国产烧录器读出部分数据。实际上那多半是对方通过调试接口执行了代码,不是绕过了RDP。真正的防御是让“即使能执行代码也拿不到有效明文”,这就要靠UID绑定和校验函数把关键逻辑藏起来。
2.2 SWD调试接口:程序加密保护的第一个入口
STM32F103上最常见的调试接口是SWD,两条线:PA13是SWDIO,PA14是SWCLK,加上复位脚,三根线就能连ST-Link或者J-Link。很多开发者在“最小系统”阶段觉得SWD只是用来下载程序,量产时把烧录排针去掉就安全了,这个想法漏掉了两件事。
第一,SWD并不只受物理引脚影响。即使你不引排针,抄板的人也可以把探针点在PCB过孔上,或者直接焊一根线到芯片引脚。真正能挡住他的是RDP Level 1或Level 2,不是物理隔离。第二,如果你在程序里把PA13/PA14复用成了普通GPIO,调试口可能连不上,但这只能增加调试难度,烧录器在复位释放瞬间强制拉起时钟线,仍然有机会访问。也就是说,把调试口“改成普通IO”这件事不能替代读保护,只能用来给开发阶段的自己添堵。
例程包里的加密实验,第一步通常是先做一个能通过串口控制开关读保护的命令。这个命令在开发阶段留着,量产前再删掉。这里顺便提一个实际工程里的配合点:如果板子上调试串口不够用,很多人用stm32f103定时器实现软件串口,把USART让给通信。软件串口的引脚一定不要选PA13/PA14,否则加密实验里最想测试的SWD通道会被自己占用,后面想通过烧录器擦除都没机会。
还要注意BOOT0引脚。如果板子上BOOT0通过跳线帽可以拉高,用户在正常运行时不小心把跳线帽插上,复位后芯片会进入系统存储器里的出厂bootloader。此时如果读保护是Level 0,出厂bootloader里自带的读内存命令可以直接读出Flash内容,这就等于给抄板者开了一扇后门。所以量产板BOOT0必须通过10k电阻下拉到GND,焊盘上不要留跳线帽。例程包里的说明一般写“实验板可以保留跳线,产品板必须固定低电平”,这个细节直接影响保护强度。
2.3 选项字节读写:标准外设库里的底层操作
在STM32标准外设库里,选项字节相关的函数放在stm32f10x_flash.c里。常见用法是先FLASH_Unlock解锁主Flash,再FLASH_UnlockOptionBytes解锁选项字节,然后FLASH_ReadOutProtection(ENABLE)写入读保护,最后FLASH_LockOptionBytes加锁。这套操作顺序不是随便写写的,它保证了你在不清除主Flash的情况下改写选项字节。
FLASH_Status status; FLASH_Unlock(); FLASH_UnlockOptionBytes(); status = FLASH_ReadOutProtection(ENABLE); FLASH_LockOptionBytes();代码后面的逻辑是:FLASH_Unlock清掉Flash控制寄存器里的锁定位;FLASH_UnlockOptionBytes再把选项字节区域的写保护权限申请下来;FLASH_ReadOutProtection的ENABLE参数写的就是RDP等级1。代码写完以后,芯片不会立刻变成保护状态,选项字节一般要等一次复位或一次上电后才真正生效。FLASH_Status返回值如果等于FLASH_COMPLETE,说明寄存器写入成功;如果返回FLASH_ERROR_PG或者FLASH_ERROR_WRP,多半是你前面主Flash没解锁,或者对应扇区被写保护挡住了。
标准外设库版本不一样,函数名可能略有差异,但原理一致。早期V2.x库调的是FLASH_GetReadOutProtectionStatus判断状态,V3.5库也保留了类似接口。有人会问,例程包源码里到底应该看哪个文件?核心就在stm32f10x_flash.h和stm32f10x_flash.c两个文件里,函数定义和状态枚举都在那里。如果把V3.5库的FLASH_ReadOutProtection实现打开,能看到它直接把NewState映射到RDP等级,ENABLE对应Level 1,DISABLE对应Level 0。想上Level 2要用独立枚举值,不是传ENABLE能搞定的。
选项字节里除了RDP,还有独立看门狗配置、启动模式、硬件看门狗、BOR阈值和WRP扇区写保护。做实验时很多人会把WRP和RDP混在一起,一通操作后程序没法擦除,以为是芯片锁死。我的经验是:加密实验阶段只动RDP,WRP留到产品最后调试完成再碰,否则IAP升级会被自己亲手堵死。
3. 例程里最值得复现的三种保护方案:从锁Flash到锁芯片
3.1 唯一ID绑定:让固件只认自己的板子
只开读保护还有一个很尴尬的漏洞:抄板的人读不出HEX,但可以把Flash里的二进制数据原封不动地复制到另一颗STM32F103上。如果对方连芯片一起抄,产品照样能跑。要堵住这条路,就得让固件和唯一的芯片身份绑定起来。STM32F103每一颗芯片内部都有96位唯一ID,地址在0x1FFFF7E8,出厂时写死,用户改不了。这个ID是程序加密保护实验里最“物美价廉”的素材。
读取代码只有四行:
uint32_t uid[3]; uid[0] = *(volatile uint32_t *)0x1FFFF7E8; uid[1] = *(volatile uint32_t *)0x1FFFF7EC; uid[2] = *(volatile uint32_t *)0x1FFFF7F0;这段代码没有任何外设依赖,烧录后立刻能读。三个32位寄存器合起来是96位,足够区分大批量生产的芯片。读取时要注意,地址是一次性写死的,不要用数组下标递增的方式去取,虽然这三个地址在F103上是连续分布的,但用指针解引用最稳妥。实际项目中,绑定时会把三个值和一个常量数组比较,或者把其中一个值异或后存到Flash里,再上电时解密比对。
比较函数要放在启动早期,但不能早到时钟还没初始化。我调试时踩过一次:把UID校验放在main函数第一行,结果串口打印不出来,LED也不亮,看起来像死机一样。后来改成先初始化时钟、串口,再校验,失败后打印一个错误码并进入死循环。这样返修时至少知道是不是绑错芯片,不需要靠玄学换芯片。
还有一个容易被抄板人员利用的点:如果你在产品里打印过UID到串口,对方可以用调试器挂住程序,从串口日志里抓到你的UID,然后去购买一片ID一样的老芯片。这种事几率不大,但存在。所以真正的量产代码里,不要直接打印UID明文,而是打印UID经过哈希或者异或后的值。例程包里一般会有带校验和脱敏的版本,这个细节很多人会忽略。
3.2 RDP读保护:即使连上调试器也读不出Hex
唯一ID绑定解决“换芯片”,RDP解决“读代码”。两个方案组合起来,才是一个完整的量产保护思路。操作上要分两步:先在开发阶段烧录固件并调试,功能稳定后再写入RDP。如果一开始就开读保护,后面每改一版代码都要经历一次整片擦除和重新烧录,效率很低。
设置RDP的常见做法是烧录一个专门的“启保护固件”,里面只做一件事:初始化系统时钟,延时几百毫秒,然后把读保护等级写入选项字节,最后软件复位。这个固件本身不包含业务逻辑,抄板的人就算读到了它,也拿不到你的产品代码。量产时先量产程序,再由最后一道工序烧“保护固件”,这是很多方案公司交代码时采用的方式。
最稳妥的不是把保护固件留在板子上,而是在主程序里留一个只有特定ASCII密钥才能触发的调试串口命令。命令触发后执行FLASH_ReadOutProtection(ENABLE),然后复位。这样返修时通过串口发一段密钥,就能在线开启保护。不过要注意,RDP从Level 1降到Level 0会触发全片擦除,所以密钥命令不能是“解除保护”,而必须是“擦除全部数据并解除保护”,否则固件仍然会泄密。
在bootloader和APP这种双层结构里,RDP最好由bootloader来控制。原因很简单:APP可能随时被擦掉,但bootloader是升级的大门,只有它自己掌握读保护状态,才能保证OEM量产时先烧APP、再开保护,也才能保证下次升级时先全片擦除、再关保护、再写新APP。如果APP里直接操作RDP,升级流程会变得非常别扭,而且容易把bootloader自己也锁进保护里。
3.3 运行期自校验:把加密从静态变成动态
前两招都是静态保护:上电时判断UID,或者让烧录器读不出Flash。但高水平的抄板者会用调试器把程序跑起来,然后修改RAM里的校验结果,让UID检查直接跳过。所以例程包里一般还会教第三招:运行期自校验。也就是程序在启动阶段自己算一遍Flash关键区域的校验值,算出来不对就直接停机。
STM32F103自带一个CRC外设,硬件计算CRC32,速度比软件CRC快很多。典型写法如下:
uint32_t crc_calc(uint32_t *addr, uint32_t len) { uint32_t i; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_CRC, ENABLE); CRC_ResetDR(); for (i = 0; i < len; i++) { CRC->DR = addr[i]; } return CRC->DR; }参数说明:addr是待校验Flash的起始地址,len是32位字的数量。比如你想校验0x08000000到0x08010000之间16KB的代码区,len就是0x4000 / 4 = 0x1000。调用前要先把CRC外设时钟打开并复位数据寄存器;CRC_ResetDR清空上次计算结果。循环里逐字写入CRC数据寄存器,硬件会自动算好,最后读出来对比预设值。
这个方案真正的难点不在CRC外设本身,而在如何确定校验范围。程序在Flash里运行,校验代码自己也在Flash里,如果把整个Flash都算进去,会出现“计算结果每次都不一样”的翻车现场。因为程序执行到CRC循环时,Flash里的指令没变,但如果你把变量放在已初始化数据段,初始值在运行时会被改写,而这段数据如果也在校验范围内,结果就跟着变了。可靠的办法是给Flash分区:主程序固定放在前面的扇区,CRC预设值放在最后一个扇区;校验时把最后一个扇区排除掉,或者把CRC结果存到内部Flash的另一个扇区,然后在RAM里执行校验函数。这个细节会在第五章再展开,但它决定了整个加密方案是不是真能落地。
另外,STM32的CRC外设用的是标准以太网CRC32多项式,初始值是0xFFFFFFFF,输出有异或处理。如果你之前用过软件CRC表,两边结果可能对不上。最简单的方法是先把一段固定数据烧进Flash,用硬件CRC算一次,再拿这个结果和上位机工具比较,确认算法口径一致,再做后续。
4. 在STM32F103最小系统上跑通加密保护:关键代码与参数
4.1 最小系统硬件与烧录配置:三个不能省的点
讲原理可以很玄学,落地还是得回到最基础的stm32f103最小系统。我画过不少产品原理图,加密保护相关的硬件要求其实就三条。
第一,SWD引脚要留出来,但不要直接引到排针。PCB上留四个焊盘:SWDIO、SWCLK、GND、NRST,用测试夹就能压住,量产时不露。第二,BOOT0必须通过下拉电阻接地,不能悬空。很多人只用一个跳线帽,量产时跳线帽忘插,BOOT0悬空变成1,芯片一复位就进系统存储器,程序不跑,返修率极高。BOOT0悬空对加密保护的破坏在于:它让对手多了跳线进入ISP模式的机会。第三,电源设计要让Flash写入稳定。Flash编程和选项字节写入最容易在电压跌落时失败,最后表现为“芯片锁死”或者“写保护位错误”。给MCU单独加钽电容,不要和电机、继电器共享一个滤波电容。
烧录配置上用ST-Link或J-Link都可以。开发期间建议保留“connect under reset”选项,勾选后烧录器会在复位释放瞬间抓住SWD时序,即使程序里把SWD引脚复用成普通IO,也能连上。加密保护开启后这个选项可能失效,那属于预期,不能当成故障。真正需要检查的是RESET引脚上的RC复位电路,如果电容太大,复位释放时间过长,有些烧录器会连接失败。
4.2 读取唯一ID并做绑定判断:可直接编译的C函数
将UID读取和判断写成一个完整函数,是例程包源码里最常见的主线。下面这个函数可以直接放在标准外设库工程里编译。
int firmware_check_uid(void) { const uint32_t expected_uid[3] = { 0x08070605U, 0x04030201U, 0xAABBCCDDU }; uint32_t real_uid[3]; real_uid[0] = *(volatile uint32_t *)0x1FFFF7E8; real_uid[1] = *(volatile uint32_t *)0x1FFFF7EC; real_uid[2] = *(volatile uint32_t *)0x1FFFF7F0; if (real_uid[0] != expected_uid[0]) return 1; if (real_uid[1] != expected_uid[1]) return 2; if (real_uid[2] != expected_uid[2]) return 3; return 0; }这段代码的返回值设计成了错误码:0表示通过,1/2/3分别对应三位UID中哪一位不一致。实际工程里不建议把UID比较结果直接作为唯一的跳转条件,因为调试器可以单步跳过判断。所以更可靠的做法是让校验失败的分支做“假成功”:程序表面上正常运行,但是在某个定时中断里持续递减一个变量,等到延时到了才真正锁死。这样抄板者模拟校验时会觉得已经通过,但产品运行一段时间后随机死机,排查成本极高。这个思路是加密对抗里的“烟雾弹”,例程包通常也会放在进阶注释里。
还要注意expected_uid数组在代码里是明文,烧录器如果开了读保护读不出Flash,但对方可以通过调试接口执行代码,从RAM里把expected_uid抓出来。所以量产时最好把expected_uid拆成几个片段,用移位和异或恢复,不要直接把完整ID放在const数组里。
4.3 设置与解除读保护:FLASH_ReadOutProtection的正确调用顺序
写保护程序的代码顺序必须对,错了轻则状态寄存器报错,重则整片意外擦除。以下是我反复验证过的顺序,直接贴进标准外设库工程可编译。
void enable_rdp_level1(void) { FLASH_Unlock(); FLASH_UnlockOptionBytes(); if (FLASH_ReadOutProtection(ENABLE) == FLASH_COMPLETE) { /* 选项字节写入成功,等待复位生效 */ } FLASH_LockOptionBytes(); FLASH_Lock(); }第一行FLASH_Unlock,是把主Flash的KEY1和KEY2写入Flash_KEYR寄存器,解锁Flash控制寄存器;第二行FLASH_UnlockOptionBytes,则是写另一个密钥序列解锁选项字节。两个解锁必须都执行,缺一个FLASH_ReadOutProtection会返回FLASH_ERROR_WRP。FLASH_ReadOutProtection(ENABLE)在标准外设库V3.5中默认写入Level 1;如果要写Level 2,需要确认你的库版本支持FLASH_RDP_Level_2这个枚举值,直接传ENABLE并不等价。
函数末尾的FLASH_LockOptionBytes和FLASH_Lock是恢复锁定状态,防止后续误操作。这套顺序在J-Flash、ST-Link Utility里看到的效果是一样的:芯片下一次复位后,调试器读Flash时返回全0或超时。注意,调用这个函数的上层代码最好放在RAM里执行。过去我遇到的问题是:固件在Flash里调用这段代码,自己把自己的选项字节写入指令区覆盖了,程序跑飞。后来把函数放到RAM里再执行,写保护就稳定了。
解除保护的代码同样重要,生产返修经常要用。
void disable_rdp(void) { FLASH_Unlock(); FLASH_UnlockOptionBytes(); FLASH_ReadOutProtection(DISABLE); FLASH_LockOptionBytes(); }这段代码执行完,芯片会强制进行一次整片擦除,Flash中的原有代码全部清空。这不是bug,是ST为了防抄板设计的机制。所以每次执行disable_rdp之前,一定先确认固件已经备份到上位机,否则返修时芯片变成空片,只能重新烧录。
5. 程序加密保护避坑记录:五个把板子搞成砖的现场
5.1 现象:开读保护后仿真器连不上,反复复位
第一次在STM32F103上写FLASH_ReadOutProtection(ENABLE)后,我习惯性点了MDK的下载按钮,结果是Keil报“Cannot access Target”,当时第一反应是芯片坏了。后来才明白,这就是Level 1生效后的正常表现:SWD调试接口不再读取Flash,调试器上电时拿不到Flash内容,自然认为目标不可用。解决方法:换一个支持connect under reset的烧录器,在复位引脚上加一个低电平脉冲,趁着MCU还没跑用户代码时把SWD时序抢回来。如果产品上复位引脚被拉死或者被大电容滤波,这个办法也会失败,所以硬件设计时要保留复位焊盘。更稳妥的做法是,在加密实验阶段保留一个串口命令,让程序收到特定字节后用软件延时再执行写保护,这样后续还能通过串口和上位机交互。
5.2 现象:RDP降级触发整片擦除,几天的工作被清空
有一次我在量产准备阶段直接在J-Flash上点Unsecure Chip,想把RDP关掉重新烧一版程序。J-Flash先是提示“This operation will erase the whole chip”,我没在意,点完一看,整个Flash全是0xFF,几天调好的量产固件没了。这个事的根本原因在前面表格里写过:STM32F103从Level 1降级到Level 0,必须先把用户Flash全部擦掉。ST的防御逻辑是防止有人用“关保护”来绕过读保护读代码。解决方法是不要在不备份的情况下接触保护状态,所有量产固件都留一份bin在电脑上,并且用脚本一键下载。量产返修时要养成习惯:先把返修板的旧固件读出来备份,再执行Unsecure,不要对着一个陌生板子直接破解。
5.3 现象:UID校验写在启动最前面,调试时找不到地方下断点
还是之前提过的错误:把firmware_check_uid放在main函数第一行,串口和LED都没初始化。校验失败直接while(1),看起来就是下载成功后没有任何反应。更麻烦的是,调试器在开读保护下不能下断点看内存,你根本不知道它卡在UID还是卡在时钟。解决这个问题要调整初始化顺序:第一步初始化RCC和SysTick,第二步初始化串口,第三步开LED,第四步再做UID校验。哪怕校验失败,至少串口会打印一个形如UID_ERR:1的代码,返修人员能直接知道是哪颗芯片不匹配。另一个相关坑是:某些例程里把expected_uid放在EEPROM模拟区,程序一跑先把EEPROM擦写一遍,如果Flash写保护开着,就会触发HardFault。遇到这种问题,先查HardFault_Handler,再把校验函数临时换到调试串口打印,定位才能快。
5.4 现象:启用RDP后IAP串口升级失效,bootloader和APP一起遭殃
量产固件都带bootloader,通过串口升级程序。一开始我在APP里直接调enable_rdp_level1,开机后首次升级正常,第二次升级时bootloader进是能进,但擦除APP区时一直返回FLASH_ERROR_WRP。原因是我把RDP和扇区写保护WRP搞混了。STM32F103的Flash写保护是WRP位,不是RDP位,但它俩都在选项字节里。例程演示程序通常只操作RDP,不会碰WRP;我自己在初始化里同时使能了WRP,结果把自己升级路径堵死。解决方法是明确区分:RDP控制读,WRP控制写。做IAP时只开RDP,别去配置WRP,否则每次升级前还要先改写选项字节,升级过程断电一次就变砖。另外,使用串口升级时要注意波特率误差,例如用stm32f103定时器实现软件串口,波特率误差超过3%后升级固件会反复校验失败,这不是加密保护的问题,却经常被误判成读保护导致串口被锁。
5.5 现象:CRC校验把自己算进去,每次运行结果都不一样
我在stm32f103上跑CRC校验时,把0x08000000到0x08008000整个区域都算了一遍,结果每次上电算出来的CRC都不一样。排查了一整天,发现CRC外设计算时会把当前正在运行的代码也算进去,而代码里肯定有自修改数据或者中断向量表重映射,算到一半向量表被改了,结果自然不稳定。后来我把校验范围改成从0x08000000到0x08006000,也就是把存放校验函数的末尾扇区排除,再把CRC结果写在最后一个扇区里,问题解决。凡是做程序加密自校验的,必须画一张Flash分配图,明确哪个扇区放程序、哪个扇区放校验值,然后让校验范围避开放校验函数自身的那个扇区。这是一个很常见的坑,网上不少人把它归为玄学,其实只是Flash规划没做好。
6. 量产前验证加密保护是否有效:一份可复用的验收清单
加密保护做完了,不能烧进去就完事。我现在每个产品量产前都会走一遍同样的验收流程,几分钟就能测完,建议你直接抄作业。
第一项,验证“读不出”。把RDP开到Level 1,用ST-Link Utility或J-Flash点击Connect,然后尝试读取0x08000000起始的Flash区域。预期结果是读取超时、返回全0xFF或地址校验失败。只要读到任何一条真正的固件指令,说明你的RDP设置没生效,回去检查选项字节写入返回值和复位。
第二项,验证“搬不走”。准备两个一样的STM32F103最小系统板,先读出A板的UID,把UID填写进expected_uid数组里,编译固件烧到A板,开机应该正常运行;再把同一份HEX烧到B板,A板HEX里附带的是A板UID,所以B板应该进入错误分支。这里有个细节:烧录器写入HEX时不会同步修改UID,因为UID是芯片出厂固定的,不是Flash里的内容。所以验证时直接烧同一个HEX就行,不需要做任何额外操作。
第三项,验证升级通道。如果你的固件带IAP,在开启RDP后连续升级五次,每次升级后立即复位测试。为什么是五次?因为第一次升级可能刚好命中缓存或者扇区状态,多跑几次能暴露WRP和RDP互相干扰的问题。如果升级失败,看一下FLASH_Status返回的是FLASH_ERROR_WRP还是FLASH_ERROR_PG,前者说明你不小心把WRP也开了。
第四项,验证返修流程。故意把一块板子的保护状态降到Level 0,观察是否整片Flash被擦除。这块板的固件必须先备份,否则返修会变成返厂。整个流程走完之后,我现在的习惯是:不在量产固件里默认开启RDP,而是由测试工装通过串口发一条保护指令,测试通过后再开。这样既保留量产烧录的灵活性,也让板子在被送出厂前处于受控状态。说到底,加密保护实验的价值不是让代码变得绝对不可破解,而是把破解成本抬高到远超重新开发的程度。希望帮到你。
本文还有配套的精品资源,点击获取