1. 现象描述:焊好板子,芯片却“装死”
先交代一下背景:最近在做STM32F103C8T6 + FreeRTOS的小项目,板子是自己画的,最简系统:3.3V供电、8MHz晶振、复位电路、BOOT0下拉、SWD下载口。元件到齐后我先焊了两块,满怀期待地接上ST-Link,结果Keil直接报了No target connected,换了另一块还是老样子。折腾了一晚上,测电压、量晶振、查复位,全都正常,最后换了一颗芯片,一次点亮。
问题就出在那两颗“新”芯片上。这年头买STM32F103,尤其是C8T6这种量大管饱的型号,踩到翻新片、打磨片、低容量冒充高容量的概率比我以往想的要高得多。市面上所谓“全新原装”的价格从三四块到十几块都有,如果只看便宜,很容易拿到体质参差不齐的散新片或者翻新片,现象千奇百怪:有的无法识别、有的能识别但烧不进代码、有的烧进去跑几秒就死、有的温度一高就复位。
这篇文章不打算重复手册上的基础电气参数,我想从一个实际踩坑者的角度,把“芯片没反应”这件事从头到尾捋一遍:怎么确认问题、怎么区分电路问题还是芯片问题、怎么识别假芯片、怎么避免在FreeRTOS项目里把软件问题误判成硬件问题。后面还会讲一些跟FreeRTOS移植相关的坑,因为很多时候芯片没反应不是芯片死了,而是软件卡死在启动阶段,表象和坏芯片几乎一样。
如果你是刚接触STM32F103的新手,或者正准备做FreeRTOS小项目,这篇文章应该能帮你在排查时少走弯路。
2. 最小系统接线:先别怪芯片,先检查这几处
芯片通电后没反应,第一步永远是确认最小系统是否真的“最小”。不要急着怀疑买到假芯片,先花十分钟把硬件底子夯实。
2.1 电源引脚:VDDA和VDD一个都不能落
STM32F103内部模拟电路和数字电路分开供电,对于LQFP48封装的C8T6,VDD(引脚24、36、48)接3.3V,VDDA(引脚9)也要接3.3V,VSSA(引脚8)接地。很多自画板子只接了VDD,漏了VDDA,芯片上电后ADC或内部复位电路工作不正常,表现出来就是整个芯片不启动,或者偶尔能启动、偶尔死机。
我测过一块板子,VDDA悬空时,按下复位键偶尔能跑起来,但LED点亮的瞬间电压会掉到2.8V以下,然后芯片就死了,看起来像是负载能力不足,实际上就是VDDA缺电导致的内部LDO工作异常。
电容也不能省:VDD引脚处至少放一个100nF的陶瓷电容,VDDA处最好再并一个1μF以上的电容,如果条件允许,加一个10Ω电阻隔离一下数字电源的纹波。这些电容的位置要尽量靠近引脚,高频退耦才有意义,不要图省事放在板子角落。
2.2 VCAP引脚:容易被忽略的“内部稳压器输出”
F103系列有个专门的VCAP引脚(LQFP48封装下是引脚47),这个引脚是内部1.8V稳压器的输出,必须外接一个2.2μF的陶瓷电容到地。这颗电容不能用钽电容或者电解电容凑合,ESR特性不对可能导致内部稳压器振荡,芯片上电后直接废掉。我第一次画板子就踩过这个坑——VCAP电容用了一个10μF的电解电容,结果芯片电流异常大,发热严重,程序永远跑不起来。
后来查了官方勘误手册和数据手册,明确建议VCAP接2.2μF陶瓷电容,电容耐压6.3V以上即可。这个位置对Layout也有要求,走线要短,地回路要直接,不要绕一圈回到GND。
2.3 NRST和BOOT0:悬空状态不可靠
NRST引脚内部有上拉,但不要指望这颗上拉能应付所有场景。如果NRST走线过长,或者附近有强干扰源,复位脚被拉低导致芯片反复复位,在调试器看来就是“连接不上”。我的习惯是:NRST接一个100nF电容到地,再并联一个10kΩ上拉到3.3V,双重保险。
BOOT0必须明确接GND或3.3V。F103的BOOT0内部没有上下拉,悬空时电平不确定,如果恰好被拉高到1.2V以上,上电后芯片会进入系统存储器Bootloader模式,用户Flash里的程序不会执行。现象就是:芯片能被ST-Link识别、能烧录,但复位后程序不跑。这个坑和“假芯片”的表现几乎一样,容易误判。
BOOT1(PB2)不要强拉,保持浮空或通过电阻上拉到GND都行。BOOT1只是在BOOT0为高时参与启动模式选择,正常用不到,但不建议直接接VCC或GND,避免以后需要用串口ISP下载时改线麻烦。
2.4 晶振:能不能跑,不只是起振问题
8MHz晶振对F103来说不是必须的,内部HSI也能启动,但外接晶振配合PLL倍频到72MHz是绝大多数项目的标配。芯片“没反应”时,很多人第一反应就是拿示波器去勾晶振引脚,结果发现没波形,就判断晶振坏了——这里有个误区:芯片没正常启动时,外部晶振本来就不会起振,量不到波形不能反推“晶振坏了”。
正确做法是:用示波器量OSC_IN(PD0/PH0,取决于封装),同时按住复位键再松开,看波形是否出现一个从无到有的起振过程。起振后振幅一般在0.5V到3.3V之间,如果只有几十毫伏的微弱振荡,多半是负载电容选得不对。F103匹配的负载电容一般是10~20pF,我用过18pF,起振时间大约几百微秒,稳定输出没有问题。
如果示波器探头带宽不够,或者探头电容太大(很多10x探头输入电容约15pF),会导致晶振停振,这也是一个隐性坑。更稳妥的办法是:程序上先用内部HSI跑通,确认芯片核心逻辑正常后,再切外部晶振。
3. 识别“假芯片”:从外观到内部特征的甄别清单
确认最小系统无误、电源稳定、复位正常之后,如果芯片还是不响应,那就踏入“假芯片排查”的核心地带了。STM32F103的仿冒和翻新问题在业界不是新闻,尤其是C8T6这个料号,出货量太大,导致市场上流通的“渠道货”鱼龙混杂。
3.1 外观辨别:丝印、封装、引脚光泽
先看丝印。原装ST的丝印通常由四行组成:第一行是ST的Logo加产品型号(比如STM32F103C8T6),第二行是封装代码和温度等级(如“L QFP48 7”),第三行是日期代码(如“934”代表2019年第34周),第四行是批次号。打磨片或者翻新片的丝印往往只有一行型号,没有批次信息,或者字体大小不均、深浅不一。用放大镜看,翻新片引脚上常有重新镀锡的光泽,与本体塑料颜色反差很明显。
但这只是初筛,现在有些假芯片丝印做得几乎可以乱真,所以外观只能作为参考,不能作为证据。
3.2 内部特征验证:RDP、容量ID、UID
真正靠谱的手段是直接读芯片内部信息。用ST-Link连接,Keil里打开Options -> Utilities -> Settings,在SW Device窗口能看到内核ID:Cortex-M3默认ID是0x3BA00477,如果读出来的ID不是这个,基本就是非原装或者非F103。
更硬核一点是写一个读取RDP(Read Out Protection)等级的测试程序。全新原装芯片出厂时RDP等级必然是0(Level 0),也就是读保护未启用。如果你买到的芯片RDP已经是Level 1,说明这颗芯片被擦写过、被烧过读保护,大概率是翻新片。要解除Level 1必须全片擦除,如果芯片内部代码区有残留内容,烧录时可能触发读保护校验不通过,导致无法连接。
还有一个验证方法是读Flash容量寄存器(Base address 0x1FFFF7E0),F103C8T6是64KB,读出0x40就是正常。有些打磨片用F103C6T6(32KB)冒充C8T6,容量寄存器会读出0x20,这时候如果按C8T6的Flash大小去烧录,代码超过32KB就会烧到不存在的地址,表现为“烧录成功但程序跑飞”。
UID也是个线索:原装F103的UID是96位,在0x1FFFF7E8地址读取。同一批次芯片的UID前面几十位相同,后几十位各不相同。如果一批芯片UID完全相同,或者某位全是FF,多半不是原厂流出。注意,UID全FF在全新芯片上也可能出现(未编程状态),但不同于同一批次大量重复。
3.3 电气行为:温度和功耗会“说实话”
翻新片最大的问题不是不能跑,而是参数退化。原装F103的静态功耗(全外设关闭、72MHz无负载)一般在20mA以内,翻新片因为内部缺陷,电流可能偏高10%甚至更多,而且发热部位不均匀。用手摸芯片表面,如果发现某一区域明显烫手,而负载并不重,基本可以断定芯片有问题。
另外,翻新片对电压纹波的容忍度通常低于原装。原装芯片在3.0V~3.6V都能稳定工作,翻新片可能在3.2V以下就不稳定。做交叉验证时不只测芯片供电脚电压,还要用示波器看纹波峰峰值。3.3V轨上纹波超过100mV时,原装芯片一般还能跑,翻新片可能直接复位。
3.4 购买渠道与价格底线
分享一个我自己的经验:STM32F103C8T6的全新原装芯片,批量采购的合理价格这几年波动很大,但明显低于行情价60%以上的所谓“原装全新”,不用看结果,直接PASS掉。常见套路是“亲,我们的货是原装正品,只是没有编带包装,散新”,散新不能说全部有问题,但来路不明的散新片翻新概率极高。
优先选择得捷、贸泽、Arrow这些授权分销商,量小贵一点但买个放心;淘宝/拼多多上买则一定要找支持ST-Link下程序测试的卖家,且拆包后尽快测试,过了窗口再发现问题就说不清了。我现在手里常备一批“备胎”芯片,都是从授权渠道小批量买的,出现异常时先换上验证,避免把时间浪费在扯皮上。
4. 软件层面:FreeRTOS项目“没反应”的隐性原因
芯片本身没问题,ST-Link也能连上,但烧完FreeRTOS固件后复位没反应,这个现象在初学者项目里非常常见。很多时候不是芯片坏了,而是移植配置出了问题,导致系统卡死在启动早期。下面几个坑,我按出现频率从高到低排一下。
4.1 SysTick被FreeRTOS接管后的冲突
FreeRTOS运行的前提是系统滴答(SysTick)归它管理,所以FreeRTOS移植完成后,你不能再在裸机代码里用SysTick做延时,否则节拍计数错乱,任务调度直接崩。典型现象:代码里调用了HAL_Delay()或者delay_ms()(基于SysTick的裸机延时实现),然后创建了几个任务,启动调度器后一个任务都不跑,或者跑了几个毫秒就HardFault。
解决办法是:所有延时统一用vTaskDelay()或vTaskDelayUntil(),裸机版本的延时函数全部替换。如果确实需要阻塞式短延时,用DWT计数器或者定时器外设自己实现,不要碰SysTick。
还有一个容易忽略的点:FreeRTOS要求SysTick中断优先级设为最低(数值最大),PendSV和SVCall优先级也必须是最低优先级。在NVIC中,F103的优先级分组建议设为NVIC_PriorityGroup_4(4位抢占优先级,无子优先级),然后将PendSV和SVCall设为15,SysTick设为15。配置方法:
NVIC_SetPriorityGrouping(NVIC_PriorityGroup_4); NVIC_SetPriority(SysTick_IRQn, 15); NVIC_SetPriority(PendSV_IRQn, 15); NVIC_SetPriority(SVCall_IRQn, 15);注意:FreeRTOS官方推荐所有ISR中调用的API中断优先级不高于
configMAX_SYSCALL_INTERRUPT_PRIORITY,在F103上通常设为5,也就是抢占优先级数值≥5的中断才能调用FromISR结尾的API。如果设置反了,中断一进来调用API直接assert失败。
4.2 堆栈大小和堆大小不足
FreeRTOS每个任务都有独立的栈空间,默认大小经常不够用。F103的RAM只有20KB(C8T6),如果创建多个任务,每个任务栈给512字(2KB),光任务栈就吃掉8KB以上,再加上FreeRTOS内核自己的堆(heap),内存很容易爆。爆内存的表现不一定立刻死机,可能是任务创建失败(xTaskCreate返回pdFAIL)、任务调度后跑一会儿HardFault、或者看起来“芯片没反应”。
我的建议是:先用最简单的最小工程验证启动,只创建两个任务,每个任务栈128字,一个任务控制LED翻转,另一个任务什么都不干只延时。跑通之后再加任务、加栈。排查时打开FreeRTOS的栈溢出检测:
// FreeRTOSConfig.h 中配置 #define configCHECK_FOR_STACK_OVERFLOW 2 #define configUSE_MALLOC_FAILED_HOOK 1配置为2时,内核会在任务切换时检测栈顶的“canary”值是否被破坏,如果破坏会调用vApplicationStackOverflowHook。实现这个钩子函数,在里面对GPIO置位或者点亮LED,就能快速定位哪个任务栈溢出。
Heap大小在FreeRTOSConfig.h里的configTOTAL_HEAP_SIZE定义。C8T6建议从8 * 1024开始,如果heap够用则不必加大;如果任务创建频繁失败,优先削减任务栈而不是加大堆。
4.3 启动文件与链接脚本不匹配
又是一个容易被忽略但是后果惨烈的坑:Keil工程的Device型号选错,导致链接脚本按错误的内存布局分配地址。很多人在Keil里新建工程时图省事,随便选了个STM32F103RB(128KB Flash,20KB RAM),实际上芯片是C8T6(64KB Flash,20KB RAM),链接脚本会按128KB Flash分配,代码超过64KB后烧录到不存在的地址,启动时直接取指失败,进HardFault。
正确做法是在Keil的Options -> Device里明确选择STM32F103C8,确保Target页的IROM1地址是0x08000000、大小为0x10000(64KB),IRAM1地址0x20000000、大小为0x5000(20KB)。如果用Cubemx生成工程,也要在Project Manager里确认MCU型号没选错。
检查Map文件能快速确认链接结果:编译后在Listings目录下打开.map文件,查看Memory Map of the image部分,确认代码段落在0x08000000~0x0800FFFF范围内。如果地址超出,Map文件会出现“ER_IROM1”溢出警告,但很多人没注意。
4.4 下载配置和复位配置导致的“假死”
Keil里常见的一个奇怪现象:烧录成功,但按复位键后程序不运行,点击全速运行后又能跑。这个问题多半出在Debug设置里。Options -> Debug -> Settings -> Flash Download页,确认勾选了Reset and Run,否则烧录后芯片停留在复位状态,必须手动复位。
更隐蔽的问题在Utilities页的Settings里:Reset选项选成了Hardware Reset还是Software Reset。有些ST-Link的固件版本对F103的硬件复位引脚时序处理不好,Hardware Reset模式下烧录完成后的复位动作不完整,导致程序没从正确状态启动。我一般改成Software Reset,实测稳定很多。
如果你用的下载器是J-Link,还要注意J-Link的SWD接口速率。速率过高时连接不稳定,表现为:有时候能识别芯片,有时候“No target connected”,尤其在你加了防静电电容或者隔离电路后更明显。把SWD速率降到1MHz以内,连接成功率会高很多。ST-Link默认速率一般是4MHz,如果线缆长了或者用了杜邦线,也建议主动降速。
5. 实操排查流程:从硬件到软件,5分钟定位问题
上面几节讲了各自独立的坑,但真遇到问题时,不可能一次性把所有点都过一遍。下面给出我实际使用的排查顺序,照着走一遍,大多数“没反应”问题都能定位。
5.1 Step 1:确认芯片最基本供电
拿万用表量芯片的VDD和VDDA引脚,确认是3.3V左右。再量VCAP,正常应该在1.8V左右(F103内部1.8V稳压器输出)。VCAP电压不正常的话,芯片内部逻辑电路没电,其他都不用看了。如果你量的VCAP是0V或者接近VDD,说明芯片内部稳压器没工作,要么是芯片坏了,要么是VCAP电容不对导致振荡。
5.2 Step 2:最小系统“空跑”测试
不烧任何程序,用示波器量NRST,确认复位脚在松开复位按键后能从0V升到3.3V。再量外部晶振,如果是8MHz晶振且芯片正常启动,OSC_IN/OSC_OUT引脚上应有正弦波或方波。如果没有波形,不要先怀疑晶振,先检查电源和VCAP。
如果一切正常还是不起振,可以试试直接烧录一个最简单的GPIO翻转程序(用内部HSI,不依赖外部晶振)。能跑起来就说明问题是外部晶振相关的,跑不起来继续往下查。
5.3 Step 3:检查下载器连接
用ST-Link连接SWDIO和SWCLK,GND和3.3V也接上。在Keil的Options -> Debug -> Settings里点“Scan”按钮,看是否识别到SW Device。如果识别到了,看ID Code的值是不是0x3BA00477。不是的话,芯片内核ID不对,基本可以断定芯片是假的或者损坏。
5.4 Step 4:擦除并烧录验证程序
烧录前先做一次“Erase Full Chip”,把芯片恢复出厂状态。烧录一个最简单的程序——例如用标准库点亮LED,不加FreeRTOS。这个程序如果在裸机状态下能跑,说明芯片基本没问题。
再烧录一个包含任务创建的FreeRTOS最小工程,任务0翻转LED,任务1空转。如果裸机能跑、FreeRTOS跑不起来,按照4.1到4.3的顺序检查配置。
5.5 Step 5:RDP和UID验证
到了这一步还没定位问题,就值得做一次彻底的芯片身份验证。写一个测试程序读取下面三个地址的值,通过串口输出:
// Flash size register, 0x1FFFF7E0, read as uint16_t uint16_t flash_size = *(volatile uint16_t *)0x1FFFF7E0; // UID, 0x1FFFF7E8, 96-bit uint32_t uid0 = *(volatile uint32_t *)0x1FFFF7E8; uint32_t uid1 = *(volatile uint32_t *)0x1FFFF7EC; uint32_t uid2 = *(volatile uint32_t *)0x1FFFF7F0;同时用ST-Link Utility或者STM32CubeProgrammer读取RDP等级。如果RDP=1,或者Flash容量不是0x40,UID读取异常,直接换芯片。
5.6 Step 6:换一颗芯片交叉验证
最后,也是最决定性的一招:换一颗确认来源的芯片。手头上常备一两颗从授权渠道买的芯片,遇到可疑情况直接换上,如果换上后所有测试顺利通过,那原来的芯片基本可以判定有问题。这个方法简单粗暴,但效率极高,比用示波器慢慢排查快得多。
6. 常见问题速查表:现象、原因与解决方案
| 现象 | 可能原因 | 排查方案 |
|---|---|---|
| 完全无法连接,No target detected | VDD/VDDA缺电、SWDIO/SWCLK接反或虚焊、芯片损坏 | 量电源,量VCAP,重新焊接SWD,更换芯片 |
| 能识别芯片,但烧录时报Flash校验错误 | Flash容量型号选错、芯片Flash损坏、RDP等级为1 | 检查Keil Device型号、读取容量寄存器、解除读保护 |
| 烧录成功,按复位无反应 | 硬件复位时序问题、BOOT0悬空导致进入Bootloader | Keil Download页勾选Reset and Run,BOOT0明确下拉接地 |
| 裸机程序正常,FreeRTOS任务不执行 | SysTick冲突、PendSV/SVCall优先级过高、任务栈溢出 | 检查优先级别设置、打开栈溢出检测、减少任务栈大小 |
| 烧录后跑一会儿就死机/复位 | 堆栈溢出、任务栈溢出、看门狗未喂、电源纹波大 | 打开FreeRTOS溢出检测、减小任务栈加入堆余量、优化电源滤波 |
| 外接晶振不起振,内部HSI能跑 | 晶振虚焊、负载电容不匹配、探头电容过大 | 用10x探头并检查负载电容、先切HSI跑通 |
| 芯片发热严重 | 电源接反、VCAP电容类型错误、芯片内部短路 | 立即断电,检查电源极性,VCAP换2.2μF陶瓷电容 |
| 多块芯片同一批次均有问题 | 买到了翻新片/打磨片 | 读取RDP状态、UID重复情况,更换采购渠道 |
这张表建议收藏,碰到类似问题时对照排查,比自己一个个试快得多。
7. FreeRTOS移植时的几个额外提醒
最后补充几个我在FreeRTOS实际项目中踩过的、跟“芯片没反应”表象相关的点。它们不是硬件问题,但很容易迷惑人。
7.1 串口重定向printf导致卡死
很多人习惯在FreeRTOS任务里用printf来打印调试信息,但如果串口驱动基于轮询(阻塞式)发送,且没有加互斥锁,多个任务同时printf时数据会交错。更严重的情况是:如果开启了系统节拍中断,而printf的阻塞时间过长,超过了一个系统节拍周期,内核会认为当前任务让出了CPU,调度器切入其他任务,这时另一个任务再次printf,两者冲突,程序进入死锁状态。
解决方法是:给串口发送加上互斥量(Mutex),或者用DMA发送+队列。如果只是想临时调试,可以只在单个任务里加一个vTaskDelay(1)再printf,避免并发冲突。我用过一个比较省事的方案:单独创建一个“日志任务”,其他任务把字符串塞进队列,日志任务负责串口输出,这样不乱串,也不会堵塞其他任务。
7.2 中断里调用非FromISR的API
这是新手经常踩的坑。形如vTaskDelay、xQueueSend(非FromISR版本)在中断上下文会被configASSERT拦住,如果assert钩子没有实现,或者实现里只是空循环,表象就是中断一触发,整个系统就像死了一样。如果你用了外部中断、定时器中断、串口中断来唤醒任务,一定要检查中断服务函数里调用的API是不是带FromISR后缀。
7.3 Keil编译器版本带来的兼容性问题
热词里提到“Keil如何使用6版本编译器”,这里多说一句:AC6(ARM Compiler 6)对代码标准要求更高,很多基于AC5语法写的裸机代码(比如隐式声明、register关键字、可重入函数声明遗漏)在AC6下会直接编译失败,或者优化后行为异常。有时候你编译通过但F103跑起来没反应,可能不是芯片问题,而是编译器版本不同导致的代码语义变化。
如果基于标准库v3.5的老工程,我建议暂时用AC5(v5.06),直到确认所有警告清零、行为正常后再切AC6。AC6下优化等级开到-O2时,未初始化的局部变量可能导致不可预期行为,排查这类问题很痛苦。
7.4 用Map文件确认代码段和RAM用量
Map文件是排查软件问题的重要手段。当程序跑不起来时,打开.map文件看两处:一是Memory Map中ER_IROM1和RW_IRAM1的起始结束地址,确认没有超出芯片实际资源;二是Startup部分,确认Reset_Handler被编译进了镜像。如果Reset_Handler被链接器丢弃(可能因为中断向量表配置错误),程序上电后直接跑飞,表现和芯片损坏一模一样。
8. 说句心里话
做嵌入式这么多年,我越来越觉得“芯片没反应”这类问题的排查,拼的不是某一招鲜的技巧,而是按部就班的耐心。买芯片多花几块钱、多验证一轮、多留一颗备胎,排查效率会高很多。
最近这波ST芯片渠道混乱,假芯片和翻新片防不胜防。我的选择是:如果项目对可靠性要求高,直接在授权渠道采购,别贪那几块钱的便宜;如果只是学习验证用,也要买“支持烧录测试”的货,而且到货后第一时间做RDP和UID验证。真遇到“芯片没反应”的时候,先默念三遍:电源、复位、时钟。三板斧查完再看芯片真伪。很多时候,你以为买到了假芯片,结果最后发现是自己VCAP电容贴错了。
FreeRTOS项目的“没反应”还可能是软件配置问题,建议先裸机验证、再最小RTOS验证、再逐渐加功能,一步步缩小范围。希望这篇文章能帮你省下几个加班的晚上。