
前阵子帮朋友调一块 STM32F103C8T6 核心板板子上已经跑通了点灯和串口后来准备再接一个 FreeRTOS 项目做多任务调度加几路传感器采集。代码在 Keil 里编译零错误零警告烧录也提示成功结果一上电整个系统就像断电一样——LED 不闪串口不吐数据示波器量引脚也没有任何波形。同事第一反应是 FreeRTOS 移植出问题了我第一反应也是可折腾了两天最后谜底揭开芯片是假的。这种“芯片没反应”的情况在 STM32F103 FreeRTOS 开发里真的太常见了。尤其是新手喜欢从淘宝买几块钱的散片、核心板便宜是真便宜但踩坑也是真踩坑。这篇文章我就把整个排查过程、FreeRTOS 侧的常见坑位、以及如何一步步验证芯片真伪的实操方法写清楚给正在被这种问题折磨的朋友一个参考。1. 项目现象与第一轮排查1.1 项目配置与“死寂”现象先说环境。核心板是 STM32F103C8T6 最小系统板开发环境 Keil MDK5固件库用的是标准外设库 V3.5很多人还在用这个版本FreeRTOS 用的 V10 以后的内核源码通过源码方式直接加入工程。功能上规划了三个任务一个 500ms 翻转 LED一个用串口打印系统状态一个采集 ADC 电压值并通过 DMA 搬运。代码写完编译通过烧录完成上电之后按复位键——没有任何反应。这个“没反应”很典型板子电源指示灯亮3.3V 输出正常SWD 接口用 ST-Link 能正常连接Keil 也能识别到 Cortex-M3 内核烧录进度条走完没有报错。但程序就是不执行LED 不闪串口没有输出进入调试模式后点击 RunPC 指针停在一个奇怪的地方单步几行就跳进了 HardFault_Handler。当时我第一反应是 FreeRTOS 移植没弄对因为裸机点灯和串口是之前验证过的单独跑没问题。但仔细想了一下裸机能跑、带 FreeRTOS 就死这种“软件问题”通常是中断优先级、SysTick、堆栈配置这几类原因。于是我开始按从易到难的顺序排查这里也建议大家不管遇到什么“芯片没反应”先别怀疑芯片软件层面的坑位远比假芯片概率高。1.2 最小系统、启动文件与烧录配置检查第一步先查硬件最小系统。STM32F103 要正常工作前提是供电、复位、时钟、BOOT 引脚都正确供电 3.3V 稳定VDDA 和 VSSA 不要悬空NRST 复位引脚要有上拉电容0.1uF 左右BOOT0 接地、BOOT1 接地以确保从主 Flash 启动8MHz 晶振两脚要有 10-20pF 负载电容22pF 在大多数情况下也能用。这些在核心板上一般都已经做好了但如果是自己画的板子任何一个条件不满足都会出现“芯片没反应”。第二步检查启动文件。标准外设库里面会根据 Flash 容量区分几个启动文件ld 对应小容量小于 64KBmd 对应中容量64KB 到 128KBhd 对应大容量256KB 以上。C8T6 的 Flash 是 64KB严格来说应该用 startup_stm32f10x_md.s。但有趣的是很多人用 hd 启动文件在 C8T6 上跑程序小的时候也能正常因为启动文件的核心是设置堆栈指针和调用 SystemInit向量表布局差别不大。不过一旦程序容量接近或超过 64KB或者用到了某些外设中断的向量就可能出现中断指向错乱的问题。这个点虽然不常出问题但值得检查。第三步是烧录配置。在 Keil 的 Options for Target - Utilities - Settings 里Flash Download 页要勾选 Reset and RunProgramming Algorithm 选择 STM32F10x Med-density 64K。如果选错成 Low-density 32K烧录时可能提示算法不匹配如果选成 High-density烧录器可能会按 128KB 去擦写导致一些诡异现象。我当时检查了这三项都正常于是转向 FreeRTOS 的配置。2. FreeRTOS 移植的坑位检查2.1 中断优先级分组与 PendSV/SVC 设置FreeRTOS 在 Cortex-M3 上跑有一个硬性要求中断优先级分组必须是全抢占式优先级也就是 NVIC 优先级分组 44 位都是抢占优先级没有子优先级。如果用的是标准库要在 main 里最开头调用 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4)。如果用的是 HAL 库HAL_Init 函数内部默认设置的就是 NVIC_PRIORITYGROUP_4这点相对安全。另一个关键是 PendSV 和 SVC 的优先级必须设置为最低。FreeRTOS 的任务切换依赖 PendSV如果 PendSV 优先级设得比某个外设中断高那这个外设中断处理过程中一旦有优先级反转问题就会影响任务切换更严重的做法是把 PendSV 优先级设成 0最高会导致系统进入临界区时任务切换还是会发生最后调度器一启动就 HardFault现象也是“上电没反应”。我当时特意检查了 FreeRTOSConfig.h 里 configPRIO_BITS 的定义确认是 4然后查了 start 文件里是否实现了三个关键函数PendSV_Handler - xPortPendSVHandler SVC_Handler - vPortSVCHandler SysTick_Handler - xPortSysTickHandler这三个函数在标准库移植里是最容易漏的。漏一个任务调度就不会发生或者第一次 SysTick 中断就进 HardFault。用 Keil 调试时可以看到 PC 是否会跳转到 0xFFFFFFFD异常返回地址如果 PC 停在类似位置大概率就是中断向量映射出了问题。2.2 堆与栈任务创建失败的隐藏坑FreeRTOS 的内存分配默认从 configTOTAL_HEAP_SIZE 指定的堆里出堆由 FreeRTOS 源码中的 heap_4.c 管理。很多新手把 configTOTAL_HEAP_SIZE 设成 2KB 或 4KB然后在 main 里创建了 3-4 个任务每个任务栈 512 字2KB还开了队列和信号量结果 xTaskCreate 返回 pdFAIL任务根本没有创建成功。但因为代码没有检查返回值程序继续往下走最后调用 vTaskStartScheduler发现就绪队列里没有任何任务系统就趴在那里看起来就是“没反应”。这个坑的内核逻辑是xTaskCreate 里面第一件事就是从堆里分配任务控制块 TCB 和任务栈如果堆空间不足直接返回 pdFAIL不会报错也不会死机就是静悄悄的失败。所以排查时一定要检查每个 xTaskCreate 的返回值或者在调试器里查看 uxCurrentNumberOfTasks 变量。另外任务栈大小本身也要注意如果任务栈开得太小比如 128 字节任务运行到一半就会溢出FreeRTOS 提供了栈溢出检测机制。在 FreeRTOSConfig.h 里设置#define configCHECK_FOR_STACK_OVERFLOW 2然后实现钩子函数 vApplicationStackOverflowHook一旦发生栈溢出就会进入这个函数在函数里打个断点就能第一时间发现。我当时开了这个检测把每个任务的栈加大到 256 字1KB任务创建仍然失败于是检查 configTOTAL_HEAP_SIZE发现只有 4KB改成 16KB 后一切正常。很多时候“芯片没反应”其实就是这么简单。2.3 SysTick 被谁占用了在裸机工程里SysTick 经常被用来做延时比如标准库的 Delay、HAL 库的 HAL_GetTick 底层都依赖 SysTick。移植 FreeRTOS 之后SysTick 归内核管用来产生系统节拍。如果在某个任务里调用 HAL_Delay 或自己写的基于 SysTick 的 Delay就会和 FreeRTOS 的节拍中断打架轻则延时时间完全不对重则系统卡死。我之前遇到过一个现象任务里先用 HAL_Delay(10)再 vTaskDelay(50)结果 LED 永远不闪。原因就是 HAL_Delay 会一直等待 SysTick 的中断计数值增长但 FreeRTOS 接管 SysTick 后SysTick_Handler 里调用的是 xPortSysTickHandlerHAL 的 tick 计数函数没有被调用HAL_Delay 相当于死等。在 FreeRTOS 工程里统一用 vTaskDelay 就对了非要用毫秒延时建议自己实现一个基于 DWT 或 TIM 的延时函数。这个点虽然不是假芯片导致的但非常容易把人的思路带偏。3. 软件查到头问题指向“芯片本身”3.1 换一颗芯片立刻好了软件层面的坑位都排查完之后我把同一个 hex 文件烧到朋友手上一块确定为正品 STM32F103C8T6 的开发板上电直接跑起来LED 正常闪串口输出正常。到这里基本可以断定代码没问题问题在芯片或者这块核心板的其他硬件上。于是我把目光放回那颗芯片越看越不对劲。丝印“STM32F103C8T6”打印得还算整齐但封装表面整体偏粗糙四边引脚有重新镀锡的痕迹底部边缘还有一点细微的打磨砂纹。再对照正品芯片正品表面应该有不规则的原厂激光打标字符深浅自然而不是像印刷上去的那样均匀发亮。把芯片放在放大镜下基本实锤了这是打磨重印片。假芯片大致分几类每一类会导致“没反应”的机制都不同低容量打磨成高容量比如把 STM32F103C6T632KB Flash磨掉丝印重新打成 C8T6。程序超过 32KB 时烧录器因为识别到了实际容量会报错但有些廉价烧录器不检查 Flash 容量上限提示烧录成功实际写入时直接溢出或者写到错误地址上电取指自然跑飞。国产兼容片打磨成 STGD32F103、APM32F103、HK32 这些引脚兼容芯片用 ST 的库大部分能跑但 Flash 等待周期、PLL 时钟树、部分外设时序有差异。跑裸机点灯没问题一旦上 FreeRTOS、高频率任务切换、Flash 擦写、DMA 大量搬运就可能出现偶发死机而且很难复现。内核都不同的芯片冒充 F103比如拿 Cortex-M0 内核的 STM32F030 打磨成 F103用 M3 的启动文件和固件直接跑不起来上电就是 HardFault有些甚至 PC 指针会停在完全无法理解的地址。翻新片、测试片、拆机片内部 Flash 有坏块引脚有虚焊表现为随机死机、外设初始化偶尔失败这种最难看。3.2 “没反应”不等于“没工作”这里分享一个判断经验遇到“没反应”先搞清楚 CPU 到底跑没跑。用 Keil 调试器连接后看 PC 指针的位置如果 PC 停在内核的 HardFault_Handler 里说明 CPU 已经启动了代码已经跑过 SystemInit 和 main 的开头是运行路径上出了问题。如果 PC 一直停在 0xFFFFFFFE 或 0xFFFFFFFF说明复位向量读取失败通常是 Flash 里没有有效代码或者芯片根本不是 Cortex-M3 内核。如果 PC 能正常走到 main但任务不切换那大概率是 FreeRTOS 调度器配置问题和芯片关系不大。我在第一次调试那颗假芯片时PC 停在 HardFault_Handler我还以为是 FreeRTOS 配置写错了。后来换了正品开发板就正常才证明不是软件问题。所以大家排查时一定要先分清“CPU 有没有跑起来”这个前提不然容易在软件里白白折腾很久。3.3 一个小插曲PWM 占空比到不了 100%顺便说个容易混淆的现象。有朋友做 STM32F103 的 PWM DMA 控制 WS2812B 灯带时发现蓝色或者白色模式下灯珠不亮示波器看波形发现占空比只能到 90% 多一直怀疑芯片有问题。实际上这是定时器计数比较值配置的问题ARR 和 CCR 的值没有对齐把 CCR 设成 ARR1占空比就能到 100%。这类问题跟假芯片无关纯粹是外设寄存器理解不到位。举这个例子是想说“芯片没反应”很像一个垃圾桶很多外设配置问题都会往里扔只有把所有软件嫌疑排除干净才适合怀疑硬件和芯片本身。4. 验证一颗“假 STM32”的完整实操4.1 读 Device ID、Flash 容量和 UID验证芯片真伪最快的方法是用 STM32CubeProgrammer 或 STM32 ST-LINK Utility通过 SWD 接口连上芯片读取芯片信息。正版 STM32F103C8T6 有几个关键身份信息Device IDSTM32F1 全系列低 12 位是 0x410读取地址 0xE0042000DBGMCU_IDCODE。Flash 容量F103C8T6 是 64KB读取地址 0x1FFFF7E0低 16 位返回值应该是 0x40。UID96 位唯一 ID读取地址 0x1FFFF7E8连续读取三个 32 位字。CPUID读取地址 0xE000ED00Cortex-M3 内核应该是 0x411FC231。我当时用 CubeProgrammer 读到的 Flash Size 是多少呢32KB。一个声称 64KB 的 C8T6实际 Flash 容量只有 32KB这不就是 C6T6 打磨成 C8T6 了吗除了容量我再读 UID发现三个字里有两个一模一样芯片身份信息重复这在正品 ST 上是不可能出现的。到这一步可以基本确定买到假芯片了。如果你习惯在代码里直接读可以用下面这段扔到上电初始化里通过串口打印uint16_t flash_size_kb *(volatile uint16_t *)0x1FFFF7E0; uint32_t uid[3]; uid[0] *(volatile uint32_t *)0x1FFFF7E8; uid[1] *(volatile uint32_t *)0x1FFFF7EC; uid[2] *(volatile uint32_t *)0x1FFFF7F0; printf(Flash Size: %d KB\r\n, flash_size_kb); printf(UID: %08X %08X %08X\r\n, uid[0], uid[1], uid[2]);4.2 读 CPUID 和 IDCODE 的取舍再读一下 CPUID 和 IDCODE这两个信息能进一步缩小问题范围uint32_t cpuid *(volatile uint32_t *)0xE000ED00; uint32_t idcode *(volatile uint32_t *)0xE0042000; printf(CPUID: 0x%08X\r\n, cpuid); printf(IDCODE: 0x%08X\r\n, idcode);Cortex-M3 的 CPUID 是 0x411FC231Cortex-M0 的 CPUID 类似 0x410CC200两者 PartNo 字段完全不同。如果读出来不是 M3说明芯片内核根本不对直接退货。注意 IDCODE 的判断要谨慎STM32F1 的 Device ID 是 0x410但很多国产兼容芯片为了兼容性也把 IDCODE 伪装成 0x410比如 GD32F103所以 IDCODE 只能用来排除不能作为“正品”的铁证。要区分正品 ST 和 GD32单靠 IDCODE 是不够的更多要看 Flash 容量、UID 特征以及运行时的外设行为。另外还有人用读取 0x1FFFF7E0 的方式发现某颗芯片返回 128KB这种情况可能是 STM32F103C8T6 的“超频版”翻新片其实不是128KB 的高密度型号往往也被用来打磨冒充 C8 出货但容量超过型号标称在量产项目中会影响 Flash 编程算法选择和代码保护策略必须要留意。4.3 上电实测MCO 时钟输出与 Flash 写入除了读寄存器还有一个非常实用的实测方法把 PA8 配置为 MCO 时钟输出引脚输出 PLLCLK/2。F103 默认 72MHz 主频下PA8 应该输出 36MHz 方波。用示波器或者逻辑分析仪量一下频率对不对一目了然。配置思路很简单第一步把 PA8 设置为复用推挽输出 50MHz第二步调用 RCC_MCOConfig选择 PLLCLK/2 作为输出源标准库函数HAL 库对应 HAL_RCC_MCOConfig。如果芯片是 GD32 或者其他兼容片PLL 配置可能和 ST 有细微差别有时候上电后 MCO 输出频率偏一点有时候直接没有输出这就能看出端倪。对于 FreeRTOS 项目考虑到以后可能会做掉电保存参数的功能很多 F103 项目都有这个需求我还会专门跑一段 Flash 擦写测试。在 FreeRTOS 调度器启动后开一个低优先级任务循环对内部 Flash 的一个空扇区做 1000 次擦写同时让其他任务继续保持 LED 闪烁和串口打印。如果芯片的 Flash 时序有问题擦写过程中释放总线、等待忙标志时最容易卡死假芯片通常在几十次内就会暴露。正品 STM32 内部 Flash 擦写有完整的等待机制跑完 1000 次没有任何问题。这个测试虽然简单但实战价值很高。4.4 显微镜下的“换皮”细节代码和工具验证是硬证据但外观检查能帮你减少很多不必要的折腾。我总结几个观察点供大家参考正品 STM32 的丝印是激光打标笔画清晰锐利深浅一致打磨重印片常用白色油墨印刷表面会有细微的颗粒感笔画边缘发虚。芯片表面如果能看到非常均匀的细砂纸纹说明被机械打磨过对比正品表面应该是自然哑光质感。引脚如果有重新镀锡或氧化色不一致的情况大概率是拆机翻新片这种芯片内部可能有隐性损伤。封装侧面和底部边缘的模具毛边正品比较圆润一致打磨片往往能在边缘看到不自然的切削痕迹。外观判断只能作为辅助最终还是要以 4.1 到 4.3 的实测结果为准。我见过外观做得很逼真的假片也见过外观粗糙但功能完全正常的拆机片别被外观带偏要综合判断。4.5 验证流程速查表整理一个可以随时对照的验证表方便大家遇到问题时直接抄作业验证项方法正品参考值Device ID读 0xE0042000 低 12 位0x410Flash 容量读 0x1FFFF7E0 低 16 位64KBUID读 0x1FFFF7E8 开始 96 位三个字不重复、非全 FFCPUID读 0xE000ED000x411FC231Cortex-M3MCO 时钟PA8 配置 MCO 输出 PLLCLK/236MHz主频 72MHz 时Flash 擦写压力测试调度器运行中循环擦写、逻辑分析仪监控无卡死稳定跑完这个表可以打印出来贴在工位上新到的芯片先跑一遍再投板。5. 采购与验货把假芯片挡在门外5.1 渠道选择和价格判断假芯片问题最好的解决方案是不让它进仓库。先说渠道。STM32F103C8T6 这款芯片太经典了出货量巨大市场上有大量散新、翻新、打磨片甚至还有用 STM32F103C6 冒充 C8 的用别的国产芯片冒充的也不少见。从正规代理商、目录分销商比如得捷、贸泽、复用的京东自营元器件频道购买基本不会有假货问题但价格相对高起订量也可能有要求。淘宝和一些电子市场的低价散片风险则高得多。价格上要注意正品 F103C8T6 正常行情价在几元到十几元人民币不等大批量采购价会更低但如果有人报价明显低出市场价一大截比如一块钱都不到那就别贪这个便宜大概率有问题。买芯片不是买菜翻车一次的时间成本、返工成本、售后成本远超省下的那点差价。5.2 到货验货三步法我的习惯是任何一批新芯片到货先抽样做三步质检动作不复杂但能拦住绝大多数问题第一步外观检查。批量芯片随机抽 5-10 颗用放大镜看丝印、看引脚、看封装边缘。如果同一批芯片里丝印字体深浅不一或者部分芯片表面有明显的打磨痕迹直接整批退回。第二步工具识别。把抽样芯片焊到测试板上或者用烧录座配合 ST-Link用 STM32CubeProgrammer 读取 Device ID、Flash 容量、UID 和 CPUID检查是否满足 4.5 的参考表。这一步能识别出型号冒充和内核不对的假片。第三步压力测试。烧录前面提到的测试固件这个固件包含 MCO 频率输出、串口自检、Flash 擦写循环、FreeRTOS 多任务并发等功能至少持续运行 24-48 小时。大批量采购时还要抽查不同批次、不同封装管脚位置的芯片确保一致性。有些假芯片在单颗验证时一切正常但批量贴片后因为参数离散性大会出现部分板子死机、部分板子正常的问题。5.3 这笔账怎么算都划算把假芯片放过去后续每出现一块返工板你都要承担人工、物料、测试、物流和客户沟通成本。一颗芯片省两块钱一批 1000 颗也才省 2000 块但只要出了 20 块假芯片导致的故障板返工成本很容易就超过这个数。更麻烦的是排查假芯片耗掉的开发时间这比钱还难补回来。所以采购环节多花一点时间看起来是“慢”实际上是最快的路。6. FreeRTOS 项目里那些容易“误伤芯片”的问题6.1 栈溢出与 HardFault 定位三板斧即便芯片没问题FreeRTOS 项目里也有几个问题非常容易让人误判为“芯片没反应”我单独列一节给大家做参考。第一板斧开启栈溢出检测。在 FreeRTOSConfig.h 里设置 configCHECK_FOR_STACK_OVERFLOW 为 2并实现 vApplicationStackOverflowHook在其中打断点。任务栈溢出时系统会先进入这个钩子函数而不是直接 HardFault定位起来会轻松很多。第二板斧定位 HardFault。程序进 HardFault_Handler 后先看 Call Stack 窗口再看 R14LR寄存器和 R15PC寄存器。LR 如果显示 0xFFFFFFF9 或 0xFFFFFFFD说明异常发生在线程模式或中断模式可以根据异常返回地址找是哪一行代码触发的。配合 map 文件可以查全局变量和栈的分布判断是否某个大数组越界了。第三板斧注意编译器优化。新版 Keil AC6 编译器优化等级高时对未初始化变量、别名访问、类型强转的容忍度低很多在 AC5 下能跑的老代码换到 AC6 后会出现诡异的跑飞现象。如果你刚升级了编译器版本芯片也“没反应”了先别怀疑芯片把优化等级降到 -O0 试试往往能快速定位。6.2 串口错误中断导致的“假死”很多人做 FreeRTOS 项目都会用到串口特别是像 Modbus RTU、RS232 通信这类场景。串口有个非常隐蔽的坑接收过程中如果出现帧错误、噪声错误或者溢出错误而没有及时清除错误标志串口外设会持续触发错误中断导致中断服务函数里反复处理错误任务里的业务代码根本得不到运行机会现象就是“整个系统卡死”。这个坑在中断接收不定长数据时尤其常见。比如热词里提到的 HAL_UART_ERROR_FEHAL 库会把帧错误等状态上报到错误回调函数。如果回调函数里只做了打印没有清除错误标志和重新使能接收串口就会一直卡在错误状态。在 FreeRTOS 里结合队列或信号量做接收通知时还要注意错误中断里不要调用非中断安全的 API数据接收逻辑建议统一走 DMA 空闲中断的方式既能减少 CPU 占用也更容易避免错误标志清理不及时的问题。6.3 FreeRTOS 学习与移植的一点建议如果你刚开始接触 FreeRTOS我的建议是先不要折腾原生源码移植直接用 STM32CubeMX 生成基础工程把 FreeRTOS 作为中间件加进去跑通之后再回过头研究源码。很多视频课程比如韦东山系列的 FreeRTOS 教程都讲得比较细适合系统理解任务调度、信号量、队列、软件定时器这些核心概念。真正移植时有几个点必须心里有数configTOTAL_HEAP_SIZE 要满足所有任务和内核对象的总内存需求中断服务函数里要用带 FromISR 后缀的 API比如 xQueueSendFromISR低优先级任务不要长时间占用 CPU否则高优先级任务即使就绪也得不到运行。这些点每一条都能让系统出现“偶尔卡住”的奇怪现象单独看每个都像是芯片问题串在一起其实就是内核使用不规范。6.4 最后提醒新批次芯片先跑“身份自检固件”关于假芯片我最后再分享一个习惯。我在工程模板里保留了一个“上电自检固件”它做的事情很简单初始化串口读取并打印上面那串身份信息Flash 容量、UID、CPUID、IDCODE然后进入一个一秒钟闪一次 LED 的 FreeRTOS 双任务测试。每次拿到新批次芯片我第一件事就是把自检固件烧进去跑一下一分钟内确认芯片身份和基本运行能力。这个流程看起来简单但真的帮我省了很多次无谓的软件排查。那次用掉两天时间排查“假芯片”问题之后我最大的体会就是FreeRTOS 移植固然有很多细节要小心但芯片本身的真伪和基本健康度就像房子的地基地基要是歪了楼上装修得再漂亮也没用。把芯片验证放在软件调试之前能帮你把最大的变量先排除掉。如果你也遇到“FreeRTOS 明明没问题芯片就是不工作”的情况不妨先花十分钟读一下芯片的“身份证”往往能省下两天的排查时间。这也是这次踩坑换来的最实在的一条经验。