STM32F103+FreeRTOS程序跑飞?排查发现芯片是假的
2026/9/8 6:00:07 网站建设 项目流程

刷完代码,上电,灯不亮,串口没输出,Debug模式下程序直接卡在HardFault_Handler里。这是我做STM32F103 + FreeRTOS项目时遇到的状况,排查了整整两天,最后终于意识到问题不在代码,而在芯片本身——我可能买到“假芯片”了。

这个标题估计不少人都觉得眼熟。尤其是刚从标准库裸机开发切换FreeRTOS的开发者,遇到芯片没反应时第一反应都是怀疑任务堆栈配小了、时钟初始化有问题、FreeRTOSConfig.h配置错了,很少有人会想到芯片根子上就有问题。这篇博文我想完整复盘一遍当时的排查思路,从软件配置一路查到硬件电路,再反推到芯片真伪,顺便把FreeRTOS移植过程中几个非常隐蔽的坑也一并整理出来。如果你也在用STM32F103 + FreeRTOS做项目,或者正准备把手头工程从裸机迁移到RTOS,这篇文章应该能帮你少走不少弯路。

1. 现象复盘:代码看起来啥都没问题,芯片就是不理你

1.1 项目背景和故障现场

先说项目背景。当时我在做一个小型数据采集设备,主控选的是STM32F103RCT6,开发环境是Keil MDK,固件库用的是标准库Std v3.5,下位机通信用RS232,基于FreeModbus v1.6移植了Modbus RTU从站协议,同时跑FreeRTOS管理三个任务:一个负责LED心跳闪烁,一个负责串口打印调试信息,一个负责周期采集ADC数据。这种组合在F103上算是非常经典的配置了,资源完全够用。

故障现象也很典型:第一次烧录程序后偶尔能跑起来,LED正常闪;但只要按一下复位键,或者重新上电,十有八九就死在那里。串口打印的初始化信息只出过一次,后面再也没有输出过。用Keil进入调试模式,单步执行到创建任务的地方,下一秒就跳进了HardFault_Handler。有时候更夸张,连下载器都连接费力,Keil报错说“Cannot access target”,得多试几次才能连上。

这种“时好时坏”的故障是最折磨人的。我先怀疑是不是自己FreeRTOS移植的锅,毕竟裸机程序跑得好好的,加上RTOS就出问题,直觉上就是操作系统配置有问题。于是我开始逐个排查FreeRTOS相关的配置项,这也是绝大多数人第一反应。

1.2 第一反应:FreeRTOS配置到底有没有问题

FreeRTOS移植到STM32F103,最常见的问题确实集中在FreeRTOSConfig.h。我当时重点检查了几个关键参数:

#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 128 ) #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 10 * 1024 ) ) #define configUSE_PREEMPTION 1 #define configUSE_IDLE_HOOK 0 #define configUSE_TICK_HOOK 0 #define configCHECK_FOR_STACK_OVERFLOW 2 #define configMAX_PRIORITIES 5 #define configKERNEL_INTERRUPT_PRIORITY 15 #define configMAX_SYSCALL_INTERRUPT_PRIORITY 5

当时我任务栈的配置是这样的:LED任务给了128字(512字节),串口打印任务给了256字(1KB),ADC采集任务同样256字。每个任务还有对应的TaskHandle,创建时也都检查了返回值,没有出现pdPASS之外的结果。按理说10KB的堆内存跑三个小任务绰绰有余。

但问题依旧。后来我甚至开启了FreeRTOS的堆栈溢出检测功能,在FreeRTOSConfig.h里把configCHECK_FOR_STACK_OVERFLOW设为2,然后补上钩子函数:

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

钩子函数确实触发了,但触发的位置不固定,有时候是LED任务,有时候是ADC任务。这就很奇怪了,任务栈明明都够用,怎么会说溢出就溢出?后来我才想明白,并不是任务栈真正的使用量超标了,而是程序跑到某个故障点后,栈指针已经完全错乱,检测机制自然认为栈溢出了。也就是说,堆栈溢出检测到的是“结果”,不是“原因”。

1.3 再查启动文件和链接脚本

排除了任务栈和堆配置之后,我把怀疑重点转向启动文件和链接脚本。STM32F103系列有一个非常经典的坑:启动文件必须和芯片型号匹配。我用的RCT6属于高密度Flash型号,应该选startup_stm32f10x_hd.s。如果选成md.s或者ld.s,虽然编译能通过,但程序启动后中断向量表位置可能不对,同样会跑飞。我核对了三遍,启动文件没有选错。

