干了这么多年嵌入式,说个挺现实的事:产品卖出去还没捂热,就被别人拆开抄走了核心参数,甚至连固件里的密钥都被人用调试器扒了个干净。后来我痛定思痛,花了两周把STM32的硬件防拆机制彻底摸了一遍,用的就是TAMPER引脚配合BKP备份寄存器这套组合拳。实话说,这是目前性价比最高、代码量最小的敏感数据保护方案:当设备外壳被拆开的那一瞬间,引脚电平一变,硬件直接清空备份域里的全部数据,整个过程根本不经过软件,也没法被固件逻辑绕过。这篇文章就把这套机制的电路设计、软件流程和踩坑经验一次讲清楚,适合做付费仪表、门禁计费、密钥管理以及所有需要保护核心算法的产品开发者参考。
1. 防拆到底防什么:先说清这个方案能解决什么问题
1.1 产品被拆带来的三类风险
做硬件产品的,最怕的就是被人拆机逆向。我归纳了一下,常见的风险大概分三类。
第一类是固件被直接读出。很多单片机默认没开读保护,攻击者拿个ST-Link或者J-Link往SWD接口一怼,整片Flash内容就导出来了。后面不管是抄算法还是找漏洞,都跟拿着源代码差不多。这种情况防拆机制能做的不多,必须靠读保护位去挡,后面我会专门讲怎么配合使用。
第二类是敏感数据被窃取或篡改。这指的是设备里存的关键参数,比如计费累计值、设备运行次数、加密密钥、校准系数。攻击者拆开外壳,通过飞线或者逻辑分析仪把数据读走,甚至改掉数据再装回去,让计费系统失效。这种场景下TAMPER+BKP就非常对症:外壳一拆,数据直接没了,谁也别想读,谁也别想改。
第三类是硬件被整体仿造。抄板、抄电路、抄元器件布局,这种其实比读固件更让人头疼。防拆机制本身没法阻止抄板,但如果你在关键芯片周围布了防拆检测回路,拆板子就得先破坏回路,破坏回路就触发数据清除,最后抄出来一块已经“失忆”的板子,等于抄了个寂寞。
1.2 为什么选TAMPER+BKP而不是其他方案
可能有人会问,防拆检测用普通GPIO加一个标志位不就行了吗?软件检测到引脚变化,然后去擦除Flash里的数据,逻辑上看起来也能实现。
但这里面有个致命问题:软件擦除Flash是“有机会被打断”的。攻击者拆开外壳后,如果第一时间把单片机的电源切断,或者用复位脚把系统按住,你的软件根本没机会执行擦除动作,数据还在Flash里躺着。而TAMPER+BKP是硬件机制,检测到侵入事件后,硬件电路直接对备份寄存器执行清零,和软件跑不跑没有任何关系,你断不断电都一样,这个特性是软件方案完全替代不了的。
还有的人会用外部安全芯片,比如ATECC608A这种,安全等级确实高,但成本、开发周期、软件协议栈复杂度都上去了。对于STM32项目来说,TAMPER引脚和BKP寄存器本来就是芯片自带的功能,不需要额外物料,初始化代码不超过二十行,性价比极高。我把几种方案放在一起对比过:
| 方案 | 成本 | 数据清除可靠性 | 实现难度 | 防护强度 |
|---|---|---|---|---|
| GPIO检测+软件擦Flash | 最低 | 低,可被断电打断 | 低 | 低 |
| TAMPER+BKP | 几乎零成本 | 高,硬件自动清除 | 低 | 中高 |
| 外部安全芯片 | 高 | 高 | 高 | 高 |
所以我的结论是:如果你做的产品对成本敏感、又需要一定防护等级,TAMPER+BKP是综合性价比最优的起点。
2. TAMPER引脚与BKP寄存器的工作原理
2.1 TAMPER引脚的检测机制
TAMPER在英文里就是“篡改、破坏”的意思,STM32把专门用来检测外部拆解动作的引脚命名为TAMPER引脚。以最常见的STM32F103系列来说,这个功能复用PC13引脚,所以你在数据手册里会看到它同时标着PC13和TAMPER-RTC两个功能。
检测机制其实很简单:引脚上有电平跳变,或者持续处于某个特定电平,系统就认为发生了侵入事件。你可以通过BKP_CR寄存器里的TPAL位来配置有效电平:TPAL为0时,引脚检测上升沿或者高电平有效;TPAL为1时,检测下降沿或者低电平有效。这个极性必须和你外部电路在“正常状态”下的电平匹配,不然一上电就误触发,这一点后面电路章节会重点讲。
需要注意的是,TAMPER引脚默认状态下是一个普通的IO口,你要使用它的防拆功能,必须把它切换到TAMPER复用模式。这和把引脚配置为推挽输出或者模拟输入不太一样,它不是通过GPIO外设来配,而是通过BKP外设的控制寄存器来启用。很多人第一步就卡在这里,拿着GPIO_Init去配置PC13,结果死活检测不到事件。
2.2 BKP备份寄存器与备份域供电
BKP是Backup Register的缩写,翻译过来就是备份寄存器。STM32F103系列内部有42个16位的备份寄存器,总容量84字节,分成BKP_DR1到BKP_DR42。这84字节就是你可以用来保存敏感数据的“保险箱”。
这个保险箱最特别的地方在于供电设计。STM32芯片内部被划分成了两个电源区域:主供电域由VDD供电,备份域则由VBAT引脚供电。VBAT一般接一个3V的纽扣电池或者超级电容。当设备主电源断电后,主供电域所有数据全部丢失,但备份域只要VBAT有电,BKP寄存器里的数据就能一直保留。
这个特性对防拆机制来说是决定性的。你想,如果数据存在普通SRAM里,断电就没了,设备没问题的时候数据也保不住;如果存在Flash里,数据倒是能持久保存,但被硬件一键清除的响应速度又跟不上。BKP寄存器正好卡在中间:主电源正常时工作、主电源断了还能靠电池保持数据、检测到侵入事件时又能被硬件瞬间清零。三条特性凑在一起,好像就是为防拆场景量身定做的。
2.3 侵入事件如何触发数据清除
侵入事件触发数据清除的完整链路是这样的:外部拆解动作引发TAMPER引脚电平变化,电平变化被BKP外设的检测电路捕获,检测电路直接对42个备份寄存器执行清零操作,同时把BKP_CSR寄存器里的TEF(Tamper Event Flag)和TIF(Tamper Interrupt Flag)两个标志位置1。
这里最值得强调的是“直接清零”这四个字。数据清除动作完全由硬件完成,不经过CPU、不跑中断服务函数、不依赖软件指令。也就意味着,攻击者没有任何机会在数据被清除之前拦截它、备份它、或者阻止清除动作。这和软件擦除Flash的方案完全是两个维度的安全性。
清零之后,两个标志位的用途不一样。TEF是事件标志,它一旦置位就会保持住,即使你后来清了中断标志,它也不会自动消失,直到你往这个位写1把它手动清除。这就给系统提供了一个“案发记录”:上电时检查TEF,就知道设备曾经被拆过。TIF是中断标志,如果你使能了侵入检测中断,这个标志置位会触发RTC中断服务函数,让你能在事件发生的瞬间得到通知,马上执行后续的锁定策略。
2.4 F1与F4系列的差异
这个方案在F1系列上用的是PC13引脚加BKP外设,到了F4系列,硬件设计做了一些升级。我自己从F103项目切到F407项目时,就踩过配置方式完全不同的坑。
F4系列不再叫TAMPER引脚,而是分成TAMPER1和TAMPER2两路,TAMPER1还是复用PC13,TAMPER2复用PA0。多了这一路意味着你可以在两个不同位置布置检测点,一个检测外壳、一个检测关键电路区域。配置方式上,F4的侵入检测功能被移到了RTC外设里,通过RTC_CR寄存器里的TAMP1E、TAMP2E等位来使能,数据清除仍然作用于BKP寄存器。所以如果你用的是F4,找资料的时候要查RTC_TAMP相关的章节,而不是BKP章节,这个方向最容易搞混。
3. 防拆检测电路设计
3.1 方案一:外壳微动开关(入门最常见)
检测电路最常用的就是微动开关方案,原理非常简单:在外壳内部安装一个微动开关或者金属弹片,正常装配时外壳合上,把开关压住,拆开外壳时开关释放,电平发生跳变。
具体接法是:TAMPER引脚接一个10kΩ上拉电阻到3.3V,微动开关一端接TAMPER引脚,另一端接地。这样正常合盖时开关闭合,引脚被拉到低电平;拆盖时开关断开,引脚被上拉到高电平。配置TPAL为高电平有效,就形成了“闭合安全、断开触发”的逻辑。
这个方案成本几乎为零,一个微动开关几毛钱。但有个隐患:如果开关接触不良,或者振动环境下开关抖动,非常容易误触发。我实际碰到过一次,产品过振动测试时防拆机制莫名其妙触发了,后来查了一圈,就是微动开关在振动时瞬间断开导致的。解决方式我在3.4节里详细讲。
3.2 方案二:防拆走线回路(覆盖整个板子)
如果你的产品更看重防拆强度,微动开关只能检测外壳有没有被打开,但攻击者可以不打外壳,直接用钻孔的方式接触内部电路。这种情况下你需要把检测面铺到整个板子,用防拆走线回路来实现。
做法是:在PCB的关键区域周围布一根很细的走线,走线在PCB上绕成类似蛇形的回路,两端分别接到TAMPER引脚和地(或者电源)。正常状态下回路是导通的,一旦有人钻孔、铲板、或者用刀片刮开走线,回路就断裂,引脚电平变化触发侵入事件。
这个方案的关键参数是线宽。线越宽越容易被一刀刮不断,线越窄越灵敏,但生产良率也会下降。我一般控制在0.15mm到0.25mm之间,再配合过孔,让走线在PCB内层也走一段,这样就算表面走线被刮掉,内层还有一段,增加破解成本。还有一个细节:走线的首尾两端要放在不容易被同时短接的位置,不然攻击者用导线把两端一短接,就能绕过检测。
3.3 方案三:红外反射式外壳检测(非接触式)
有些产品外壳是金属的,没法用微动开关,也不适合布置防拆走线,这时候可以考虑非接触式检测。我用的比较多的是红外反射式接近开关,比如ITR8307这类反射型光电传感器。
原理是:传感器安装在PCB上,朝向外壳内壁。正常状态外壳贴近传感器,红外光反射回来被接收管收到;外壳被移开后反射光消失,接收管输出电平变化。把这个输出信号经过比较器整形后接入TAMPER引脚,就能实现无接触的拆壳检测。
这个方案的优势是外壳上不需要开孔装开关,不影响外观,也不会产生机械磨损。缺点是功耗比前两种方案大,红外发射管一直亮着,对电池供电的产品不友好。我建议只在有外部供电、功耗不敏感的产品上用。另外红外传感器对强环境光比较敏感,最好在传感器外面加一个遮光罩,或者用遮光海绵挡住侧面光线。
3.4 检测电路与触发极性的匹配
这部分是电路设计的重中之重,也是最容易出问题的地方。TAMPER引脚的触发极性必须和你的检测电路逻辑严格匹配,否则上电的瞬间就会误触发一次。
匹配原则很简单:先确定正常状态下引脚的电平,再配置TPAL为“反状态”有效。举个例子,微动开关方案里正常合盖时引脚是低电平,拆盖时才变高电平,那就要配置高电平触发,也就是TPAL=0。如果配置反了,上电时引脚是低电平,但低电平又正好是触发条件,系统开机的瞬间就认为自己被拆了,BKP数据直接清零。
还有一个非常容易忽略的点:TAMPER引脚默认内部没有上拉或者下拉电阻,如果你的外部电路没有把引脚电平固定住,引脚悬空时电平是随机的,TAMPER检测就会非常不稳定,隔三差五误触发一次。所以外部必须加上拉或者下拉电阻,确保任何状态下引脚都有确定的电平。
关于开关抖动的问题,硬件上可以在TAMPER引脚到地之间并联一个100nF的电容,形成一个低通滤波,把ms级的抖动毛刺滤掉。不过这样会降低检测的实时性,如果攻击者拆壳后瞬间又盖回去,可能检测不到。另一个做法是软件上做“延后确认”,检测到事件后延迟几十毫秒再确认,如果电平持续异常才判定为真事件。这两种方式我建议都做,硬件滤毛刺,软件防误判,双保险。
4. 软件配置与完整代码
4.1 初始化流程
软件方面我用的是STM32标准外设库,以F103为例,完整的初始化流程可以分成六步:打开时钟、解锁备份域访问、清除残留标志、写入敏感数据、配置检测极性、使能侵入检测。
第一步的两个时钟必须一起打开:PWR电源管理时钟和BKP备份接口时钟。很多人只开了BKP时钟,结果后面的PWR_BackupAccessCmd函数调用了也没反应。第二步PWR_BackupAccessCmd(ENABLE)是解锁备份域的写访问权限,BKP寄存器大部分时间处于写保护状态,不解锁写进去的数据会被忽略。第三步非常关键,上电后先调用BKP_ClearFlag清除上次可能残留的TEF和TIF标志,不然系统会认为上次的事件还没处理完,甚至上电就再次触发中断。
基础初始化代码可以写成这样:
void tamper_hw_init(void) { // 1. 使能PWR和BKP时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); // 2. 解锁备份域访问 PWR_BackupAccessCmd(ENABLE); // 3. 清除残留的侵入标志(写1清除TIF和TEF) BKP_ClearFlag(); // 4. 配置触发极性:高电平有效,对应外壳合上为低电平、拆开为高电平的电路 BKP_TamperPinLevelConfig(BKP_TamperPinLevel_High); // 5. 使能侵入检测和侵入检测中断 BKP_TamperPinCmd(ENABLE); BKP_ITConfig(ENABLE); // 6. 配置RTC中断通道(侵入事件走RTC中断线) NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = RTC_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }这里提醒一下,BKP_TamperPinCmd和BKP_ITConfig这两个函数在不同版本的标准库、或者换成HAL库以后,函数名和调用方式可能会变。如果你用的版本编译不过,就直接操作寄存器,把BKP->CR的TPE位置1使能检测、TPIE位置1使能中断,效果完全一样。
4.2 侵入事件中断处理
侵入事件在F1系列上挂在RTC中断上,中断服务函数是RTC_IRQHandler。事件发生后,这个中断函数会被调用,你需要在这里马上做三件事:确认事件来源、置一个全局标志给主循环、清除中断标志。
一个完整的处理函数大概长这样:
volatile uint8_t g_tamper_event = 0; void RTC_IRQHandler(void) { // 确认是侵入检测事件,而不是RTC闹钟等其他RTC中断 if (BKP_GetITStatus(BKP_IT_TAMPER) != RESET) { // 置标志,主循环根据这个标志进入锁定模式 g_tamper_event = 1; // 清除BKP侵入中断标志 BKP_ClearITPendingBit(BKP_IT_TAMPER); // 清除RTC层的中断标志 RTC_ClearITPendingBit(RTC_IT_TAMPER); } }中断函数里不建议做太多事情,比如写Flash、驱动蜂鸣器、控制LED什么的,都应该放到主循环去做。中断函数只负责记录事件、快速退出。我见过有人直接在中断里写Flash,结果Flash写入时间太长,中断嵌套加超时,系统直接卡死。数据清除这件事硬件已经帮你做完了,软件层面只需要响应事件逻辑,完全没必要在中断里抢时间。
4.3 敏感数据写入与自检策略
BKP寄存器的写入方式非常简单,标准库提供了现成函数。但在写入之前,你必须先检查事件标志,确认上一次没有发生过侵入事件,否则你写进去的数据可能瞬间就被清掉了。
我建议的写入流程是:上电后先检查TEF标志,如果置位说明发生过拆解,此时不要写入任何敏感数据,直接进入锁定模式;如果标志干净,再往BKP寄存器写敏感数据,写完读出来校验一遍,校验通过才开始正常工作。
参考代码:
// 检查是否发生过侵入事件 if (BKP_GetFlagStatus(BKP_FLAG_TAMPER) != RESET) { device_enter_lock_mode(); } else { // 写入一个魔数用于后续自检,比如0xA5A5 BKP_WriteBackupRegister(BKP_DR1, 0xA5A5); BKP_WriteBackupRegister(BKP_DR2, device_power_count); BKP_WriteBackupRegister(BKP_DR3, encryption_key_slot); // 读回校验,防止写入过程中出现问题 if (BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) { device_enter_lock_mode(); } }BKP_DR1到BKP_DR42总共42个寄存器,你可以按项目需要规划数据存储布局。我习惯固定用DR1存自检魔数,DR2、DR3存核心敏感数据,剩下的留给后续扩展。这样代码逻辑清晰,查起来也方便。
还有一点要注意,上电后如果检测到TEF标志位被置位,也不要急着清除它。这个标志是“案发记录”,最好让它在系统里保留一段时间,等锁定逻辑确认执行完毕后再清除,方便后期排查问题。
4.4 多任务环境下的注意事项
如果你的产品跑了RTOS,比如FreeRTOS,或者主循环里有很多任务,侵入检测的初始化要特别小心。我吃过的一个亏是:初始化函数里PWR_BackupAccessCmd(ENABLE)解锁了备份域,但没有在初始化完成后关闭写保护,结果别的任务误操作BKP相关寄存器,把数据改乱了。
正确做法是:所有对BKP的写入操作完成之后,立刻调用PWR_BackupAccessCmd(DISABLE)重新锁上写保护。这样后续即使有程序bug,也不会轻易改动备份域数据。
另外一个和RTOS相关的细节是:侵入检测中断的优先级设置。如果系统里还有其他关键外设中断,建议把RTC中断的抢占优先级设成最高一级,保证侵入事件发生瞬间能被及时响应。你想,防拆机制本身就是安全相关的功能,如果被其他中断阻塞了,事件处理就会延迟,锁定策略的效果就打了折扣。
5. 进阶加固:把防拆做成一套体系
5.1 配合Flash读保护,堵住调试接口
TAMPER+BKP方案保护的是备份域里的数据,但如果你连Flash读保护都没开,攻击者还能通过调试接口直接把Flash内容读走,密钥照样泄露。所以这两个措施必须配合用。
STM32的选项字节里有一个RDP(Read Protection)位,分三个级别。级别0是不保护;级别1是禁止通过调试接口读取Flash,但程序本身还能正常跑;级别2是永久保护,一旦设置就再也无法通过调试接口访问芯片,连你自己都不行,所以使用前一定要慎重。
我的建议是量产产品至少设置成级别1。代码里可以使用选项字节编程接口来设置,也可以直接用STM32 ST-LINK Utility这类工具在量产时烧录配置。这里有一个和防拆机制的联动设计:即使攻击者用特殊手段解除了Flash读保护,解除RDP的操作本身会触发一次芯片全片擦除,Flash里的固件和数据照样拿不到,相当于上了双保险。
5.2 唯一ID绑定与密钥派生
光把密钥存在BKP寄存器里还是不够的,因为BKP寄存器只有84字节,空间很紧张。更合理的做法是:把密钥的“派生因子”放在BKP寄存器里,真正的密钥由程序运行时根据芯片唯一ID计算出来。
STM32每一颗芯片都有96位的唯一ID,存放在芯片信息区。你可以在首次启动时读取这个ID,经过一个哈希算法生成密钥,然后把这个密钥保存在BKP寄存器中。由于每一颗芯片的ID都不同,即使攻击者把一块芯片里的密钥完整抄出来,移植到另一块板子上也完全无效,因为另一块芯片的ID算出来的密钥不一样。
我实际项目里用的方案是:唯一ID加一个固定盐值,做CRC32或者AES加密后,得到128位的密钥,高64位存BKP_DR1到BKP_DR4,低64位存BKP_DR5到BKP_DR8。这样就算数据被读出,攻击者拿到的是“ID相关”的派生结果,离开这颗芯片就等于废纸。
5.3 双重防拆与冗余检测
如果你的产品防护等级要求高,可以考虑双重防拆:同时利用TAMPER引脚检测外壳状态,再用普通GPIO检测防拆走线回路。或者在同一条防拆回路上接两个检测点,形成冗余。
我设计过一个IP相机项目,外壳有两层,要求任何一个层面被打开都要报警。当时的做法是:TAMPER1检测外层外壳的微动开关,TAMPER2检测内层屏蔽罩的走线回路。配置F407的两个TAMPER通道,两个事件都走同一个总中断处理函数。实现起来不复杂,但防护覆盖面积比单点检测大得多。
冗余检测还有一个作用:防止单一检测点被绕过。比如攻击者知道你在外壳开关上做了检测,他可能用一个夹子把开关两端短接,让开关永远保持闭合状态。如果你同时还有一条防拆走线回路,他就得再想办法处理那条回路。每多一道检测,破解成本就翻一倍。
5.4 实际项目案例:计费设备
结合我自己做过的一台智能计费设备,说说这套机制在真实项目里是怎么落地的。
这个设备的BKP寄存器布局是这样的:DR1存自检魔数,DR2存累计使用次数,DR3到DR8存AES密钥的派生因子,DR9存设备运行状态字。外壳侧装了一个微动开关,正常合盖低电平,开盖高电平,TPAL配高电平有效。上电流程是:开时钟、清标志、检查TEF、确认无误后写魔数、写计费数据、锁备份域、使能TAMPER检测。
第一次做样机联调时,我打死没想到是微动开关本身的机械公差出了问题:开关在某个装配角度下没有完全压到底,引脚电平处于一个中间状态,结果一上电就误触发。后来换了一款行程更长的开关,并且把触发检测放到上电后延时500ms再使能,问题才彻底解决。从那以后,我凡是用微动开关检测的项目,都会在软件里加上电延时逻辑。
设备触发防拆后的行为是:全局标志置1,主循坏检测到标志后,蜂鸣器间隔鸣叫、LED快闪,同时写入一个“已拆机”标记到Flash的独立扇区,然后进入死循环,除了支持售后工程师通过特殊指令恢复出厂,其他操作一概不响应。
6. 常见问题与踩坑速查
6.1 问题速查表
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 上电瞬间就触发侵入 | TPAL极性配置反了 | 对照电路确认常态电平,重新配置触发极性 |
| 上电瞬间就触发侵入 | 引脚悬空,电平随机 | 外部加上拉或下拉电阻,固定常态电平 |
| 拆壳后数据没被清掉 | 没有使能BKP_TamperPinCmd | 检查BKP->CR的TPE位是否为1 |
| 拆壳后数据没被清掉 | 引脚复用配置错误 | 确认PC13处于TAMPER复用模式,而非普通GPIO |
| 中断不触发,但数据被清了 | 中断使能位没配 | 检查TPIE位、RTC_ITConfig、NVIC配置 |
| 振动环境下误触发 | 开关抖动 | 硬件加RC滤波、软件延后确认 |
| BKP数据掉电后丢失 | VBAT没接电池或电压不足 | 确保VBAT正常接3V纽扣电池或超级电容 |
| BKP数据一上电就异常 | 初始化时调用了BKP_DeInit | 删除BKP_DeInit,它会把整个备份域复位 |
| 侵入事件标志清不掉 | 清除方式错误 | TEF和TIF需要“写1”清除,不是写0 |
6.2 几个容易忽略的细节
写1清除标志这个反直觉的设计,我一开始栽过跟头。BKP_CSR里的TEF和TIF标志位,清除动作是往对应位写1,不是写0。如果你对着数据手册看半天没搞明白为什么清不掉,八成是卡在这里。标准库的BKP_ClearFlag函数封装好了这个逻辑,建议直接调用库函数。
调试器连接对防拆机制的影响也得注意。当你用ST-Link连接开发板在线调试时,目标芯片会处于复位状态或者调试状态,如果代码在这个阶段去操作BKP,很容易出现不可预期的情况。我碰到过一次,在Keil里点击下载程序后,BKP里的数据莫名清零了,排查半天发现是调试复位序列里触发了备份域复位。后来我量产验证时都改用脱机烧录器,不在线调试防拆初始化相关代码,问题就再没出现。
VBAT供电也是一个高频坑。很多人画PCB时把VBAT直接接3.3V,觉得反正系统一直有电。这样做确实能让BKP寄存器在系统运行时正常工作,但一旦主电源断电,BKP数据也跟着丢。真正的备份域设计应该给VBAT单独接电池座,或者至少接一个大容量的超级电容,保证设备断电后一段时间内数据不丢。这块电路的静态功耗极低,一颗CR2032纽扣电池撑一年以上完全没问题。
6.3 关于误触发的专项优化
误触发是所有防拆机制最头疼的问题。我总结了一套三层防误触发的策略,供你参考。
第一层是硬件滤波。在TAMPER引脚上加100nF到470nF的电容,把高频毛刺滤掉,同时外部电阻把电平拉到一个确定状态。第二层是软件延时使能。系统上电后不立即使能TAMPER检测,而是延时200ms到1秒,等电源稳定、电容充电完成后再使能。第三层是软件延后确认。即使中断已经触发,也不要马上判定为“真的被拆了”,在中断里只记录事件,主循环延时几十毫秒后再检查一次引脚电平,如果电平已经恢复常态,就判定为抖动,只做记录不做锁定。
三层策略全部做好之后,我手里的设备过振动测试、温湿度循环测试,都没有再出现过误触发的情况。当然,这个策略也不是没有代价:如果攻击者拆壳之后马上把盖子合回去,整个过程在几十毫秒内完成,触发的检测可能被当作抖动忽略掉。但说实话,正常的拆解行为不可能那么快,这个代价我觉得值得。
最后再分享一个小技巧
防拆机制做完,别忘了在产品文档里记录每一台设备的BKP寄存器布局和触发日志记录方法。我遇到过售后的设备需要分析是否真的被人拆过,但因为当时没把TEF标志的状态保留下来,最后只能靠外观判断,吃了不少亏。
另外,你可以把BKP寄存器的自检魔数换成一个有业务含义的值,比如设备的出厂日期编码,这样即使触发清除后,售后人员通过特殊读取流程也能知道这台设备大概是哪个批次生产的。这个细节不复杂,但对后期维护很有帮助。我自己后来做新产品,凡是带防拆功能的,都会在BKP_DR1里存一个“批次号+魔数”的复合值,一举两得。