STM32F103+FreeRTOS芯片没反应?从假芯片鉴别到硬件排查全指南
2026/9/5 8:43:16 网站建设 项目流程

上个月有个朋友给我发过来一块板子的照片,说STM32F103 + FreeRTOS做的最小系统,上电之后芯片没反应,下载器也连不上,怀疑自己焊接把芯片烧了。我让他先别拆,把芯片丝印放大拍一张。照片一放大我就觉得不太对劲:丝印字体发虚,位置歪歪扭扭,芯片底部的原厂定位孔几乎看不出来。我问他这芯片哪来的,他说是网上十来块钱一把买的“散新片”。听到这我心里基本有数了——这颗“STM32F103”大概率不是正经原装货。

这几年芯片供应紧张之后,各种渠道冒出来的STM32F103翻新片、打磨片、兼容片多到超乎你的想象。很多人把“芯片没反应”归咎于代码、归咎于FreeRTOS移植、归咎于自己的焊接水平,唯独没有想到,问题最可能出在那颗几块钱的芯片本身。这篇文章我想把这几年在F103 + FreeRTOS项目里踩过的坑、用过的排查手段、以及鉴别假芯片的实操方法完整写出来。无论你是刚开始玩最小系统的新手,还是被偶发HardFault折磨的嵌入式老兵,这篇都能帮你省下至少一整天调试时间。

1. 给“没反应”定性:先把故障分成三档,再动手排雷

很多人一上来就怀疑芯片,这其实不对。我这几年调试的经验是:所有“没反应”都必须先分档,不同档位的排查路径完全不同。分错档,后面全是白忙活。

1.1 三种常见的“没反应”现象

我把遇到过的“芯片没反应”归纳成三档:

  • 第一档:上电后完全没有任何电流反应,电源指示灯不亮,板子总电流接近0mA。这种情况基本是电源路径断了,比如5V没进来、3.3V稳压芯片虚焊、电源对地短路,跟芯片本身关系不大。
  • 第二档:上电后电流有几十mA,但程序不跑,IO口电平不变,下载器也连不上或只能“烧录后不运行”。这一档嫌疑最大的是硬件复位、外部晶振、BOOT引脚,再往后才是芯片本身有毛病。
  • 第三档:调试器能正常连接,程序也能烧进去,但全速运行就死机、HardFault或不断复位。这一档容易让人以为是FreeRTOS配置问题,实际上翻新片、Remark片也经常表现为随机死机。

判断到底是哪一档,不需要复杂仪器。一块万用表测电流,一块示波器看复位和时钟波形,基本就够了。

1.2 从上电到运行的固定检查顺序

我在项目里总结了一套固定顺序,照着走,效率最高:

顺序检查对象目标状态异常时可能的结论
1电源输入万用表确认3.3V稳定,纹波小于50mV电源芯片/电容问题
2NRST复位脚上电后从0V拉高到3.3V,无周期性跌落复位电容过大或复位芯片异常
3BOOT0/BOOT1正常运行时BOOT0为低电平BOOT0悬空/拉高启动到了Bootloader
4外部晶振用示波器测OSC_IN/OSC_OUT,能测到几MHz振荡晶振没起振或电容配置不对
5SWD连接ST-Link能识别到内核,读取芯片ID芯片电源没进/内部损坏/翻新片异常
6程序运行下载最小点灯程序,IO翻转进入HardFault或启动文件配置错误

这里有个关键点:如果第5步用ST-Link或STM32CubeProgrammer都连不上,先别急着怀疑芯片坏了,先试“Connect under reset”(连接时按住复位)。有些程序把SWD引脚复用掉了,或者芯片本身跑飞占用了调试口,按住复位连接就能避开。这个功能在STM32CubeProgrammer里叫“Mode: Under reset”,在Keil里对应“Connect under reset”,新手往往不知道还有这招。

1.3 为什么FreeRTOS工程最容易把“硬件病”放大成“软件病”

我经常跟人说:裸机点灯都没问题,不代表芯片没问题;而FreeRTOS工程出问题,也不代表一定是软件问题。FreeRTOS跑起来之后,内核要依赖SysTick定时中断、PendSV异常、SVC异常来做任务切换,还要在启动调度器那一刻把当前上下文切到第一个任务。这个过程对Flash时序、内部时钟稳定性、RAM读写可靠性都很敏感。