链接脚本倒是确实出过一个低级问题。我用的是标准库,startup文件里默认把栈顶设置在了RAM末尾,也就是0x2000C000(RCT6有48KB SRAM)。但后来我核对芯片手册发现,我的工程里keil的Target页SRAM起始地址填错了,写成了0x20000000,大小却填了64KB,超出了芯片实际的SRAM大小。这会导致编译器认为RAM比实际大,某些大数组和任务栈分配的位置可能在芯片不存在的地址上,一运行就踩空。这个问题修正之后,依然跑不起来,但方向已经逐渐从软件走向硬件了。

2. 从软件到硬件:我的排查顺序可能和你想的不一样

2.1 先查最小系统电路

我把FreeRTOS的配置翻了个底朝天都没有结论,决定先回到最基本的硬件最小系统去验证。STM32F103的最小系统就四样东西:电源、复位电路、晶振、BOOT引脚。任何一个有问题,芯片都不可能正常工作。

先量电源,3.3V输出稳定,纹波在20mV以内,看起来正常。再查复位脚NRST,上拉电阻10k到3.3V,按键复位电路也正常。用示波器测8MHz晶振,奇怪的事情出现了——晶振引脚上几乎看不到波形。我当时第一反应是晶振没起振,换了个新的晶振,负载电容也重新按手册配了一遍,依然是同样的现象。

后来我才反应过来,F103的晶振电路只有在程序里使能了外部高速振荡器(HSE)之后,晶振才会稳定起振。单片机刚上电的时候,晶振引脚上测不到正确波形其实是正常的。所以这个现象并不能说明硬件有问题,只能说在跑FreeRTOS的程序里,HSE被初始化了,但初始化之后是否稳定,还需要进一步验证。

再查BOOT0和BOOT1引脚,BOOT0接10k下拉电阻到地,BOOT1悬空或下拉,状态没问题。到这里,最小系统电路看起来都是健康的,但芯片就是跑不起来。

2.2 软件侧的一个关键动作:PA8引时钟出来看看

排查遇到瓶颈后,我想到一个非常古老的验证手段:用PA8的MCO功能把系统时钟引出来,用示波器量频率,确认芯片内部时钟到底跑在多少。

MCO是STM32的时钟输出引脚,可以把SYSCLK、PLLCLK、HSE、HSI等时钟信号输出到PA8上。标准库的配置代码很简单:

void MCO_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); 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); RCC_MCOConfig(RCC_MCO_PLLCLK_Div2); }

这里我选择的是把PLLCLK二分频后输出。如果系统时钟正常工作在72MHz,PLL输出也是72MHz,二分频后PA8应该量到36MHz的方波。但实际测出来的结果非常离谱,PA8输出频率只有7.2MHz左右,偏差极大。如果PLL锁定不稳定或者分频系数计算有偏差,根本不可能精确输出这个频率。

这是一个非常关键的异常信号。频率不对,意味着芯片内部的PLL可能没有正常工作,或者这个芯片内部的核心逻辑和正版STM32F103并不完全一致。也正是从这一刻起,我开始怀疑芯片本身了。

2.3 串口通信异常带来的另一个线索

除了PA8输出频率不对,串口也暴露了问题。我用的是RS232,通过SP3232做电平转换,FreeModbus模块跑Modbus RTU从站,波特率配置成9600。正常情况下,主机发一帧请求,从机应当回复。但实测下来,主机这边收到的数据全是乱码,个别时候完全没有响应。

乱码说明波特率对不上。我一开始以为是SP3232电路问题,后来把示波器探到串口TX引脚上看波形,发现单帧数据的位宽明显不对。波特率9600时,一个bit的宽度理论上应该是104us,但实际量出来差不多130us,误差接近25%。这种误差绝对不是晶振电容配错能解释的,更像是芯片内部串口外设的时钟源本身就不准。

结合PA8输出频率异常和串口波特率偏差,我基本确定问题出在芯片内部时钟系统的根基上。也就是说,这个芯片内部可能根本没在用8MHz外部晶振跑,或者在PLL环节就已经出问题了。软件再怎么调都治不了硬件层面的硬伤。

3. FreeRTOS为什么会“掀开”假芯片的老底

3.1 裸机能跑,RTOS却跑了就挂的本质原因

很多人会有个疑惑:同一个芯片,裸机程序能跑,为什么上了FreeRTOS就各种死机?这其实非常好解释。裸机程序是单线程的轮询逻辑,对RAM的占用比较小,主要代码都放在Flash里线性执行。如果芯片本身有部分Flash坏块,或者RAM比标称的小,只要不踩到那几个坏的区域,程序勉强能转起来。

FreeRTOS的情况完全不一样。它要管理任务控制块(TCB)、内核堆栈、任务堆栈、队列、信号量等,这些都需要比较大的RAM做动态分配。更关键的是,任务调度器会把上下文保存在对应任务的内核栈里,如果RAM实际可用空间比芯片标称小,任务一切换就可能写进了非法地址。加上FreeRTOS默认用PendSV和Systick中断做任务切换,这两个中断的优先级配置也有讲究,一旦配置越界,系统直接进HardFault。

