一、故障现象
小批量打样回来的板子,烧录程序后一切正常,蜂鸣器响0.5s,LED闪烁等待握手;但是断电重启后蜂鸣器长鸣,LED不闪烁,无法正常运行。
二、分析解决过程
首先我看了一下电源,电压、电流都是对的,这说明硬件上没有短路等大毛病。
那么我感觉这是程序跑飞了,可能是程序启动有问题,是不是从SRAM起了,而不是从flash起的?
我第一怀疑板子焊接有问题,目检了一遍,特别是boot0引脚那边,重点检查了一下,没有任何毛病。谨慎起见,我还是将整个MCU重新加焊了一遍。可惜的是故障依旧,看来不是焊接的问题。
其实从故障现象来看,基本可以肯定是跟flash有关了。想一想,断电重启后到程序跑到main函数中间,芯片经过几个过程:
1、上电电源稳定后,硬件自动从flash起始地址0x80000000读取栈顶地址MSP,然后从0x80000004读取复位中断函数地址Reset_Handler,再设置堆栈指针MSP,最后跳转Reset_Handler。这个是硬件自动执行的,死规定,改不了的。
2、进入复位函数Reset_Handler干4件事:初始化数据段、清零BBS段、调用SystemInit()、调用_main入口。
在这个过程中,如果初始化没有完成,或者完成错误了,那也有可能程序跑飞。
这里有一定可能性的是时钟,如果用外部晶振的话,如果晶振坏掉了、没有焊接好,那么这里可能会有问题。但是我这个现象不是,我是烧完程序后是OK的,断电重启后不行了。看来很可能还是flash读前面两个地址的时候挂了。
我检查了一下KEIL中程序地址,都没有什么问题。

但是在检查算法ram的时候,我注意到擦除选项选的是erase sectors,这是之前为了烧录快一点而设置的。我们都知道擦sectors只擦除用到的扇区,会不会是这里有问题。

于是,我将擦除选项改成Erase Full Chip,烧录后再断电重启,这下子一切都正常了。
三、原因分析
首先Erase Full Chip和Erase Sectors的区别:
| 特性 | Erase Full Chip | Erase Sectors |
|---|---|---|
| 擦除范围 | 整个flash (除OTP、选项字节) | 用到的扇区 |
| 速度 | 慢 | 快 |
| 数据 | 全部清除 | 保留未擦除扇区的数据 |
| 寿命 | 消耗1次flash擦写次数 | 消耗对应扇区擦鞋次数 |
这里要先讲一下flash的特性:只能从非FF写0,不能从非FF写1。
这是因为STM32用的是浮栅型Flash,可以理解成每个bit有一个电子陷阱,电子在陷阱里=0,电子被拿走=1。而擦除动作是加高压,将所有浮栅上的电子全部抽走,这样所有的bit都变成了1。写入动作就是加低压,把电子注入某些bit,这样就把1变成了0。擦除是批量抽电子,写入是逐个塞电子。flash不支持批量塞电子,所以擦除后的部分变成了1。
还有一个事实是:虽然理论上flash出厂时应该是全0xFF,但是工程应用中拿到手的焊接成品的PCBA上,这颗MCU中的flash很有可能已经经过测试,甚至不能保证芯片是全新的。这种时候,拿到手的芯片中的数据是脏的,flash中可能是随机数据。
所以选择erase sectors后,只有用到的部分sectors变成了FF,但是其他部分很可能还有00存在,而写入只能将1变成0,而不能将0变成1。这就导致其他sectors中很可能还是乱码。
那么还有一个问题:为什么我用到的sectors已经擦除了,还会跑飞呢?
这是因为:
1、中断向量表附近有脏数据
例如程序占sector0,但中断向量表、堆栈指针在flash开头,这部分没有被擦除。如果这部分不干净,MCU一读就错,直接HardFault。
2、堆栈区域是脏数据
程序运行需要栈空间,如果栈所在的地址有脏数据,那么一进函数就死机。
3、编译器填充区域残留乱码
有些位置编译器会留空、填0xFF,实际却是乱码,这会导致程序判断逻辑出错。
4、选项字节/保护位干扰
有部分芯片会默认读保护,扇区擦除不会碰这些位置。
这些原因导致的现象一般有:
1、下载成功,但芯片不运行;
2、一运行就硬件错误中断Hardfault;
3、偶尔能跑,偶尔死机,不稳定;
4、仿真时,PC指针跳到很奇怪的地方。
四、工程建议
1、拿到手的板子,第一次烧录,选择Erase Full Chip;
2、开发调试优先用Erase Full Chip;
3、只有明确要保留Flash里的数据,例如配置、序列号时,采用扇区擦除。