翻新片最大的问题恰恰就在这里:芯片内部Flash可能是老化过的,写进去的内容能跑,但长时间运行或温度变化后读出出错;内部RC振荡器漂移,导致系统时钟不稳定;甚至RAM单元有少量坏块,裸机程序只用了前几百字节看不出来,一跑FreeRTOS,任务栈一分配,就随机死机。

所以我的原则是:一切“没反应”先用裸机最小点灯程序验证,再上FreeRTOS。如果裸机点灯都点不亮,代码部分先放一边,检查硬件和芯片;如果裸机没问题,FreeRTOS起不来,才去怀疑代码。这个先后顺序能帮你少走一半弯路。

2. 翻新片、Remark片和“换标兼容片”:用外观和寄存器拆穿伪装

判断“假芯片”不能靠玄学,要有依据。我把市面上冒充STM32F103的芯片大致分成三类,每一类的识别方法不一样。

2.1 三种“假芯片”的真实身份

  • 翻新拆机片:从旧电路板拆下来,清洗、重新打脚、打磨表面,再印上新的丝印。这种芯片引脚往往有轻微焊接痕迹或氧化,芯片本体可能有打磨后的哑光区,关键是内部Flash已经有一定损耗,高温老化和写入可靠性都差。
  • Remark片(打磨重标高):把低容量、低规格的芯片打磨后印成高容量、高规格的丝印。最常见的操作是把STM32F103C6T6(32KB Flash)印成C8T6(64KB Flash),或者把RAM更小的型号印成大RAM型号。外观上很难辨别,只有读内部寄存器才能发现。
  • 换标兼容片:把兼容型号芯片磨标后印上“ST”logo和STM32丝印。比如某些GD32F103兼容芯片本身是好芯片,但被不良渠道磨掉原厂标,冒充ST卖高价。这种芯片这才叫“假ST”,但不代表GD32本身差——它只是被冒充了。

这三种情况,单看外观越来越难识别,但只要接上调试器,基本都能现原形。

2.2 不用拆机就能做的外观检查

外观检查虽然不能100%判定,但能做初筛。重点看几处:

  • 丝印字体:原装ST芯片的丝印字迹清晰、边缘锐利,字体大小和位置有固定规律。翻新片和Remark片的丝印往往发虚、字体粗细不均匀,甚至同一个批次的芯片丝印位置都不一样。
  • 引脚状态:原装新片引脚光亮、镀锡层均匀。翻新片引脚可能发暗、有细微划痕,或者引脚根部残留烙铁高温过的痕迹。用放大镜或手机微距镜头看最清楚。
  • 芯片背面和定位孔:原装芯片底部有一个圆形定位凹点,位置固定在某个角附近;翻新打磨片这个点可能变浅、模糊甚至消失。芯片侧面如果有明显的二次塑封或边缘毛刺,也要警惕。
  • 溶剂擦拭:用酒精棉轻轻擦拭丝印,如果是二次油墨印刷,很容易变淡甚至被擦掉;原装激光刻字则非常牢固。这个只能做参考,不要作为唯一判断依据。

当然,外观检查最大的问题是没法量化。真正能一锤定音的,是读芯片内部的寄存器信息。

2.3 用调试器读取“芯片身份证”:IDCODE、Flash容量和96位UID

STM32F103内部有两个非常关键的“身份证”信息:DBGMCU_IDCODE寄存器和Flash容量寄存器。不需要额外硬件,在Keil里用调试表达式,或者直接用STM32CubeProgrammer读。

如果用STM32CubeProgrammer,连接成功后选择“Option Bytes”或“Memory and File”界面,就能直接看到Device ID和Flash Size。STM32F103C8T6正品的信息一般是:

项目正品STM32F103C8T6期望值
Device ID0x410(中容量)
Flash Size64 KB
96位UID唯一且非全0xFF

如果你读出来Flash Size只有32KB,那这颗基本就是C6T6磨标成C8T6;如果Device ID是0x414,那是大容量芯片磨标冒充小容量;如果ID读出来完全不认识,那就要高度怀疑是换标兼容片。