所以,FreeRTOS对芯片资源的要求比裸机高得多,芯片任何一项“缩水”都会被RTOS放大并快速暴露。

3.2 市面上的“假芯片”常见的几种情况

结合这次经历和我过去几年的采购经验,市面上所谓“STM32F103假芯片”或者说“问题芯片”,大概可以分成三类。

第一类是打磨重标片。把原厂芯片表面的丝印磨掉,重新打上更高型号的标记,这就叫打磨片。这种片子的来源很复杂,有可能是回收料,也有可能是不同型号的库存散料。打磨片最大的问题不是型号不对,而是引脚、内部Die可能已经受过损伤,稳定性完全没有保证。偶尔遇到能用的算是运气好,像晶振不起振、复位异常、PLL锁不住这些问题都可能发生。

第二类是低配冒充高配。用C8T6冒充RCT6,用CBT6冒充RCT6,这是非常常见的操作。C8T6只有64KB Flash和20KB SRAM,RCT6是256KB Flash和48KB SRAM。如果工程是按RCT6的容量链接的,程序烧进去之后,Flash后半段的数据根本没有物理存储空间,要么烧录时校验失败,要么运行时取指令取到无效数据,程序自然跑飞。我在博客开头说的“烧录后第一次能跑,复位就不行”,就非常符合这种低配冒充高配的特征。

第三类是兼容芯片冒充原厂。有些国产兼容型号,引脚定义和ST基本一致,但内部外设寄存器细节并不完全相同。裸机点个灯还行,一旦跑FreeRTOS,任务切换依赖Systick和PendSV,如果芯片的中断控制器行为跟ARM标准不完全一致,就会出现调度不稳定的问题。此外,部分兼容芯片的Flash和RAM实际容量比STM32对应型号小,运行RTOS时很容易超限。

3.3 哪些现象属于“假芯片特有”

并不是所有跑不起来的芯片都是假芯片,但下面这些现象组合出现时,就需要高度警惕了:

  • 同样的程序在别的板子上正常,在这块板上时好时坏,尤其是复位后大概率死机;
  • PA8的MCO输出频率严重偏离预期值(不是简单的误差,而是百分之几十的偏差);
  • 串口波特率怎么配都不对,波形位宽明显异常;
  • 用示波器测外部晶振,波形幅度偏低或起振困难;
  • 下载程序时经常出现Flash校验失败,但降低速度后又偶尔能成功;
  • 芯片工作温度异常,摸上去烫手,电流明显偏大。

如果出现两条以上这类现象,基本上可以判定芯片来源有问题。这时候再往下调试FreeRTOS的堆栈、优先级,意义已经不大了,应该先解决芯片本身的问题。

4. 验明正身:我自己在用的几种检测方法

4.1 读芯片ID和Flash容量寄存器

最直接的验证方式,是让芯片自己“报上名来”。STM32F103有对应的DBGMCU_IDCODE寄存器,地址在0xE0042000,低16位是Device ID。不同型号的F103,这个值也不一样。比如0x410对应的是中容量产品(如C8T6),0x414对应高容量产品(如RCT6、ZET6)。用代码读一下就知道它到底是哪一类。

uint32_t dbgmcu_idcode; uint16_t device_id; dbgmcu_idcode = *(__IO uint32_t *)0xE0042000; device_id = (uint16_t)(dbgmcu_idcode & 0xFFFF);

还有一个更直观的信息,芯片内部有一个Flash容量寄存器,地址在0x1FFFF7E0,16位宽,单位是KB。比如我的RCT6,读出来应该是256。如果买的是RCT6却读出来64或者128,那基本可以确定是低容量芯片冒充的。

uint16_t flash_size_kb; flash_size_kb = *(__IO uint16_t *)0x1FFFF7E0;

这两个寄存器读起来非常简单,但效果极好。我把这个检测代码放到板子最开头执行,通过串口把读到的值打印出来,几秒钟就能判断芯片型号是否对得上。

4.2 写满Flash做全片校验

读容量只是第一步,Flash内部的存储介质是否可靠,还得实际写一遍才知道。我给这块问题板子写了一个全片校验的小程序:从0x08000000开始,以4KB为块单位,依次写入0xAA、0x55、0x00、0xFF等固定模式,然后读出来逐字节比对。

写Flash的时候要注意,千万不要覆盖到程序自身所在的区域。我的策略是先把校验代码放到SRAM里执行(通过Keil的分散加载文件把校验函数放到RAM),或者把程序放在Flash起始地址,校验范围从Flash末尾往低地址方向走,避开前64KB程序区。实际操作中,用J-Flash或STM32CubeProgrammer也能直接做整片擦除和写入校验,但有时候低端下载器对假芯片的兼容性反而掩盖了问题,还是用芯片自身的操作更可靠。

