新手搞STM32F103,焊完板子,烧完程序,屏幕没反应、LED不闪、串口没输出,第一反应往往是查电路、查代码、查FreeRTOS配置,来回折腾一晚上什么结果都没有。这种时候我真建议你先停下来,认真怀疑一下手里的芯片到底是不是正品。我去年就吃过一次大亏,一口气买了二十片STM32F103C8T6,单价低得诱人,结果焊了七块板子,五块完全“沉默”,剩下两块偶尔能跑起来,一旦FreeRTOS任务一多就死机。后来逐片测下来,发现那批芯片全是打磨重印的翻新片,硅片本身来源不明,有的甚至根本不是ST的原厂晶圆。这篇文章就结合我踩坑的完整过程,从现象、原理、排查链路到验货方法,把这个“芯片没反应”的问题拆开讲透,给正在用STM32F103 + FreeRTOS做项目或者学习的你一个可复现的排查方案。
1. 现象复盘:一块“点不亮”的最小系统板
1.1 故障现象:烧录成功但无任何输出
先说当时的实际场景。我自己画了一块最常规的STM32F103C8T6最小系统板,8MHz外部晶振、两个20pF负载电容、复位电路、Boot0下拉到GND、3.3V供电,电源部分用的AMS1117-3.3,输入5V。布局谈不上多漂亮,但绝对不算离谱,这也是我一开始根本没想到芯片会有问题的原因。
用ST-Link V2烧写一个最简单的FreeRTOS点灯Demo,编译下载都很顺利,Keil提示Flash下载完成,校验通过。但是按下复位键之后,板子完全没有反应,PA1上接的LED不闪,串口不打印任何东西。更奇怪的是,把FreeRTOS去掉,换成一个非常简化的裸机延时点灯程序,居然能正常跑。这就很迷惑了,看起来系统本身没什么大缺陷,但一上RTOS就“死”。
我当时第一反应是:是不是FreeRTOS配置有问题?优先级配置不对?SysTick没有启动?还是堆栈分配不够?于是开始一轮一轮地改配置,删任务、调堆栈、关中断保护,搞了一整个下午,毫无进展。后来换了一片另一批次的芯片,同样的板子、同样的程序,一次就点亮了。这时候才意识到,问题很可能不在软件,而在芯片本身。
1.2 常规三板斧排查都给不出结论
很多教程里讲排查“芯片没反应”,都会让你先查三样东西:电源、复位、时钟。这套流程本身没问题,但它有一个前提,就是芯片本体是可信的。遇到假芯片,这三板斧经常会给你一个“看起来很健康”的假象,反而耽误时间。
我当时也做了相同排查,但结果如下:
| 检查项 | 测试方法 | 结果 |
|---|---|---|
| 3.3V电源 | 万用表测VDD引脚对GND | 3.32V,稳定 |
| 复位引脚 | 万用表测NRST电压 | 3.3V,高电平 |
| 外部晶振 | 示波器探头点OSC_IN/OSC_OUT | 8MHz波形正常起振 |
| Boot0引脚 | 万用表测Boot0 | 0V,接地正确 |
| SWD连接 | ST-Link能否识别 | 能识别到Cortex-M3 |
| 程序烧录 | Keil下载校验 | 成功,校验通过 |
这套结果放在正常芯片上,基本可以排除硬件问题了。但问题是,它确实就有一颗“换了主控就正常”的芯片。后来我用逻辑分析仪抓了PA1引脚的波形,才发现FreeRTOS版程序运行时,PA1一直保持低电平,根本没进入任务调度。也就是说,芯片死在了启动调度器之前的某个位置,而不是死在一个具体任务里。这个现象后来成了我怀疑芯片真假的重要突破口,因为正品芯片在相同程序下绝对不应该卡在调度器启动阶段。
2. FreeRTOS不是故障原因,但它把问题放大了
2.1 裸机能跑的陷阱在哪
为什么假芯片能跑裸机程序,却跑不动FreeRTOS?这个问题的答案,是理解整个事件的关键。
裸机点灯程序本质上只依赖CPU内核执行指令,哪怕内部Flash有问题、RC振荡器偏差大,只要CPU能取指、能执行简单的GPIO操作,灯就能亮。而且很多翻新片在出厂前会被“筛选”过,它们大概率能在最基本的功能上工作,否则卖家也没法出货。但这类芯片往往存在两种情况:一是硅片来自早期测试淘汰批次,内部Flash可靠性存疑;二是芯片虽然印着STM32F103的丝印,但内部实际是别的型号甚至别的厂家的内核,只不过引脚兼容。
FreeRTOS和裸机程序最大的不同,在于它引入了三样裸机程序不太依赖的东西:
- SysTick定时器中断
- PendSV异常
- SVC异常
这三样都属于Cortex-M3内核的系统级外设。如果芯片的真身不是标准STM32F103,或者内部的调试组件、异常向量表、Flash控制器存在缺陷,那么FreeRTOS在启动调度器时很容易触发各种意想不到的异常。一旦异常处理函数里没有一个正确的处理逻辑,程序就会进入默认的HardFault_Handler死循环。表现就是GPIO保持原来状态,整颗芯片像被冻住一样。
我当时还犯了一个错误,就是太相信Keil的下载成功提示。后来我才意识到,翻新片的Flash往往是拆机片重新封装的老Flash,寿命所剩无几。Keil写入的时候校验通过,不代表数据能长时间稳定保存,也不代表芯片每次上电都能正确加载程序。有时候程序确实写进去了,但芯片内部Flash读出时发生位翻转,PC指针跳到错误地址,然后死机。这种随机性和裸机程序那种“每次都能跑”的表现截然不同。
2.2 调度器启动失败的调试抓手
排查FreeRTOS启动失败,有几个非常好用的调试手段,可以帮你快速把问题定位在“芯片硬件”还是“系统配置”上。就算不怀疑假芯片,也建议你在遇到RTOS死机时先走一遍这套流程。
第一招,在HardFault_Handler里打断点。用Keil仿真模式或者直接连着ST-Link跑,程序卡死后停下内核,查看PC指针的值和异常返回地址。如果是0xFFFFFFF0之类的特殊值,说明异常发生在异常处理过程中,多半和栈指针错乱有关。如果PC指向一个合法Flash地址,就去反汇编窗口看这段代码是哪个函数,顺着函数名往往能定位到问题,比如PendSV_Handler里访问了非法地址。
第二招,检查PRIMASK和CONTROL寄存器。FreeRTOS启动调度器时,会先关中断再开中断,如果芯片的中断控制器有问题,开中断后SysTick无法触发,PendSV永远挂起,任务调度就无法开始。我之前在另一颗芯片上碰到过类似现象,后来发现是芯片的NVIC中断挂起寄存器读出来全是0,配置完全无效,这几乎可以断定芯片内部逻辑有问题。
第三招,用configASSERT和configCHECK_FOR_STACK_OVERFLOW。在FreeRTOSConfig.h里打开这两个宏,一旦系统内部发现非法调用或者栈溢出,会立刻进断言失败函数。如果你在断言函数里打断点,能拦住很多“看起来像没反应”的故障。不过要提醒一句,这个方法是软件层面的兜底,对假芯片造成的硬件级异常往往拦不住,因为故障发生在更底层。
打开configCHECK_FOR_STACK_OVERFLOW之后,还需要在代码里实现一个钩子函数:
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) { // 断点打在这里,能挡住一部分栈溢出导致的死机 __disable_irq(); while(1); }如果你的程序连这个函数都进不去,而是直接死在HardFault里,那硬件嫌疑就非常大了。我当时就是卡在这一步,软件层面全部排查干净之后,才彻底转向硬件怀疑。
3. 逐层拆解:从外观识别到程序验货
3.1 外观与丝印:翻新片和打磨片的细节差异
排查到最后,我把这批芯片全部拆下来,和手头从正规渠道买的同型号芯片放在一起仔细对比。不对比不知道,对完之后发现假芯片的外观破绽非常明显。
正品STM32F103C8T6的丝印通常是激光刻字,字体清晰、笔画均匀,用放大镜看有轻微的凹刻感。翻新片用的多是丝网印刷或者激光重刻,新印上去的字明显浮在表面,周围还能隐约看到被磨掉的原来丝印的痕迹,尤其是芯片表面的磨砂质感会被破坏,局部会有一块发亮的小区域,这就是打磨过的铁证。
引脚也是重灾区。翻新片很多是拆机片洗脚重新上锡的,引脚根部会残留助焊剂或者焊锡颜色不均,用指甲刮一下引脚侧面,新片是光亮的银白色,翻新片容易露出略带黄色的铜底或者不规则的锡层。还有一点很关键:看芯片侧面的封装缝线,正品的封装线均匀一致,翻新片因为经历过二次封装或者暴力拆解,侧边常常有不自然的凹痕或者毛刺。
我当时拿手机微距镜头拍了几张对比图,发到技术群里让朋友看,几个人异口同声说第一片“字太假了”。丝印中“ST”标志的弧度、字母间距、批次号编码规则,都和正品有明显差异。批次号这种东西,一般人不会注意,但正因为不注意,造假者也很少认真仿。
| 观察项 | 正品特征 | 假芯片常见特征 |
|---|---|---|
| 丝印质感 | 激光刻字,有凹感 | 表面印刷,字浮于表面 |
| 表面处理 | 整体磨砂均匀 | 局部发亮,有打磨区 |
| 引脚状态 | 光亮平整,无焊锡残留 | 引脚根部有焊锡痕迹,颜色不均 |
| 封装边线 | 均匀自然 | 有毛刺、凹痕 |
| 批次号 | 编码规范,位置固定 | 编码混乱,深浅不一 |
3.2 引脚与电气特性测试
外观只能说明“可疑”,真正让我确定这批芯片有问题的,是电气测试结果。翻新片虽然引脚兼容,但很多电气参数是达不到STM32F103规格书标准的。
最简单的测试是用万用表的二极管档位,测任意一个GPIO引脚对VDD或者对GND的压降。正品芯片的内部保护二极管特性非常一致,我用同一个表笔极性测PA0到GND,正品是0.45V左右,那批假芯片只有0.31V,而且同一批芯片之间数值离散度很大,有的0.28V,有的0.35V。这说明内部ESD结构和晶圆来源跟ST原厂对不上,基本可以肯定是第三方晶圆。
如果手头有示波器,还可以测MCO引脚的时钟输出。在代码里把PA8配置为MCO功能,选择PLLCLK 2分频输出:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_MCOConfig(RCC_MCO_PLLCLK_Div2); GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure);正常情况下,外部8MHz晶振倍频到72MHz后2分频,MCO引脚应该输出36MHz的方波。如果芯片内部PLL工作不稳定,或者外部晶振起振后倍频失败,MCO引脚输出的波形就会杂乱无章,频率偏差巨大。我那批假芯片里,有两颗甚至完全无输出,另外几颗输出的频率在21MHz到35MHz之间乱跳。这种问题在裸机点灯程序里根本体现不出来,但在FreeRTOS里会导致SysTick节拍不均匀,任务调度时序全部错乱。
注意,这种方法需要芯片能运行代码,如果芯片完全“锁死”,可以先排除软件问题再测。如果连最简单的IO翻转程序都无法执行,那芯片内部的Flash或者核心逻辑已经不可信了,没必要再纠结。
3.3 用程序读ID:三行代码识别假芯片
外观和电气测试之外,程序层面的验货才是最有说服力的。STM32F103有多个内部标识可以被软件读取,假芯片在这些标识上几乎都会露出马脚。
第一个要读的是DBGMCU_IDCODE,地址0xE0042000。这是一个32位寄存器,低12位是设备ID,高16位是版本号。STM32F103系列正常情况读出来是0x20036410,这里的0x410就是Cortex-M3的设备ID,0x364是F103的标识。如果读出来不是这个值,那芯片的真实身份就很可疑了。
uint32_t idcode = *(__IO uint32_t *)0xE0042000; printf("IDCODE = 0x%08X\r\n", idcode);第二个要读的是Flash容量寄存器,地址0x1FFFF7E0。STM32F103C8T6标称64KB Flash,这个寄存器读出来应该是0x40,表示64KB。我测的那批假芯片,有两颗读出来是0x100,也就是256KB,那根本不是F103C8T6的规格,更像某个大容量型号改标的。还有一颗读出来是0x00,Flash大小完全不可识别,这种基本上是晶圆损坏或者测试不合格的废片。
第三个要读的是96位Unique ID,地址从0x1FFFF7E8开始连续三个32位寄存器。正品芯片的UID全世界唯一,同批次都不重复。假芯片很多是把同一个UID复制到所有芯片上。我当时写了个小工具,通过串口把UID打印出来,发现五颗“坏”芯片的UID完全一样。这个现象基本可以实锤:它们来自同一个母体,是批量复制的翻新片。
uint32_t uid0 = *(__IO uint32_t *)0x1FFFF7E8; uint32_t uid1 = *(__IO uint32_t *)0x1FFFF7EC; uint32_t uid2 = *(__IO uint32_t *)0x1FFFF7F0; printf("UID = %08X %08X %08X\r\n", uid2, uid1, uid0);这三个寄存器读取操作在正品STM32F103上非常稳定,但如果芯片被做过手脚,或者本身就不是ST原厂晶圆,读出来的值往往五花八门。你可以在普通正品芯片上先跑一遍这个代码,记录正常值,再去测可疑芯片时心里就有底了。
4. 这批芯片的最终结论与后续验货流程
4.1 拆解后的真相
结合外观、电气特性、程序读ID三方面证据,那批芯片最终判定为翻新片。再往深挖一层,有几颗芯片的Flash容量寄存器读出来是256KB,基本可以断定它们是STM32F103VCT6之类的大容量型号打磨掉原丝印,重新印上C8T6的标记。这种操作在市场上挺常见的,因为大容量型号如果本身有瑕疵或者来源不正规,价格可能反而便宜,打磨重印后按C8T6卖,利润空间很大。
但这里有一个非常坑的地方:VCT6和C8T6虽然都基于Cortex-M3,但Flash容量、SRAM容量都不同,内部Flash控制器的配置也存在差异。如果固件按照64KB Flash的C8T6进行编译下载,跑在一颗被伪装成C8T6的VCT6上,内核能运行串口能打印,但在某些特定操作下会出现地址映射和Flash保护方面的诡异故障。FreeRTOS的任务栈如果正好分配在某些区域,就很容易莫名其妙地触发异常。
所以当我用外部8MHz晶振倍频到72MHz时,内部Flash等待周期设置不正确,就可能随机出现取指错误。假芯片的系统时钟和Flash读时序又不像正品那样经过严格出厂校准,问题就被进一步放大。这也解释了为什么裸机点灯能跑、FreeRTOS跑不了——裸机代码少、访问Flash频率低,错误概率低;FreeRTOS代码量大、任务切换频繁、Flash访问压力高,错误概率急剧上升。
4.2 批量验货流程和采购建议
这次踩坑之后,我把芯片验货流程固定成了标准操作,每一批新芯片到货都会走一遍完整流程,绝不再凭外观判断。现在分享出来,给同样在DIY或者小批量生产的你一个参考。
第一步,外观抽检。拆开包装后,用放大镜或者手机微距逐片看丝印,重点看“ST”标志、批次号、封装边线。出现任何打磨痕迹、字体异常、引脚焊锡残留,直接整批退回,不要抱侥幸心理。
第二步,电气快速筛查。用万用表二极管档测每一片芯片的VDD对GND压降,以及任意几个GPIO对VDD的压降,记录数值并对比离散度。正品芯片的数值一致性非常好,同一批芯片之间差异通常在0.01V以内。如果差异超过0.05V,就要警惕。
第三步,上电功能测试。画一个测试治具,把芯片放到最小系统板上,烧写一个专门设计的测试固件,这个固件至少要包含四件事:串口打印IDCODE、打印Flash容量、打印Unique ID、在MCO引脚输出一个已知频率的时钟。然后逐一测试每颗芯片,把串口打印结果和预期值比对。
第四步,压力测试。功能测试通过后,再烧写一个完整的FreeRTOS程序,至少创建三个不同优先级的任务,包含串口收发、定时器、LED翻转。让板子连续跑24小时,如果中间出现死机、复位、串口乱码,说明芯片存在隐性缺陷。我们当时测出五颗坏片中,有三颗是在压力测试阶段才暴露的。
采购渠道方面,我的建议是:小批量打样尽量选择正规目录分销商,比如立创商城,虽然单价看起来比其他平台贵一两块钱,但省下了大量排查时间。如果必须从第三方渠道拿货,一定要要求对方提供原厂出货证明或者批次溯源码,并先购买样品做完整测试再批量下单。低于市场均价30%以上的所谓“原装正品”,十有八九有猫腻,这条经验在哪个平台都适用。
芯片测试固件里还有一个容易被忽略的细节:在串口打印内容中包含一个芯片编号,这个编号可以手动设定为烧录时的顺序号。这样如果某颗芯片在压力测试阶段挂了,你能准确知道是第几颗,而不是在乱糟糟的测试台上找“那颗没反应的芯片”。
最后再分享一个小技巧,这是我踩过坑之后养成的习惯:芯片买回来之后,不管多忙,一定先读一遍Unique ID并记录在案,把UID和一个自定义的编号对应起来,统一存放在一个表格里。这样后面一旦出问题,翻表格就能确认是不是同一批次、有没有混料。这几十秒的操作,能帮你避免不少说不清道不明的“灵异”故障。