想在自己的工程里直接打印,可以用一段很短的代码:

#define DBGMCU_IDCODE (*(volatile uint32_t *)0xE0042000) #define FLASH_SIZE (*(volatile uint16_t *)0x1FFFF7E0) #define UID_BASE 0x1FFFF7E8 void CheckChipInfo(void) { uint32_t uid0, uid1, uid2; uid0 = *(volatile uint32_t *)(UID_BASE); uid1 = *(volatile uint32_t *)(UID_BASE + 4); uid2 = *(volatile uint32_t *)(UID_BASE + 8); printf("DBGMCU_IDCODE = 0x%08X\r\n", DBGMCU_IDCODE); printf("FLASH_SIZE = %d KB\r\n", FLASH_SIZE); printf("UID = %08X %08X %08X\r\n", uid0, uid1, uid2); }

这段代码放在main函数最开头,串口初始化完成后调用,打印结果留档。如果发现IDCODE或Flash容量和丝印不符,这颗芯片就可以直接判死刑了。

2.4 一个简单的Flash压力测试,让翻新片现原形

读寄存器能查出Remark和换标,但翻新片这种“半真半假”的家伙,寄存器读出来可能都是对的,就是运行不稳定。这时候需要做Flash压力测试:对芯片Flash的空白区域反复擦写、回读、比对,循环上百次,看看有没有写入错误或读出错误。

这个测试我一般放在定制测试板上做,不会在产品固件里跑。示例代码可以这样写:

#define TEST_ADDR 0x0801F800 // 最后2KB区域,确保代码区不在这个范围内 void Flash_StressTest(void) { uint32_t i, err = 0; uint32_t write_val = 0xA5A5A5A5; FLASH_Unlock(); for (i = 0; i < 100; i++) { FLASH_ErasePage(TEST_ADDR); FLASH_ErasePage(TEST_ADDR + 1024); for (int j = 0; j < 512; j++) { FLASH_ProgramWord(TEST_ADDR + j * 4, write_val + j); } for (int j = 0; j < 512; j++) { uint32_t read_val = *(volatile uint32_t *)(TEST_ADDR + j * 4); if (read_val != write_val + j) { err++; } } } FLASH_Lock(); printf("Flash stress test done, error count = %d\r\n", err); }

正品新片跑一百遍,错误计数通常是0;翻新片或老化片跑着跑着就开始出错了。注意:测试区域不能选代码所在扇区,否则等于把自己正在执行的代码擦掉了,直接跑飞。这个测试比较粗暴,适合开发阶段的抽检,不适合量产全检。

3. FreeRTOS起不来的软件陷阱:不要在芯片没问题时误杀芯片

排查完芯片真伪之后,再来说软件。我必须替FreeRTOS说句公道话:很多时候芯片是正经原装,用户把工程拷过来就是跑不起来,最后破口大骂芯片有问题,其实是被几个经典软件陷阱坑了。

3.1 优先级分组和PendSV/SysTick的优先级陷阱

FreeRTOS跑在Cortex-M3上,靠的是SVC、PendSV和SysTick这三个异常。内核移植代码在启动调度器时,会自动把PendSV和SysTick设置为最低优先级,这是它实现任务切换的前提。如果你在工程里手动调用了NVIC_PriorityGroupConfig,把优先级分组从默认的全抢占分组改成了带子优先级的分组,比如NVIC_PriorityGroup_2,那么FreeRTOS计算中断优先级偏移时就会算错,表现为:vTaskStartScheduler之后直接死机,或者任务切了几次之后突然HardFault。

标准做法是在main函数最前面,初始化所有外设之前,固定使用全抢占分组:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

这个配置表示4位全部用于抢占优先级,没有子优先级。FreeRTOS官方要求Cortex-M3移植就是这个分组方式。你一旦改成别的分组,很多优先级相关的宏就会失效。

另外一个我经常看到的问题:用户自己写了SysTick_Handler中断函数,实现软件延时,然后移植FreeRTOS时忘了删,结果SysTick中断入口被占用了,FreeRTOS的时基就停了,所有任务调度全部卡住。这种问题,现象也和“芯片没反应”一模一样。