这块问题板子的校验结果非常难看:写到中后段地址时,芯片报编程错误,读出来的数据和写入的完全对不上。这些区域看起来能写,但内部存储单元实际上已经损坏或不存在了。这进一步印证了Flash容量不足的猜测。

4.3 串口波特率验证法和内部RC校准

前面提到我量到串口波特率偏差很大,其实这也可以做成一个自动检测脚本。方法很简单:让芯片初始化串口并发送连续的0x55(这个字节的bit序列是01010101,示波器上看非常工整),用示波器测相邻两个下降沿之间的时间,计算实际波特率。用逻辑分析仪的话更简单,直接解析帧格式,看设备上报的波特率是否和配置值吻合。

如果偏差在2%以内,基本正常;偏差超过5%,就要考虑外部晶振没工作、芯片内部PLL配置不对,或者芯片本身时钟系统异常。这块问题板子的偏差高达25%,已经不能用“测量误差”来解释了。

4.4 正规采购渠道和到货抽检建议

检测方法再多,也不如从源头上控制。芯片采购尽量走原厂代理商、正规授权分销商或者信誉较好的大型电子元件平台。散新片、拆机片不是不能用,但要做坏品率评估,千万别拿量产项目的需求去买那种价格低得离谱的货。

到货之后,我现在的习惯是先抽检3-5片,跑一遍包含“读ID + 读容量 + 全片Flash读写校验 + 定时器72MHz校准 + 串口回环测试”的自检固件,全部通过再入库。这套流程看起来费时间,但能避免在调试阶段浪费大量精力。

5. 后续建议:除了假芯片,FreeRTOS移植还有这几个坑

5.1 堆栈溢出检测一定要开

经历了这次假芯片时间之后,我又对FreeRTOS本身的常见问题做了几轮加固。第一个就是堆栈溢出检测。很多人用FreeRTOS都不开configCHECK_FOR_STACK_OVERFLOW,直到程序跑飞了才后悔。我建议开发阶段一定要开启,而且要选方法2。方法1只检测任务栈指针是否越界,方法2会在任务切换时检查栈是否有被覆盖的痕迹,检测更全面。代价是多一点CPU开销,但这在调试阶段完全值得。

一旦检测到溢出,钩子函数里不要只是死循环,至少要把出问题的任务名打出来,或者点一个独立的错误指示灯。否则钩子触发了你都不知道,还以为是系统调度卡死了。

5.2 中断优先级不要乱设,Systick和PendSV必须最低

FreeRTOS移植到Cortex-M3上,一个很重要的规则是:中断优先级数值越大,优先级越低,而FreeRTOS要求PendSV和Systick中断的优先级必须设为最低,也就是数值最大。同时,所有调用FreeRTOS API的中断,其优先级数值必须大于configMAX_SYSCALL_INTERRUPT_PRIORITY。如果某个中断的优先级数值小于这个阈值(也就是逻辑优先级更高),那它就能打断FreeRTOS的临界区,可能造成调度器状态错乱。

我在之前的裸机工程里把串口中断优先级设成了0,也就是最高优先级。移植到FreeRTOS之后忘了改,结果就是程序在串口中断里调用FreeRTOS队列发送函数,直接把系统干挂。这个坑和假芯片现象有一定相似性,排查时容易混淆。

5.3 拿到新板子,先验证芯片再铺RTOS

最后分享一个我个人的操作习惯:拿到任何一块新板子,我不会直接往下铺FreeRTOS工程,而是先烧一个最小的验证固件,包含“读ID、读Flash容量、点亮板载LED、串口回环”四个功能。固件代码不超过100行,完全不用RTOS,裸机就能跑。确认芯片身份和基本外设正常之后,再把这个裸机工程作为底座,往上叠FreeRTOS。

这一步看起来多余,但在踩过打磨片的坑之后,我深刻意识到:在不可靠的硬件基础上调试软件,等于拿着一个坏温度计去修空调,永远找不到真实原因。先花十分钟确认芯片没问题,后面省下的是几十个小时的排查时间。

这次假芯片事件之后,我团队内部也定了一个不成文的规定:所有STM32项目的硬件方案评审,第一页必须放“芯片验证方案”,包括型号读取、容量校验和最小外设测试这三个环节。这不是流程繁琐,而是吃过亏之后总结出来的保命手段。如果你正准备用STM32F103 + FreeRTOS做项目,尤其是从非正规渠道采购芯片,强烈建议你先把这块板子的芯片底细查清楚,再让FreeRTOS接管任务调度。芯片是地基,地基本身不稳,上面盖再漂亮的楼,早晚都会塌。

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

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

立即咨询