3.2 堆和栈:任务栈溢出和堆不足的表现

FreeRTOS一跑就死,第二个高频原因是内存配置。Cortex-M3的RAM有限,STM32F103C8T6只有20KB RAM,分给全局变量、FreeRTOS堆、任务栈之后非常紧张。

堆大小由FreeRTOSConfig.h里的configTOTAL_HEAP_SIZE控制。如果设太小,xTaskCreate创建任务时会返回内存不足,但很多人没检查返回值,结果任务根本没创建成功,程序看起来就像没反应。如果设太大,加上任务栈之后总RAM超了,链接器会报错或者运行后栈覆盖,表现就是随机死机。

任务栈溢出更隐蔽。默认的configMINIMAL_STACK_SIZE一般是128个word,也就是512字节,跑一个空任务足够。但如果任务里定义了大数组,或者调用了printf这类吃栈的库函数,512字节瞬间就爆了。开启栈溢出检测是必须的,在FreeRTOSConfig.h里:

#define configCHECK_FOR_STACK_OVERFLOW 1

同时实现钩子函数:

void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) { while(1); }

一旦任务栈溢出,程序会进这个死循环,至少能定位到是哪个任务把栈吃光了。我一般会在每个任务里留几KB余量,只在确定需要的地方加大栈。

可以配合map文件查看整个工程的RAM使用情况。在Keil里编译完成后生成的.map文件里搜索“Total RW Size”或“Stack”,能直接看到栈顶地址和全局变量占用。如果发现RW Size已经接近20KB,那FreeRTOS任务栈就只能省着用。

3.3 启动文件、AC5/AC6和芯片型号:三个最容易被忽略的工程配置

启动文件选错是一个特别经典的坑。STM32F103C8T6和RBT6是中容量芯片,应该用startup_stm32f10x_md.s;ZET6、RCT6等大容量芯片用startup_stm32f10x_hd.s。如果你把大容量的启动文件用在中容量芯片上,向量表长度和异常处理入口不对,烧录能成功,一运行就死。

Keil工程里还有一个更隐蔽的问题:Target选项卡里的Device型号没有选对。如果设备型号选成了STM32F103ZE,但实际焊的是C8T6,下载算法会按大容量芯片擦写Flash,轻则下载失败,重则把芯片选项字节或Flash配置搞乱。

还有一个新趋势:很多人从AC5编译器切到AC6(Arm Compiler 6)后,FreeRTOS莫名其妙跑不起来。AC6的优化策略和AC5差别很大,默认O2优化下,某些未初始化变量、中断服务函数内的局部变量可能被优化出问题。我的建议是新工程直接用AC6没问题,但老工程免费RTOS迁移时,先保持AC5,跑通后再切AC6,且把优化等级从O0逐步往O2调,每次调完都要跑完整测试。Keil里对应“Options for Target → Target → ARM Compiler”选项。另外,F103没有FPU,千万不要在Target选项卡里开启硬件浮点单元,否则编译出来的指令在芯片上执行就会触发HardFault。

3.4 外部晶振频率和SystemInit:程序卡在启动阶段的现象

这是我在新手群里见过最多的一种“没反应”:下载完程序,芯片完全不跑,但调试器能连上,复位后PC指针停在SystemInit里的某个while循环。

原因很简单:STM32F103的SystemInit默认要等待外部8MHz高速晶振(HSE)就绪,然后把它作为PLL的输入,倍频到72MHz。如果你的板子上根本没接外部晶振,或者晶振没起振,或者晶振实际是12MHz而不是8MHz,SystemInit就会一直等HSE稳定,停在while里出不去,用户程序当然不会执行。

排查方法有两个。第一,用示波器量OSC_IN和OSC_OUT引脚,确认晶振是不是真的在振荡。第二,临时绕过HSE,把系统时钟切到内部HSI(8MHz内部RC)上,如果程序能跑起来,说明芯片没问题,问题出在外部晶振电路。切内部时钟的代码大致是:

RCC_DeInit(); RCC_HSICmd(ENABLE); while(RCC_GetFlagStatus(RCC_FLAG_HSIRDY) == RESET); RCC_SYSCLKConfig(RCC_SYSCLKSource_HSI);

在开发阶段,我习惯先这样验证芯片能不能点灯,再回去排查晶振。

PA8引脚的MCO功能也可以用来观察内部时钟是否工作正常。通过RCC_MCOConfig把PA8配置为输出SYSCLK,用示波器量PA8,如果能看到72MHz方波(或者你配置的时钟频率),就说明时钟树已经跑起来了。这个功能在调试“芯片到底有没有在跑”的时候特别好用。

4. 最小系统硬件的那些“隐形雷”:电源、复位、BOOT和晶振逐一过堂

把芯片本身排除掉之后,还要过最小系统的硬件关。这部分的坑不深,但很阴,因为排查起来都特别基础,基础到很多人不好意思怀疑。

4.1 3.3V不干净,再好的芯片也白搭

STM32F103对电源的要求绝对没有很多人想的那么宽松。VDD和VDDA必须同时供电,尤其是VDDA,如果悬空或者电压低于VDD,芯片内部复位电路和ADC模块会处于异常状态,可能出现上电复位不彻底、运行中随机复位。

实际项目中我遇到过一块板子,现象是FreeRTOS跑几分钟就死一次,裸机也偶尔死。排查到最后发现是3.3V LDO输出端的10uF电容虚焊,导致动态负载时电压瞬跌到2.8V左右。FreeRTOS任务切换瞬间电流变化大,电源扛不住,芯片就“没反应”了。这个教训让我养成了习惯:凡是新板子,先拿示波器看3.3V的纹波,纹波大于50mV就先修电源,再谈代码。

给F103的每个VDD引脚配一个100nF去耦电容,VDDA额外加一个10uF电容,这不是玄学,是数据手册的基本要求。用FreeRTOS做多任务项目时,瞬态电流大,这些电容一个都不能省。

4.2 复位电路和NRST波形

复位电路看似简单,出问题却非常隐蔽。STM32F103的NRST引脚内部有上拉,外部只需要接一个100nF电容到地即可。但很多开发板为了“稳定”,额外加了一个10K上拉到3.3V,这本身没问题,问题出在电容取值过大。

如果把复位电容加到1uF甚至10uF,上电时NRST被拉低的时间会变得很长,或者复位释放的上升沿过于平缓,导致芯片内部的复位阈值判断不稳定,结果就是:上电后芯片没有正常进入用户程序,看着就像“没反应”。

用示波器量NRST,正常的波形应该是:上电瞬间从0V快速跳到3.3V,没有周期性跌落,没有长时间保持中间电平。如果波形呈“锯齿状”持续几秒,或者反复下跳,就是复位电路有问题。

4.3 BOOT引脚决定程序能不能跑

BOOT0和BOOT1这两个引脚,是很多“芯片没反应”的元凶。STM32F103有三种启动模式:BOOT0拉低时从用户Flash启动,这是正常模式;BOOT0拉高、BOOT1拉低时从系统存储器启动,进入内置Bootloader;BOOT0和BOOT1都拉高时从SRAM启动,不上电。

我自己犯过一个错:为了省事,BOOT0直接悬空,结果芯片上电后大概率进入系统Bootloader,程序烧进去就是不运行。后来规范改成BOOT0和BOOT1各接一个10K下拉电阻到GND,确保上电时是确定的低电平。这里说的“确定”很关键,悬空在芯片内部虽然有默认下拉,但外部干扰或焊接残留会让它处于不稳定状态。

4.4 外部晶振电路的关键细节

STM32F103最小系统里的外部晶振通常用8MHz无源晶振,接在OSC_IN和OSC_OUT之间,两端各接一个负载电容到地,负载电容一般取12pF到22pF,同时在两个引脚之间并联一个1M电阻用于偏置。

不起振的常见原因有几个:负载电容太大导致起振困难;晶体两端虚焊;OSC_IN和OSC_OUT接反(无源晶振无极性问题,但如果是用有源晶振,接错方向就会出大事);或者是用了质量很差的晶振。

有源晶振和无源晶振的接法完全不同。无源晶振接两脚,有源晶振只需要把输出信号接到OSC_IN,OSC_OUT保持悬空。有些人习惯性把有源晶振信号接OSC_OUT,那就怎么都不起振。如果用示波器探针去量OSC_IN时,波形出现了一个明显的正弦波但幅度偏小,多半是负载电容太大;如果完全没波形,先用万用表确认晶振两端没有对地短路,再考虑换一颗晶振试。

5. 采购验货与量产前的真伪测试:把假芯片拦在焊接之前

识别假芯片最好的时机,是它还没焊到板子上的时候。等贴片机打完、进入测试环节再发现,成本已经付出去了。这块的经验,对研发阶段和批量生产都有参考价值。

5.1 开发阶段怎么选芯片来源才稳

我理解开源节流的心情,但开发阶段买芯片,首要目标不是省钱,而是可控。FreeRTOS工程偶发死机,如果一直在劣质芯片上排查,可能查一个星期都找不到原因。我的建议是:开发板或小批量验证阶段,尽量从正规目录分销商或可信平台采购,哪怕单价贵一两块钱,换来的却是“芯片没问题”的确定性。

更重要的是索要证据。批量采购时要求供应商提供原厂出货的批次号、包装标签,或代理商的采购凭证。很多翻新片渠道根本拿不出这些文件。如果供应商含糊其辞,只说“保证原装正品”,那就果断换一家。

有个简单的价格判断:STM32F103C8T6的市场行情低于某个明显不合理的价位时,基本可以断定不是原装新片。芯片不是白菜,签收前先看一眼价格,能避免后面所有的麻烦。

5.2 到货验货流程:从包装到焊前抽检

我现在的流程分四步,每一步都不能省:

  • 外包装检查:正规原厂编带包装封口整齐,标签信息完整,批次统一。如果收到的是散装袋、白纸标签,或者同一袋里丝印批次五花八门,直接拒收。
  • 丝印和外观抽检:从整批里随机抽几颗,用微距镜头看丝印,重点看字体、定位孔、引脚状态。同一批次如果出现两种不同的丝印样式,大概率是混料。
  • 上机读取ID:把抽检的芯片放到测试座上,用STM32CubeProgrammer逐颗读取Device ID、Flash Size和UID,和丝印标称型号比对。所有C8T6的Flash Size都必须是64KB,IDCODE必须是0x410。
  • Flash压力测试:在正式开发板或测试板上烧录压力测试程序,对Flash连续擦写上100次,检查错误计数。翻新片或者劣质片在这个环节基本都会暴露。

这套流程看起来麻烦,实际做一次只要半小时左右,但能把99%的问题芯片拦在贴片之前。

5.3 我踩过的一次“假芯片”事件复盘

有一年我批量采购了一批STM32F103C8T6,外观包装都挺正规,价格也接近市场价。裸机点灯、串口打印都没问题,Flash读写也能通过。结果产品开始量产跑FreeRTOS多任务+串口通信时,故障率突然到了3%左右,设备不定时死机。

一开始我怀疑是FreeRTOS配置问题,调了一周的栈和堆,问题还在。后来把故障板和正常板同时用CubeProgrammer读Flash Size,发现故障板的Flash容量竟然是32KB,Device ID是0x412而不是0x410。也就是说,这批混入了一批C6T6(32KB)打磨重印成C8T6的Remark片。32KB Flash的芯片跑64KB固件,编译链接时按64KB分配,实际Flash后半段写入失败,程序在运行过程中随机读到异常数据,自然死机。

这件事给我的教训是:单片机程序层面能查的问题,一定要在程序层面之前先堵住。从那以后,每批芯片到货先抽检ID和Flash容量,并把读取信息归档留底。省下来的调试时间和返工成本,远超多加的那一点点采购精力。

这几年我处理过不少“STM32F103 + FreeRTOS 没反应”的求助,真正代码写错导致任务起不来的是少数,更多时候是芯片来源、最小系统硬件、移植配置这三类问题叠加。强烈建议你拿到一块新板子的第一步,先做一个基础自检:通电量电流、示波器看NRST和晶振、CubeProgrammer读ID和Flash容量,全部通过之后再烧FreeRTOS工程。五分钟的验证,能帮你省掉五个小时甚至五天的盲目排查。芯片是地基,FreeRTOS是上层建筑,地基都没验证过,就别急着优化上层代码了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询